Initial commit: OmniAI backend server
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { createRequire } = require("node:module");
|
||||
|
||||
const nodeRequire = createRequire(__filename);
|
||||
|
||||
const routeModulePaths = [
|
||||
"../src/routes.js",
|
||||
"../src/routes/index.js",
|
||||
"../src/routes/community.js",
|
||||
"../src/routes/notifications.js",
|
||||
];
|
||||
const contextPath = "../src/routes/context.js";
|
||||
|
||||
function passThrough(_req, _res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
function loadRouter(pool) {
|
||||
const contextResolvedPath = nodeRequire.resolve(contextPath);
|
||||
const originalContextModule = nodeRequire.cache[contextResolvedPath];
|
||||
const resolvedRouteModules = routeModulePaths.map((modulePath) => nodeRequire.resolve(modulePath));
|
||||
|
||||
for (const resolvedPath of resolvedRouteModules) {
|
||||
delete nodeRequire.cache[resolvedPath];
|
||||
}
|
||||
|
||||
nodeRequire.cache[contextResolvedPath] = {
|
||||
id: contextResolvedPath,
|
||||
filename: contextResolvedPath,
|
||||
loaded: true,
|
||||
exports: {
|
||||
express: nodeRequire("express"),
|
||||
requireAuth: passThrough,
|
||||
requireAdmin: passThrough,
|
||||
requireEnterpriseAdmin: passThrough,
|
||||
requireManagementAccess: passThrough,
|
||||
pool,
|
||||
withTransaction: async (fn) => fn(pool),
|
||||
clampPositiveInteger: (value, fallback) => Math.max(1, Number(value) || fallback),
|
||||
clampNonNegativeInteger: (value, fallback) => Math.max(0, Number(value) || fallback),
|
||||
normalizeProjectOssKey: (value) => String(value || "").trim(),
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
router: nodeRequire("../src/routes.js"),
|
||||
restore() {
|
||||
for (const resolvedPath of resolvedRouteModules) {
|
||||
delete nodeRequire.cache[resolvedPath];
|
||||
}
|
||||
if (originalContextModule) {
|
||||
nodeRequire.cache[contextResolvedPath] = originalContextModule;
|
||||
} else {
|
||||
delete nodeRequire.cache[contextResolvedPath];
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readRouterInventory(router) {
|
||||
return router.stack
|
||||
.filter((layer) => Boolean(layer.route))
|
||||
.flatMap((layer) =>
|
||||
Object.keys(layer.route.methods)
|
||||
.filter((method) => layer.route.methods[method])
|
||||
.map((method) => ({ method: method.toUpperCase(), path: layer.route.path })),
|
||||
);
|
||||
}
|
||||
|
||||
function getRouteHandler(router, method, routePath) {
|
||||
const layer = router.stack.find(
|
||||
(candidate) => candidate.route?.path === routePath && candidate.route.methods[method.toLowerCase()],
|
||||
);
|
||||
const handler = layer?.route?.stack.at(-1)?.handle;
|
||||
if (!handler) throw new Error(`Route not found: ${method.toUpperCase()} ${routePath}`);
|
||||
return handler;
|
||||
}
|
||||
|
||||
function createMockResponse() {
|
||||
const res = {};
|
||||
res.status = (statusCode) => {
|
||||
res.statusCode = statusCode;
|
||||
return res;
|
||||
};
|
||||
res.json = (body) => {
|
||||
res.body = body;
|
||||
return res;
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
async function testNotificationRoutesAreMounted() {
|
||||
const { router, restore } = loadRouter({ query: async () => ({ rows: [] }) });
|
||||
try {
|
||||
const inventory = readRouterInventory(router);
|
||||
assert(inventory.some((route) => route.method === "GET" && route.path === "/notifications"));
|
||||
assert(inventory.some((route) => route.method === "PATCH" && route.path === "/notifications/:id/read"));
|
||||
assert(inventory.some((route) => route.method === "POST" && route.path === "/notifications/read-all"));
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
async function testReviewStatusSurvivesNotificationWriteFailure() {
|
||||
const calls = [];
|
||||
const pool = {
|
||||
async query(sql, params) {
|
||||
calls.push({ sql, params });
|
||||
if (/UPDATE community_cases/.test(sql)) {
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
id: 2,
|
||||
user_id: 9,
|
||||
username: "creator",
|
||||
project_id: null,
|
||||
title: "待审核案例",
|
||||
description: "desc",
|
||||
cover_url: null,
|
||||
tags_json: "[]",
|
||||
metadata_json: "{}",
|
||||
status: "approved",
|
||||
review_note: null,
|
||||
reviewed_by: 1,
|
||||
reviewed_at: "2026-05-19T00:00:00.000Z",
|
||||
published_at: "2026-05-19T00:00:00.000Z",
|
||||
copy_count: 0,
|
||||
created_at: "2026-05-19T00:00:00.000Z",
|
||||
updated_at: "2026-05-19T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (/INSERT INTO web_notifications/.test(sql)) {
|
||||
throw new Error("relation web_notifications does not exist");
|
||||
}
|
||||
if (/FROM community_case_assets/.test(sql)) return { rows: [] };
|
||||
if (/FROM community_case_reactions/.test(sql)) return { rows: [] };
|
||||
return { rows: [] };
|
||||
},
|
||||
};
|
||||
const { router, restore } = loadRouter(pool);
|
||||
try {
|
||||
const handler = getRouteHandler(router, "patch", "/admin/community/cases/:id/status");
|
||||
const res = createMockResponse();
|
||||
|
||||
await handler(
|
||||
{
|
||||
params: { id: "2" },
|
||||
body: { status: "approved" },
|
||||
user: { id: 1, role: "admin" },
|
||||
},
|
||||
res,
|
||||
);
|
||||
|
||||
assert.equal(res.statusCode, undefined);
|
||||
assert.equal(res.body.case.id, 2);
|
||||
assert.equal(res.body.case.status, "approved");
|
||||
const updateCall = calls.find((call) => /UPDATE community_cases/.test(call.sql));
|
||||
assert.match(updateCall.sql, /status = \$1::varchar\(24\)/);
|
||||
assert.match(updateCall.sql, /CASE WHEN \$1::varchar\(24\) = 'approved'/);
|
||||
assert(calls.some((call) => /INSERT INTO web_notifications/.test(call.sql)));
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await testNotificationRoutesAreMounted();
|
||||
await testReviewStatusSurvivesNotificationWriteFailure();
|
||||
console.log("community route contract tests passed");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user