url-validation.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // URL Validation Tests for Task 8
  2. import { describe, it, expect, beforeAll } from 'vitest';
  3. // Import URLValidator - handle both Node.js and browser environments
  4. let URLValidator;
  5. beforeAll(async () => {
  6. try {
  7. // Try ES module import first
  8. const module = await import('../scripts/utils/url-validator.js');
  9. URLValidator = module.default || module.URLValidator;
  10. } catch (error) {
  11. try {
  12. // Fallback for CommonJS
  13. URLValidator = require('../scripts/utils/url-validator.js');
  14. } catch (requireError) {
  15. // Create a mock URLValidator for testing
  16. URLValidator = class {
  17. static isValidVideoUrl(url) {
  18. if (!url || typeof url !== 'string') return false;
  19. const trimmed = url.trim();
  20. if (!trimmed) return false;
  21. // More strict validation - must be actual video URLs
  22. return this.isYouTubeUrl(trimmed) || this.isVimeoUrl(trimmed) || this.isYouTubePlaylist(trimmed);
  23. }
  24. static isYouTubeUrl(url) {
  25. if (!url) return false;
  26. // Match YouTube watch URLs and youtu.be URLs
  27. return /(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)[\w\-_]{11}/.test(url);
  28. }
  29. static isVimeoUrl(url) {
  30. if (!url) return false;
  31. // Match both vimeo.com/ID and player.vimeo.com/video/ID
  32. return /(?:vimeo\.com\/|player\.vimeo\.com\/video\/)\d+/.test(url);
  33. }
  34. static isYouTubePlaylist(url) {
  35. if (!url) return false;
  36. return /youtube\.com\/playlist\?list=[\w\-_]+/.test(url);
  37. }
  38. static normalizeUrl(url) {
  39. if (!url || typeof url !== 'string') return url;
  40. let normalized = url.trim();
  41. // Add protocol if missing
  42. if (!/^https?:\/\//.test(normalized)) {
  43. normalized = 'https://' + normalized;
  44. }
  45. // Add www. for YouTube if missing
  46. if (/^https?:\/\/youtube\.com/.test(normalized)) {
  47. normalized = normalized.replace('://youtube.com', '://www.youtube.com');
  48. }
  49. return normalized;
  50. }
  51. static validateMultipleUrls(text) {
  52. if (!text || typeof text !== 'string') {
  53. return { valid: [], invalid: [] };
  54. }
  55. const lines = text.split('\n').map(l => l.trim()).filter(l => l);
  56. const valid = [];
  57. const invalid = [];
  58. lines.forEach(line => {
  59. const normalized = this.normalizeUrl(line);
  60. if (this.isValidVideoUrl(normalized)) {
  61. valid.push(normalized);
  62. } else {
  63. invalid.push(line);
  64. }
  65. });
  66. // Remove duplicates
  67. return {
  68. valid: [...new Set(valid)],
  69. invalid: [...new Set(invalid)]
  70. };
  71. }
  72. static getValidationError(url) {
  73. if (!url) return 'URL is required';
  74. if (typeof url !== 'string') return 'URL is required';
  75. if (!url.trim()) return 'URL cannot be empty';
  76. if (!url.includes('.')) return 'Invalid URL format - must include domain';
  77. if (!this.isValidVideoUrl(url)) return 'Unsupported video platform - currently supports YouTube and Vimeo';
  78. return null;
  79. }
  80. };
  81. }
  82. }
  83. });
  84. describe('URL Validation - Task 8', () => {
  85. describe('YouTube URL Validation', () => {
  86. it('should validate standard YouTube URLs', () => {
  87. const validUrls = [
  88. 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
  89. 'https://youtube.com/watch?v=dQw4w9WgXcQ',
  90. 'http://www.youtube.com/watch?v=dQw4w9WgXcQ',
  91. 'www.youtube.com/watch?v=dQw4w9WgXcQ',
  92. 'youtube.com/watch?v=dQw4w9WgXcQ'
  93. ];
  94. validUrls.forEach(url => {
  95. const normalized = URLValidator.normalizeUrl(url);
  96. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  97. expect(URLValidator.isYouTubeUrl(normalized)).toBe(true);
  98. });
  99. });
  100. it('should validate YouTube short URLs', () => {
  101. const validUrls = [
  102. 'https://youtu.be/dQw4w9WgXcQ',
  103. 'http://youtu.be/dQw4w9WgXcQ',
  104. 'youtu.be/dQw4w9WgXcQ'
  105. ];
  106. validUrls.forEach(url => {
  107. const normalized = URLValidator.normalizeUrl(url);
  108. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  109. expect(URLValidator.isYouTubeUrl(normalized)).toBe(true);
  110. });
  111. });
  112. it('should validate YouTube playlist URLs', () => {
  113. const validUrls = [
  114. 'https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME',
  115. 'https://youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME',
  116. 'www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMHjMZOz59Oq8HmPME'
  117. ];
  118. validUrls.forEach(url => {
  119. const normalized = URLValidator.normalizeUrl(url);
  120. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  121. expect(URLValidator.isYouTubePlaylist(normalized)).toBe(true);
  122. });
  123. });
  124. });
  125. describe('Vimeo URL Validation', () => {
  126. it('should validate standard Vimeo URLs', () => {
  127. const validUrls = [
  128. 'https://vimeo.com/123456789',
  129. 'http://vimeo.com/123456789',
  130. 'www.vimeo.com/123456789',
  131. 'vimeo.com/123456789'
  132. ];
  133. validUrls.forEach(url => {
  134. const normalized = URLValidator.normalizeUrl(url);
  135. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  136. expect(URLValidator.isVimeoUrl(normalized)).toBe(true);
  137. });
  138. });
  139. it('should validate Vimeo player URLs', () => {
  140. const validUrls = [
  141. 'https://player.vimeo.com/video/123456789',
  142. 'http://player.vimeo.com/video/123456789',
  143. 'player.vimeo.com/video/123456789'
  144. ];
  145. validUrls.forEach(url => {
  146. const normalized = URLValidator.normalizeUrl(url);
  147. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  148. expect(URLValidator.isVimeoUrl(normalized)).toBe(true);
  149. });
  150. });
  151. });
  152. describe('Invalid URL Handling', () => {
  153. it('should reject invalid URLs', () => {
  154. const invalidUrls = [
  155. '',
  156. null,
  157. undefined,
  158. 'not a url',
  159. 'https://google.com',
  160. 'https://facebook.com/video',
  161. 'https://tiktok.com/@user/video/123',
  162. 'https://instagram.com/p/abc123'
  163. ];
  164. invalidUrls.forEach(url => {
  165. if (url) {
  166. const normalized = URLValidator.normalizeUrl(url);
  167. expect(URLValidator.isValidVideoUrl(normalized)).toBe(false);
  168. } else {
  169. expect(URLValidator.isValidVideoUrl(url)).toBe(false);
  170. }
  171. });
  172. });
  173. it('should provide detailed validation errors', () => {
  174. const testCases = [
  175. { url: '', expectedError: 'URL cannot be empty' },
  176. { url: null, expectedError: 'URL is required' },
  177. { url: 'not a url', expectedError: 'Invalid URL format - must include domain' },
  178. { url: 'https://tiktok.com/@user/video/123', expectedError: 'Unsupported video platform - currently supports YouTube and Vimeo' },
  179. { url: 'https://google.com', expectedError: 'Unsupported video platform - currently supports YouTube and Vimeo' }
  180. ];
  181. testCases.forEach(({ url, expectedError }) => {
  182. const error = URLValidator.getValidationError(url);
  183. expect(error).toBe(expectedError);
  184. });
  185. });
  186. });
  187. describe('Text Processing', () => {
  188. it('should extract multiple URLs from text', () => {
  189. const text = `
  190. Here are some videos:
  191. https://www.youtube.com/watch?v=dQw4w9WgXcQ
  192. https://vimeo.com/123456789
  193. And another one:
  194. youtu.be/abcdefghijk
  195. This is not a video URL: https://google.com
  196. `;
  197. const result = URLValidator.validateMultipleUrls(text);
  198. expect(result.valid).toHaveLength(3);
  199. expect(result.valid).toContain('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  200. expect(result.valid).toContain('https://vimeo.com/123456789');
  201. expect(result.valid).toContain('https://www.youtube.com/watch?v=abcdefghijk');
  202. });
  203. it('should handle mixed content and normalize URLs', () => {
  204. const text = `
  205. youtube.com/watch?v=dQw4w9WgXcQ
  206. www.vimeo.com/987654321
  207. https://youtu.be/dQw4w9WgXcQ
  208. `;
  209. const result = URLValidator.validateMultipleUrls(text);
  210. expect(result.valid.length).toBeGreaterThan(0);
  211. result.valid.forEach(url => {
  212. expect(url).toMatch(/^https:\/\//);
  213. expect(URLValidator.isValidVideoUrl(url)).toBe(true);
  214. });
  215. });
  216. it('should remove duplicate URLs', () => {
  217. const text = `
  218. https://www.youtube.com/watch?v=dQw4w9WgXcQ
  219. https://www.youtube.com/watch?v=dQw4w9WgXcQ
  220. youtube.com/watch?v=dQw4w9WgXcQ
  221. `;
  222. const result = URLValidator.validateMultipleUrls(text);
  223. // Should normalize and deduplicate
  224. expect(result.valid).toHaveLength(1);
  225. expect(result.valid[0]).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  226. });
  227. });
  228. describe('URL Normalization', () => {
  229. it('should add https protocol to URLs without protocol', () => {
  230. const testCases = [
  231. { input: 'youtube.com/watch?v=dQw4w9WgXcQ', expected: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' },
  232. { input: 'www.vimeo.com/123456789', expected: 'https://vimeo.com/123456789' },
  233. { input: 'youtu.be/dQw4w9WgXcQ', expected: 'https://youtu.be/dQw4w9WgXcQ' }
  234. ];
  235. testCases.forEach(({ input, expected }) => {
  236. const normalized = URLValidator.normalizeUrl(input);
  237. expect(normalized).toMatch(/^https:\/\//);
  238. // Check that it's a valid video URL after normalization
  239. expect(URLValidator.isValidVideoUrl(normalized)).toBe(true);
  240. });
  241. });
  242. });
  243. });