Initial commit: OmniAI backend server
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
buildDashscopeImageSuperResolveBody,
|
||||
buildDashscopeVideoStyleTransformBody,
|
||||
normalizeImageUpscaleFactor,
|
||||
normalizeVideoStyleTransformOptions,
|
||||
} = require("../src/aiUpscaleHelpers");
|
||||
const { extractVideoUrl } = require("../src/aiTaskWorker");
|
||||
|
||||
function testImageSuperResolveBody() {
|
||||
const body = buildDashscopeImageSuperResolveBody({
|
||||
imageUrl: "https://example.com/input.png",
|
||||
scale: "4x",
|
||||
});
|
||||
|
||||
assert.equal(body.model, "wanx2.1-imageedit");
|
||||
assert.deepEqual(body.input, {
|
||||
function: "super_resolution",
|
||||
prompt: "图像超分。",
|
||||
base_image_url: "https://example.com/input.png",
|
||||
});
|
||||
assert.equal(body.parameters.upscale_factor, 4);
|
||||
assert.equal(body.parameters.n, 1);
|
||||
assert.equal(Object.hasOwn(body.parameters, "watermark"), false);
|
||||
}
|
||||
|
||||
function testVideoStyleTransformBody() {
|
||||
const body = buildDashscopeVideoStyleTransformBody({
|
||||
videoUrl: "https://example.com/input.mp4",
|
||||
style: 7,
|
||||
videoFps: 99,
|
||||
minLen: 540,
|
||||
useSR: true,
|
||||
animateEmotion: false,
|
||||
});
|
||||
|
||||
assert.equal(body.model, "video-style-transform");
|
||||
assert.deepEqual(body.input, {
|
||||
video_url: "https://example.com/input.mp4",
|
||||
});
|
||||
assert.equal(body.parameters.style, 7);
|
||||
assert.equal(body.parameters.video_fps, 25);
|
||||
assert.equal(body.parameters.min_len, 540);
|
||||
assert.equal(body.parameters.use_SR, true);
|
||||
assert.equal(body.parameters.animate_emotion, false);
|
||||
}
|
||||
|
||||
function testNormalizers() {
|
||||
assert.equal(normalizeImageUpscaleFactor("4x"), 4);
|
||||
assert.equal(normalizeImageUpscaleFactor("3"), 2);
|
||||
assert.deepEqual(normalizeVideoStyleTransformOptions({ style: "9", videoFps: 8, minLen: 999 }), {
|
||||
style: 0,
|
||||
videoFps: 15,
|
||||
minLen: 720,
|
||||
useSR: true,
|
||||
animateEmotion: true,
|
||||
});
|
||||
}
|
||||
|
||||
function testVideoStyleResultExtraction() {
|
||||
assert.equal(
|
||||
extractVideoUrl({
|
||||
output: {
|
||||
task_status: "SUCCEEDED",
|
||||
output_video_url: "https://dashscope-result.example.com/result.mp4",
|
||||
},
|
||||
}),
|
||||
"https://dashscope-result.example.com/result.mp4",
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
testImageSuperResolveBody();
|
||||
testVideoStyleTransformBody();
|
||||
testNormalizers();
|
||||
testVideoStyleResultExtraction();
|
||||
console.log("ai upscale helper contract tests passed");
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,70 @@
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
checkBetaInviteCodeForRegistration,
|
||||
consumeBetaInviteCode,
|
||||
loadBetaInviteCodesFromText,
|
||||
normalizeBetaInviteCode,
|
||||
validateBetaInviteCode,
|
||||
} = require("../src/betaInviteCodes");
|
||||
|
||||
const sampleMarkdown = `
|
||||
# OmniAI Internal Beta Invite Codes
|
||||
|
||||
| # | HEX | Octal |
|
||||
|---|-----|-------|
|
||||
| 1 | A1B2C3D4E5F60718 | 120746072324712600340 |
|
||||
| 2 | 0011223344556677 | 000104421463210531467 |
|
||||
`;
|
||||
|
||||
const codes = loadBetaInviteCodesFromText(sampleMarkdown);
|
||||
|
||||
assert.equal(normalizeBetaInviteCode(" a1b2-c3d4 e5f60718 "), "A1B2C3D4E5F60718");
|
||||
assert.equal(normalizeBetaInviteCode("120746072324712600340"), "120746072324712600340");
|
||||
assert.equal(codes.has("A1B2C3D4E5F60718"), true);
|
||||
assert.equal(codes.has("120746072324712600340"), true);
|
||||
assert.equal(validateBetaInviteCode("a1b2c3d4e5f60718", codes), true);
|
||||
assert.equal(validateBetaInviteCode("000104421463210531467", codes), true);
|
||||
assert.equal(validateBetaInviteCode("", codes), false);
|
||||
assert.equal(validateBetaInviteCode("NOT-A-CODE", codes), false);
|
||||
|
||||
function createInviteCodeClient() {
|
||||
const used = new Map();
|
||||
return {
|
||||
used,
|
||||
async query(sql, params) {
|
||||
if (/SELECT 1 FROM beta_invite_code_uses/i.test(sql)) {
|
||||
return { rows: used.has(params[0]) ? [{ 1: 1 }] : [] };
|
||||
}
|
||||
if (/INSERT INTO beta_invite_code_uses/i.test(sql)) {
|
||||
const [code, userId] = params;
|
||||
if (used.has(code)) return { rows: [] };
|
||||
used.set(code, userId);
|
||||
return { rows: [{ code }] };
|
||||
}
|
||||
throw new Error(`Unexpected SQL: ${sql}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const client = createInviteCodeClient();
|
||||
const firstCheck = await checkBetaInviteCodeForRegistration("a1b2c3d4e5f60718", client, codes);
|
||||
assert.equal(firstCheck.ok, true);
|
||||
|
||||
const firstConsume = await consumeBetaInviteCode("a1b2c3d4e5f60718", 101, client, codes);
|
||||
assert.equal(firstConsume.ok, true);
|
||||
assert.equal(client.used.get("A1B2C3D4E5F60718"), 101);
|
||||
|
||||
const secondCheck = await checkBetaInviteCodeForRegistration("A1B2C3D4E5F60718", client, codes);
|
||||
assert.equal(secondCheck.ok, false);
|
||||
assert.equal(secondCheck.status, 409);
|
||||
|
||||
const secondConsume = await consumeBetaInviteCode("A1B2C3D4E5F60718", 102, client, codes);
|
||||
assert.equal(secondConsume.ok, false);
|
||||
assert.equal(secondConsume.status, 409);
|
||||
assert.equal(client.used.get("A1B2C3D4E5F60718"), 101);
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const source = fs.readFileSync(path.resolve(__dirname, "../src/dbSetup.js"), "utf8");
|
||||
|
||||
assert.match(source, /runMigration\("024_generation_tasks_project_queue_index"/);
|
||||
assert.match(source, /CREATE UNIQUE INDEX IF NOT EXISTS idx_generation_tasks_project_queue_unique/);
|
||||
assert.match(source, /ON generation_tasks\(project_id, client_queue_id\)\s+WHERE project_id IS NOT NULL/);
|
||||
|
||||
console.log("db setup contract tests passed");
|
||||
@@ -0,0 +1,52 @@
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
process.env.STS_ACCESS_KEY_ID = "test-key";
|
||||
process.env.STS_ACCESS_KEY_SECRET = "test-secret";
|
||||
process.env.OSS_BUCKET = "test-bucket";
|
||||
process.env.OSS_REGION = "oss-cn-test";
|
||||
|
||||
const { getObject, createSignedReadUrl } = require("../src/ossClient");
|
||||
|
||||
async function main() {
|
||||
const originalFetch = global.fetch;
|
||||
global.fetch = async () => ({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: async () => `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>NoSuchKey</Code>
|
||||
<Message>The specified key does not exist.</Message>
|
||||
</Error>`,
|
||||
});
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => getObject("users/1/projects/missing/current/project.json"),
|
||||
(error) => {
|
||||
assert.equal(error.status, 404);
|
||||
assert.equal(error.code, "oss_no_such_key");
|
||||
return true;
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
global.fetch = originalFetch;
|
||||
}
|
||||
|
||||
const beforeExpires = Math.floor(Date.now() / 1000) + 60;
|
||||
const signedUrl = createSignedReadUrl("tmp/admin-1/generation-inputs/audios/demo audio.mp3", 60);
|
||||
const parsedSignedUrl = new URL(signedUrl);
|
||||
const afterExpires = Math.floor(Date.now() / 1000) + 60;
|
||||
assert.equal(parsedSignedUrl.host, "test-bucket.oss-cn-test.aliyuncs.com");
|
||||
assert.equal(parsedSignedUrl.pathname, "/tmp/admin-1/generation-inputs/audios/demo%20audio.mp3");
|
||||
assert.equal(parsedSignedUrl.searchParams.get("OSSAccessKeyId"), "test-key");
|
||||
assert.ok(Number(parsedSignedUrl.searchParams.get("Expires")) >= beforeExpires);
|
||||
assert.ok(Number(parsedSignedUrl.searchParams.get("Expires")) <= afterExpires);
|
||||
assert.ok(parsedSignedUrl.searchParams.get("Signature"));
|
||||
|
||||
console.log("oss client contract tests passed");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { createRequire } = require("node:module");
|
||||
|
||||
const nodeRequire = createRequire(__filename);
|
||||
const contextPath = "../src/routes/context.js";
|
||||
const ossRoutePath = "../src/routes/oss.js";
|
||||
const ossClientPath = "../src/ossClient.js";
|
||||
|
||||
function passThrough(_req, _res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
function createMockResponse() {
|
||||
const res = {};
|
||||
res.status = (statusCode) => {
|
||||
res.statusCode = statusCode;
|
||||
return res;
|
||||
};
|
||||
res.json = (body) => {
|
||||
res.body = body;
|
||||
return res;
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
function loadOssRouter(ossClient) {
|
||||
const express = nodeRequire("express");
|
||||
const router = express.Router();
|
||||
const contextResolvedPath = nodeRequire.resolve(contextPath);
|
||||
const ossRouteResolvedPath = nodeRequire.resolve(ossRoutePath);
|
||||
const ossClientResolvedPath = nodeRequire.resolve(ossClientPath);
|
||||
const originalContextModule = nodeRequire.cache[contextResolvedPath];
|
||||
const originalOssRouteModule = nodeRequire.cache[ossRouteResolvedPath];
|
||||
const originalOssClientModule = nodeRequire.cache[ossClientResolvedPath];
|
||||
|
||||
delete nodeRequire.cache[ossRouteResolvedPath];
|
||||
|
||||
nodeRequire.cache[contextResolvedPath] = {
|
||||
id: contextResolvedPath,
|
||||
filename: contextResolvedPath,
|
||||
loaded: true,
|
||||
exports: {
|
||||
requireAuth: passThrough,
|
||||
},
|
||||
};
|
||||
nodeRequire.cache[ossClientResolvedPath] = {
|
||||
id: ossClientResolvedPath,
|
||||
filename: ossClientResolvedPath,
|
||||
loaded: true,
|
||||
exports: ossClient,
|
||||
};
|
||||
|
||||
nodeRequire(ossRoutePath).registerOssRoutes(router);
|
||||
|
||||
return {
|
||||
router,
|
||||
restore() {
|
||||
if (originalContextModule) nodeRequire.cache[contextResolvedPath] = originalContextModule;
|
||||
else delete nodeRequire.cache[contextResolvedPath];
|
||||
if (originalOssRouteModule) nodeRequire.cache[ossRouteResolvedPath] = originalOssRouteModule;
|
||||
else delete nodeRequire.cache[ossRouteResolvedPath];
|
||||
if (originalOssClientModule) nodeRequire.cache[ossClientResolvedPath] = originalOssClientModule;
|
||||
else delete nodeRequire.cache[ossClientResolvedPath];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function uploadWithScope(scope, mimeType) {
|
||||
const putCalls = [];
|
||||
const { router, restore } = loadOssRouter({
|
||||
isOssConfigured: () => true,
|
||||
putObject: async (objectKey, body, contentType, headers) => {
|
||||
putCalls.push({ objectKey, body, contentType, headers });
|
||||
},
|
||||
createSignedReadUrl: (objectKey) => `https://signed.example.com/${objectKey}?Expires=86400`,
|
||||
});
|
||||
|
||||
try {
|
||||
const handler = getRouteHandler(router, "post", "/oss/upload");
|
||||
const res = createMockResponse();
|
||||
await handler(
|
||||
{
|
||||
body: {
|
||||
dataUrl: `data:${mimeType};base64,${Buffer.from("{}").toString("base64")}`,
|
||||
mimeType,
|
||||
scope,
|
||||
},
|
||||
user: { id: "admin-1" },
|
||||
},
|
||||
res,
|
||||
);
|
||||
return { res, putCalls };
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
process.env.OSS_PUBLIC_BASE_URL = "https://cdn.example.com";
|
||||
|
||||
const imageUpload = await uploadWithScope("community-case-cover", "image/png");
|
||||
assert.equal(imageUpload.res.statusCode, 201);
|
||||
assert.match(imageUpload.putCalls[0].objectKey, /^community\/images\/.+\.png$/);
|
||||
assert.equal(imageUpload.res.body.ossKey, imageUpload.putCalls[0].objectKey);
|
||||
assert.equal(imageUpload.res.body.url, `https://cdn.example.com/${imageUpload.putCalls[0].objectKey}`);
|
||||
assert.equal(
|
||||
imageUpload.res.body.signedUrl,
|
||||
`https://signed.example.com/${imageUpload.putCalls[0].objectKey}?Expires=86400`,
|
||||
);
|
||||
|
||||
const workflowUpload = await uploadWithScope("community-case-workflow", "application/json");
|
||||
assert.equal(workflowUpload.res.statusCode, 201);
|
||||
assert.match(workflowUpload.putCalls[0].objectKey, /^community\/canvas\/.+\.json$/);
|
||||
assert.equal(workflowUpload.putCalls[0].contentType, "application/json");
|
||||
|
||||
console.log("oss route contract tests passed");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { createRequire } = require("node:module");
|
||||
|
||||
const nodeRequire = createRequire(__filename);
|
||||
const contextPath = "../src/routes/context.js";
|
||||
const projectRoutesPath = "../src/routes/projects.js";
|
||||
const ossClientPath = "../src/ossClient.js";
|
||||
|
||||
function passThrough(_req, _res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
function createMockResponse() {
|
||||
const res = {};
|
||||
res.status = (statusCode) => {
|
||||
res.statusCode = statusCode;
|
||||
return res;
|
||||
};
|
||||
res.json = (body) => {
|
||||
res.body = body;
|
||||
return res;
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
function loadProjectRouter(pool, ossClient) {
|
||||
const express = nodeRequire("express");
|
||||
const router = express.Router();
|
||||
const contextResolvedPath = nodeRequire.resolve(contextPath);
|
||||
const projectsResolvedPath = nodeRequire.resolve(projectRoutesPath);
|
||||
const ossResolvedPath = nodeRequire.resolve(ossClientPath);
|
||||
const originalContextModule = nodeRequire.cache[contextResolvedPath];
|
||||
const originalProjectsModule = nodeRequire.cache[projectsResolvedPath];
|
||||
const originalOssModule = nodeRequire.cache[ossResolvedPath];
|
||||
|
||||
delete nodeRequire.cache[projectsResolvedPath];
|
||||
|
||||
nodeRequire.cache[contextResolvedPath] = {
|
||||
id: contextResolvedPath,
|
||||
filename: contextResolvedPath,
|
||||
loaded: true,
|
||||
exports: {
|
||||
requireAuth: passThrough,
|
||||
pool,
|
||||
withTransaction: async (fn) => fn(pool),
|
||||
computeNextRevision: () => ({ nextRevision: 1 }),
|
||||
normalizeRevisionValue: (value) => Number(value || 0),
|
||||
shouldRejectStaleRevision: () => false,
|
||||
formatGenerationTaskRow: (row) => row,
|
||||
normalizeGenerationTaskPayload: (body) => ({ value: body }),
|
||||
normalizeProjectOssKey: (value) => ({ value }),
|
||||
buildOssPublicUrl: (key) => `https://cdn.example/${key}`,
|
||||
requireOwnedProject: async () => true,
|
||||
upsertGenerationTask: async () => ({}),
|
||||
},
|
||||
};
|
||||
nodeRequire.cache[ossResolvedPath] = {
|
||||
id: ossResolvedPath,
|
||||
filename: ossResolvedPath,
|
||||
loaded: true,
|
||||
exports: ossClient,
|
||||
};
|
||||
|
||||
nodeRequire(projectRoutesPath).registerProjectRoutes(router);
|
||||
|
||||
return {
|
||||
router,
|
||||
restore() {
|
||||
if (originalContextModule) nodeRequire.cache[contextResolvedPath] = originalContextModule;
|
||||
else delete nodeRequire.cache[contextResolvedPath];
|
||||
if (originalProjectsModule) nodeRequire.cache[projectsResolvedPath] = originalProjectsModule;
|
||||
else delete nodeRequire.cache[projectsResolvedPath];
|
||||
if (originalOssModule) nodeRequire.cache[ossResolvedPath] = originalOssModule;
|
||||
else delete nodeRequire.cache[ossResolvedPath];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const missing = new Error("OSS GET failed (404): NoSuchKey");
|
||||
missing.status = 404;
|
||||
missing.code = "oss_no_such_key";
|
||||
const { router, restore } = loadProjectRouter(
|
||||
{ query: async () => ({ rows: [{ oss_key: "users/1/projects/missing/current/project.json" }] }) },
|
||||
{
|
||||
isOssConfigured: () => true,
|
||||
getObject: async () => {
|
||||
throw missing;
|
||||
},
|
||||
putObject: async () => ({}),
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const handler = getRouteHandler(router, "get", "/projects/:id/content");
|
||||
const res = createMockResponse();
|
||||
await handler(
|
||||
{ params: { id: "workflow-missing" }, query: { resolveMedia: "1" }, user: { id: 1 } },
|
||||
res,
|
||||
);
|
||||
|
||||
assert.equal(res.statusCode, 404);
|
||||
assert.equal(res.body.code, "project_content_missing");
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
|
||||
console.log("project route contract tests passed");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user