cursedbelt-server 4.43.0 → 4.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/docs/ctgr.md +24 -0
- package/docs/skipped-tests.md +10 -10
- package/package.json +2 -2
- package/src/cursedopsRange.spec.ts +26 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +31 -11
- package/src/server/storage/chunked.integration.spec.ts +6 -2
- package/src/server/storage/fileUploadClient-chunked.integration.spec.ts +6 -2
- package/src/server/storage/transcode.integration.spec.ts +31 -10
package/README.md
CHANGED
|
@@ -81,7 +81,8 @@ after typing it (task 2138). `src/server/master-lock/sessions.ts` has the why.
|
|
|
81
81
|
|
|
82
82
|
## Standards
|
|
83
83
|
|
|
84
|
-
`docs/retention.md`, `docs/activity.md`, `docs/notifications.md`, `docs/engagement.md
|
|
84
|
+
`docs/retention.md`, `docs/activity.md`, `docs/notifications.md`, `docs/engagement.md`,
|
|
85
|
+
`docs/ctgr.md` (the GET-tunnel, getOnly and CSRF — register routes through `buildRoutes`) — the
|
|
85
86
|
contracts every app that uses those modules is agreeing to. Cite them from an app with the
|
|
86
87
|
package prefix (`cursedbelt-server/docs/retention.md`).
|
|
87
88
|
|
package/docs/ctgr.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ctgr + getOnly — the server invariants (don't reinvent the transport)
|
|
2
|
+
|
|
3
|
+
This package owns the GET-tunnel ("ctgr") and the auth guards (`src/server/ctgr.ts`,
|
|
4
|
+
`src/server/ctgrTunnel.ts`, `src/server/middleware/`). Use the primitives; do
|
|
5
|
+
not re-implement the wiring per app or per route.
|
|
6
|
+
|
|
7
|
+
- **Register routes with `buildRoutes`.** One `register(method, path, ...handlers)` call wires
|
|
8
|
+
both the real verb route **and** its `/ctgr<XX>` GET twin from a single call. Don't register
|
|
9
|
+
the twin by hand.
|
|
10
|
+
- **Mount the decode middleware on `CTGR_ROUTE_PATTERN`**, never on a literal `/ctgr*`. Hono's
|
|
11
|
+
router does **not** treat `/ctgr*` as a prefix wildcard (a bare-suffix `*` matches only one
|
|
12
|
+
following segment) — `CTGR_ROUTE_PATTERN` (`/:ctgrTwin{ctgr.+}`) is the correct portable
|
|
13
|
+
matcher. Reuse it everywhere a `/ctgr*` mount is meant.
|
|
14
|
+
- **`unGETify` only decodes** — it turns a `/ctgr<XX>` twin's query params back into the
|
|
15
|
+
original request and stashes the decoded request/method on the context. Protection lives
|
|
16
|
+
elsewhere by design:
|
|
17
|
+
- **getOnly** keys off the **RAW** request method (always `GET` on a twin) in the auth
|
|
18
|
+
middleware — that's what makes a tunneled write safe.
|
|
19
|
+
- **CSRF** double-submit fires on **every** `/ctgr*` path.
|
|
20
|
+
- **Binary/download routes MUST NOT go through the tunnel.** Register those as a plain
|
|
21
|
+
`app.get(path, handler)` so their bytes never ride the base64url codec and get mangled.
|
|
22
|
+
|
|
23
|
+
Moved here from `cursedbelt/docs/STANDARDS.md` §5 on 2026-10-05: it described this package's
|
|
24
|
+
server tier, which `cursedbelt` stopped shipping at the 2026-09-15 split.
|
package/docs/skipped-tests.md
CHANGED
|
@@ -7,13 +7,13 @@ carrying one goes red until it is removed. Inherited from `cursedbelt` at the
|
|
|
7
7
|
|
|
8
8
|
| file | waiting for |
|
|
9
9
|
| --- | --- |
|
|
10
|
-
| `src/server/search/pgSearchProvider.pg.test.ts` |
|
|
11
|
-
| `src/server/storage/transcode.integration.spec.ts` |
|
|
12
|
-
| `src/server/storage/binaryServerProcessing.integration.spec.ts` |
|
|
13
|
-
| `src/server/storage/chunked.integration.spec.ts` |
|
|
14
|
-
| `src/server/media-bun/image.spec.ts` | the optional `sharp` peer |
|
|
15
|
-
| `src/server/media-bun/video.spec.ts` | `ffmpeg` + `ffprobe` on `PATH` |
|
|
16
|
-
| `src/server/storage/imageThumbnail.spec.ts` | `ffmpeg` on `PATH` |
|
|
17
|
-
| `src/server/storage/videoProcessor.posterOnly.spec.ts` | `ffmpeg` on `PATH` |
|
|
18
|
-
| `src/server/storage/files/adapter.photo.display.spec.ts` | `ffmpeg` on `PATH` |
|
|
19
|
-
| `src/server/storage/fileUploadClient-chunked.integration.spec.ts` | a real `binary-server` checkout — set `BINARY_SERVER_DIR
|
|
10
|
+
| `src/server/search/pgSearchProvider.pg.test.ts` | `CC_PG_URL` naming a throwaway Postgres | `bun run test:pg` supplies one |
|
|
11
|
+
| `src/server/storage/transcode.integration.spec.ts` | a real `binary-server` checkout — set `BINARY_SERVER_DIR`. It starts binary-server's processing worker beside the store, because only the worker runs a job |
|
|
12
|
+
| `src/server/storage/binaryServerProcessing.integration.spec.ts` | a real `binary-server` checkout (`BINARY_SERVER_DIR`) and `ffmpeg` on `PATH`. It starts the processing worker too |
|
|
13
|
+
| `src/server/storage/chunked.integration.spec.ts` | a real `binary-server` checkout — set `BINARY_SERVER_DIR` |
|
|
14
|
+
| `src/server/media-bun/image.spec.ts` | the optional `sharp` peer |
|
|
15
|
+
| `src/server/media-bun/video.spec.ts` | `ffmpeg` + `ffprobe` on `PATH` |
|
|
16
|
+
| `src/server/storage/imageThumbnail.spec.ts` | `ffmpeg` on `PATH` |
|
|
17
|
+
| `src/server/storage/videoProcessor.posterOnly.spec.ts` | `ffmpeg` on `PATH` |
|
|
18
|
+
| `src/server/storage/files/adapter.photo.display.spec.ts` | `ffmpeg` on `PATH` |
|
|
19
|
+
| `src/server/storage/fileUploadClient-chunked.integration.spec.ts` | a real `binary-server` checkout — set `BINARY_SERVER_DIR` to its path (`apps/binary-server` in this generation). No default by design: a published package must not go looking in one machine's directory layout. Moved here from `cursedbelt` at the 2026-09-15 split, with the storage internals it drives. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt-server",
|
|
3
|
-
"version": "4.43.
|
|
3
|
+
"version": "4.43.1",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
|
|
@@ -365,7 +365,7 @@
|
|
|
365
365
|
"dependencies": {
|
|
366
366
|
"argon2id": "1.0.1",
|
|
367
367
|
"cursedbelt-core": "^2.1.1",
|
|
368
|
-
"cursedops": ">=0.8.1 <0.
|
|
368
|
+
"cursedops": ">=0.8.1 <1.0.0",
|
|
369
369
|
"cwip": "^4.6.0",
|
|
370
370
|
"jose": "^6.2.3"
|
|
371
371
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import pkg from '../package.json';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `cursedops` is a dependency here, so an app that takes a cursedops this range does not admit
|
|
6
|
+
* installs TWO copies — the app's at the top and a nested one under `cursedbelt-server`. The
|
|
7
|
+
* range used to stop at the next 0.x minor (`<0.10.0`, `<0.12.0`, `<0.13.0`, `<0.14.0`), and every
|
|
8
|
+
* cursedops minor needed a hand-widening here before the ship could bump apps onto it with one
|
|
9
|
+
* copy (4.30.1, 4.40.3, 4.40.4, and again for cursedops 0.14.0 — forge task 2832). All this
|
|
10
|
+
* package takes from it is `forgeState` from `cursedops/roots`, and `tools/publish-libs.sh`'s
|
|
11
|
+
* consumer preflight runs every app on a new cursedops before it ships, so the cap bought nothing
|
|
12
|
+
* but the chore. It is gone; this keeps it gone for the whole 0.x line.
|
|
13
|
+
*/
|
|
14
|
+
describe('cursedops range', () => {
|
|
15
|
+
const range = pkg.dependencies.cursedops;
|
|
16
|
+
|
|
17
|
+
it('admits every later cursedops 0.x minor, so an app bumping cursedops keeps one copy', () => {
|
|
18
|
+
for (const version of ['0.14.0', '0.15.0', '0.20.3', '0.99.0']) {
|
|
19
|
+
expect({ version, admitted: Bun.semver.satisfies(version, range) }).toEqual({ version, admitted: true });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('still refuses what predates forgeState', () => {
|
|
24
|
+
expect(Bun.semver.satisfies('0.8.0', range)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { Subprocess } from 'bun';
|
|
2
3
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
4
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
5
|
import { tmpdir } from 'node:os';
|
|
@@ -19,7 +20,11 @@ const FFMPEG_PRESENT = Bun.which('ffmpeg') !== null;
|
|
|
19
20
|
|
|
20
21
|
// SKIPPED until a real binary-server checkout is present: point BINARY_SERVER_DIR at one.
|
|
21
22
|
// There is deliberately no default — a published package must not go looking in one machine's
|
|
22
|
-
// directory layout
|
|
23
|
+
// directory layout. In this generation that checkout is `$FORGE/apps/binary-server`.
|
|
24
|
+
// BS_ALLOW_NEW_STORE=1: the spawned server refuses to invent a store it was not given, and this
|
|
25
|
+
// throwaway one is exactly a store it should create.
|
|
26
|
+
// BS_SECRETS_FILE=/dev/null: otherwise it folds in this machine's binary-server.env — the real R2
|
|
27
|
+
// keys and passcode — and a throwaway store has no business holding them.
|
|
23
28
|
// ffmpeg must be on PATH too: the proof is that the REMOTE side runs real ffmpeg, not that the client posted a job.
|
|
24
29
|
describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
25
30
|
'binaryServerProcessing → real binary-server (integration)',
|
|
@@ -27,6 +32,7 @@ describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
|
27
32
|
let port: number;
|
|
28
33
|
let storeDir: string;
|
|
29
34
|
let server: TestServer | null = null;
|
|
35
|
+
let worker: Subprocess | null = null;
|
|
30
36
|
let tmpData: string;
|
|
31
37
|
const INTERNAL_SECRET = 'test-processing-secret';
|
|
32
38
|
const APP = 'ptest';
|
|
@@ -36,21 +42,33 @@ describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
|
36
42
|
storeDir = path.join(tmpData, 'binaries');
|
|
37
43
|
mkdirSync(storeDir, { recursive: true });
|
|
38
44
|
port = await pickFreePort();
|
|
45
|
+
const env = {
|
|
46
|
+
PORT: String(port),
|
|
47
|
+
HOST: '127.0.0.1',
|
|
48
|
+
STORAGE_ROOT: storeDir,
|
|
49
|
+
DB_PATH: path.join(tmpData, 'test.sqlite'),
|
|
50
|
+
BS_ALLOW_NEW_STORE: '1',
|
|
51
|
+
BS_SECRETS_FILE: '/dev/null',
|
|
52
|
+
INTERNAL_SECRET,
|
|
53
|
+
NODE_ENV: 'test',
|
|
54
|
+
};
|
|
39
55
|
server = await startTestServer({
|
|
40
56
|
cwd: BS_DIR,
|
|
41
57
|
cmd: ['bun', path.join(BS_DIR, 'src', 'index.ts')],
|
|
42
58
|
port,
|
|
43
|
-
env
|
|
44
|
-
PORT: String(port),
|
|
45
|
-
HOST: '127.0.0.1',
|
|
46
|
-
STORAGE_ROOT: storeDir,
|
|
47
|
-
DB_PATH: path.join(tmpData, 'test.sqlite'),
|
|
48
|
-
INTERNAL_SECRET,
|
|
49
|
-
NODE_ENV: 'test',
|
|
50
|
-
},
|
|
59
|
+
env,
|
|
51
60
|
healthPath: '/healthz',
|
|
52
61
|
timeoutMs: 90_000,
|
|
53
62
|
});
|
|
63
|
+
// The store only ENQUEUES a job; binary-server's processing worker (its own process since
|
|
64
|
+
// bs task 2694) leases and runs it, and writes each output back through the store's API.
|
|
65
|
+
// Without one, every job stays `pending` and the client times out.
|
|
66
|
+
worker = Bun.spawn(['bun', path.join(BS_DIR, 'src', 'processingWorker.ts')], {
|
|
67
|
+
cwd: BS_DIR,
|
|
68
|
+
env: { ...process.env, ...env },
|
|
69
|
+
stdout: 'inherit',
|
|
70
|
+
stderr: 'inherit',
|
|
71
|
+
});
|
|
54
72
|
|
|
55
73
|
// Register the tenant, the way `register-app` does for a real one. This is
|
|
56
74
|
// not harness ceremony: since binary-server started INDEXING every transcode
|
|
@@ -71,7 +89,7 @@ describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
|
71
89
|
],
|
|
72
90
|
{
|
|
73
91
|
cwd: BS_DIR,
|
|
74
|
-
env: { ...process.env, STORAGE_ROOT: storeDir, DB_PATH: path.join(tmpData, 'test.sqlite') },
|
|
92
|
+
env: { ...process.env, STORAGE_ROOT: storeDir, DB_PATH: path.join(tmpData, 'test.sqlite'), BS_ALLOW_NEW_STORE: '1', BS_SECRETS_FILE: '/dev/null' },
|
|
75
93
|
stdout: 'inherit',
|
|
76
94
|
stderr: 'inherit',
|
|
77
95
|
},
|
|
@@ -80,6 +98,8 @@ describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
|
80
98
|
}, 90_000);
|
|
81
99
|
|
|
82
100
|
afterAll(async () => {
|
|
101
|
+
worker?.kill();
|
|
102
|
+
await worker?.exited;
|
|
83
103
|
await server?.stop();
|
|
84
104
|
await rm(tmpData, { recursive: true, force: true }).catch(() => void 0);
|
|
85
105
|
});
|
|
@@ -97,7 +117,7 @@ describe.skipIf(!BS_PRESENT || !FFMPEG_PRESENT)(
|
|
|
97
117
|
`;
|
|
98
118
|
const proc = Bun.spawn(['bun', '-e', helper, `${APP}/${key}`, localFile], {
|
|
99
119
|
cwd: BS_DIR,
|
|
100
|
-
env: { ...process.env, STORAGE_ROOT: storeDir, DB_PATH: path.join(tmpData, 'test.sqlite') },
|
|
120
|
+
env: { ...process.env, STORAGE_ROOT: storeDir, DB_PATH: path.join(tmpData, 'test.sqlite'), BS_ALLOW_NEW_STORE: '1', BS_SECRETS_FILE: '/dev/null' },
|
|
101
121
|
stdout: 'inherit',
|
|
102
122
|
stderr: 'inherit',
|
|
103
123
|
});
|
|
@@ -40,7 +40,11 @@ const pem = (der: ArrayBuffer, label: string) =>
|
|
|
40
40
|
|
|
41
41
|
// SKIPPED until a real binary-server checkout is present: point BINARY_SERVER_DIR at one.
|
|
42
42
|
// There is deliberately no default — a published package must not go looking in one machine's
|
|
43
|
-
// directory layout
|
|
43
|
+
// directory layout. In this generation that checkout is `$FORGE/apps/binary-server`.
|
|
44
|
+
// BS_ALLOW_NEW_STORE=1: the spawned server refuses to invent a store it was not given, and this
|
|
45
|
+
// throwaway one is exactly a store it should create.
|
|
46
|
+
// BS_SECRETS_FILE=/dev/null: otherwise it folds in this machine's binary-server.env — the real R2
|
|
47
|
+
// keys and passcode — and a throwaway store has no business holding them.
|
|
44
48
|
describe.skipIf(!BS_PRESENT)('chunked upload → real binary-server (integration)', () => {
|
|
45
49
|
let server: TestServer | null = null;
|
|
46
50
|
let tmp = '';
|
|
@@ -83,7 +87,7 @@ describe.skipIf(!BS_PRESENT)('chunked upload → real binary-server (integration
|
|
|
83
87
|
PORT: String(port),
|
|
84
88
|
HOST: '127.0.0.1',
|
|
85
89
|
STORAGE_ROOT: path.join(tmp, 'binaries'),
|
|
86
|
-
DB_PATH: path.join(tmp, 'bs.sqlite'),
|
|
90
|
+
DB_PATH: path.join(tmp, 'bs.sqlite'), BS_ALLOW_NEW_STORE: '1', BS_SECRETS_FILE: '/dev/null',
|
|
87
91
|
FILE_TOKEN_PUBLIC_KEY: spki,
|
|
88
92
|
FILE_TOKEN_ISSUER: ISSUER,
|
|
89
93
|
CA_APP_ID: APP_ID,
|
|
@@ -50,7 +50,11 @@ const pem = (der: ArrayBuffer, label: string) =>
|
|
|
50
50
|
|
|
51
51
|
// SKIPPED until a real binary-server checkout is present: point BINARY_SERVER_DIR at one.
|
|
52
52
|
// There is deliberately no default — a published package must not go looking in one machine's
|
|
53
|
-
// directory layout
|
|
53
|
+
// directory layout. In this generation that checkout is `$FORGE/apps/binary-server`.
|
|
54
|
+
// BS_ALLOW_NEW_STORE=1: the spawned server refuses to invent a store it was not given, and this
|
|
55
|
+
// throwaway one is exactly a store it should create.
|
|
56
|
+
// BS_SECRETS_FILE=/dev/null: otherwise it folds in this machine's binary-server.env — the real R2
|
|
57
|
+
// keys and passcode — and a throwaway store has no business holding them.
|
|
54
58
|
describe.skipIf(!BS_PRESENT)('fileUploadClient chunked path → real binary-server (integration)', () => {
|
|
55
59
|
let server: TestServer | null = null;
|
|
56
60
|
let tmp = '';
|
|
@@ -90,7 +94,7 @@ describe.skipIf(!BS_PRESENT)('fileUploadClient chunked path → real binary-serv
|
|
|
90
94
|
PORT: String(port),
|
|
91
95
|
HOST: '127.0.0.1',
|
|
92
96
|
STORAGE_ROOT: path.join(tmp, 'binaries'),
|
|
93
|
-
DB_PATH: path.join(tmp, 'bs.sqlite'),
|
|
97
|
+
DB_PATH: path.join(tmp, 'bs.sqlite'), BS_ALLOW_NEW_STORE: '1', BS_SECRETS_FILE: '/dev/null',
|
|
94
98
|
FILE_TOKEN_PUBLIC_KEY: spki,
|
|
95
99
|
FILE_TOKEN_ISSUER: ISSUER,
|
|
96
100
|
CA_APP_ID: APP_ID,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { Subprocess } from 'bun';
|
|
2
3
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
4
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
5
|
import { tmpdir } from 'node:os';
|
|
@@ -18,11 +19,16 @@ const BS_PRESENT = BS_DIR !== '' && existsSync(path.join(BS_DIR, 'src', 'index.t
|
|
|
18
19
|
|
|
19
20
|
// SKIPPED until a real binary-server checkout is present: point BINARY_SERVER_DIR at one.
|
|
20
21
|
// There is deliberately no default — a published package must not go looking in one machine's
|
|
21
|
-
// directory layout
|
|
22
|
+
// directory layout. In this generation that checkout is `$FORGE/apps/binary-server`.
|
|
23
|
+
// BS_ALLOW_NEW_STORE=1: the spawned server refuses to invent a store it was not given, and this
|
|
24
|
+
// throwaway one is exactly a store it should create.
|
|
25
|
+
// BS_SECRETS_FILE=/dev/null: otherwise it folds in this machine's binary-server.env — the real R2
|
|
26
|
+
// keys and passcode — and a throwaway store has no business holding them.
|
|
22
27
|
describe.skipIf(!BS_PRESENT)('transcode API → real binary-server (integration)', () => {
|
|
23
28
|
let port: number;
|
|
24
29
|
let storeDir: string;
|
|
25
30
|
let server: TestServer | null = null;
|
|
31
|
+
let worker: Subprocess | null = null;
|
|
26
32
|
let tmpData: string;
|
|
27
33
|
const INTERNAL_SECRET = 'test-transcode-secret';
|
|
28
34
|
|
|
@@ -32,24 +38,37 @@ describe.skipIf(!BS_PRESENT)('transcode API → real binary-server (integration)
|
|
|
32
38
|
mkdirSync(storeDir, { recursive: true });
|
|
33
39
|
|
|
34
40
|
port = await pickFreePort();
|
|
41
|
+
const env = {
|
|
42
|
+
PORT: String(port),
|
|
43
|
+
HOST: '127.0.0.1',
|
|
44
|
+
STORAGE_ROOT: storeDir,
|
|
45
|
+
DB_PATH: path.join(tmpData, 'test.sqlite'),
|
|
46
|
+
BS_ALLOW_NEW_STORE: '1',
|
|
47
|
+
BS_SECRETS_FILE: '/dev/null',
|
|
48
|
+
INTERNAL_SECRET,
|
|
49
|
+
NODE_ENV: 'test',
|
|
50
|
+
};
|
|
35
51
|
server = await startTestServer({
|
|
36
52
|
cwd: BS_DIR,
|
|
37
53
|
cmd: ['bun', path.join(BS_DIR, 'src', 'index.ts')],
|
|
38
54
|
port,
|
|
39
|
-
env
|
|
40
|
-
PORT: String(port),
|
|
41
|
-
HOST: '127.0.0.1',
|
|
42
|
-
STORAGE_ROOT: storeDir,
|
|
43
|
-
DB_PATH: path.join(tmpData, 'test.sqlite'),
|
|
44
|
-
INTERNAL_SECRET,
|
|
45
|
-
NODE_ENV: 'test',
|
|
46
|
-
},
|
|
55
|
+
env,
|
|
47
56
|
healthPath: '/healthz',
|
|
48
57
|
timeoutMs: 90_000,
|
|
49
58
|
});
|
|
59
|
+
// The store only ENQUEUES a job; binary-server's processing worker (its own process since
|
|
60
|
+
// bs task 2694) leases and runs it. Without one, every job stays `pending` forever.
|
|
61
|
+
worker = Bun.spawn(['bun', path.join(BS_DIR, 'src', 'processingWorker.ts')], {
|
|
62
|
+
cwd: BS_DIR,
|
|
63
|
+
env: { ...process.env, ...env },
|
|
64
|
+
stdout: 'inherit',
|
|
65
|
+
stderr: 'inherit',
|
|
66
|
+
});
|
|
50
67
|
}, 90_000);
|
|
51
68
|
|
|
52
69
|
afterAll(async () => {
|
|
70
|
+
worker?.kill();
|
|
71
|
+
await worker?.exited;
|
|
53
72
|
await server?.stop();
|
|
54
73
|
await rm(tmpData, { recursive: true, force: true }).catch(() => void 0);
|
|
55
74
|
});
|
|
@@ -118,7 +137,9 @@ describe.skipIf(!BS_PRESENT)('transcode API → real binary-server (integration)
|
|
|
118
137
|
await Bun.sleep(100);
|
|
119
138
|
}
|
|
120
139
|
expect(status).toBe('failed');
|
|
121
|
-
|
|
140
|
+
// Past bun's 5 s default: a timeout kills the spawned server, and the tests after this one
|
|
141
|
+
// then fail at `fetch` instead of here, naming the wrong thing.
|
|
142
|
+
}, 15_000);
|
|
122
143
|
|
|
123
144
|
test('GET /api/transcode/:jobId returns 404 for unknown job', async () => {
|
|
124
145
|
const res = await fetch(`http://localhost:${port}/api/transcode/nonexistent-job-id`, {
|