ObsiViewer/server/ensureFrontmatter.test.mjs

325 lines
8.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Unit tests for front-matter enrichment
*/
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { enrichFrontmatterOnOpen, extractFrontmatter } from './ensureFrontmatter.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const TEST_DIR = path.join(__dirname, '..', 'test-vault-frontmatter');
// Test utilities
async function setupTestDir() {
await fs.mkdir(TEST_DIR, { recursive: true });
}
async function cleanupTestDir() {
try {
await fs.rm(TEST_DIR, { recursive: true, force: true });
} catch (err) {
console.warn('Cleanup warning:', err.message);
}
}
async function createTestFile(filename, content) {
const filePath = path.join(TEST_DIR, filename);
await fs.writeFile(filePath, content, 'utf-8');
return filePath;
}
// Test runner
const tests = [];
function test(name, fn) {
tests.push({ name, fn });
}
// Tests
test('Should add front-matter to file without any', async () => {
const filePath = await createTestFile('no-frontmatter.md', '# Hello World\n\nThis is content.');
const result = await enrichFrontmatterOnOpen(filePath);
if (!result.modified) {
throw new Error('Expected file to be modified');
}
const content = await fs.readFile(filePath, 'utf-8');
if (!content.startsWith('---\n')) {
throw new Error('Expected front-matter to be added');
}
if (!content.includes('titre: no-frontmatter')) {
throw new Error('Expected titre to be set to filename');
}
if (!content.includes('auteur: Bruno Charest')) {
throw new Error('Expected auteur to be set');
}
if (!content.includes('favoris: false')) {
throw new Error('Expected favoris to be false');
}
if (!content.includes('template: false')) {
throw new Error('Expected template to be false');
}
if (!content.includes('task: false')) {
throw new Error('Expected task to be false');
}
console.log('✓ Front-matter added successfully');
});
test('Should be idempotent (no changes on second run)', async () => {
const filePath = await createTestFile('idempotent.md', '# Test\n\nContent here.');
// First enrichment
const result1 = await enrichFrontmatterOnOpen(filePath);
if (!result1.modified) {
throw new Error('Expected first enrichment to modify file');
}
const content1 = await fs.readFile(filePath, 'utf-8');
// Wait a bit to ensure timestamp would differ if modified
await new Promise(resolve => setTimeout(resolve, 100));
// Second enrichment
const result2 = await enrichFrontmatterOnOpen(filePath);
if (result2.modified) {
throw new Error('Expected second enrichment to NOT modify file (idempotent)');
}
const content2 = await fs.readFile(filePath, 'utf-8');
if (content1 !== content2) {
throw new Error('Expected content to be identical after second enrichment');
}
console.log('✓ Idempotence verified');
});
test('Should preserve existing properties', async () => {
const initialContent = `---
titre: Custom Title
auteur: Bruno Charest
custom_field: custom value
favoris: true
---
# Content`;
const filePath = await createTestFile('preserve.md', initialContent);
const result = await enrichFrontmatterOnOpen(filePath);
const content = await fs.readFile(filePath, 'utf-8');
if (!content.includes('custom_field: custom value')) {
throw new Error('Expected custom field to be preserved');
}
if (!content.includes('favoris: true')) {
throw new Error('Expected favoris: true to be preserved');
}
if (!content.includes('titre: Custom Title')) {
throw new Error('Expected custom title to be preserved');
}
console.log('✓ Existing properties preserved');
});
test('Should maintain correct key order', async () => {
const filePath = await createTestFile('order.md', '# Test\n\nContent.');
await enrichFrontmatterOnOpen(filePath);
const content = await fs.readFile(filePath, 'utf-8');
// Extract front-matter
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!fmMatch) {
throw new Error('No front-matter found');
}
const lines = fmMatch[1].split('\n').filter(l => l.trim());
// Check order of required keys
const expectedOrder = [
'titre:',
'auteur:',
'creation_date:',
'modification_date:',
'catégorie:',
'tags:',
'aliases:',
'status:',
'publish:',
'favoris:',
'template:',
'task:',
'archive:',
'draft:',
'private:'
];
let lastIndex = -1;
for (const key of expectedOrder) {
const currentIndex = lines.findIndex(l => l.startsWith(key));
if (currentIndex === -1) {
throw new Error(`Expected key ${key} not found`);
}
if (currentIndex < lastIndex) {
throw new Error(`Key order incorrect: ${key} appears before previous key`);
}
lastIndex = currentIndex;
}
console.log('✓ Key order is correct');
});
test('Should have no blank lines in front-matter', async () => {
const filePath = await createTestFile('no-blanks.md', '# Test\n\nContent.');
await enrichFrontmatterOnOpen(filePath);
const content = await fs.readFile(filePath, 'utf-8');
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!fmMatch) {
throw new Error('No front-matter found');
}
const fmContent = fmMatch[1];
if (fmContent.includes('\n\n')) {
throw new Error('Found blank lines in front-matter');
}
console.log('✓ No blank lines in front-matter');
});
test('Should use correct boolean types', async () => {
const filePath = await createTestFile('booleans.md', '# Test\n\nContent.');
await enrichFrontmatterOnOpen(filePath);
const fm = await extractFrontmatter(filePath);
if (fm.favoris !== false) {
throw new Error(`Expected favoris to be boolean false, got ${typeof fm.favoris}: ${fm.favoris}`);
}
if (fm.template !== false) {
throw new Error(`Expected template to be boolean false, got ${typeof fm.template}: ${fm.template}`);
}
if (fm.task !== false) {
throw new Error(`Expected task to be boolean false, got ${typeof fm.task}: ${fm.task}`);
}
if (fm.publish !== false) {
throw new Error(`Expected publish to be boolean false, got ${typeof fm.publish}: ${fm.publish}`);
}
console.log('✓ Boolean types are correct');
});
test('Should use correct array types for tags and aliases', async () => {
const filePath = await createTestFile('arrays.md', '# Test\n\nContent.');
await enrichFrontmatterOnOpen(filePath);
const fm = await extractFrontmatter(filePath);
if (!Array.isArray(fm.tags)) {
throw new Error(`Expected tags to be array, got ${typeof fm.tags}`);
}
if (!Array.isArray(fm.aliases)) {
throw new Error(`Expected aliases to be array, got ${typeof fm.aliases}`);
}
if (fm.tags.length !== 0) {
throw new Error(`Expected tags to be empty array, got length ${fm.tags.length}`);
}
if (fm.aliases.length !== 0) {
throw new Error(`Expected aliases to be empty array, got length ${fm.aliases.length}`);
}
console.log('✓ Array types are correct');
});
test('Should format dates in ISO 8601 with timezone', async () => {
const filePath = await createTestFile('dates.md', '# Test\n\nContent.');
await enrichFrontmatterOnOpen(filePath);
const fm = await extractFrontmatter(filePath);
// Check creation_date format
if (!fm.creation_date || typeof fm.creation_date !== 'string') {
throw new Error('Expected creation_date to be a string');
}
if (!fm.creation_date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/)) {
throw new Error(`creation_date format incorrect: ${fm.creation_date}`);
}
// Check modification_date format
if (!fm.modification_date || typeof fm.modification_date !== 'string') {
throw new Error('Expected modification_date to be a string');
}
if (!fm.modification_date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/)) {
throw new Error(`modification_date format incorrect: ${fm.modification_date}`);
}
console.log('✓ Date formats are correct');
});
// Run all tests
async function runTests() {
console.log('\n🧪 Running front-matter enrichment tests...\n');
await setupTestDir();
let passed = 0;
let failed = 0;
for (const { name, fn } of tests) {
try {
await fn();
passed++;
} catch (err) {
console.error(`${name}`);
console.error(` ${err.message}`);
failed++;
}
}
await cleanupTestDir();
console.log(`\n📊 Results: ${passed} passed, ${failed} failed\n`);
if (failed > 0) {
process.exit(1);
}
}
// Execute if run directly
if (process.argv[1] === __filename) {
runTests().catch(err => {
console.error('Test runner error:', err);
process.exit(1);
});
}