Initial Version of sitemap.xml spec
This commit is contained in:
366
tests/unit/sitemap-generator.test.js
Normal file
366
tests/unit/sitemap-generator.test.js
Normal file
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* 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 = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>http://localhost:3000/documents/doc1</loc>
|
||||
<lastmod>2024-03-01</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>http://localhost:3000/documents/doc2</loc>
|
||||
<lastmod>2024-03-02</lastmod>
|
||||
</url>
|
||||
</urlset>`;
|
||||
|
||||
// assert.ok(xml.includes('<?xml version="1.0" encoding="UTF-8"?>'), 'Should have XML declaration');
|
||||
// assert.ok(xml.includes('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'), 'Should have urlset with namespace');
|
||||
// assert.ok(xml.includes('</urlset>'), 'Should close urlset');
|
||||
// assert.ok(xml.includes('<loc>http://localhost:3000/documents/doc1</loc>'), 'Should include first URL');
|
||||
// assert.ok(xml.includes('<loc>http://localhost:3000/documents/doc2</loc>'), '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, /<loc>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('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'), 'Should have urlset');
|
||||
// assert.ok(xml.includes('</urlset>'), 'Should close urlset');
|
||||
// assert.ok(!xml.includes('<url>'), '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&b<c>d"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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user