Skip to content
Merged
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
109 changes: 109 additions & 0 deletions src/shared/backend/__tests__/sdcard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,4 +1271,113 @@ describe("Sdcard Job", () => {
`);
expect(directorySnapshot(tempDir.path)).toMatchSnapshot();
});

it("should continue erasing even if a file/directory is not found during deletion", async () => {
const handle = await getOriginPrivateDirectory(nodeAdapter, tempDir.path);
// @ts-expect-error is readonly but this is testing
handle.name = tempDir.path;

// Create some files and directories to erase
await Promise.all([
fs.mkdir(path.join(tempDir.path, "EEPROM")), // Should not be deleted
fs.mkdir(path.join(tempDir.path, "THEMES")),
fs.mkdir(path.join(tempDir.path, "SCRIPTS")),
]);

// Mock removeEntry to throw NotFoundError for one entry
const originalRemoveEntry = handle.removeEntry.bind(handle);
handle.removeEntry = vitest.fn(
async (name: string, options?: { recursive?: boolean }) => {
if (name === "THEMES") {
const error = new Error("Entry not found");
error.name = "NotFoundError";
throw error;
}
return originalRemoveEntry(name, options);
}
);

requestWritableDirectory.mockResolvedValue(handle);

const directoryRequest = await backend.mutate({
mutation: gql`
mutation RequestDirectory {
pickSdcardDirectory {
id
name
}
}
`,
});

const { id: directoryId } = directoryRequest.data?.pickSdcardDirectory as {
id: string;
};

const { nockDone } = await nock.back("sdcard-job-jumper-t8-cn-latest.json");

const createJobRequest = await backend.mutate({
mutation: gql`
mutation CreateSdcardJob($directoryId: ID!) {
createSdcardWriteJob(
directoryId: $directoryId
pack: { target: "t8", version: "latest" }
sounds: { ids: ["cn"], version: "latest" }
) {
id
}
}
`,
variables: {
directoryId,
},
});

const jobId = (
createJobRequest.data?.createSdcardWriteJob as { id: string } | null
)?.id;

expect(createJobRequest.errors).toBeFalsy();
expect(jobId).toBeTruthy();

await waitForSdcardJobCompleted(jobId!);
nockDone();

const { data, errors } = await backend.query({
query: gql`
query SdcardJobStatus($id: ID!) {
sdcardWriteJobStatus(jobId: $id) {
cancelled
stages {
erase {
started
completed
progress
error
}
write {
started
completed
progress
}
}
}
}
`,
variables: {
id: jobId,
},
});

expect(errors).toBeFalsy();
// Erase should complete successfully despite NotFoundError
expect((data as any)?.sdcardWriteJobStatus?.stages.erase.completed).toBe(
true
);
expect((data as any)?.sdcardWriteJobStatus?.stages.erase.error).toBeNull();
// Write should also complete
expect((data as any)?.sdcardWriteJobStatus?.stages.write.completed).toBe(
true
);
});
});
8 changes: 5 additions & 3 deletions src/shared/backend/services/sdcardJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,13 @@ const erase = async (
entry.kind === "directory" ? { recursive: true } : undefined
)
.catch((e) => {
// Some weird macos folder
const error = e as Error;
// Handle stale file system handles or file already deleted
if (
(e as Error).message.includes(
error.message.includes(
"An operation that depends on state cached in an interface object"
)
) ||
error.name === "NotFoundError"
) {
return;
}
Expand Down
Loading