214 lines
6.5 KiB
JavaScript
214 lines
6.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import { syncGeneratedPagesFromPublicAssets } from './mindspace-page-sync.mjs';
|
|
|
|
test('syncGeneratedPagesFromPublicAssets registers html files from publish public dir', async () => {
|
|
const publishDir = await fs.mkdtemp(path.join(os.tmpdir(), 'page-sync-publish-'));
|
|
const publicDir = path.join(publishDir, 'public');
|
|
await fs.mkdir(publicDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(publicDir, 'demo.html'),
|
|
'<html><head><title>Demo Page</title></head><body>Hi</body></html>',
|
|
'utf8',
|
|
);
|
|
|
|
const created = [];
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_records p')) return [[]];
|
|
if (sql.includes('FROM h5_assets a')) return [[]];
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
async createFromChat(userId, input, source) {
|
|
created.push({ userId, input, source });
|
|
return { id: 'page-new' };
|
|
},
|
|
async updatePage() {
|
|
throw new Error('should not update');
|
|
},
|
|
};
|
|
|
|
const result = await syncGeneratedPagesFromPublicAssets({
|
|
pool,
|
|
pageService,
|
|
assetService: null,
|
|
userId: 'user-1',
|
|
publishDir,
|
|
});
|
|
|
|
assert.equal(result.created, 1);
|
|
assert.equal(created.length, 1);
|
|
assert.equal(created[0].input.title, 'Demo Page');
|
|
assert.equal(created[0].source.snapshot.relative_path, 'public/demo.html');
|
|
});
|
|
|
|
test('syncGeneratedPagesFromPublicAssets scopes agent-run sync to current session html', async () => {
|
|
const publishDir = await fs.mkdtemp(path.join(os.tmpdir(), 'page-sync-scoped-'));
|
|
const publicDir = path.join(publishDir, 'public');
|
|
await fs.mkdir(publicDir, { recursive: true });
|
|
await fs.writeFile(path.join(publicDir, 'current.html'), '<title>Current</title>', 'utf8');
|
|
await fs.writeFile(path.join(publicDir, 'historical.html'), '<title>Historical</title>', 'utf8');
|
|
|
|
const created = [];
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_records p')) return [[]];
|
|
if (sql.includes('FROM h5_assets a')) return [[]];
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
async createFromChat(_userId, input, source) {
|
|
created.push({ input, source });
|
|
return { id: `page-${created.length}` };
|
|
},
|
|
};
|
|
|
|
await syncGeneratedPagesFromPublicAssets({
|
|
pool,
|
|
pageService,
|
|
userId: 'user-1',
|
|
publishDir,
|
|
onlyRelativePaths: ['public/current.html'],
|
|
});
|
|
|
|
assert.deepEqual(created.map((item) => item.source.snapshot.relative_path), ['public/current.html']);
|
|
await fs.rm(publishDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('syncGeneratedPagesFromPublicAssets uses file mtime as createdAt', async () => {
|
|
const publishDir = await fs.mkdtemp(path.join(os.tmpdir(), 'page-sync-mtime-'));
|
|
const publicDir = path.join(publishDir, 'public');
|
|
await fs.mkdir(publicDir, { recursive: true });
|
|
const htmlPath = path.join(publicDir, 'timed.html');
|
|
await fs.writeFile(
|
|
htmlPath,
|
|
'<html><head><title>Timed</title></head><body>Hi</body></html>',
|
|
'utf8',
|
|
);
|
|
const mtimeMs = Date.parse('2026-06-20T10:00:00.000Z');
|
|
await fs.utimes(htmlPath, mtimeMs / 1000, mtimeMs / 1000);
|
|
|
|
const created = [];
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_records p')) return [[]];
|
|
if (sql.includes('FROM h5_assets a')) return [[]];
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
async createFromChat(userId, input, source) {
|
|
created.push({ userId, input, source });
|
|
return { id: 'page-new' };
|
|
},
|
|
async updatePage() {
|
|
throw new Error('should not update');
|
|
},
|
|
};
|
|
|
|
await syncGeneratedPagesFromPublicAssets({
|
|
pool,
|
|
pageService,
|
|
assetService: null,
|
|
userId: 'user-1',
|
|
publishDir,
|
|
});
|
|
|
|
assert.equal(created.length, 1);
|
|
assert.equal(created[0].source.createdAt, mtimeMs);
|
|
});
|
|
|
|
test('syncGeneratedPagesFromPublicAssets creates pages for unlinked public html assets', async () => {
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'page-sync-'));
|
|
const htmlPath = path.join(tempDir, 'demo.html');
|
|
await fs.writeFile(htmlPath, '<html><head><title>Demo</title></head><body>Hi</body></html>', 'utf8');
|
|
|
|
const created = [];
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_records p')) return [[]];
|
|
if (sql.includes('FROM h5_assets a')) {
|
|
return [[{ id: 'asset-1', display_name: 'demo.html', original_filename: 'demo.html', updated_at: 100 }]];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
async createFromChat(userId, input, source) {
|
|
created.push({ userId, input, source });
|
|
return { id: 'page-new' };
|
|
},
|
|
async updatePage() {
|
|
throw new Error('should not update');
|
|
},
|
|
};
|
|
const assetService = {
|
|
async readAsset() {
|
|
return { path: htmlPath };
|
|
},
|
|
};
|
|
|
|
const result = await syncGeneratedPagesFromPublicAssets({
|
|
pool,
|
|
pageService,
|
|
assetService,
|
|
userId: 'user-1',
|
|
});
|
|
assert.equal(result.created, 1);
|
|
assert.equal(created[0].source.assetId, 'asset-1');
|
|
});
|
|
|
|
test('syncGeneratedPagesFromPublicAssets reuses db page when memory index is empty', async () => {
|
|
const publishDir = await fs.mkdtemp(path.join(os.tmpdir(), 'page-sync-dedupe-'));
|
|
const publicDir = path.join(publishDir, 'public');
|
|
await fs.mkdir(publicDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(publicDir, 'demo.html'),
|
|
'<html><head><title>Demo Page</title></head><body>Hi</body></html>',
|
|
'utf8',
|
|
);
|
|
|
|
const created = [];
|
|
const updated = [];
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('JSON_UNQUOTE(JSON_EXTRACT(pv.source_snapshot_json')) {
|
|
return [[{ id: 'page-existing', updated_at: 100, version_no: 3 }]];
|
|
}
|
|
if (sql.includes('FROM h5_page_records p')) return [[]];
|
|
if (sql.includes('FROM h5_assets a')) return [[]];
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
async createFromChat() {
|
|
created.push(true);
|
|
return { id: 'page-new' };
|
|
},
|
|
async updatePage(userId, pageId, input) {
|
|
updated.push({ userId, pageId, input });
|
|
return { id: pageId };
|
|
},
|
|
};
|
|
|
|
const result = await syncGeneratedPagesFromPublicAssets({
|
|
pool,
|
|
pageService,
|
|
assetService: null,
|
|
userId: 'user-1',
|
|
publishDir,
|
|
});
|
|
|
|
assert.equal(result.created, 0);
|
|
assert.equal(created.length, 0);
|
|
assert.equal(updated.length, 1);
|
|
assert.equal(updated[0].pageId, 'page-existing');
|
|
assert.equal(updated[0].input.expectedVersion, 3);
|
|
});
|