feat(page-data): complete Phase 4-5, ops UI, and publish integration
Add visitor roles, row-level scope, owner ops APIs, MySQL policy index, Turnstile captcha, browser client SDK, publish-panel dataset binding, acceptance tests, and usage documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
extractPageDataCaptchaToken,
|
||||
stripPageDataMetaFields,
|
||||
verifyPageDataCaptcha,
|
||||
} from './page-data-captcha.mjs';
|
||||
import {
|
||||
buildPageDataPolicyFromPublishInput,
|
||||
mapPublicationAccessModeToPageData,
|
||||
} from './page-access-policy.mjs';
|
||||
|
||||
test('stripPageDataMetaFields removes captcha and meta keys', () => {
|
||||
assert.deepEqual(
|
||||
stripPageDataMetaFields({ name: '张三', turnstile_token: 'abc', updated_by_label: 'x' }),
|
||||
{ name: '张三' },
|
||||
);
|
||||
});
|
||||
|
||||
test('verifyPageDataCaptcha skips when secret is not configured', async () => {
|
||||
const prev = process.env.PAGE_DATA_TURNSTILE_SECRET;
|
||||
delete process.env.PAGE_DATA_TURNSTILE_SECRET;
|
||||
try {
|
||||
const result = await verifyPageDataCaptcha(null, '127.0.0.1');
|
||||
assert.equal(result.skipped, true);
|
||||
} finally {
|
||||
if (prev) process.env.PAGE_DATA_TURNSTILE_SECRET = prev;
|
||||
}
|
||||
});
|
||||
|
||||
test('verifyPageDataCaptcha rejects missing token when enabled', async () => {
|
||||
const prev = process.env.PAGE_DATA_TURNSTILE_SECRET;
|
||||
process.env.PAGE_DATA_TURNSTILE_SECRET = 'test-secret';
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => verifyPageDataCaptcha('', '127.0.0.1'),
|
||||
(error) => error.code === 'captcha_required',
|
||||
);
|
||||
} finally {
|
||||
if (prev) process.env.PAGE_DATA_TURNSTILE_SECRET = prev;
|
||||
else delete process.env.PAGE_DATA_TURNSTILE_SECRET;
|
||||
}
|
||||
});
|
||||
|
||||
test('extractPageDataCaptchaToken reads header and body fields', () => {
|
||||
assert.equal(
|
||||
extractPageDataCaptchaToken({ headers: { 'x-page-data-captcha': 'hdr' } }, {}),
|
||||
'hdr',
|
||||
);
|
||||
assert.equal(extractPageDataCaptchaToken({}, { turnstile_token: 'body' }), 'body');
|
||||
});
|
||||
|
||||
test('buildPageDataPolicyFromPublishInput applies defaults by access mode', () => {
|
||||
const policy = buildPageDataPolicyFromPublishInput({
|
||||
pageId: 'page-1',
|
||||
ownerUserId: 'user-1',
|
||||
accessMode: 'public',
|
||||
datasetName: 'signups',
|
||||
registryDataset: {
|
||||
name: 'signups',
|
||||
table: 'signups',
|
||||
columns: {
|
||||
read: ['id', 'name'],
|
||||
insert: ['name', 'phone'],
|
||||
update: ['status'],
|
||||
soft_delete: ['id'],
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(policy.accessMode, 'public');
|
||||
assert.equal(policy.datasets.signups.insert, true);
|
||||
assert.equal(policy.datasets.signups.read, false);
|
||||
assert.equal(mapPublicationAccessModeToPageData('private_link'), null);
|
||||
});
|
||||
Reference in New Issue
Block a user