mirror of
https://github.com/penpot/penpot.git
synced 2026-06-29 10:42:06 +00:00
217 lines
7.0 KiB
JavaScript
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.");
|
|
}
|