diff --git a/packages/filesystem/onedrive/onedrive.test.ts b/packages/filesystem/onedrive/onedrive.test.ts index f2092991b..b9c1deca4 100644 --- a/packages/filesystem/onedrive/onedrive.test.ts +++ b/packages/filesystem/onedrive/onedrive.test.ts @@ -93,4 +93,38 @@ describe("OneDriveFileSystem", () => { await expect(fs.delete("missing.txt")).resolves.toBeUndefined(); }); + + it("createDir should create nested directories from root", async () => { + const fs = new OneDriveFileSystem("/", "token"); + const requestSpy = vi.spyOn(fs, "request").mockResolvedValue({}); + + await expect(fs.createDir("A/B/C")).resolves.toBeUndefined(); + + expect(requestSpy).toHaveBeenCalledTimes(3); + expect(requestSpy.mock.calls[0][0]).toBe("https://graph.microsoft.com/v1.0/me/drive/special/approot/children"); + expect(requestSpy.mock.calls[1][0]).toBe("https://graph.microsoft.com/v1.0/me/drive/special/approot:/A:/children"); + expect(requestSpy.mock.calls[2][0]).toBe( + "https://graph.microsoft.com/v1.0/me/drive/special/approot:/A/B:/children" + ); + expect(JSON.parse((requestSpy.mock.calls[2][1] as RequestInit).body as string)).toMatchObject({ + name: "C", + folder: {}, + "@microsoft.graph.conflictBehavior": "fail", + }); + }); + + it("createDir should continue when an intermediate directory already exists", async () => { + const fs = new OneDriveFileSystem("/", "token"); + const requestSpy = vi + .spyOn(fs, "request") + .mockRejectedValueOnce(new Error('{"error":{"code":"nameAlreadyExists"}}')) + .mockResolvedValueOnce({}); + + await expect(fs.createDir("A/B")).resolves.toBeUndefined(); + + expect(requestSpy).toHaveBeenCalledTimes(2); + expect(JSON.parse((requestSpy.mock.calls[1][1] as RequestInit).body as string)).toMatchObject({ + name: "B", + }); + }); }); diff --git a/packages/filesystem/onedrive/onedrive.ts b/packages/filesystem/onedrive/onedrive.ts index d7792203e..0a0f686b1 100644 --- a/packages/filesystem/onedrive/onedrive.ts +++ b/packages/filesystem/onedrive/onedrive.ts @@ -45,31 +45,37 @@ export default class OneDriveFileSystem implements FileSystem { if (!dir) { return; } - dir = joinPath(this.path, dir); - const dirs = dir.split("/"); - let parent = ""; - if (dirs.length > 2) { - parent = dirs.slice(0, dirs.length - 1).join("/"); - } + const dirs = joinPath(this.path, dir).split("/").filter(Boolean); const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); - if (parent !== "") { - parent = `:${parent}:`; - } - const data = await this.request(`https://graph.microsoft.com/v1.0/me/drive/special/approot${parent}/children`, { - method: "POST", - headers: myHeaders, - body: JSON.stringify({ - name: dirs[dirs.length - 1], - folder: {}, - "@microsoft.graph.conflictBehavior": "replace", - }), - }); - if (data.errno) { - throw new Error(JSON.stringify(data)); + + for (let i = 0; i < dirs.length; i++) { + const parentPath = dirs.slice(0, i).join("/"); + const parent = parentPath ? `:/${parentPath}:` : ""; + try { + await this.request(`https://graph.microsoft.com/v1.0/me/drive/special/approot${parent}/children`, { + method: "POST", + headers: myHeaders, + body: JSON.stringify({ + name: dirs[i], + folder: {}, + "@microsoft.graph.conflictBehavior": "fail", + }), + }); + } catch (error) { + if (this.isDirectoryAlreadyExistsError(error)) { + continue; + } + throw error; + } } } + private isDirectoryAlreadyExistsError(error: unknown): boolean { + const msg = String(error); + return msg.includes("nameAlreadyExists") || msg.includes("itemAlreadyExists"); + } + request(url: string, config?: RequestInit, nothen?: boolean): Promise { config = config || {}; const headers = config.headers || new Headers();