fs: fix failed tests in move and stat

This commit is contained in:
XiaochenCui
2025-07-23 12:46:39 -07:00
committed by Eric Dubé
parent f3b09cf170
commit 97eeadb3e4
6 changed files with 149 additions and 11 deletions

View File

@@ -147,7 +147,7 @@ class HLMove extends HLFilesystemOperation {
if ( await dest.exists() ) {
if ( ! values.overwrite && ! values.dedupe_name ) {
throw APIError.create('item_with_same_name_exists', null, {
entry_name: target_name,
entry_name: await dest.get('name'),
});
}

View File

@@ -55,7 +55,10 @@ class HLStat extends HLFilesystemOperation {
if (return_size) await subject.fetchSize(user);
if (return_subdomains) await subject.fetchSubdomains(user)
if (return_permissions) await subject.fetchShares();
if (return_permissions) {
subject.entry.permissions = [];
await subject.fetchShares();
}
if (return_versions) await subject.fetchVersions();
await subject.fetchIsEmpty();

View File

@@ -1,5 +1,130 @@
## It takes 3 steps to run the tests :)
# API Tester
1. run `npm install`
2. copy `example_config.yml` and add the correct values
3. run `node apitest.js --config=your_config_file.yml`
A test framework for testing the backend API of puter.
## Table of Contents
- [API Tester](#api-tester)
- [How to use](#how-to-use)
- [Workflow](#workflow)
- [Shorthands](#shorthands)
- [Basic Concepts](#basic-concepts)
- [Behaviors](#behaviors)
- [Isolation of `t.cwd`](#isolation-of-t-cwd)
- [Implementation](#implementation)
- [TODO](#todo)
## How to use
### Workflow
All commands below should be run from the root directory of puter.
1. (Optional) Start a backend server:
```bash
npm start
```
2. Copy `example_config.yml` and add the correct values:
```bash
cp ./tools/api-tester/example_config.yml ./tools/api-tester/config.yml
```
Fields:
- url: The endpoint of the backend server. (default: http://api.puter.localhost:4100/)
- username: The username of the admin user. (e.g. admin)
- token: The token of the user. (can be obtained by typing `puter.authToken` in Developer Tools's console)
3. Run the tests:
```bash
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml
```
### Shorthands
- Run unit tests only:
```bash
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml --unit
```
- Filter tests by suite name:
```bash
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml --unit --suite=mkdir
```
- Rerun failed tests in the last run:
```bash
node ./tools/api-tester/apitest.js --config=./tools/api-tester/config.yml --rerun-failed
```
## Basic Concepts
A *test case* is a function that tests a specific behavior of the backend API. Test cases can be nested:
```js
await t.case('normal mkdir', async () => {
const result = await t.mkdir_v2('foo');
expect(result.name).equal('foo');
await t.case('can stat the created directory', async () => {
const stat = await t.stat('foo');
expect(stat.name).equal('foo');
});
});
```
A *test suite* is a collection of test cases. A `.js` file should contain exactly one test suite.
```js
module.exports = {
name: 'mkdir',
do: async t => {
await t.case('normal mkdir', async () => {
...
});
await t.case('recursive mkdir', async () => {
...
});
}
};
```
## Behaviors
### Isolation of `t.cwd`
- `t.cwd` is reset at the beginning of each test suite, since a test suite usually doesn't want to be affected by other test suites.
- `t.cwd` will be inherited from the cases in the same test suite, since a leaf case might want to share the context with its parent/sibling cases.
```js
module.exports = {
name: 'readdir',
do: async t => {
// t.cwd is reset to /admin/api_test
await t.case('normal mkdir', async () => {
// inherits cwd from parent/sibling cases
await t.case('mkdir in subdir', async () => {
// inherits cwd from parent/sibling cases
});
});
}
};
```
## Implementation
- Test suites are registered in `tools/api-tester/tests/__entry__.js`.
## TODO
- [ ] Update usage of apitest.js. (Is it possible to generate the usage automatically?)
- [ ] Integrate it into CI, optionally running it only in specific scenarios (e.g., when backend code changes).

View File

@@ -9,7 +9,7 @@ const { parseArgs } = require('node:util');
const args = process.argv.slice(2);
let config, report;
let config, report, suiteName;
try {
const parsed = parseArgs({
@@ -23,6 +23,7 @@ try {
onlycase: { type: 'string' },
bench: { type: 'boolean' },
unit: { type: 'boolean' },
suite: { type: 'string' },
},
allowPositionals: true,
});
@@ -33,9 +34,12 @@ try {
onlycase,
bench,
unit,
suite: suiteName,
}, positionals: [id] } = parsed);
onlycase = onlycase !== undefined ? Number.parseInt(onlycase) : undefined;
// Ensure suiteName is a string or undefined
suiteName = suiteName || undefined;
} catch (e) {
console.error(e);
console.error(
@@ -44,6 +48,7 @@ try {
'Options:\n' +
' --config=<path> (required) Path to configuration file\n' +
' --report=<path> (optional) Output file for full test results\n' +
' --suite=<name> (optional) Run only tests with matching suite name\n' +
''
);
process.exit(1);
@@ -56,6 +61,7 @@ const main = async () => {
const context = {
options: {
onlycase,
suite: suiteName,
}
};
const ts = new TestSDK(conf, context);
@@ -87,7 +93,7 @@ const main = async () => {
}
if ( unit ) {
await registry.run_all_tests();
await registry.run_all_tests(suiteName);
} else if ( bench ) {
await registry.run_all_benches();
} else {

View File

@@ -18,8 +18,12 @@ module.exports = class TestRegistry {
this.benches[id] = benchDefinition;
}
async run_all_tests () {
async run_all_tests(suiteName) {
for ( const id in this.tests ) {
if (suiteName && id !== suiteName) {
continue;
}
const testDefinition = this.tests[id];
await this.t.runTestPackage(testDefinition);
}

View File

@@ -96,8 +96,8 @@ 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.
// Always reset cwd at the beginning of a test suite to prevent it
// from affected by others.
if (this.nameStack.length === 1) {
this.resetCwd();
}