Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions apps/desktop/src/features/score/scoreStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,47 @@ describe("scoreStorage bridge resolution", () => {
delete tauriWindow.__TAURI_INVOKE__;
});

it("throws INVALID_RESPONSE_MESSAGE when readScorePdf returns an array with non-number elements (early exit)", async () => {
const mockInvoke = vi.fn().mockResolvedValue([1, 2, "not-a-number", 4]);
const tauriWindow = {
__TAURI_INVOKE__: mockInvoke
} as unknown as TauriWindow;
vi.stubGlobal("window", tauriWindow);

await expect(readScorePdf("project-1", "score-1")).rejects.toThrow("Invalid score bridge response");
expect(mockInvoke).toHaveBeenCalledWith("read_score_pdf", { projectId: "project-1", scoreId: "score-1" });
});

it.each([
["negative", -1],
["above the byte range", 256],
["fractional", 1.5],
["NaN", Number.NaN],
["infinite", Number.POSITIVE_INFINITY]
])("rejects %s numeric values before Uint8Array coercion", async (_label, invalidByte) => {
const mockInvoke = vi.fn().mockResolvedValue([0, invalidByte, 255]);
const tauriWindow = {
__TAURI_INVOKE__: mockInvoke
} as unknown as TauriWindow;
vi.stubGlobal("window", tauriWindow);

await expect(readScorePdf("project-1", "score-1")).rejects.toThrow("Invalid score bridge response");
expect(mockInvoke).toHaveBeenCalledWith("read_score_pdf", { projectId: "project-1", scoreId: "score-1" });
});

it("returns Uint8Array when readScorePdf returns a valid number array", async () => {
const mockInvoke = vi.fn().mockResolvedValue([1, 2, 3, 4]);
const tauriWindow = {
__TAURI_INVOKE__: mockInvoke
} as unknown as TauriWindow;
vi.stubGlobal("window", tauriWindow);

const result = await readScorePdf("project-1", "score-1");
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new Uint8Array([1, 2, 3, 4]));
expect(mockInvoke).toHaveBeenCalledWith("read_score_pdf", { projectId: "project-1", scoreId: "score-1" });
});

it("fails closed on every command when there is no window (non-browser runtime)", async () => {
// Simulate a runtime without a DOM window (e.g. SSR / bundler prerender):
// getInvoke() must take the `typeof window === "undefined"` branch and
Expand Down
13 changes: 12 additions & 1 deletion apps/desktop/src/features/score/scoreStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,18 @@ export async function readScorePdf(projectId: string, scoreId: string): Promise<
if (response instanceof ArrayBuffer) {
return new Uint8Array(response);
}
if (Array.isArray(response) && response.every((byte) => typeof byte === "number")) {
if (Array.isArray(response)) {
for (let index = 0; index < response.length; index += 1) {
const byte = response[index];
if (
typeof byte !== "number" ||
!Number.isInteger(byte) ||
byte < 0 ||
byte > 255
) {
throw new Error(INVALID_RESPONSE_MESSAGE);
}
}
return Uint8Array.from(response as number[]);
}

Expand Down
Loading