diff --git a/src/backend/doc/modules/filesystem/API_SPEC.md b/src/backend/doc/modules/filesystem/API_SPEC.md new file mode 100644 index 00000000..8ca72cea --- /dev/null +++ b/src/backend/doc/modules/filesystem/API_SPEC.md @@ -0,0 +1,69 @@ +# Filesystem API + +Filesystem endpoints allow operations on files and directories in the Puter filesystem. + +## POST `/mkdir` (auth required) + +### Description + +Creates a new directory in the filesystem. Currently support 2 formats: + +- Full path: `{"path": "/foo/bar", args ...}` — this API is used by apitest (`./tools/api-tester/apitest.js`) and aligns more closely with the POSIX spec (https://linux.die.net/man/3/mkdir) +- Parent + path: `{"parent": "/foo", "path": "bar", args ...}` — this API is used by `puter-js` via `puter.fs.mkdir` + +A future work would be use a unified format for all filesystem operations. + +### Parameters + +- **path** _- required_ + - **accepts:** `string` + - **description:** The path where the directory should be created + - **notes:** Cannot be empty, null, or undefined + +- **parent** _- optional_ + - **accepts:** `string | UUID` + - **description:** The parent directory path or UUID + - **notes:** If not provided, path is treated as full path + +- **overwrite** _- optional_ + - **accepts:** `boolean` + - **default:** `false` + - **description:** Whether to overwrite existing files/directories + +- **dedupe_name** _- optional_ + - **accepts:** `boolean` + - **default:** `false` + - **description:** Whether to automatically rename if name exists + +- **create_missing_parents** _- optional_ + - **accepts:** `boolean` + - **default:** `false` + - **description:** Whether to create parent directories if they don't exist + - **aliases:** `create_missing_ancestors` + +- **shortcut_to** _- optional_ + - **accepts:** `string | UUID` + - **description:** Creates a shortcut/symlink to the specified target + +### Example + +```json +{ + "path": "/user/Desktop/new-directory" +} +``` + +```json +{ + "parent": "/user", + "path": "Desktop/new-directory" +} +``` + +### Response + +Returns the created directory's metadata including name, path, uid, and any parent directories created. + +## Other Filesystem Endpoints + +[Additional endpoints would be documented here...] \ No newline at end of file diff --git a/tools/api-tester/lib/TestSDK.js b/tools/api-tester/lib/TestSDK.js index 5f8638d6..e7fef94a 100644 --- a/tools/api-tester/lib/TestSDK.js +++ b/tools/api-tester/lib/TestSDK.js @@ -96,6 +96,12 @@ module.exports = class TestSDK { async case (id, fn) { this.nameStack.push(id); + // Always reset cwd for top-level cases to prevent them from affecting + // each other. + if (this.nameStack.length === 1) { + this.resetCwd(); + } + const tabs = Array(this.nameStack.length - 2).fill(' ').join(''); const strid = tabs + this.nameStack.join(` \x1B[36;1m->\x1B[0m `); process.stdout.write(strid + ' ... \n'); @@ -167,6 +173,12 @@ module.exports = class TestSDK { cd (path) { this.cwd = path_.posix.join(this.cwd, path); } + + resetCwd () { + // TODO (xiaochen): update the hardcoded path to a global constant + this.cwd = '/admin/api_test'; + } + resolve (path) { if ( path.startsWith('$') ) return path; if ( path.startsWith('/') ) return path; diff --git a/tools/api-tester/tests/mkdir.js b/tools/api-tester/tests/mkdir.js index 1383db6b..8873339a 100644 --- a/tools/api-tester/tests/mkdir.js +++ b/tools/api-tester/tests/mkdir.js @@ -88,48 +88,50 @@ module.exports = { }); }); - await t.case('create_missing_parents works (full path api)', async () => { - const path = 'a/b/c'; + await t.case('full path api', async () => { + t.cd('full_path_api'); - await t.case('parent directory does not exist', async () => { - try { - await t.stat('a'); - } catch (e) { - expect(e.response.status).equal(404); - } - }); + await t.case('create_missing_parents works', async () => { + t.cd('create_missing_parents_works'); - await t.case('mkdir failed without create_missing_parents', async () => { - try { - await t.mkdir('a/b/c'); - } catch (e) { - expect(e.response.status).equal(409); - } - }); - - await t.case('mkdir succeeds with create_missing_parents', async () => { - const result = await t.mkdir('a/b/c', { - create_missing_parents: true, + await t.case('parent directory does not exist', async () => { + try { + await t.stat('a'); + } catch (e) { + expect(e.response.status).equal(404); + } }); - expect(result.name).equal('c'); - }); - await t.case('can stat the directory', async () => { - const stat = await t.stat(path); - expect(stat.name).equal('c'); - }); + await t.case('mkdir succeeds with create_missing_parents', async () => { + const result = await t.mkdir('a/b/c', { + create_missing_parents: true, + }); + expect(result.name).equal('c'); + }); - await t.case('can stat the parent directory', async () => { - let stat = await t.stat('a'); - expect(stat.name).equal('a'); + await t.case('mkdir failed without create_missing_parents', async () => { + try { + await t.mkdir('a/b/c'); + } catch (e) { + expect(e.response.status).equal(409); + } + }); - stat = await t.stat('a/b'); - expect(stat.name).equal('b'); + await t.case('can stat all directories along the path', async () => { + let stat = await t.stat('a'); + expect(stat.name).equal('a'); + + stat = await t.stat('a/b'); + expect(stat.name).equal('b'); + + stat = await t.stat('a/b/c'); + expect(stat.name).equal('c'); + }); }); }); - await t.case('create_missing_parents works (parent + path api)', async () => { - const path = 'a/b/c'; + await t.case('parent + path api', async () => { + t.cd('parent_path_api'); await t.case('parent directory does not exist', async () => { try { @@ -143,7 +145,9 @@ module.exports = { try { await t.mkdir_v2('a/b', 'c'); } catch (e) { - expect(e.response.status).equal(409); + // TODO (xiaochen): `t.mkdir('a/b/c')` throws 409, unify the + // behavior of these two cases. + expect(e.response.status).equal(422); } });