Initial Version of sitemap.xml spec
This commit is contained in:
438
tests/unit/proxy-export.test.js.old
Normal file
438
tests/unit/proxy-export.test.js.old
Normal file
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user