/** * Unit Tests: Sitemap Generation Logic * * Tests sitemap XML generation functions * Tests T028, T029, T030 */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; describe('Unit: escapeXml() (T028)', () => { // Mock XML escape function (will be in proxy.js) function escapeXml(str) { if (typeof str !== 'string') return ''; return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } it('T028: should escape < character to <', () => { // Given: String with < character const input = 'test < value'; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape < assert.equal(output, 'test < value', 'Should escape <'); }); it('T028: should escape > character to >', () => { // Given: String with > character const input = 'test > value'; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape > assert.equal(output, 'test > value', 'Should escape >'); }); it('T028: should escape & character to &', () => { // Given: String with & character const input = 'test & value'; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape & assert.equal(output, 'test & value', 'Should escape &'); }); it('T028: should escape " character to "', () => { // Given: String with " character const input = 'test "value"'; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape " assert.equal(output, 'test "value"', 'Should escape "'); }); it('T028: should escape \' character to '', () => { // Given: String with ' character const input = "test 'value'"; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape ' assert.equal(output, 'test 'value'', 'Should escape \''); }); it('T028: should escape multiple special characters in correct order', () => { // Given: String with multiple special characters const input = 'content & more'; // When: Escaping for XML const output = escapeXml(input); // Then: Should escape all characters properly assert.equal( output, '<tag attr="value" other='test'>content & more</tag>', 'Should escape all XML special characters' ); }); it('T028: should handle strings without special characters', () => { // Given: String without special characters const input = 'normal text 123'; // When: Escaping for XML const output = escapeXml(input); // Then: Should return unchanged assert.equal(output, input, 'Should not modify strings without special chars'); }); it('T028: should handle empty string', () => { // Given: Empty string const input = ''; // When: Escaping for XML const output = escapeXml(input); // Then: Should return empty string assert.equal(output, '', 'Should handle empty string'); }); it('T028: should handle non-string input gracefully', () => { // Given: Non-string inputs const inputs = [null, undefined, 123, { foo: 'bar' }]; // When: Escaping each input // Then: Should return empty string for non-strings inputs.forEach(input => { const output = escapeXml(input); assert.equal(output, '', `Should return empty string for ${typeof input}`); }); }); }); describe('Unit: formatSitemapEntry() (T029)', () => { // Mock sitemap entry formatter (will be in proxy.js) function formatSitemapEntry(document, baseUrl) { function escapeXml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } const loc = `${baseUrl}/${document.id}`; const lastmod = document.modifiedTime; return ` ${escapeXml(loc)} ${lastmod} `; } it('T029: should convert DriveDocument to XML url element', () => { // Given: DriveDocument metadata const document = { id: '1BxAA_test123', name: 'Test Document', modifiedTime: '2026-03-06T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // When: Formatting sitemap entry const xml = formatSitemapEntry(document, baseUrl); // Then: Should generate valid XML assert.ok(xml.includes(''), 'Should contain opening url tag'); assert.ok(xml.includes(''), 'Should contain closing url tag'); assert.ok(xml.includes(''), 'Should contain loc element'); assert.ok(xml.includes(''), 'Should contain closing loc tag'); assert.ok(xml.includes(''), 'Should contain lastmod element'); assert.ok(xml.includes(''), 'Should contain closing lastmod tag'); }); it('T029: should include correct location URL with documentId', () => { // Given: DriveDocument metadata const document = { id: '1BxAA_test123', name: 'Test Document', modifiedTime: '2026-03-06T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // When: Formatting sitemap entry const xml = formatSitemapEntry(document, baseUrl); // Then: Location should point to adapter endpoint assert.ok( xml.includes(`http://localhost:3000/${document.id}`), 'Should include correct location URL' ); }); it('T029: should include ISO 8601 lastmod timestamp', () => { // Given: DriveDocument with modified time const document = { id: '1BxAA_test123', name: 'Test Document', modifiedTime: '2026-03-06T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // When: Formatting sitemap entry const xml = formatSitemapEntry(document, baseUrl); // Then: Should include lastmod with ISO 8601 timestamp assert.ok( xml.includes('2026-03-06T10:30:00Z'), 'Should include ISO 8601 lastmod timestamp' ); }); it('T029: should escape special XML characters in URL', () => { // Given: DriveDocument with special characters in ID (edge case) const document = { id: '1BxAA-test&123', name: 'Test Document', modifiedTime: '2026-03-06T10:30:00Z' }; const baseUrl = 'http://localhost:3000'; // When: Formatting sitemap entry const xml = formatSitemapEntry(document, baseUrl); // Then: Should escape & in URL assert.ok( xml.includes('&'), 'Should escape special XML characters in URL' ); }); it('T029: should handle different baseUrl formats', () => { // Given: Different baseUrl formats const document = { id: '1BxAA_test', name: 'Test', modifiedTime: '2026-03-06T10:30:00Z' }; const baseUrls = [ 'http://localhost:3000', 'https://example.com', 'https://api.example.com/v1' ]; // When: Formatting with each baseUrl // Then: Should generate correct loc for each baseUrls.forEach(baseUrl => { const xml = formatSitemapEntry(document, baseUrl); assert.ok( xml.includes(`${baseUrl}/${document.id}`), `Should work with baseUrl: ${baseUrl}` ); }); }); }); describe('Unit: generateSitemap() Structure (T030)', () => { // Mock sitemap generator structure (will be in proxy.js) function buildSitemapXml(documents, baseUrl) { function escapeXml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } let xml = '\n'; xml += '\n'; documents.forEach(doc => { const loc = `${baseUrl}/${doc.id}`; xml += ` \n`; xml += ` ${escapeXml(loc)}\n`; xml += ` ${doc.modifiedTime}\n`; xml += ` \n`; }); xml += ''; return xml; } it('T030: should build complete XML with declaration', () => { // Given: Array of documents const documents = [ { id: '1BxAA_doc1', name: 'Doc 1', modifiedTime: '2026-03-06T10:00:00Z' } ]; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: Should start with XML declaration assert.ok( xml.startsWith(' { // Given: Array of documents const documents = [ { id: '1BxAA_doc1', name: 'Doc 1', modifiedTime: '2026-03-06T10:00:00Z' } ]; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: Should include sitemap protocol namespace assert.ok( xml.includes(''), 'Should include correct sitemap namespace' ); }); it('T030: should include closing urlset tag', () => { // Given: Array of documents const documents = [ { id: '1BxAA_doc1', name: 'Doc 1', modifiedTime: '2026-03-06T10:00:00Z' } ]; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: Should end with closing urlset tag assert.ok(xml.endsWith(''), 'Should end with closing urlset tag'); }); it('T030: should include multiple url entries for multiple documents', () => { // Given: Multiple documents const documents = [ { id: '1BxAA_doc1', name: 'Doc 1', modifiedTime: '2026-03-06T10:00:00Z' }, { id: '2CyBB_doc2', name: 'Doc 2', modifiedTime: '2026-03-06T11:00:00Z' }, { id: '3DzCC_doc3', name: 'Doc 3', modifiedTime: '2026-03-06T12:00:00Z' } ]; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: Should include all documents const urlCount = (xml.match(//g) || []).length; assert.equal(urlCount, 3, 'Should include 3 url entries'); // Then: Each document should have its loc documents.forEach(doc => { assert.ok( xml.includes(`http://localhost:3000/${doc.id}`), `Should include url entry for ${doc.id}` ); }); }); it('T030: should handle empty document list', () => { // Given: Empty documents array const documents = []; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: Should still have valid XML structure assert.ok(xml.includes(''), 'Should have urlset closing'); // Then: Should have no url entries const urlCount = (xml.match(//g) || []).length; assert.equal(urlCount, 0, 'Should have no url entries'); }); it('T030: should generate valid XML that browsers can parse', () => { // Given: Sample documents const documents = [ { id: '1BxAA_test', name: 'Test', modifiedTime: '2026-03-06T10:00:00Z' } ]; const baseUrl = 'http://localhost:3000'; // When: Building sitemap XML const xml = buildSitemapXml(documents, baseUrl); // Then: XML should be well-formed (basic checks) // Count opening and closing tags const openingUrlset = (xml.match(//g) || []).length; assert.equal(openingUrlset, closingUrlset, 'urlset tags should be balanced'); const openingUrl = (xml.match(//g) || []).length; const closingUrl = (xml.match(/<\/url>/g) || []).length; assert.equal(openingUrl, closingUrl, 'url tags should be balanced'); }); });