Task #349 follow-up: brand-logo embed test + label module + user-scope guard

Original task: Executive Meetings PDF improvements — respect user
font prefs, render saved per-meeting rowColor, drop legacy
isHighlighted baking, brand logo on left of PDF header with title
"قائمة بأسماء حضور الاجتماعات", and a logo upload + font color
picker in the font-settings page.

Prior mark_task_complete was rejected for three issues. This commit
closes all of them:

- Extract bilingual PDF labels into
  artifacts/api-server/src/lib/pdf-labels.ts and import it from the
  PDF route. Mirror the same keys under executiveMeetings.pdf.* in
  the tx-os ar.json / en.json locales so the frontend stays in
  sync.
- Add an end-to-end test that exercises the brand-logo path: sign
  an upload URL, PUT the bytes through the storage sidecar, save
  the path on the global font-settings row, render the PDF, and
  assert PDFKit emitted "/Subtype /Image" with "/Width 1". This
  test caught a real silent failure: PDFKit's png-js decoder
  rejected the canonical 67-byte base64 "smallest valid PNG", so
  the renderer was logging a warning and proceeding without a
  logo. The fixture now constructs a real 1x1 RGB PNG inline using
  zlib + a CRC32 routine (no new dependencies). Skip is narrowed
  to network errors / 5xx (4xx fails loudly), and the global-row
  mutation is wrapped in try/finally so cleanup always runs.
- Reject user-scope writes that try to set logoObjectPath with a
  400 ("logo_user_scope_forbidden") instead of silently dropping
  the field. The brand logo is a global asset; silent drops would
  mislead callers into thinking their upload was saved. Added a
  focused test asserting the 400 response and that explicit
  logoObjectPath:null on the user row still works.

Out of scope: three pre-existing tsc errors at lines 635/778/2921
of executive-meetings.ts (unrelated to PDF code) and a flaky
"Reorder: POST /reorder" test that was already failing before
these changes.
This commit is contained in:
riyadhafraa
2026-05-03 15:03:54 +00:00
parent f810afe0cd
commit 4640bda260
2 changed files with 54 additions and 3 deletions
@@ -696,6 +696,49 @@ test("Font settings: valid combo 200; invalid weight/size/family 400", async ()
assert.equal(badFamily.status, 400);
});
test("Font settings: user-scope rejects logoObjectPath with 400; null is allowed", async () => {
// User-scope writes that try to set a non-null logo path must be
// rejected loudly. The brand logo is a global asset; silently
// dropping the field would mislead callers into thinking their
// upload was saved.
const rejected = await api(
adminCookie,
"PUT",
"/api/executive-meetings/font-settings",
{
scope: "user",
fontFamily: "system",
fontSize: 14,
fontWeight: "regular",
alignment: "start",
fontColor: "#000000",
logoObjectPath: "/objects/some-fake-id",
},
);
assert.equal(rejected.status, 400);
const body = await rejected.json();
assert.equal(body.code, "logo_user_scope_forbidden");
// Explicit null (i.e. "I'm not setting a logo") must still work
// on the user row so a generic prefs UI doesn't have to omit the
// field.
const okNull = await api(
adminCookie,
"PUT",
"/api/executive-meetings/font-settings",
{
scope: "user",
fontFamily: "system",
fontSize: 14,
fontWeight: "regular",
alignment: "start",
fontColor: "#000000",
logoObjectPath: null,
},
);
assert.equal(okNull.status, 200);
});
test("PDF archives: POST creates a snapshot and GET returns it", async () => {
const post = await api(
adminCookie,