/** * Unit Tests: Sitemap Generator * * Tests T035-T037, T040: Test sitemap XML generation and transformations * Tests the sitemap-generator.js module in isolation * * @module tests/unit/sitemap-generator */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; // ============================================================================= // T035: Unit test for sitemap XML generation // ============================================================================= describe('T035: Sitemap XML Generation', () => { it('should generate valid sitemap XML with correct structure', () => { // Mock sitemap entries const mockEntries = [ { loc: 'http://localhost:3000/documents/doc1', lastmod: '2024-03-01' }, { loc: 'http://localhost:3000/documents/doc2', lastmod: '2024-03-02' } ]; // TODO: Import generateSitemapXML from src/sitemap-generator.js // const { generateSitemapXML } = await import('../../src/sitemap-generator.js'); // const xml = generateSitemapXML(mockEntries); // Verify XML structure const expectedXml = ` http://localhost:3000/documents/doc1 2024-03-01 http://localhost:3000/documents/doc2 2024-03-02 `; // assert.ok(xml.includes(''), 'Should have XML declaration'); // assert.ok(xml.includes(''), 'Should have urlset with namespace'); // assert.ok(xml.includes(''), 'Should close urlset'); // assert.ok(xml.includes('http://localhost:3000/documents/doc1'), 'Should include first URL'); // assert.ok(xml.includes('http://localhost:3000/documents/doc2'), 'Should include second URL'); }); it('should generate URL entries in correct RESTful format /documents/{documentId}', () => { const mockEntries = [ { loc: 'http://localhost:3000/documents/abc123', lastmod: '2024-03-01' } ]; // TODO: Import generateSitemapXML // const { generateSitemapXML } = await import('../../src/sitemap-generator.js'); // const xml = generateSitemapXML(mockEntries); // Verify RESTful URL format // assert.match(xml, /http:\/\/localhost:3000\/documents\/abc123<\/loc>/, 'Should use RESTful URL format'); }); it('should generate empty sitemap when no entries provided', () => { const mockEntries = []; // TODO: Import generateSitemapXML // const { generateSitemapXML } = await import('../../src/sitemap-generator.js'); // const xml = generateSitemapXML(mockEntries); // Verify empty sitemap structure // assert.ok(xml.includes(''), 'Should have urlset'); // assert.ok(xml.includes(''), 'Should close urlset'); // assert.ok(!xml.includes(''), 'Should not contain any url entries'); }); }); // ============================================================================= // T036: Unit test for Document to SitemapEntry transformation // ============================================================================= describe('T036: Document to SitemapEntry Transformation', () => { it('should transform Document to SitemapEntry with correct URL format', () => { // Mock Document from Drive API const mockDocument = { id: 'abc123', name: 'Test Document', mimeType: 'application/pdf', modifiedTime: '2024-03-01T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // TODO: Import toSitemapEntry from src/sitemap-generator.js // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); // const entry = toSitemapEntry(mockDocument, baseUrl); // Verify transformation // assert.equal(entry.loc, 'http://localhost:3000/documents/abc123', 'Should construct URL with baseUrl + /documents/ + documentId'); // assert.equal(entry.lastmod, '2024-03-01', 'Should format lastmod as YYYY-MM-DD'); }); it('should use encodeURIComponent for document ID in URL', () => { // Document ID with special characters that need URL encoding const mockDocument = { id: 'doc with spaces', name: 'Test', mimeType: 'application/pdf', modifiedTime: '2024-03-01T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // TODO: Import toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); // const entry = toSitemapEntry(mockDocument, baseUrl); // Verify URL encoding // assert.equal(entry.loc, 'http://localhost:3000/documents/doc%20with%20spaces', 'Should URL-encode document ID'); }); it('should concatenate baseUrl + /documents/ + documentId correctly', () => { const testCases = [ { baseUrl: 'http://localhost:3000', documentId: 'doc1', expected: 'http://localhost:3000/documents/doc1' }, { baseUrl: 'https://example.com', documentId: 'doc2', expected: 'https://example.com/documents/doc2' }, { baseUrl: 'http://localhost:3000/', // With trailing slash documentId: 'doc3', expected: 'http://localhost:3000/documents/doc3' // Should handle trailing slash } ]; // TODO: Import toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); testCases.forEach(testCase => { const mockDocument = { id: testCase.documentId, name: 'Test', mimeType: 'application/pdf' }; // const entry = toSitemapEntry(mockDocument, testCase.baseUrl); // assert.equal(entry.loc, testCase.expected, `Should correctly concatenate URL for baseUrl: ${testCase.baseUrl}`); }); }); it('should handle documents without modifiedTime', () => { const mockDocument = { id: 'doc1', name: 'Test Document', mimeType: 'application/pdf' // No modifiedTime }; const baseUrl = 'http://localhost:3000'; // TODO: Import toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); // const entry = toSitemapEntry(mockDocument, baseUrl); // Verify lastmod is undefined or omitted // assert.equal(entry.loc, 'http://localhost:3000/documents/doc1', 'Should have loc'); // assert.equal(entry.lastmod, undefined, 'Should not have lastmod when modifiedTime is missing'); }); }); // ============================================================================= // T037: Unit test for lastmod date formatting // ============================================================================= describe('T037: lastmod Date Formatting', () => { it('should format modifiedTime as ISO 8601 date (YYYY-MM-DD)', () => { const testCases = [ { modifiedTime: '2024-03-01T10:30:00Z', expected: '2024-03-01' }, { modifiedTime: '2024-12-31T23:59:59Z', expected: '2024-12-31' }, { modifiedTime: '2024-01-15T00:00:00Z', expected: '2024-01-15' } ]; // TODO: Import formatLastmod or toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); testCases.forEach(testCase => { const mockDocument = { id: 'doc1', name: 'Test', mimeType: 'application/pdf', modifiedTime: testCase.modifiedTime }; // const entry = toSitemapEntry(mockDocument, 'http://localhost:3000'); // assert.equal(entry.lastmod, testCase.expected, `Should format ${testCase.modifiedTime} as ${testCase.expected}`); }); }); it('should extract date part from ISO 8601 timestamp', () => { // modifiedTime from Drive API is full ISO 8601 timestamp const modifiedTime = '2024-03-01T10:30:45.123Z'; // TODO: Import formatLastmod or toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); const mockDocument = { id: 'doc1', name: 'Test', mimeType: 'application/pdf', modifiedTime }; // const entry = toSitemapEntry(mockDocument, 'http://localhost:3000'); // Should extract only date part (YYYY-MM-DD) // assert.equal(entry.lastmod, '2024-03-01', 'Should extract date part only'); // assert.match(entry.lastmod, /^\d{4}-\d{2}-\d{2}$/, 'Should match YYYY-MM-DD format'); }); it('should handle different timezone formats in modifiedTime', () => { const testCases = [ '2024-03-01T10:30:00Z', // UTC '2024-03-01T10:30:00+00:00', // UTC with offset '2024-03-01T10:30:00-08:00', // PST '2024-03-01T10:30:00+05:30' // IST ]; // TODO: Import toSitemapEntry // const { toSitemapEntry } = await import('../../src/sitemap-generator.js'); testCases.forEach(modifiedTime => { const mockDocument = { id: 'doc1', name: 'Test', mimeType: 'application/pdf', modifiedTime }; // const entry = toSitemapEntry(mockDocument, 'http://localhost:3000'); // Should parse all timezone formats correctly // assert.match(entry.lastmod, /^\d{4}-\d{2}-\d{2}$/, `Should format date correctly for ${modifiedTime}`); }); }); }); // ============================================================================= // T040: Unit test for XML special character escaping // ============================================================================= describe('T040: XML Special Character Escaping', () => { it('should escape ampersand (&) as &', () => { const url = 'http://localhost:3000/documents/doc&test'; // TODO: Import escapeXml from src/xml-utils.js // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, 'http://localhost:3000/documents/doc&test', 'Should escape & as &'); // assert.ok(!escaped.includes('&test'), 'Should not contain unescaped &'); }); it('should escape less than (<) as <', () => { const url = 'http://localhost:3000/documents/doc<123'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, 'http://localhost:3000/documents/doc<123', 'Should escape < as <'); }); it('should escape greater than (>) as >', () => { const url = 'http://localhost:3000/documents/doc>456'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, 'http://localhost:3000/documents/doc>456', 'Should escape > as >'); }); it('should escape double quote (") as "', () => { const url = 'http://localhost:3000/documents/doc"test'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, 'http://localhost:3000/documents/doc"test', 'Should escape " as "'); }); it('should escape single quote (\') as '', () => { const url = "http://localhost:3000/documents/doc'xyz"; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, "http://localhost:3000/documents/doc'xyz", "Should escape ' as '"); }); it('should escape multiple special characters in same string', () => { const url = 'http://localhost:3000/documents/a&bd"e\'f'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal( // escaped, // 'http://localhost:3000/documents/a&b<c>d"e'f', // 'Should escape all special characters' // ); }); it('should not double-escape already escaped characters', () => { const url = 'http://localhost:3000/documents/doc&test'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // Should not double-escape // assert.ok(!escaped.includes('&amp;'), 'Should not double-escape &'); }); it('should handle empty string', () => { // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(''); // assert.equal(escaped, '', 'Should return empty string for empty input'); }); it('should handle string with no special characters', () => { const url = 'http://localhost:3000/documents/doc123'; // TODO: Import escapeXml // const { escapeXml } = await import('../../src/xml-utils.js'); // const escaped = escapeXml(url); // assert.equal(escaped, url, 'Should return unchanged string when no special chars'); }); });