/**
* 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');
});
});