Skip to content
Open
4 changes: 4 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 48 additions & 0 deletions apps/desktop/src/features/score/ScoreView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<ScoreView song={song} projectId="project-1-2" onSongUpdate={onSongUpdate} />);

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(<ScoreView song={song} projectId="project-1-2" onSongUpdate={onSongUpdate} />);

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(<ScoreView song={song} projectId="project-1-2" onSongUpdate={onSongUpdate} />);

fireEvent.click(screen.getByRole("button", { name: "Add score" }));

expect(await screen.findByRole("alert")).toHaveTextContent("First line of error");
});
});
Loading