From a9f9a591cb46b1109d9cbaf647e0d446626ba3be Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 6 May 2026 18:33:28 +0900 Subject: [PATCH] fix(sync): OneDrive zero-byte upload fix --- packages/filesystem/onedrive/onedrive.test.ts | 51 +++++++++++++++++++ packages/filesystem/onedrive/rw.ts | 11 +++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/filesystem/onedrive/onedrive.test.ts b/packages/filesystem/onedrive/onedrive.test.ts index b9c1deca4..8e8e4826d 100644 --- a/packages/filesystem/onedrive/onedrive.test.ts +++ b/packages/filesystem/onedrive/onedrive.test.ts @@ -127,4 +127,55 @@ describe("OneDriveFileSystem", () => { name: "B", }); }); + + it("writer should upload empty string with simple PUT", async () => { + const fs = new OneDriveFileSystem("/", "token"); + const requestSpy = vi.spyOn(fs, "request").mockResolvedValue({}); + + const writer = await fs.create("empty.txt"); + await writer.write(""); + + expect(requestSpy).toHaveBeenCalledTimes(1); + expect(requestSpy.mock.calls[0][0]).toBe( + "https://graph.microsoft.com/v1.0/me/drive/special/approot:/empty.txt:/content" + ); + expect(requestSpy.mock.calls[0][1]).toMatchObject({ + method: "PUT", + body: "", + }); + }); + + it("writer should upload empty Blob with simple PUT", async () => { + const fs = new OneDriveFileSystem("/", "token"); + const requestSpy = vi.spyOn(fs, "request").mockResolvedValue({}); + const emptyBlob = new Blob([]); + + const writer = await fs.create("empty.bin"); + await writer.write(emptyBlob); + + expect(requestSpy).toHaveBeenCalledTimes(1); + expect(requestSpy.mock.calls[0][0]).toBe( + "https://graph.microsoft.com/v1.0/me/drive/special/approot:/empty.bin:/content" + ); + expect((requestSpy.mock.calls[0][1] as RequestInit).body).toBe(emptyBlob); + }); + + it("writer should keep upload session for non-empty content", async () => { + const fs = new OneDriveFileSystem("/", "token"); + const requestSpy = vi + .spyOn(fs, "request") + .mockResolvedValueOnce({ uploadUrl: "https://upload.example/session" }) + .mockResolvedValueOnce({}); + + const writer = await fs.create("not-empty.txt"); + await writer.write("abc"); + + expect(requestSpy).toHaveBeenCalledTimes(2); + expect(requestSpy.mock.calls[0][0]).toBe( + "https://graph.microsoft.com/v1.0/me/drive/special/approot:/not-empty.txt:/createUploadSession" + ); + expect(requestSpy.mock.calls[1][0]).toBe("https://upload.example/session"); + const headers = (requestSpy.mock.calls[1][1] as RequestInit).headers as Headers; + expect(headers.get("Content-Range")).toBe("bytes 0-2/3"); + }); }); diff --git a/packages/filesystem/onedrive/rw.ts b/packages/filesystem/onedrive/rw.ts index 4d212c5e6..b0d33d55a 100644 --- a/packages/filesystem/onedrive/rw.ts +++ b/packages/filesystem/onedrive/rw.ts @@ -58,7 +58,14 @@ export class OneDriveFileWriter implements FileWriter { async write(content: string | Blob): Promise { // 预上传获取id - const size = this.size(content).toString(); + const size = this.size(content); + if (size === 0) { + return this.fs.request(`https://graph.microsoft.com/v1.0/me/drive/special/approot:${this.path}:/content`, { + method: "PUT", + body: content, + }); + } + let myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); const uploadUrl = await this.fs @@ -83,7 +90,7 @@ export class OneDriveFileWriter implements FileWriter { return data.uploadUrl; }); myHeaders = new Headers(); - myHeaders.append("Content-Range", `bytes 0-${parseInt(size, 10) - 1}/${size}`); + myHeaders.append("Content-Range", `bytes 0-${size - 1}/${size}`); return this.fs.request(uploadUrl, { method: "PUT", body: content,