ee670552a8
Build backend images / build content-svc (push) Failing after 1s
Build backend images / build file-svc (push) Failing after 0s
Build backend images / build gateway (push) Failing after 0s
Build backend images / build identity-svc (push) Failing after 0s
Build backend images / build notification-svc (push) Failing after 1s
Build backend images / build render-svc (push) Failing after 2s
Build backend images / build studio-svc (push) Failing after 0s
- content-svc: DuplicateProjectAsync clones full scene/element/colour graph
(identical keys, new dimensions/aspect; AEP intentionally not copied;
starts unpublished) + POST /v1/projects/{id}/duplicate.
- admin: «تکثیر» button + modal on each project row; aspects reduced to
supported 16:9/1:1/9:16; free fps default 21 (clamped 1-60).
- docs/aep-template-convention.md: versioned (v1/v2) convention + rule-engine
spec — modes, scene types, flatrender assembly, duration/fade model,
fit-box, input types, expression-driven data flow, output spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
3.3 KiB
JavaScript
69 lines
3.3 KiB
JavaScript
// Functional test for project cross-aspect duplication.
|
|
// Mints an admin JWT (HS256, same secret/issuer/audience as identity-svc),
|
|
// adds a scene to a real project, duplicates it to 1:1, verifies the clone has
|
|
// the scene under a NEW project id + new aspect, then cleans up.
|
|
import crypto from "node:crypto";
|
|
|
|
const GW = "http://172.28.144.1:8088";
|
|
const SECRET = "p9Xv7Lm2Qq8Nz4TfKc1Hs6YwRe3Ud0BafwefWEFw324234QEWF";
|
|
const b64url = (b) => Buffer.from(b).toString("base64url");
|
|
|
|
function mintToken() {
|
|
const header = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const payload = b64url(JSON.stringify({
|
|
sub: "00000000-0000-0000-0000-0000000000aa",
|
|
jti: crypto.randomUUID(),
|
|
tenant_id: "00000000-0000-0000-0000-0000000000bb",
|
|
tenant_slug: "flatrender",
|
|
is_admin: "true", is_tenant_admin: "false", role: "Admin",
|
|
iss: "flatrender-identity", aud: "flatrender",
|
|
exp: now + 3600, iat: now,
|
|
}));
|
|
const sig = crypto.createHmac("sha256", SECRET).update(`${header}.${payload}`).digest("base64url");
|
|
return `${header}.${payload}.${sig}`;
|
|
}
|
|
|
|
const H = { "Content-Type": "application/json", Authorization: `Bearer ${mintToken()}` };
|
|
async function j(method, path, body) {
|
|
const res = await fetch(GW + path, { method, headers: H, body: body ? JSON.stringify(body) : undefined, redirect: "follow" });
|
|
const text = await res.text();
|
|
let data; try { data = text ? JSON.parse(text) : null; } catch { data = text; }
|
|
return { status: res.status, data };
|
|
}
|
|
|
|
// 1. find a project
|
|
const pl = await j("GET", "/v1/projects/?page=1&page_size=1");
|
|
const items = pl.data?.items ?? pl.data?.data ?? (Array.isArray(pl.data) ? pl.data : []);
|
|
const pid = items[0]?.id;
|
|
console.log("source project:", pid ?? "<none>");
|
|
if (!pid) { console.log("NO PROJECT — create one in admin first."); process.exit(0); }
|
|
|
|
// 2. add a test scene to source
|
|
const SK = "scene_dup_TEST";
|
|
console.log("add test scene:", (await j("POST", "/v1/scenes", {
|
|
project_id: pid, key: SK, title: "Dup Test Scene", scene_type: "Normal",
|
|
default_duration_sec: 4, overlap_at_end_sec: 0, can_handle_duration: true,
|
|
generate_kf: false, manual_color_selection: false, sort: 99, is_active: true,
|
|
})).status);
|
|
|
|
// 3. duplicate to 1:1
|
|
const dup = await j("POST", `/v1/projects/${pid}/duplicate`, { aspect: "1:1", original_width: 1080, original_height: 1080, name: "DUP TEST 1:1" });
|
|
const newId = dup.data?.id;
|
|
console.log("duplicate:", dup.status, "| new id:", newId, "| aspect:", dup.data?.aspect, "| width:", dup.data?.original_width);
|
|
|
|
// 4. verify clone has the scene, scoped to the new project
|
|
if (newId) {
|
|
const r = await j("GET", `/v1/scenes/?project_id=${newId}`);
|
|
const scenes = Array.isArray(r.data) ? r.data : r.data?.data ?? [];
|
|
const found = scenes.find((s) => s.key === SK);
|
|
console.log(`VERIFY → clone scenes: ${scenes.length} | test scene present: ${!!found} | scoped to new project: ${found?.project_id === newId} | new id != source: ${newId !== pid}`);
|
|
}
|
|
|
|
// 5. cleanup
|
|
if (newId) console.log("cleanup dup project:", (await j("DELETE", `/v1/projects/${newId}`)).status);
|
|
const ss = await j("GET", `/v1/scenes/?project_id=${pid}`);
|
|
const srcScenes = Array.isArray(ss.data) ? ss.data : ss.data?.data ?? [];
|
|
const ts = srcScenes.find((x) => x.key === SK);
|
|
if (ts) console.log("cleanup test scene:", (await j("DELETE", `/v1/scenes/${ts.id}`)).status);
|