mirror of
https://github.com/penpot/penpot.git
synced 2026-08-10 06:48:49 +00:00
Add end-to-end HTTP tests under backend/test/e2e/ using Node.js built-in test runner (node:test) and native fetch. Tests run through the devenv nginx proxy on port 3450. Test suites (19 tests total): - auth-flow: demo profile creation, login, session cookies, access tokens - export-binfile: file creation, export to asset URL via SSE - asset-download: download with cookie/token auth, 401 without auth, S3 redirect behavior, full export-to-download flow Key findings documented in tests: - nginx @handle_redirect intercepts backend 307 and proxies to S3 directly, stripping the client Authorization header (bug does not reproduce in devenv) - SSE end event uses ~#uri tagged format for URLs - Unauthenticated RPC returns uuid/zero profile (not null) AI-assisted-by: mimo-v2.5-pro Signed-off-by: Andrey Antukh <niwi@niwi.nz>
73 lines
1.9 KiB
JavaScript
73 lines
1.9 KiB
JavaScript
import { createParser } from "eventsource-parser";
|
|
|
|
export function parseSSE(text) {
|
|
const events = [];
|
|
const parser = createParser({
|
|
onEvent(event) {
|
|
events.push({ event: event.event || "message", data: event.data });
|
|
},
|
|
});
|
|
parser.feed(text);
|
|
return events;
|
|
}
|
|
|
|
export function extractResult(events) {
|
|
const endEvent = events.find((e) => e.event === "end");
|
|
if (!endEvent) {
|
|
const errEvent = events.find((e) => e.event === "error");
|
|
if (errEvent) {
|
|
throw new Error(`SSE error: ${errEvent.data}`);
|
|
}
|
|
throw new Error(`No end event found in SSE stream. Events: ${JSON.stringify(events)}`);
|
|
}
|
|
|
|
const raw = JSON.parse(endEvent.data);
|
|
|
|
// Transit JSON verbose format:
|
|
// For URIs (e.g. asset URL): {"~#uri":"https://..."}
|
|
// For objects: {"~:key":"val",...} or ["^ ","~:key","val",...]
|
|
// For strings: plain string
|
|
if (raw && typeof raw === "object") {
|
|
// Tagged URI
|
|
if ("~#uri" in raw) {
|
|
return raw["~#uri"];
|
|
}
|
|
// Transit map with ~:value key
|
|
if ("~:value" in raw) {
|
|
const value = raw["~:value"];
|
|
if (Array.isArray(value)) {
|
|
return transitArrayToObj(value);
|
|
}
|
|
return value;
|
|
}
|
|
// Direct transit map (keys starting with ~:)
|
|
const firstKey = Object.keys(raw)[0];
|
|
if (firstKey && firstKey.startsWith("~:")) {
|
|
return transitMapToObj(raw);
|
|
}
|
|
}
|
|
|
|
return raw;
|
|
}
|
|
|
|
function transitArrayToObj(arr) {
|
|
// Transit verbose object: ["^ ","~:key1","val1","~:key2","val2",...]
|
|
const obj = {};
|
|
for (let i = 1; i < arr.length; i += 2) {
|
|
const key = arr[i].replace(/^~:/, "");
|
|
const val = arr[i + 1];
|
|
obj[key] = val;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
function transitMapToObj(map) {
|
|
// Transit verbose map: {"~:key1":"val1","~:key2":"val2",...}
|
|
const obj = {};
|
|
for (const [key, val] of Object.entries(map)) {
|
|
const cleanKey = key.replace(/^~:/, "");
|
|
obj[cleanKey] = val;
|
|
}
|
|
return obj;
|
|
}
|