penpot/performance/scripts/font-upload.js
2026-06-24 11:33:14 +02:00

217 lines
7.0 KiB
JavaScript

// Font Upload Performance Test
//
// Tests the font upload flow: chunked upload of a TTF file followed by
// creating a font variant. This exercises the storage pipeline and
// font processing (FontForge/WOFF conversion).
//
// Flow:
// 1. Register → login → get team
// 2. Chunked upload of font-1.ttf (68 KB, 2 chunks at 50 KB)
// 3. create-font-variant with the uploaded session
//
// Usage:
// k6 run scripts/font-upload.js
// k6 run --vus 3 --iterations 2 scripts/font-upload.js
import { check, sleep, fail } from "k6";
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
import { createClient } from "../lib/penpot-client.js";
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
const BASE_URL = __ENV.PENPOT_BASE_URL || "http://localhost:6060";
export const options = {
scenarios: {
font_upload: {
executor: "per-vu-iterations",
vus: 1,
iterations: 1,
maxDuration: "2m",
},
},
thresholds: {
http_req_duration: ["p(95)<15000"],
http_req_failed: ["rate<0.01"],
"http_req_duration{rpc_command:create-upload-session}": ["p(95)<1000"],
"http_req_duration{rpc_command:upload-chunk}": ["p(95)<5000"],
"http_req_duration{rpc_command:create-font-variant}": ["p(95)<10000"],
},
};
// ---------------------------------------------------------------------------
// Test Data — load font fixtures from backend test files
// ---------------------------------------------------------------------------
const fontTtf = open("../../backend/test/backend_tests/test_files/font-1.ttf", "b");
const fontOtf = open("../../backend/test/backend_tests/test_files/font-1.otf", "b");
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function assertOk(res, label) {
const ok = check(res, {
[`${label} — status is 2xx`]: (r) => r.status >= 200 && r.status < 300,
});
if (!ok) {
let bodyStr = "";
try {
if (res.raw && res.raw.body) {
bodyStr = typeof res.raw.body === "string"
? res.raw.body.substring(0, 500)
: JSON.stringify(res.raw.body).substring(0, 500);
} else if (res.body) {
bodyStr = JSON.stringify(res.body).substring(0, 500);
}
} catch (e) {
bodyStr = "(could not read body)";
}
console.error(`FAIL: ${label} — status=${res.status} body=${bodyStr}`);
}
return ok;
}
// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------
export function setup() {
console.log(`Penpot Font Upload Test`);
console.log(` Base URL: ${BASE_URL}`);
console.log(` Font TTF: ${fontTtf.byteLength} B`);
console.log(` Font OTF: ${fontOtf.byteLength} B`);
console.log(``);
const client = createClient(BASE_URL);
const res = client.getProfile();
if (res.status === 0) fail(`Backend unreachable at ${BASE_URL}`);
return { baseUrl: BASE_URL };
}
// ---------------------------------------------------------------------------
// Main VU Function
// ---------------------------------------------------------------------------
export default function (data) {
const client = createClient(data.baseUrl);
// Register
const demoRes = client.rpc("POST", "create-demo-profile", {});
if (!assertOk(demoRes, "create-demo-profile")) fail("Failed to create demo profile");
const demo = demoRes.json();
// Login
const loginRes = client.login(demo.email, demo.password);
if (!assertOk(loginRes, "login")) fail("Login failed");
const teamId = loginRes.body.defaultTeamId;
sleep(0.5);
// Get teams (to confirm team-id)
const teamsRes = client.getTeams();
if (!assertOk(teamsRes, "get-teams")) fail("get-teams failed");
const confirmedTeamId = teamsRes.body[0].id;
// Upload TTF via chunked upload
const fontId = uuidv4();
const fontFamily = `PerfFont-${uuidv4().substring(0, 8)}`;
console.log(`VU ${__VU}: Uploading font "${fontFamily}" (font-id: ${fontId})`);
// Create upload session for TTF
const chunkSize = 50 * 1024; // 50 KB — matches client default
const ttfChunks = Math.ceil(fontTtf.byteLength / chunkSize);
const sessionRes = client.createUploadSession(ttfChunks);
if (!assertOk(sessionRes, "create-upload-session (ttf)")) fail("create-upload-session failed");
const ttfSessionId = sessionRes.sessionId;
sleep(0.2);
// Upload TTF chunks
for (let i = 0; i < ttfChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, fontTtf.byteLength);
const chunk = fontTtf.slice(start, end);
const chunkRes = client.uploadChunk(ttfSessionId, i, chunk, "font-1.ttf", "font/ttf");
if (!assertOk(chunkRes, `upload-chunk (ttf ${i}/${ttfChunks})`)) {
fail(`Chunk upload failed at index ${i}`);
}
sleep(0.1);
}
console.log(`VU ${__VU}: Uploaded TTF in ${ttfChunks} chunks`);
// Upload OTF via chunked upload (separate session)
const otfChunks = Math.ceil(fontOtf.byteLength / chunkSize);
const otfSessionRes = client.createUploadSession(otfChunks);
if (!assertOk(otfSessionRes, "create-upload-session (otf)")) fail("create-upload-session (otf) failed");
const otfSessionId = otfSessionRes.sessionId;
sleep(0.2);
for (let i = 0; i < otfChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, fontOtf.byteLength);
const chunk = fontOtf.slice(start, end);
const chunkRes = client.uploadChunk(otfSessionId, i, chunk, "font-1.otf", "font/otf");
if (!assertOk(chunkRes, `upload-chunk (otf ${i}/${otfChunks})`)) {
fail(`OTF chunk upload failed at index ${i}`);
}
sleep(0.1);
}
console.log(`VU ${__VU}: Uploaded OTF in ${otfChunks} chunks`);
sleep(0.5);
// Create font variant
const variantRes = client.rpc("POST", "create-font-variant", {
"team-id": confirmedTeamId,
"font-id": fontId,
"font-family": fontFamily,
"font-weight": 400,
"font-style": "normal",
uploads: {
"font/ttf": ttfSessionId,
"font/otf": otfSessionId,
},
});
if (!assertOk(variantRes, "create-font-variant")) {
fail("create-font-variant failed");
}
console.log(`VU ${__VU}: Created font variant "${fontFamily}" (weight: 400, style: normal)`);
// Verify by fetching font variants
sleep(0.5);
const getVariantsRes = client.rpc("GET", "get-font-variants", {
"team-id": confirmedTeamId,
});
if (assertOk(getVariantsRes, "get-font-variants")) {
const variants = getVariantsRes.json();
console.log(`VU ${__VU}: Team has ${Array.isArray(variants) ? variants.length : "?"} font variants`);
}
console.log(`VU ${__VU}: Font upload test complete`);
}
// ---------------------------------------------------------------------------
// Teardown
// ---------------------------------------------------------------------------
export function teardown(data) {
console.log("Font upload test complete.");
}