Skip to content

Commit bfaa863

Browse files
committed
puterjs/fs: add "share" arg to fs.write api, add test as well
1 parent a87739a commit bfaa863

File tree

9 files changed

+210
-5
lines changed

9 files changed

+210
-5
lines changed

src/puter-js/src/modules/FileSystem/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import upload from "./operations/upload.js";
99
import read from "./operations/read.js";
1010
import move from "./operations/move.js";
1111
import write from "./operations/write.js";
12+
import share from "./operations/share.js";
1213
import sign from "./operations/sign.js";
1314
import symlink from './operations/symlink.js';
1415
// Why is this called deleteFSEntry instead of just delete? because delete is
@@ -32,7 +33,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
3233
write = write;
3334
sign = sign;
3435
symlink = symlink;
35-
36+
share = share;
3637
FSItem = FSItem
3738

3839
static NARI_METHODS = {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
2+
3+
const share = async function (targetPath, options = {}) {
4+
// targetPath is required
5+
if (!targetPath) {
6+
throw new Error('No target path provided.');
7+
}
8+
9+
// If targetPath is not provided or it's not starting with a slash, it means it's a relative path
10+
// in that case, we need to prepend the app's root directory to it
11+
targetPath = getAbsolutePathForApp(targetPath);
12+
13+
// Extract options
14+
const recipients = options.recipients || [];
15+
const access = options.access || 'read';
16+
17+
// Validate access level
18+
if (!['read', 'write'].includes(access)) {
19+
throw new Error('Invalid access level. Must be "read" or "write".');
20+
}
21+
22+
// Validate recipients
23+
if (!Array.isArray(recipients) || recipients.length === 0) {
24+
throw new Error('Recipients must be a non-empty array.');
25+
}
26+
27+
// Prepare the share request
28+
const shareData = {
29+
recipients: recipients,
30+
shares: [
31+
{
32+
$: 'fs-share',
33+
path: targetPath,
34+
access: access,
35+
}
36+
]
37+
};
38+
39+
// Make the API call to share the file
40+
console.log(`api origin: ${puter.APIOrigin}`);
41+
const response = await fetch(`${puter.APIOrigin}/share`, {
42+
method: 'POST',
43+
headers: {
44+
'Content-Type': 'application/json',
45+
'Authorization': `Bearer ${puter.authToken}`
46+
},
47+
body: JSON.stringify(shareData)
48+
});
49+
50+
if (!response.ok) {
51+
const errorText = await response.text();
52+
throw new Error(`Share failed: ${response.status} ${errorText}`);
53+
}
54+
55+
const result = await response.json();
56+
return result;
57+
};
58+
59+
export default share;

src/puter-js/src/modules/FileSystem/operations/write.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import path from "../../../lib/path.js"
1+
import path from "../../../lib/path.js";
22
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
33

44
const write = async function (targetPath, data, options = {}) {
@@ -56,7 +56,35 @@ const write = async function (targetPath, data, options = {}) {
5656
}
5757

5858
// perform upload
59-
return this.upload(data, parent, options);
59+
const result = await this.upload(data, parent, options);
60+
61+
if (options.share) {
62+
try {
63+
// Call the share API after successful write
64+
let share_result = await this.share(result.path, {
65+
recipients: options.share.recipients || [],
66+
access: options.share.access || 'read'
67+
});
68+
console.log('share_result', share_result);
69+
70+
// Add share information to the result
71+
result.share = {
72+
status: share_result.status,
73+
recipients: share_result.recipients || [],
74+
shares: share_result.shares || []
75+
};
76+
} catch (error) {
77+
console.error('Failed to share file after write:', error);
78+
// Add error information to the result
79+
result.share = {
80+
status: 'error',
81+
error: error.message,
82+
recipients: []
83+
};
84+
}
85+
}
86+
87+
return result;
6088
}
6189

6290
export default write;

tools/api-tester/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# API Tester
22

3-
A test framework for testing the backend API of puter.
3+
A test framework for testing the API of puter backend and puter-js client.
44

55
## Table of Contents
66

@@ -43,6 +43,12 @@ All commands below should be run from the root directory of puter.
4343
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml
4444
```
4545
46+
4. (experimental) Run tests against the puter-js client:
47+
48+
```bash
49+
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml --client
50+
```
51+
4652
### Shorthands
4753
4854
- Run all tests (unit tests and benchmarks):

tools/api-tester/apitest.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ const { parseArgs } = require('node:util');
99

1010
const args = process.argv.slice(2);
1111

12-
let config, report, suiteName, onlycase, bench, unit, stopOnFailure, id;
12+
let config, report, suiteName, onlycase, bench, unit, stopOnFailure, id, puterjs;
1313

1414
try {
1515
const parsed = parseArgs({
1616
options: {
1717
config: {
1818
type: 'string',
19+
default: './tools/api-tester/config.yml',
1920
},
2021
report: {
2122
type: 'string',
@@ -25,6 +26,7 @@ try {
2526
unit: { type: 'boolean' },
2627
suite: { type: 'string' },
2728
'stop-on-failure': { type: 'boolean' },
29+
puterjs: { type: 'boolean' },
2830
},
2931
allowPositionals: true,
3032
});
@@ -37,6 +39,7 @@ try {
3739
unit,
3840
suite: suiteName,
3941
'stop-on-failure': stopOnFailure,
42+
puterjs,
4043
}, positionals: [id] } = parsed);
4144

4245
onlycase = onlycase !== undefined ? Number.parseInt(onlycase) : undefined;
@@ -49,6 +52,7 @@ try {
4952
'\n' +
5053
'Options:\n' +
5154
' --config=<path> (required) Path to configuration file\n' +
55+
' --puterjs (optional) Use puter-js puterjs\n' +
5256
' --report=<path> (optional) Output file for full test results\n' +
5357
' --suite=<name> (optional) Run only tests with matching suite name\n' +
5458
' --stop-on-failure (optional) Stop execution on first test failure\n' +
@@ -61,6 +65,29 @@ const conf = YAML.parse(fs.readFileSync(config).toString());
6165

6266

6367
const main = async () => {
68+
if (puterjs) {
69+
// const run = require('./puter_js/__entry__.js');
70+
71+
const context = {
72+
mountpoint: {
73+
path: '/',
74+
}
75+
};
76+
77+
const ts = new TestSDK(conf, context, {});
78+
const registry = new TestRegistry(ts);
79+
80+
await require('./puter_js/__entry__.js')(registry);
81+
82+
await registry.run_all_tests();
83+
84+
// await run(conf);
85+
ts.printTestResults();
86+
ts.printBenchmarkResults();
87+
process.exit(0);
88+
return;
89+
}
90+
6491
const unit_test_results = [];
6592
const benchmark_results = [];
6693
for (const mountpoint of conf.mountpoints) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const load_puterjs = require('./load.cjs');
2+
3+
async function run(conf) {
4+
const puter = await load_puterjs();
5+
if (conf.token) {
6+
puter.setAuthToken(conf.token);
7+
} else {
8+
throw new Error('No token found in config file. Please add a "token" field to your config.yaml');
9+
}
10+
return;
11+
};
12+
13+
module.exports = async registry => {
14+
const puter = await load_puterjs();
15+
if (registry.t?.conf?.token) {
16+
puter.setAuthToken(registry.t.conf.token);
17+
} else {
18+
throw new Error('No token found in config file. Please add a "token" field to your config.yaml');
19+
}
20+
21+
registry.t.puter = puter;
22+
23+
console.log('__entry__.js');
24+
require('./filesystem/__entry__.js')(registry);
25+
26+
// registry.add_test('filesystem', require('./filesystem/__entry__.js'));
27+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = registry => {
2+
console.log('filesystem __entry__.js');
3+
registry.add_test('write', require('./write.js'));
4+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const chai = require('chai');
2+
chai.use(require('chai-as-promised'))
3+
const expect = chai.expect;
4+
5+
module.exports = {
6+
name: 'write',
7+
description: 'a collection of tests for writing to the filesystem',
8+
do: async t => {
9+
const puter = t.puter;
10+
11+
await t.case('demo (whoami)', async () => {
12+
const result = await puter.auth.whoami();
13+
expect(result.username).to.equal('admin');
14+
});
15+
16+
await t.case('write and share', async () => {
17+
let result = await puter.fs.write('~/test.txt', 'hello');
18+
expect(result.name).to.equal('test.txt');
19+
20+
result = await puter.fs.share('~/test.txt', {
21+
recipients: ['tom', 'jerry'],
22+
access: 'read',
23+
withPermissions: true,
24+
});
25+
console.log('result', result);
26+
expect(result.recipients.length).to.equal(2);
27+
});
28+
29+
await t.case('write with share args', async () => {
30+
let result = await puter.fs.write('~/test.txt', 'hello', {
31+
share: {
32+
recipients: ['tom', 'jerry'],
33+
access: 'read',
34+
},
35+
withPermissions: true,
36+
});
37+
expect(result.share.recipients.length).to.equal(2);
38+
});
39+
}
40+
}

tools/api-tester/puter_js/load.cjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const vm = require('vm');
2+
3+
async function load_puterjs() {
4+
const goodContext = {}
5+
Object.getOwnPropertyNames(globalThis).forEach(name => { try { goodContext[name] = globalThis[name]; } catch { } })
6+
goodContext.globalThis = goodContext
7+
const code = await fetch("http://puter.localhost:4100/puter.js/v2").then(res => res.text());
8+
const context = vm.createContext(goodContext);
9+
const result = vm.runInNewContext(code, context);
10+
return goodContext.puter;
11+
}
12+
13+
module.exports = load_puterjs;

0 commit comments

Comments
 (0)