226 lines
7.4 KiB
TypeScript
226 lines
7.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
isSameOrigin,
|
|
isBlobOrDataURL,
|
|
validateVideoURL,
|
|
getCORSErrorMessage,
|
|
isCORSError,
|
|
checkVideoCORS,
|
|
} from './corsHelper';
|
|
|
|
describe('corsHelper', () => {
|
|
describe('isSameOrigin', () => {
|
|
it('returns true for same origin URLs', () => {
|
|
const sameOriginUrl = `${window.location.origin}/video.mp4`;
|
|
expect(isSameOrigin(sameOriginUrl)).toBe(true);
|
|
});
|
|
|
|
it('returns false for different origin URLs', () => {
|
|
expect(isSameOrigin('https://example.com/video.mp4')).toBe(false);
|
|
});
|
|
|
|
it('returns true for relative URLs', () => {
|
|
expect(isSameOrigin('/videos/test.mp4')).toBe(true);
|
|
});
|
|
|
|
it('returns true for relative path-like strings', () => {
|
|
// In browsers, "not-a-url" is treated as a relative URL
|
|
expect(isSameOrigin('not-a-url')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('isBlobOrDataURL', () => {
|
|
it('returns true for blob URLs', () => {
|
|
expect(isBlobOrDataURL('blob:http://example.com/123456')).toBe(true);
|
|
});
|
|
|
|
it('returns true for data URLs', () => {
|
|
expect(isBlobOrDataURL('data:video/mp4;base64,AAAA')).toBe(true);
|
|
});
|
|
|
|
it('returns false for regular URLs', () => {
|
|
expect(isBlobOrDataURL('https://example.com/video.mp4')).toBe(false);
|
|
});
|
|
|
|
it('returns false for empty string', () => {
|
|
expect(isBlobOrDataURL('')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('validateVideoURL', () => {
|
|
it('returns invalid for empty URL', () => {
|
|
const result = validateVideoURL('');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.error).toBe('Video URL is empty');
|
|
});
|
|
|
|
it('returns invalid for whitespace-only URL', () => {
|
|
const result = validateVideoURL(' ');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.error).toBe('Video URL is empty');
|
|
});
|
|
|
|
it('returns valid for relative path strings', () => {
|
|
// Browser treats this as a relative URL
|
|
const result = validateVideoURL('not a valid url');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('returns valid for same origin URL without warning', () => {
|
|
const result = validateVideoURL(`${window.location.origin}/video.mp4`);
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warning).toBeUndefined();
|
|
});
|
|
|
|
it('returns valid for blob URL without warning', () => {
|
|
const result = validateVideoURL('blob:http://example.com/123456');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warning).toBeUndefined();
|
|
});
|
|
|
|
it('returns valid with warning for external URL', () => {
|
|
const result = validateVideoURL('https://example.com/video.mp4');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warning).toContain('CORS');
|
|
});
|
|
|
|
it('returns valid for relative URLs', () => {
|
|
const result = validateVideoURL('/videos/test.mp4');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warning).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getCORSErrorMessage', () => {
|
|
it('returns generic message for same origin', () => {
|
|
const message = getCORSErrorMessage(`${window.location.origin}/video.mp4`);
|
|
expect(message).toBe('Failed to load video. Please check the URL.');
|
|
});
|
|
|
|
it('returns generic message for blob URLs', () => {
|
|
const message = getCORSErrorMessage('blob:http://example.com/123456');
|
|
expect(message).toBe('Failed to load video. Please check the URL.');
|
|
});
|
|
|
|
it('returns CORS-specific message for external URLs', () => {
|
|
const message = getCORSErrorMessage('https://example.com/video.mp4');
|
|
expect(message).toContain('CORS Error');
|
|
expect(message).toContain('example.com');
|
|
expect(message).toContain('Access-Control-Allow-Origin');
|
|
});
|
|
});
|
|
|
|
describe('isCORSError', () => {
|
|
it('returns true for errors containing "cors"', () => {
|
|
const error = new Error('CORS policy blocked this request');
|
|
expect(isCORSError(error)).toBe(true);
|
|
});
|
|
|
|
it('returns true for errors containing "cross-origin"', () => {
|
|
const error = new Error('Cross-origin request blocked');
|
|
expect(isCORSError(error)).toBe(true);
|
|
});
|
|
|
|
it('returns true for errors containing "blocked by cors policy"', () => {
|
|
const error = new Error('Request blocked by CORS policy');
|
|
expect(isCORSError(error)).toBe(true);
|
|
});
|
|
|
|
it('returns true for errors containing "access-control-allow-origin"', () => {
|
|
const error = new Error('No \'access-control-allow-origin\' header present');
|
|
expect(isCORSError(error)).toBe(true);
|
|
});
|
|
|
|
it('returns false for non-CORS errors', () => {
|
|
const error = new Error('Network timeout');
|
|
expect(isCORSError(error)).toBe(false);
|
|
});
|
|
|
|
it('is case insensitive', () => {
|
|
const error = new Error('BLOCKED BY CORS POLICY');
|
|
expect(isCORSError(error)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('checkVideoCORS', () => {
|
|
beforeEach(() => {
|
|
globalThis.fetch = vi.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('returns supported when CORS headers are present', async () => {
|
|
(globalThis.fetch as any).mockResolvedValue({
|
|
ok: true,
|
|
headers: new Map([
|
|
['Access-Control-Allow-Origin', '*'],
|
|
['Accept-Ranges', 'bytes'],
|
|
]),
|
|
});
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supported).toBe(true);
|
|
expect(result.needsProxy).toBe(false);
|
|
expect(result.supportsRange).toBe(true);
|
|
});
|
|
|
|
it('returns not supported when CORS headers are missing', async () => {
|
|
(globalThis.fetch as any).mockResolvedValue({
|
|
ok: false,
|
|
headers: new Map(),
|
|
});
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supported).toBe(false);
|
|
expect(result.needsProxy).toBe(true);
|
|
expect(result.error).toContain('CORS not enabled');
|
|
});
|
|
|
|
it('detects range support', async () => {
|
|
(globalThis.fetch as any).mockResolvedValue({
|
|
ok: true,
|
|
headers: new Map([
|
|
['Access-Control-Allow-Origin', '*'],
|
|
['Accept-Ranges', 'bytes'],
|
|
]),
|
|
});
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supportsRange).toBe(true);
|
|
});
|
|
|
|
it('detects no range support when header is "none"', async () => {
|
|
(globalThis.fetch as any).mockResolvedValue({
|
|
ok: true,
|
|
headers: new Map([
|
|
['Access-Control-Allow-Origin', '*'],
|
|
['Accept-Ranges', 'none'],
|
|
]),
|
|
});
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supportsRange).toBe(false);
|
|
});
|
|
|
|
it('handles CORS fetch errors', async () => {
|
|
(globalThis.fetch as any).mockRejectedValue(new TypeError('Failed to fetch (CORS)'));
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supported).toBe(false);
|
|
expect(result.needsProxy).toBe(true);
|
|
expect(result.error).toContain('CORS blocked');
|
|
});
|
|
|
|
it('handles general fetch errors', async () => {
|
|
(globalThis.fetch as any).mockRejectedValue(new Error('Network error'));
|
|
|
|
const result = await checkVideoCORS('https://example.com/video.mp4');
|
|
expect(result.supported).toBe(false);
|
|
expect(result.needsProxy).toBe(true);
|
|
expect(result.error).toBe('Network error');
|
|
});
|
|
});
|
|
});
|