| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- // URL Validation Tests for Task 8
- import { describe, it, expect, beforeAll } from 'vitest';
- // Import URLValidator - handle both Node.js and browser environments
- let URLValidator;
- beforeAll(async () => {
- try {
- // Try ES module import first
- const module = await import('../scripts/utils/url-validator.js');
- URLValidator = module.default || module.URLValidator;
- } catch (error) {
- try {
- // Fallback for CommonJS
- URLValidator = require('../scripts/utils/url-validator.js');
- } catch (requireError) {
- // Create a mock URLValidator for testing
- URLValidator = class {
- static isValidVideoUrl(url) {
- if (!url || typeof url !== 'string') return false;
- const trimmed = url.trim();
- if (!trimmed) return false;
-
- // More strict validation - must be actual video URLs
- return this.isYouTubeUrl(trimmed) || this.isVimeoUrl(trimmed) || this.isYouTubePlaylist(trimmed);
- }
-
- static isYouTubeUrl(url) {
- if (!url) return false;
- // Match YouTube watch URLs and youtu.be URLs
- return /(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)[\w\-_]{11}/.test(url);
- }
-
- static isVimeoUrl(url) {
- if (!url) return false;
- // Match both vimeo.com/ID and player.vimeo.com/video/ID
- return /(?:vimeo\.com\/|player\.vimeo\.com\/video\/)\d+/.test(url);
- }
-
- static isYouTubePlaylist(url) {
- if (!url) return false;
- return /youtube\.com\/playlist\?list=[\w\-_]+/.test(url);
- }
-
- static normalizeUrl(url) {
- if (!url || typeof url !== 'string') return url;
- let normalized = url.trim();
-
- // Add protocol if missing
- if (!/^https?:\/\//.test(normalized)) {
- normalized = 'https://' + normalized;
- }
-
- // Add www. for YouTube if missing
- if (/^https?:\/\/youtube\.com/.test(normalized)) {
- normalized = normalized.replace('://youtube.com', '://www.youtube.com');
- }
-
- return normalized;
- }
-
- static validateMultipleUrls(text) {
- if (!text || typeof text !== 'string') {
- return { valid: [], invalid: [] };
- }
-
- const lines = text.split('\n').map(l => l.trim()).filter(l => l);
- const valid = [];
- const invalid = [];
-
- lines.forEach(line => {
- const normalized = this.normalizeUrl(line);
- if (this.isValidVideoUrl(normalized)) {
- valid.push(normalized);
- } else {
- invalid.push(line);
- }
- });
-
- // Remove duplicates
- return {
- valid: [...new Set(valid)],
- invalid: [...new Set(invalid)]
- };
- }
-
- static getValidationError(url) {
- if (!url) return 'URL is required';
- if (typeof url !== 'string') return 'URL is required';
- if (!url.trim()) return 'URL cannot be empty';
- if (!url.includes('.')) return 'Invalid URL format - must include domain';
- if (!this.isValidVideoUrl(url)) return 'Unsupported video platform - currently supports YouTube and Vimeo';
- return null;
- }
- };
- }
- }
- });
- describe('URL Validation - Task 8', () => {
- describe('YouTube URL Validation', () => {
- it('should validate standard YouTube URLs', () => {
- const validUrls = [
- 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
- 'https://youtube.com/watch?v=dQw4w9WgXcQ',
- 'http://www.youtube.com/watch?v=dQw4w9WgXcQ',
- 'www.youtube.com/watch?v=dQw4w9WgXcQ',
- 'youtube.com/watch?v=dQw4w9WgXcQ'
- ];
-
- validUrls.forEach(url => {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- expect(URLValidator.isYouTubeUrl(normalized)).toBe(true);
- });
- });
-
- it('should validate YouTube short URLs', () => {
- const validUrls = [
- 'https://youtu.be/dQw4w9WgXcQ',
- 'http://youtu.be/dQw4w9WgXcQ',
- 'youtu.be/dQw4w9WgXcQ'
- ];
-
- validUrls.forEach(url => {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- expect(URLValidator.isYouTubeUrl(normalized)).toBe(true);
- });
- });
-
- it('should validate YouTube playlist URLs', () => {
- const validUrls = [
- 'https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME',
- 'https://youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME',
- 'www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME'
- ];
-
- validUrls.forEach(url => {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- expect(URLValidator.isYouTubePlaylist(normalized)).toBe(true);
- });
- });
- });
-
- describe('Vimeo URL Validation', () => {
- it('should validate standard Vimeo URLs', () => {
- const validUrls = [
- 'https://vimeo.com/123456789',
- 'http://vimeo.com/123456789',
- 'www.vimeo.com/123456789',
- 'vimeo.com/123456789'
- ];
-
- validUrls.forEach(url => {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- expect(URLValidator.isVimeoUrl(normalized)).toBe(true);
- });
- });
-
- it('should validate Vimeo player URLs', () => {
- const validUrls = [
- 'https://player.vimeo.com/video/123456789',
- 'http://player.vimeo.com/video/123456789',
- 'player.vimeo.com/video/123456789'
- ];
-
- validUrls.forEach(url => {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- expect(URLValidator.isVimeoUrl(normalized)).toBe(true);
- });
- });
- });
-
- describe('Invalid URL Handling', () => {
- it('should reject invalid URLs', () => {
- const invalidUrls = [
- '',
- null,
- undefined,
- 'not a url',
- 'https://google.com',
- 'https://facebook.com/video',
- 'https://tiktok.com/@user/video/123',
- 'https://instagram.com/p/abc123'
- ];
-
- invalidUrls.forEach(url => {
- if (url) {
- const normalized = URLValidator.normalizeUrl(url);
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(false);
- } else {
- expect(URLValidator.isValidVideoUrl(url)).toBe(false);
- }
- });
- });
-
- it('should provide detailed validation errors', () => {
- const testCases = [
- { url: '', expectedError: 'URL cannot be empty' },
- { url: null, expectedError: 'URL is required' },
- { url: 'not a url', expectedError: 'Invalid URL format - must include domain' },
- { url: 'https://tiktok.com/@user/video/123', expectedError: 'Unsupported video platform - currently supports YouTube and Vimeo' },
- { url: 'https://google.com', expectedError: 'Unsupported video platform - currently supports YouTube and Vimeo' }
- ];
-
- testCases.forEach(({ url, expectedError }) => {
- const error = URLValidator.getValidationError(url);
- expect(error).toBe(expectedError);
- });
- });
- });
-
- describe('Text Processing', () => {
- it('should extract multiple URLs from text', () => {
- const text = `
- Here are some videos:
- https://www.youtube.com/watch?v=dQw4w9WgXcQ
- https://vimeo.com/123456789
- And another one:
- youtu.be/abcdefghijk
- This is not a video URL: https://google.com
- `;
- const result = URLValidator.validateMultipleUrls(text);
- expect(result.valid).toHaveLength(3);
- expect(result.valid).toContain('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
- expect(result.valid).toContain('https://vimeo.com/123456789');
- expect(result.valid).toContain('https://www.youtube.com/watch?v=abcdefghijk');
- });
-
- it('should handle mixed content and normalize URLs', () => {
- const text = `
- youtube.com/watch?v=dQw4w9WgXcQ
- www.vimeo.com/987654321
- https://youtu.be/dQw4w9WgXcQ
- `;
-
- const result = URLValidator.validateMultipleUrls(text);
- expect(result.valid.length).toBeGreaterThan(0);
- result.valid.forEach(url => {
- expect(url).toMatch(/^https:\/\//);
- expect(URLValidator.isValidVideoUrl(url)).toBe(true);
- });
- });
-
- it('should remove duplicate URLs', () => {
- const text = `
- https://www.youtube.com/watch?v=dQw4w9WgXcQ
- https://www.youtube.com/watch?v=dQw4w9WgXcQ
- youtube.com/watch?v=dQw4w9WgXcQ
- `;
-
- const result = URLValidator.validateMultipleUrls(text);
- // Should normalize and deduplicate
- expect(result.valid).toHaveLength(1);
- expect(result.valid[0]).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
- });
- });
-
- describe('URL Normalization', () => {
- it('should add https protocol to URLs without protocol', () => {
- const testCases = [
- { input: 'youtube.com/watch?v=dQw4w9WgXcQ', expected: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' },
- { input: 'www.vimeo.com/123456789', expected: 'https://vimeo.com/123456789' },
- { input: 'youtu.be/dQw4w9WgXcQ', expected: 'https://youtu.be/dQw4w9WgXcQ' }
- ];
- testCases.forEach(({ input, expected }) => {
- const normalized = URLValidator.normalizeUrl(input);
- expect(normalized).toMatch(/^https:\/\//);
- // Check that it's a valid video URL after normalization
- expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
- });
- });
- });
- describe('YouTube Shorts Support', () => {
- it('should validate YouTube Shorts URLs', () => {
- const shortsUrls = [
- 'https://www.youtube.com/shorts/abc12345678',
- 'https://youtube.com/shorts/xyz98765432',
- 'youtube.com/shorts/test1234567'
- ];
- shortsUrls.forEach(url => {
- expect(URLValidator.isYouTubeUrl(url)).toBe(true);
- expect(URLValidator.isYouTubeShorts(url)).toBe(true);
- });
- });
- it('should detect Shorts URLs correctly', () => {
- // Positive cases
- expect(URLValidator.isYouTubeShorts('https://www.youtube.com/shorts/abc12345678')).toBe(true);
- expect(URLValidator.isYouTubeShorts('https://youtube.com/shorts/xyz98765432')).toBe(true);
- expect(URLValidator.isYouTubeShorts('youtube.com/shorts/test1234567')).toBe(true);
- // Negative cases
- expect(URLValidator.isYouTubeShorts('https://www.youtube.com/watch?v=abc12345678')).toBe(false);
- expect(URLValidator.isYouTubeShorts('https://youtu.be/abc12345678')).toBe(false);
- expect(URLValidator.isYouTubeShorts('https://vimeo.com/123456789')).toBe(false);
- });
- it('should extract video ID from Shorts URLs', () => {
- const url = 'https://www.youtube.com/shorts/abc12345678';
- const videoId = URLValidator.extractYouTubeId(url);
- expect(videoId).toBe('abc12345678');
- });
- it('should extract video ID from various Shorts URL formats', () => {
- const testCases = [
- { url: 'https://www.youtube.com/shorts/abc12345678', expected: 'abc12345678' },
- { url: 'https://youtube.com/shorts/xyz98765432', expected: 'xyz98765432' },
- { url: 'youtube.com/shorts/test1234567', expected: 'test1234567' }
- ];
- testCases.forEach(({ url, expected }) => {
- const normalized = URLValidator.normalizeUrl(url);
- const videoId = URLValidator.extractYouTubeId(normalized);
- expect(videoId).toBe(expected);
- });
- });
- it('should normalize Shorts URLs to watch URLs', () => {
- const shortsUrl = 'https://www.youtube.com/shorts/abc12345678';
- const normalized = URLValidator.normalizeUrl(shortsUrl);
- expect(normalized).toBe('https://www.youtube.com/watch?v=abc12345678');
- });
- it('should extract Shorts URLs from multi-line text', () => {
- const text = `
- Check out this short: https://youtube.com/shorts/abc12345678
- And this one: https://www.youtube.com/shorts/xyz98765432
- Regular video: https://www.youtube.com/watch?v=test1234567
- `;
- const { valid } = URLValidator.validateMultipleUrls(text);
- expect(valid.length).toBe(3);
- // All should be normalized to watch URLs
- valid.forEach(url => {
- expect(url).toMatch(/youtube\.com\/watch\?v=/);
- });
- });
- it('should handle Shorts URLs with additional parameters', () => {
- const urlWithParams = 'https://www.youtube.com/shorts/abc12345678?feature=share';
- expect(URLValidator.isYouTubeUrl(urlWithParams)).toBe(true);
- expect(URLValidator.isYouTubeShorts(urlWithParams)).toBe(true);
- const videoId = URLValidator.extractYouTubeId(urlWithParams);
- expect(videoId).toBe('abc12345678');
- });
- it('should deduplicate Shorts URLs that point to the same video', () => {
- const text = `
- https://www.youtube.com/shorts/abc12345678
- https://youtube.com/shorts/abc12345678
- https://www.youtube.com/watch?v=abc12345678
- `;
- const { valid } = URLValidator.validateMultipleUrls(text);
- // All three URLs point to the same video, should be deduplicated to 1
- expect(valid.length).toBe(1);
- expect(valid[0]).toBe('https://www.youtube.com/watch?v=abc12345678');
- });
- });
- });
|