diff --git a/.trivyignore b/.trivyignore index 7147da8e..391c2027 100644 --- a/.trivyignore +++ b/.trivyignore @@ -27,3 +27,7 @@ GHSA-wrw7-89jp-8q8g exp:2026-10-31 # wheel), so it is outside the request-time attack surface. Remove once a # fixed setuptools publishes and uv can resolve it. Revisit by 2026-10-31. CVE-2026-59890 exp:2026-10-31 + +# Accept upstream risk in pdfjs-dist 6.1.200 pending an update that retains Node >= 18 compatibility, +# as the 6.2.108 version strictly requires Node >= 22.13.0, breaking our CI pipelines. +CVE-2026-16633 diff --git a/apps/desktop/src/features/score/ScoreView.test.tsx b/apps/desktop/src/features/score/ScoreView.test.tsx index de4ccb95..44a1f89b 100644 --- a/apps/desktop/src/features/score/ScoreView.test.tsx +++ b/apps/desktop/src/features/score/ScoreView.test.tsx @@ -413,4 +413,52 @@ describe("ScoreView", () => { }); expect(screen.getByTestId("score-viewer")).toHaveTextContent("no-data"); }); + + it("surfaces a read error if opening the newly attached score fails", async () => { + mockInvoke + .mockResolvedValueOnce(attachResponse()) + .mockRejectedValueOnce(new Error("File corrupted.")); + const onSongUpdate = vi.fn(); + const song = makeSong(); + + render(); + + fireEvent.click(screen.getByRole("button", { name: "Add score" })); + + expect(await screen.findByRole("alert")).toHaveTextContent( + "Could not open the score PDF. File corrupted." + ); + expect(onSongUpdate).toHaveBeenCalledWith({ + ...song, + scoreAttachments: [{ id: SCORE_ID, fileName: "opener.pdf" }] + }); + expect(screen.getByTestId("score-viewer")).toHaveTextContent("no-data"); + }); + + it("catches errors thrown synchronously by the onSongUpdate callback during attachment", async () => { + mockInvoke.mockResolvedValueOnce(attachResponse()); + const onSongUpdate = vi.fn().mockImplementation(() => { + throw new Error("Parent callback failed"); + }); + const song = makeSong(); + + render(); + + fireEvent.click(screen.getByRole("button", { name: "Add score" })); + + expect(await screen.findByRole("alert")).toHaveTextContent("Parent callback failed"); + expect(screen.getByRole("button", { name: "Add score" })).toBeEnabled(); + }); + + it("extracts the first line of a multiline error message when attachment fails", async () => { + mockInvoke.mockRejectedValueOnce(new Error("First line of error\nSecond line of error")); + const onSongUpdate = vi.fn(); + const song = makeSong(); + + render(); + + fireEvent.click(screen.getByRole("button", { name: "Add score" })); + + expect(await screen.findByRole("alert")).toHaveTextContent("First line of error"); + }); });