/** * Unit Tests: Document Export Logic * * Tests document export functions in proxy.js * Tests T012, T013, T014, T040, T041 */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; describe('Unit: validateDocumentId() (T012)', () => { // Mock function to test (will be in proxy.js) function validateDocumentId(id) { const pattern = /^[a-zA-Z0-9_-]{8,128}$/; return pattern.test(id); } it('T012: should accept valid 8-character alphanumeric ID', () => { // Given: Valid 8-character document ID const validId = '1BxAA789'; // When: Validating document ID const isValid = validateDocumentId(validId); // Then: Should return true assert.equal(isValid, true, 'Should accept 8-character alphanumeric ID'); }); it('T012: should accept valid 128-character alphanumeric ID', () => { // Given: Valid 128-character document ID const validId = 'a'.repeat(128); // When: Validating document ID const isValid = validateDocumentId(validId); // Then: Should return true assert.equal(isValid, true, 'Should accept 128-character alphanumeric ID'); }); it('T012: should accept IDs with hyphens and underscores', () => { // Given: Valid IDs with hyphens and underscores const idWithHyphen = '1BxAA-test-123'; const idWithUnderscore = '1BxAA_test_123'; const idWithBoth = '1BxAA-test_123'; // When: Validating document IDs const isValidHyphen = validateDocumentId(idWithHyphen); const isValidUnderscore = validateDocumentId(idWithUnderscore); const isValidBoth = validateDocumentId(idWithBoth); // Then: Should return true for all assert.equal(isValidHyphen, true, 'Should accept IDs with hyphens'); assert.equal(isValidUnderscore, true, 'Should accept IDs with underscores'); assert.equal(isValidBoth, true, 'Should accept IDs with both hyphens and underscores'); }); it('T012: should reject IDs shorter than 8 characters', () => { // Given: Invalid short ID const shortId = '1BxAA78'; // When: Validating document ID const isValid = validateDocumentId(shortId); // Then: Should return false assert.equal(isValid, false, 'Should reject IDs shorter than 8 characters'); }); it('T012: should reject IDs longer than 128 characters', () => { // Given: Invalid long ID const longId = 'a'.repeat(129); // When: Validating document ID const isValid = validateDocumentId(longId); // Then: Should return false assert.equal(isValid, false, 'Should reject IDs longer than 128 characters'); }); it('T012: should reject IDs with invalid characters', () => { // Given: IDs with invalid characters const invalidChars = [ '1BxAA@test', // @ symbol '1BxAA test', // space '1BxAA!test', // exclamation '1BxAA#test', // hash '1BxAA.test', // period ]; // When: Validating each ID // Then: All should return false invalidChars.forEach(id => { const isValid = validateDocumentId(id); assert.equal(isValid, false, `Should reject ID with invalid character: ${id}`); }); }); it('T012: should reject empty string', () => { // Given: Empty string const emptyId = ''; // When: Validating document ID const isValid = validateDocumentId(emptyId); // Then: Should return false assert.equal(isValid, false, 'Should reject empty string'); }); }); describe('Unit: findExportLink() (T013, T041)', () => { // Mock function to test (will be in proxy.js) function findExportLink(exportLinks, format = 'markdown') { if (!exportLinks) return null; const formatMap = { 'markdown': ['text/x-markdown', 'text/markdown', 'text/html'], 'html': ['text/html'], 'pdf': ['application/pdf'] }; const mimeTypes = formatMap[format.toLowerCase()] || []; for (const mimeType of mimeTypes) { if (exportLinks[mimeType]) { return exportLinks[mimeType]; } } return null; } it('T013: should select text/x-markdown from exportLinks when available', () => { // Given: exportLinks with text/x-markdown const exportLinks = { 'text/x-markdown': 'https://docs.google.com/export?format=markdown', 'text/html': 'https://docs.google.com/export?format=html', 'application/pdf': 'https://docs.google.com/export?format=pdf' }; // When: Finding export link for markdown format const link = findExportLink(exportLinks, 'markdown'); // Then: Should select text/x-markdown assert.equal(link, exportLinks['text/x-markdown'], 'Should select text/x-markdown'); }); it('T013: should fall back to text/html when text/x-markdown unavailable', () => { // Given: exportLinks without text/x-markdown or text/markdown const exportLinks = { 'text/html': 'https://docs.google.com/export?format=html', 'application/pdf': 'https://docs.google.com/export?format=pdf' }; // When: Finding export link for markdown format const link = findExportLink(exportLinks, 'markdown'); // Then: Should fall back to text/html assert.equal(link, exportLinks['text/html'], 'Should fall back to text/html'); }); it('T013: should prefer text/markdown over text/html when available', () => { // Given: exportLinks with text/markdown const exportLinks = { 'text/markdown': 'https://docs.google.com/export?format=markdown', 'text/html': 'https://docs.google.com/export?format=html' }; // When: Finding export link for markdown format const link = findExportLink(exportLinks, 'markdown'); // Then: Should select text/markdown assert.equal(link, exportLinks['text/markdown'], 'Should prefer text/markdown'); }); it('T041: should select text/html MIME type for html format', () => { // Given: exportLinks with multiple formats const exportLinks = { 'text/html': 'https://docs.google.com/export?format=html', 'text/x-markdown': 'https://docs.google.com/export?format=markdown', 'application/pdf': 'https://docs.google.com/export?format=pdf' }; // When: Finding export link for html format const link = findExportLink(exportLinks, 'html'); // Then: Should select text/html assert.equal(link, exportLinks['text/html'], 'Should select text/html for html format'); }); it('T041: should select application/pdf MIME type for pdf format', () => { // Given: exportLinks with multiple formats const exportLinks = { 'text/html': 'https://docs.google.com/export?format=html', 'application/pdf': 'https://docs.google.com/export?format=pdf' }; // When: Finding export link for pdf format const link = findExportLink(exportLinks, 'pdf'); // Then: Should select application/pdf assert.equal(link, exportLinks['application/pdf'], 'Should select application/pdf for pdf format'); }); it('T041: should return null when requested format unavailable', () => { // Given: exportLinks without PDF const exportLinks = { 'text/html': 'https://docs.google.com/export?format=html' }; // When: Finding export link for pdf format const link = findExportLink(exportLinks, 'pdf'); // Then: Should return null assert.equal(link, null, 'Should return null when format unavailable'); }); it('should return null when exportLinks is null or undefined', () => { // Given: Null or undefined exportLinks const linkFromNull = findExportLink(null, 'markdown'); const linkFromUndefined = findExportLink(undefined, 'markdown'); // Then: Should return null assert.equal(linkFromNull, null, 'Should return null for null exportLinks'); assert.equal(linkFromUndefined, null, 'Should return null for undefined exportLinks'); }); }); describe('Unit: validateDocumentSize() (T014)', () => { // Mock function to test (will be in proxy.js) function validateDocumentSize(metadata) { const maxSize = 20 * 1024 * 1024; // 20MB // Native Drive files (Docs, Sheets, Slides) don't have size property if (!metadata.size) { return { valid: true }; } const size = parseInt(metadata.size, 10); if (size > maxSize) { return { valid: false, error: 'Document exceeds 20MB size limit', statusCode: 413 }; } return { valid: true, size }; } it('T014: should accept documents under 20MB', () => { // Given: Document metadata with size < 20MB const metadata = { id: '1BxAA_test', name: 'test.pdf', size: '10485760' // 10MB }; // When: Validating document size const result = validateDocumentSize(metadata); // Then: Should be valid assert.equal(result.valid, true, 'Should accept document < 20MB'); assert.equal(result.size, 10485760, 'Should return parsed size'); }); it('T014: should accept documents exactly at 20MB', () => { // Given: Document metadata with size exactly 20MB const metadata = { id: '1BxAA_test', name: 'test.pdf', size: '20971520' // Exactly 20MB }; // When: Validating document size const result = validateDocumentSize(metadata); // Then: Should be valid assert.equal(result.valid, true, 'Should accept document exactly at 20MB'); }); it('T014: should reject documents over 20MB', () => { // Given: Document metadata with size > 20MB const metadata = { id: '1BxAA_test', name: 'large.pdf', size: '20971521' // 20MB + 1 byte }; // When: Validating document size const result = validateDocumentSize(metadata); // Then: Should be invalid assert.equal(result.valid, false, 'Should reject document > 20MB'); assert.equal(result.statusCode, 413, 'Should return 413 status code'); assert.ok(result.error, 'Should include error message'); }); it('T014: should accept native Google Drive documents without size', () => { // Given: Google Doc metadata (no size property) const metadata = { id: '1BxAA_test', name: 'My Document', mimeType: 'application/vnd.google-apps.document' // Note: No size property for native Drive files }; // When: Validating document size const result = validateDocumentSize(metadata); // Then: Should be valid (native files exported on-the-fly) assert.equal(result.valid, true, 'Should accept native Drive documents without size'); }); it('T014: should handle size as number string', () => { // Given: Document metadata with size as string (Drive API returns strings) const metadata = { id: '1BxAA_test', name: 'test.pdf', size: '5242880' // 5MB as string }; // When: Validating document size const result = validateDocumentSize(metadata); // Then: Should parse and validate correctly assert.equal(result.valid, true, 'Should handle size as string'); assert.equal(result.size, 5242880, 'Should parse size to number'); }); }); describe('Unit: parseFormatParam() (T040)', () => { // Mock function to test (will be in proxy.js) function parseFormatParam(url) { const urlObj = new URL(url, 'http://localhost'); const format = urlObj.searchParams.get('format'); if (!format) { return { valid: true, format: 'markdown' }; // Default } const normalized = format.toLowerCase(); const validFormats = ['markdown', 'html', 'pdf']; if (!validFormats.includes(normalized)) { return { valid: false, error: 'Invalid format parameter', statusCode: 400 }; } return { valid: true, format: normalized }; } it('T040: should extract format parameter from query string', () => { // Given: URL with format parameter const url = '/1BxAA_test?format=html'; // When: Parsing format parameter const result = parseFormatParam(url); // Then: Should extract format assert.equal(result.valid, true, 'Should be valid'); assert.equal(result.format, 'html', 'Should extract html format'); }); it('T040: should validate against allowed values (markdown|html|pdf)', () => { // Given: URLs with valid formats const urls = [ '/doc?format=markdown', '/doc?format=html', '/doc?format=pdf' ]; // When: Parsing each URL // Then: All should be valid urls.forEach(url => { const result = parseFormatParam(url); assert.equal(result.valid, true, `Should accept format in ${url}`); }); }); it('T040: should return default markdown when format parameter missing', () => { // Given: URL without format parameter const url = '/1BxAA_test'; // When: Parsing format parameter const result = parseFormatParam(url); // Then: Should default to markdown assert.equal(result.valid, true, 'Should be valid'); assert.equal(result.format, 'markdown', 'Should default to markdown'); }); it('T040: should normalize format to lowercase', () => { // Given: URL with uppercase format const urls = [ '/doc?format=HTML', '/doc?format=Markdown', '/doc?format=PDF' ]; // When: Parsing each URL // Then: Should normalize to lowercase assert.equal(parseFormatParam(urls[0]).format, 'html', 'Should normalize HTML to html'); assert.equal(parseFormatParam(urls[1]).format, 'markdown', 'Should normalize Markdown to markdown'); assert.equal(parseFormatParam(urls[2]).format, 'pdf', 'Should normalize PDF to pdf'); }); it('T040: should return 400 status for invalid format values', () => { // Given: URL with invalid format const url = '/1BxAA_test?format=invalid'; // When: Parsing format parameter const result = parseFormatParam(url); // Then: Should be invalid assert.equal(result.valid, false, 'Should be invalid'); assert.equal(result.statusCode, 400, 'Should return 400 status'); assert.ok(result.error, 'Should include error message'); }); it('T040: should handle multiple query parameters', () => { // Given: URL with multiple query parameters const url = '/1BxAA_test?format=pdf&other=value&another=param'; // When: Parsing format parameter const result = parseFormatParam(url); // Then: Should extract format correctly assert.equal(result.valid, true, 'Should be valid'); assert.equal(result.format, 'pdf', 'Should extract format from multi-param URL'); }); });