133 lines
4.9 KiB
JavaScript
133 lines
4.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import { evaluatePageDataHtmlContent } from '../mindspace-page-data-finish-guard.mjs';
|
|
import { resolveRegisteredPageDataPolicy } from '../page-data-workspace-bind.mjs';
|
|
import { createUserDataSpaceService } from '../user-data-space-service.mjs';
|
|
|
|
test('DATA-05 child survey adds diet while preserving historical rows', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-children-'));
|
|
try {
|
|
const service = createUserDataSpaceService({ workspaceRoot: root });
|
|
await service.executeSql(`CREATE TABLE children (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
hobby TEXT,
|
|
created_at TEXT
|
|
)`);
|
|
await service.upsertDataset({
|
|
name: 'children',
|
|
table: 'children',
|
|
actions: ['read', 'insert', 'update'],
|
|
columns: {
|
|
read: ['id', 'name', 'hobby', 'created_at'],
|
|
insert: ['name', 'hobby'],
|
|
update: ['hobby'],
|
|
},
|
|
});
|
|
await service.insertDatasetRow('children', { name: '小明', hobby: '足球' });
|
|
|
|
await service.executeSql('ALTER TABLE children ADD COLUMN diet TEXT');
|
|
await service.upsertDataset({
|
|
name: 'children',
|
|
table: 'children',
|
|
actions: ['read', 'insert', 'update'],
|
|
columns: {
|
|
read: ['id', 'name', 'hobby', 'diet', 'created_at'],
|
|
insert: ['name', 'hobby', 'diet'],
|
|
update: ['hobby', 'diet'],
|
|
},
|
|
});
|
|
await service.insertDatasetRow('children', {
|
|
name: '小红',
|
|
hobby: '绘画',
|
|
diet: '不吃花生',
|
|
});
|
|
const rows = await service.readDatasetRows('children', { orderBy: 'id', orderDir: 'asc' });
|
|
assert.equal(rows.rows.length, 2);
|
|
assert.equal(rows.rows[0].name, '小明');
|
|
assert.equal(rows.rows[0].hobby, '足球');
|
|
assert.equal(rows.rows[0].diet, null);
|
|
assert.equal(rows.rows[1].diet, '不吃花生');
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('DATA-13 incomplete schema fails closed, repairs additively, and rebinds modern client policy', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-repair-'));
|
|
try {
|
|
const service = createUserDataSpaceService({ workspaceRoot: root });
|
|
await service.executeSql(`CREATE TABLE responses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
answer TEXT NOT NULL,
|
|
created_at TEXT
|
|
)`);
|
|
await service.executeSql(
|
|
"INSERT INTO responses (answer, created_at) VALUES ('历史答案', '2026-01-01T00:00:00.000Z')",
|
|
);
|
|
await service.upsertDataset({
|
|
name: 'responses',
|
|
table: 'responses',
|
|
actions: ['read'],
|
|
columns: {
|
|
read: ['id', 'answer', 'created_at'],
|
|
insert: ['answer', 'diet'],
|
|
soft_delete: ['id'],
|
|
},
|
|
});
|
|
const html = `<script src="/assets/mindspace-page-data-client.js"></script>
|
|
<script>const c=MindSpacePageData.createClient();
|
|
c.insertRow("responses",{answer:"新答案",diet:"清淡"});
|
|
c.deleteRow("responses","1");</script>`;
|
|
assert.throws(
|
|
() => resolveRegisteredPageDataPolicy({
|
|
html,
|
|
userId: 'user-1',
|
|
accessMode: 'public',
|
|
userDataSpace: service,
|
|
}),
|
|
(error) => ['dataset_action_not_registered', 'dataset_schema_mismatch'].includes(error?.code),
|
|
);
|
|
|
|
await service.executeSql('ALTER TABLE responses ADD COLUMN diet TEXT');
|
|
await service.executeSql('ALTER TABLE responses ADD COLUMN deleted_at TEXT');
|
|
await service.executeSql('ALTER TABLE responses ADD COLUMN deleted_by TEXT');
|
|
await service.upsertDataset({
|
|
name: 'responses',
|
|
table: 'responses',
|
|
actions: ['read', 'insert', 'soft_delete'],
|
|
columns: {
|
|
read: ['id', 'answer', 'diet', 'created_at'],
|
|
insert: ['answer', 'diet'],
|
|
soft_delete: ['id'],
|
|
},
|
|
});
|
|
const policy = resolveRegisteredPageDataPolicy({
|
|
html,
|
|
userId: 'user-1',
|
|
accessMode: 'public',
|
|
userDataSpace: service,
|
|
});
|
|
assert.equal(policy.datasets.responses.insert, true);
|
|
assert.equal(policy.datasets.responses.softDelete, true);
|
|
const columns = service.listTableColumns('responses').map((column) => column.name);
|
|
assert.equal(columns.filter((name) => name === 'created_at').length, 1);
|
|
const rows = await service.readDatasetRows('responses');
|
|
assert.equal(rows.rows[0].answer, '历史答案');
|
|
|
|
const legacy = evaluatePageDataHtmlContent(
|
|
'<script>localStorage.setItem("rows","[]");fetch("/api/page-data/responses")</script>',
|
|
{ relativePath: 'public/legacy.html' },
|
|
);
|
|
assert.equal(legacy.usesPageDataApi, true);
|
|
assert.ok(legacy.issues.includes('forbidden_legacy_page_data_api'));
|
|
assert.ok(legacy.issues.includes('forbidden_local_storage'));
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|