#!/usr/bin/env node /** * Local-only E2E proof for the questionnaire -> Page Data API -> PostgreSQL path. * * The probe uses an existing local user space, creates a uniquely named survey * table/dataset and page policy, submits one answer through the public HTTP API, * verifies the row directly in PostgreSQL, and removes all probe objects. */ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import express from 'express'; import pg from 'pg'; import { attachPageDataRoutes } from '../page-data-routes.mjs'; import { createPageDataService } from '../page-data-service.mjs'; import { createPageDataPublicService } from '../page-data-public-service.mjs'; import { deletePageAccessPolicy, writePageAccessPolicy } from '../page-data-policy-store.mjs'; import { createUserDataSpaceService } from '../user-data-space-service.mjs'; import { deriveUserSpaceNames, quotePgIdentifier } from '../mindspace-userdata-postgres.mjs'; import { loadH5Environment } from './load-env.mjs'; loadH5Environment(import.meta.dirname); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const userId = process.argv[2] ?? '1c99b83b-0454-474f-a5d2-129d34506a32'; const workspaceRoot = path.join(repoRoot, 'MindSpace', userId); const suffix = `${Date.now()}_${process.pid}`; const tableName = `survey_e2e_${suffix}`; const datasetName = tableName; const pageId = `page-survey-e2e-${suffix}`; const names = deriveUserSpaceNames(userId); const policyPath = path.join(workspaceRoot, '.mindspace', 'page-data-policies', `${pageId}.json`); const logPath = path.join(workspaceRoot, '.mindspace', 'page-data-logs', `${pageId}.jsonl`); assert.equal(process.env.MINDSPACE_USERDATA_BACKEND, 'postgres', '本机未启用 PostgreSQL 用户数据后端'); assert.ok(fs.existsSync(workspaceRoot), `用户工作区不存在: ${workspaceRoot}`); const pgPool = new pg.Pool({ host: process.env.MINDSPACE_USERDATA_PG_HOST ?? '/tmp', port: Number(process.env.MINDSPACE_USERDATA_PG_PORT ?? 5433), database: process.env.MINDSPACE_USERDATA_PG_DATABASE ?? 'mindspace_userdata_dev', user: process.env.MINDSPACE_USERDATA_PG_USER ?? process.env.USER, max: 2, }); function publicationPool() { return { async query(sql) { if (sql.includes('FROM h5_publish_records')) { return [[{ id: `publication-${suffix}`, user_id: userId, page_id: pageId, access_mode: 'public', password_hash: null, status: 'online', expires_at: null, }]]; } return [[]]; }, }; } function buildApp() { const fakePublicationPool = publicationPool(); const publicService = createPageDataPublicService({ getPool: () => fakePublicationPool, resolveWorkspaceRootForOwner: () => workspaceRoot, }); const api = express.Router(); api.use(express.json()); attachPageDataRoutes(api, { sendData: (res, _req, data, status = 200) => res.status(status).json({ data }), sendError: (res, _req, status, code, message) => res.status(status).json({ error: { code, message } }), getPageDataService: () => createPageDataService({ resolveWorkspaceRoot: async () => workspaceRoot }), getPageDataPublicService: () => publicService, }); const app = express(); app.set('trust proxy', true); app.use('/api', api); return app; } async function submitQuestionnaire(app, row) { const server = app.listen(0, '127.0.0.1'); await new Promise((resolve, reject) => { server.once('listening', resolve); server.once('error', reject); }); try { const { port } = server.address(); const apiPath = `/api/public/pages/${pageId}/data/${datasetName}/rows`; const response = await fetch(`http://127.0.0.1:${port}${apiPath}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(row), }); return { apiPath, status: response.status, body: await response.json() }; } finally { await new Promise((resolve) => server.close(resolve)); } } async function main() { const ownerService = createUserDataSpaceService({ workspaceRoot, userId }); let tableCreated = false; try { // Equivalent to the Agent's private_data_execute + register_dataset + bind policy flow. await ownerService.executeSql(`CREATE TABLE ${tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, satisfaction TEXT NOT NULL, favorite_feature TEXT NOT NULL, suggestion TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')) )`); tableCreated = true; const dataset = await ownerService.upsertDataset({ name: datasetName, table: tableName, description: '本机 PG 问卷端到端验证', actions: ['read', 'insert'], columns: { read: ['id', 'satisfaction', 'favorite_feature', 'suggestion', 'created_at'], insert: ['satisfaction', 'favorite_feature', 'suggestion'], }, }); writePageAccessPolicy(workspaceRoot, { pageId, ownerUserId: userId, accessMode: 'public', datasets: { [datasetName]: { insert: true, read: false, columns: { insert: ['satisfaction', 'favorite_feature', 'suggestion'] }, }, }, }); const answer = { satisfaction: '非常满意', favorite_feature: '个人数据报表', suggestion: '增加按月趋势分析', }; const submitted = await submitQuestionnaire(buildApp(), answer); assert.equal(submitted.status, 201, JSON.stringify(submitted.body)); assert.equal(submitted.body.data.dataset, datasetName); assert.equal(submitted.body.data.row.satisfaction, answer.satisfaction); const direct = await pgPool.query( `SELECT id,satisfaction,favorite_feature,suggestion,created_at FROM ${quotePgIdentifier(names.schemaName)}.${quotePgIdentifier(tableName)} ORDER BY id DESC LIMIT 1`, ); assert.equal(direct.rowCount, 1); assert.equal(direct.rows[0].satisfaction, answer.satisfaction); assert.equal(direct.rows[0].favorite_feature, answer.favorite_feature); assert.equal(direct.rows[0].suggestion, answer.suggestion); const registered = await pgPool.query( `SELECT name,table_name FROM ${quotePgIdentifier(names.schemaName)}.__page_data_datasets WHERE name=$1`, [datasetName], ); assert.equal(registered.rowCount, 1); console.log(JSON.stringify({ result: 'PASS', localOnly: true, userId, schema: names.schemaName, dataset: { name: dataset.name, table: dataset.table, actions: dataset.actions }, pageDataApi: { method: 'POST', path: submitted.apiPath, status: submitted.status }, postgresRow: direct.rows[0], checks: [ 'Agent-style table creation succeeded in the user schema', 'Dataset registry was stored in PostgreSQL', 'Questionnaire submission used the public Page Data HTTP API', 'Submitted answers matched a direct PostgreSQL query', ], }, null, 2)); } finally { if (tableCreated) { await ownerService.executeSql(`DELETE FROM __page_data_datasets WHERE name='${datasetName}'`).catch(() => {}); await ownerService.executeSql(`DROP TABLE IF EXISTS ${tableName}`).catch(() => {}); } deletePageAccessPolicy(workspaceRoot, pageId); fs.rmSync(logPath, { force: true }); fs.rmSync(policyPath, { force: true }); await pgPool.end(); } } main().catch((error) => { console.error(error instanceof Error ? error.stack ?? error.message : error); process.exitCode = 1; });