@serverkgg/bridge 0.0.8 → 0.0.9
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/package.json +3 -3
- package/src/cli/validate.ts +1 -0
- package/src/context/bridgeContext.ts +10 -0
- package/src/context/index.ts +1 -0
- package/src/core/bridgeDownloadError.ts +10 -0
- package/src/core/bridgeHold.ts +12 -0
- package/src/core/bridgeKind.ts +2 -0
- package/src/core/bridgeUserError.test.ts +23 -0
- package/src/core/bridgeUserError.ts +12 -0
- package/src/core/index.ts +3 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +85 -0
- package/src/manifest/manifest.ts +21 -0
- package/src/manifest/presence.ts +48 -0
- package/src/manifest/primitives.ts +6 -0
- package/src/modules/bridge.ts +45 -2
- package/src/modules/bridgeDriver.ts +2 -0
- package/src/modules/bridgeModule.ts +25 -3
- package/src/panel/bridgeDetail.ts +49 -0
- package/src/panel/bridgeLayout.ts +1 -0
- package/src/panel/bridgePanel.ts +30 -0
- package/src/panel/bridgeUploadMode.ts +4 -0
- package/src/panel/index.ts +2 -0
- package/src/protocol/bridgeEgress.ts +8 -1
- package/src/protocol/bridgeEvents.ts +4 -0
- package/src/protocol/bridgeFrame.ts +28 -1
- package/src/protocol/bridgeLimits.ts +10 -0
- package/src/runtime/bridgeActions.ts +7 -0
- package/src/runtime/bridgeDriver.test.ts +477 -2
- package/src/runtime/bridgeDriver.ts +116 -3
- package/src/runtime/bridgeEgress.ts +3 -0
- package/src/runtime/bridgeEvents.ts +7 -5
- package/src/runtime/bridgeFiles.test.ts +625 -15
- package/src/runtime/bridgeFiles.ts +152 -45
- package/src/runtime/bridgeInstall.test.ts +118 -0
- package/src/runtime/bridgeInstall.ts +55 -0
- package/src/runtime/bridgeNet.ts +32 -3
- package/src/runtime/bridgePending.test.ts +148 -0
- package/src/runtime/bridgePending.ts +43 -0
- package/src/runtime/bridgeRegistry.test.ts +134 -0
- package/src/runtime/bridgeRegistry.ts +26 -4
- package/src/runtime/bridgeServer.test.ts +338 -0
- package/src/runtime/bridgeServer.ts +25 -3
- package/src/runtime/bridgeSupervisor.ts +18 -0
- package/src/runtime/bridgeZip.ts +179 -58
- package/src/runtime/serve.ts +64 -4
- package/src/wire/panel.test.ts +166 -0
- package/src/wire/panel.ts +32 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { BRIDGE_PENDING_FILES_LIMIT, type BridgePendingFile } from "../protocol";
|
|
3
|
+
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
4
|
+
import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
|
|
5
|
+
|
|
6
|
+
const file = (id: string): BridgePendingFile => ({
|
|
7
|
+
id,
|
|
8
|
+
title: {
|
|
9
|
+
ar: id,
|
|
10
|
+
en: id,
|
|
11
|
+
},
|
|
12
|
+
fileName: `${id}.jar`,
|
|
13
|
+
sizeBytes: null,
|
|
14
|
+
digest: null,
|
|
15
|
+
sourceUrl: null,
|
|
16
|
+
note: null,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const files = (count: number) => {
|
|
20
|
+
return Array.from({
|
|
21
|
+
length: count,
|
|
22
|
+
}).map((_entry, index) => file(`file-${index}`));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const recorder = () => {
|
|
26
|
+
const warnings: string[] = [];
|
|
27
|
+
|
|
28
|
+
const onLog: BridgeLeveledLogger = Object.assign(
|
|
29
|
+
() => {
|
|
30
|
+
return;
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
warn: (message: string) => {
|
|
34
|
+
warnings.push(message);
|
|
35
|
+
},
|
|
36
|
+
error: () => {
|
|
37
|
+
return;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
onLog,
|
|
44
|
+
warnings,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("unsupportedPending", () => {
|
|
49
|
+
test("answers the sentinel a driver without a pending module gets", () => {
|
|
50
|
+
expect(unsupportedPending()).toEqual({
|
|
51
|
+
supported: false,
|
|
52
|
+
staging: "",
|
|
53
|
+
extensions: [],
|
|
54
|
+
files: [],
|
|
55
|
+
rejected: null,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("pendingResult", () => {
|
|
61
|
+
test("carries the staging path, the extensions and the files the driver listed", () => {
|
|
62
|
+
const { onLog, warnings } = recorder();
|
|
63
|
+
|
|
64
|
+
expect(
|
|
65
|
+
pendingResult(
|
|
66
|
+
".serverk-staging/pending-upload",
|
|
67
|
+
[
|
|
68
|
+
"jar",
|
|
69
|
+
],
|
|
70
|
+
files(2),
|
|
71
|
+
onLog,
|
|
72
|
+
),
|
|
73
|
+
).toEqual({
|
|
74
|
+
supported: true,
|
|
75
|
+
staging: ".serverk-staging/pending-upload",
|
|
76
|
+
extensions: [
|
|
77
|
+
"jar",
|
|
78
|
+
],
|
|
79
|
+
files: files(2),
|
|
80
|
+
rejected: null,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(warnings).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("truncates a list longer than the platform takes and warns instead of throwing", () => {
|
|
87
|
+
const { onLog, warnings } = recorder();
|
|
88
|
+
|
|
89
|
+
const result = pendingResult(
|
|
90
|
+
".serverk-staging/pending-upload",
|
|
91
|
+
[
|
|
92
|
+
"jar",
|
|
93
|
+
],
|
|
94
|
+
files(BRIDGE_PENDING_FILES_LIMIT + 5),
|
|
95
|
+
onLog,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(result.files).toHaveLength(BRIDGE_PENDING_FILES_LIMIT);
|
|
99
|
+
expect(result.files.at(0)?.id).toBe("file-0");
|
|
100
|
+
expect(warnings).toEqual([
|
|
101
|
+
"pending list truncated",
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("rejectedPending", () => {
|
|
107
|
+
test("names every file still pending as what the upload was expected to be", () => {
|
|
108
|
+
const { onLog } = recorder();
|
|
109
|
+
|
|
110
|
+
const result = rejectedPending(
|
|
111
|
+
pendingResult(
|
|
112
|
+
".serverk-staging/pending-upload",
|
|
113
|
+
[
|
|
114
|
+
"jar",
|
|
115
|
+
],
|
|
116
|
+
files(2),
|
|
117
|
+
onLog,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(result.rejected).toEqual({
|
|
122
|
+
expected: [
|
|
123
|
+
"file-0.jar",
|
|
124
|
+
"file-1.jar",
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
expect(result.files).toHaveLength(2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("leaves an empty expectation when the driver has nothing pending left", () => {
|
|
131
|
+
const { onLog } = recorder();
|
|
132
|
+
|
|
133
|
+
expect(
|
|
134
|
+
rejectedPending(
|
|
135
|
+
pendingResult(
|
|
136
|
+
".serverk-staging/pending-upload",
|
|
137
|
+
[
|
|
138
|
+
"jar",
|
|
139
|
+
],
|
|
140
|
+
[],
|
|
141
|
+
onLog,
|
|
142
|
+
),
|
|
143
|
+
).rejected,
|
|
144
|
+
).toEqual({
|
|
145
|
+
expected: [],
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BRIDGE_PENDING_FILES_LIMIT, type BridgePendingFile, type BridgePendingResult } from "../protocol";
|
|
2
|
+
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
3
|
+
|
|
4
|
+
export const unsupportedPending = (): BridgePendingResult => {
|
|
5
|
+
return {
|
|
6
|
+
supported: false,
|
|
7
|
+
staging: "",
|
|
8
|
+
extensions: [],
|
|
9
|
+
files: [],
|
|
10
|
+
rejected: null,
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const pendingResult = (
|
|
15
|
+
staging: string,
|
|
16
|
+
extensions: string[],
|
|
17
|
+
files: BridgePendingFile[],
|
|
18
|
+
onLog: BridgeLeveledLogger,
|
|
19
|
+
): BridgePendingResult => {
|
|
20
|
+
if (files.length > BRIDGE_PENDING_FILES_LIMIT) {
|
|
21
|
+
onLog.warn("pending list truncated", {
|
|
22
|
+
files: files.length,
|
|
23
|
+
limit: BRIDGE_PENDING_FILES_LIMIT,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
supported: true,
|
|
29
|
+
staging,
|
|
30
|
+
extensions,
|
|
31
|
+
files: files.slice(0, BRIDGE_PENDING_FILES_LIMIT),
|
|
32
|
+
rejected: null,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const rejectedPending = (result: BridgePendingResult): BridgePendingResult => {
|
|
37
|
+
return {
|
|
38
|
+
...result,
|
|
39
|
+
rejected: {
|
|
40
|
+
expected: result.files.map((file) => file.fileName),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -426,3 +426,137 @@ describe("preview modules", () => {
|
|
|
426
426
|
);
|
|
427
427
|
});
|
|
428
428
|
});
|
|
429
|
+
|
|
430
|
+
describe("detail modules", () => {
|
|
431
|
+
test("lists read and its own action ids in the manifest", () => {
|
|
432
|
+
const registry = registryFor({
|
|
433
|
+
...baseDriver(),
|
|
434
|
+
modules: {
|
|
435
|
+
modpackStatus: {
|
|
436
|
+
kind: BridgeKind.Detail,
|
|
437
|
+
read: async () => null,
|
|
438
|
+
actions: {
|
|
439
|
+
update: async () => null,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
expect(registry.manifest().modules).toEqual([
|
|
446
|
+
{
|
|
447
|
+
id: "modpackStatus",
|
|
448
|
+
kind: BridgeKind.Detail,
|
|
449
|
+
requiresRunning: false,
|
|
450
|
+
refreshSeconds: null,
|
|
451
|
+
pageSize: null,
|
|
452
|
+
dependsOn: [],
|
|
453
|
+
actions: [
|
|
454
|
+
BridgeAction.Read,
|
|
455
|
+
"update",
|
|
456
|
+
],
|
|
457
|
+
},
|
|
458
|
+
]);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test("returns what read answers", async () => {
|
|
462
|
+
const registry = registryFor({
|
|
463
|
+
...baseDriver(),
|
|
464
|
+
modules: {
|
|
465
|
+
modpackStatus: {
|
|
466
|
+
kind: BridgeKind.Detail,
|
|
467
|
+
read: async () => ({
|
|
468
|
+
id: "modrinth:abc",
|
|
469
|
+
title: "Better MC",
|
|
470
|
+
subtitle: "1.21.1 · NeoForge",
|
|
471
|
+
description: null,
|
|
472
|
+
image: null,
|
|
473
|
+
badges: [],
|
|
474
|
+
stats: [],
|
|
475
|
+
links: [],
|
|
476
|
+
stale: false,
|
|
477
|
+
actions: [],
|
|
478
|
+
}),
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
expect(await registry.invoke("modpackStatus", BridgeAction.Read)).toMatchObject({
|
|
484
|
+
id: "modrinth:abc",
|
|
485
|
+
title: "Better MC",
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("hands the argument bag to an action and returns its outcome", async () => {
|
|
490
|
+
const received: BridgeValues[] = [];
|
|
491
|
+
|
|
492
|
+
const registry = registryFor({
|
|
493
|
+
...baseDriver(),
|
|
494
|
+
modules: {
|
|
495
|
+
modpackStatus: {
|
|
496
|
+
kind: BridgeKind.Detail,
|
|
497
|
+
read: async () => null,
|
|
498
|
+
actions: {
|
|
499
|
+
update: async (_context, args) => {
|
|
500
|
+
received.push(args);
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
variables: {
|
|
504
|
+
MODPACK: "modrinth:abc:v2",
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
expect(
|
|
514
|
+
await registry.invoke("modpackStatus", "update", {
|
|
515
|
+
id: "modrinth:abc",
|
|
516
|
+
}),
|
|
517
|
+
).toEqual({
|
|
518
|
+
variables: {
|
|
519
|
+
MODPACK: "modrinth:abc:v2",
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(received).toEqual([
|
|
524
|
+
{
|
|
525
|
+
id: "modrinth:abc",
|
|
526
|
+
},
|
|
527
|
+
]);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("refuses an action named after a read", () => {
|
|
531
|
+
expect(() =>
|
|
532
|
+
registryFor({
|
|
533
|
+
...baseDriver(),
|
|
534
|
+
modules: {
|
|
535
|
+
modpackStatus: {
|
|
536
|
+
kind: BridgeKind.Detail,
|
|
537
|
+
read: async () => null,
|
|
538
|
+
actions: {
|
|
539
|
+
read: async () => null,
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
}),
|
|
544
|
+
).toThrow('module "modpackStatus" declares action "read", which is reserved for reads');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("refuses an action the module does not answer", async () => {
|
|
548
|
+
const registry = registryFor({
|
|
549
|
+
...baseDriver(),
|
|
550
|
+
modules: {
|
|
551
|
+
modpackStatus: {
|
|
552
|
+
kind: BridgeKind.Detail,
|
|
553
|
+
read: async () => null,
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await expect(registry.invoke("modpackStatus", "update")).rejects.toThrow(
|
|
559
|
+
'module "modpackStatus" does not support "update"',
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
@@ -18,7 +18,11 @@ interface CacheEntry {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const requiresRunning = (module: BridgePanelModule) => {
|
|
21
|
-
if (
|
|
21
|
+
if (
|
|
22
|
+
module.kind === BridgeKind.Actions
|
|
23
|
+
|| module.kind === BridgeKind.Collection
|
|
24
|
+
|| module.kind === BridgeKind.Detail
|
|
25
|
+
) {
|
|
22
26
|
return module.requiresRunning ?? false;
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -26,7 +30,11 @@ const requiresRunning = (module: BridgePanelModule) => {
|
|
|
26
30
|
};
|
|
27
31
|
|
|
28
32
|
const refreshOf = (module: BridgePanelModule) => {
|
|
29
|
-
|
|
33
|
+
if (module.kind === BridgeKind.Collection || module.kind === BridgeKind.Detail) {
|
|
34
|
+
return module.refreshSeconds ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
30
38
|
};
|
|
31
39
|
|
|
32
40
|
const dependsOf = (module: BridgePanelModule) => {
|
|
@@ -138,11 +146,11 @@ export const createRegistry = (
|
|
|
138
146
|
const modules = driver.modules ?? {};
|
|
139
147
|
|
|
140
148
|
for (const [id, module] of Object.entries(modules)) {
|
|
141
|
-
if (module.kind !== BridgeKind.Actions) {
|
|
149
|
+
if (module.kind !== BridgeKind.Actions && module.kind !== BridgeKind.Detail) {
|
|
142
150
|
continue;
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
for (const action of Object.keys(module.actions)) {
|
|
153
|
+
for (const action of Object.keys(module.actions ?? {})) {
|
|
146
154
|
if (isReadAction(action)) {
|
|
147
155
|
throw new BridgeDriverError(
|
|
148
156
|
`module "${id}" declares action "${action}", which is reserved for reads — pick another id`,
|
|
@@ -290,6 +298,20 @@ export const createRegistry = (
|
|
|
290
298
|
break;
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
case BridgeKind.Detail: {
|
|
302
|
+
if (action === BridgeAction.Read) {
|
|
303
|
+
return await module.read(context);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const detailAction = module.actions?.[action];
|
|
307
|
+
|
|
308
|
+
if (detailAction) {
|
|
309
|
+
return await detailAction(context, args);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
293
315
|
case BridgeKind.Catalog: {
|
|
294
316
|
if (action === BridgeAction.Search) {
|
|
295
317
|
return await module.search(context, {
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { BridgeDesired, BridgeFailureCode, BridgeFailureError, BridgeRuntime, BridgeUserError } from "../core";
|
|
6
|
+
import { BridgeMethod, type BridgePendingResult, type BridgeResponse } from "../protocol";
|
|
7
|
+
import { createServer, type ServerOptions } from "./bridgeServer";
|
|
8
|
+
|
|
9
|
+
const TOKEN = "test-token";
|
|
10
|
+
|
|
11
|
+
const unreachable = (what: string) => {
|
|
12
|
+
return () => {
|
|
13
|
+
throw new Error(`${what} is not part of this test`);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const baseOptions = (socket: string): ServerOptions => ({
|
|
18
|
+
socket,
|
|
19
|
+
token: TOKEN,
|
|
20
|
+
onStatus: () => ({
|
|
21
|
+
version: "0.0.0",
|
|
22
|
+
runtime: BridgeRuntime.Stopped,
|
|
23
|
+
desired: BridgeDesired.Stopped,
|
|
24
|
+
exitCode: null,
|
|
25
|
+
outOfMemory: false,
|
|
26
|
+
restarts: 0,
|
|
27
|
+
installError: null,
|
|
28
|
+
installHold: null,
|
|
29
|
+
}),
|
|
30
|
+
onManifest: unreachable("manifest"),
|
|
31
|
+
onStart: unreachable("start"),
|
|
32
|
+
onStop: unreachable("stop"),
|
|
33
|
+
onCommand: unreachable("command"),
|
|
34
|
+
onInvoke: unreachable("invoke"),
|
|
35
|
+
onEvents: unreachable("events"),
|
|
36
|
+
onSample: unreachable("sample"),
|
|
37
|
+
onQuiesce: unreachable("quiesce"),
|
|
38
|
+
onRelease: unreachable("release"),
|
|
39
|
+
onDescribe: unreachable("describe"),
|
|
40
|
+
onAnnounce: unreachable("announce"),
|
|
41
|
+
onPendingList: unreachable("pendingList"),
|
|
42
|
+
onPendingAdd: unreachable("pendingAdd"),
|
|
43
|
+
onLog: () => {
|
|
44
|
+
return;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const call = async (socket: string, frame: Record<string, unknown>): Promise<BridgeResponse> => {
|
|
49
|
+
return await new Promise<BridgeResponse>((resolve, reject) => {
|
|
50
|
+
void Bun.connect<{
|
|
51
|
+
buffer: string;
|
|
52
|
+
}>({
|
|
53
|
+
unix: socket,
|
|
54
|
+
socket: {
|
|
55
|
+
open(connection) {
|
|
56
|
+
connection.data = {
|
|
57
|
+
buffer: "",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
connection.write(`${JSON.stringify(frame)}\n`);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
data(connection, chunk) {
|
|
64
|
+
connection.data.buffer += chunk.toString();
|
|
65
|
+
|
|
66
|
+
const [line] = connection.data.buffer.split("\n");
|
|
67
|
+
|
|
68
|
+
if (!line) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
connection.end();
|
|
73
|
+
|
|
74
|
+
resolve(JSON.parse(line) as BridgeResponse);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
error(_connection, error) {
|
|
78
|
+
reject(error);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}).catch(reject);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
describe("pending dispatch", () => {
|
|
86
|
+
let dir = "";
|
|
87
|
+
let socket = "";
|
|
88
|
+
|
|
89
|
+
beforeEach(async () => {
|
|
90
|
+
dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
|
|
91
|
+
socket = join(dir, "bridge.sock");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(async () => {
|
|
95
|
+
await rm(dir, {
|
|
96
|
+
force: true,
|
|
97
|
+
recursive: true,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const listed: BridgePendingResult = {
|
|
102
|
+
supported: true,
|
|
103
|
+
staging: ".serverk-staging/pending-upload",
|
|
104
|
+
extensions: [
|
|
105
|
+
"jar",
|
|
106
|
+
],
|
|
107
|
+
files: [],
|
|
108
|
+
rejected: null,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
test("answers pendingList with what the driver lists", async () => {
|
|
112
|
+
const server = createServer({
|
|
113
|
+
...baseOptions(socket),
|
|
114
|
+
onPendingList: async () => listed,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const response = await call(socket, {
|
|
118
|
+
id: 1,
|
|
119
|
+
token: TOKEN,
|
|
120
|
+
method: BridgeMethod.PendingList,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
server.stop(true);
|
|
124
|
+
|
|
125
|
+
expect(response).toEqual({
|
|
126
|
+
id: 1,
|
|
127
|
+
ok: true,
|
|
128
|
+
result: listed,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("answers pendingList with the unsupported sentinel when the driver declares no pending module", async () => {
|
|
133
|
+
const server = createServer({
|
|
134
|
+
...baseOptions(socket),
|
|
135
|
+
onPendingList: async () => ({
|
|
136
|
+
supported: false,
|
|
137
|
+
staging: "",
|
|
138
|
+
extensions: [],
|
|
139
|
+
files: [],
|
|
140
|
+
rejected: null,
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const response = await call(socket, {
|
|
145
|
+
id: 2,
|
|
146
|
+
token: TOKEN,
|
|
147
|
+
method: BridgeMethod.PendingList,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
server.stop(true);
|
|
151
|
+
|
|
152
|
+
expect(response).toEqual({
|
|
153
|
+
id: 2,
|
|
154
|
+
ok: true,
|
|
155
|
+
result: {
|
|
156
|
+
supported: false,
|
|
157
|
+
staging: "",
|
|
158
|
+
extensions: [],
|
|
159
|
+
files: [],
|
|
160
|
+
rejected: null,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("passes the uploaded path to pendingAdd and answers with the refreshed list", async () => {
|
|
166
|
+
const received: string[] = [];
|
|
167
|
+
|
|
168
|
+
const server = createServer({
|
|
169
|
+
...baseOptions(socket),
|
|
170
|
+
onPendingAdd: async (input) => {
|
|
171
|
+
received.push(input);
|
|
172
|
+
|
|
173
|
+
return listed;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const response = await call(socket, {
|
|
178
|
+
id: 3,
|
|
179
|
+
token: TOKEN,
|
|
180
|
+
method: BridgeMethod.PendingAdd,
|
|
181
|
+
input: ".serverk-staging/pending-upload/patch.jar",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
server.stop(true);
|
|
185
|
+
|
|
186
|
+
expect(received).toEqual([
|
|
187
|
+
".serverk-staging/pending-upload/patch.jar",
|
|
188
|
+
]);
|
|
189
|
+
expect(response).toEqual({
|
|
190
|
+
id: 3,
|
|
191
|
+
ok: true,
|
|
192
|
+
result: listed,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("refuses a pendingAdd carrying no path", async () => {
|
|
197
|
+
const server = createServer({
|
|
198
|
+
...baseOptions(socket),
|
|
199
|
+
onPendingAdd: unreachable("pendingAdd"),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const response = await call(socket, {
|
|
203
|
+
id: 4,
|
|
204
|
+
token: TOKEN,
|
|
205
|
+
method: BridgeMethod.PendingAdd,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
server.stop(true);
|
|
209
|
+
|
|
210
|
+
expect(response).toEqual({
|
|
211
|
+
id: 4,
|
|
212
|
+
ok: false,
|
|
213
|
+
error: "pendingAdd needs an input path",
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("answers pendingAdd with the rejection the driver reported beside the refreshed list", async () => {
|
|
218
|
+
const rejected: BridgePendingResult = {
|
|
219
|
+
...listed,
|
|
220
|
+
rejected: {
|
|
221
|
+
expected: [
|
|
222
|
+
"patch.jar",
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const server = createServer({
|
|
228
|
+
...baseOptions(socket),
|
|
229
|
+
onPendingAdd: async () => rejected,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const response = await call(socket, {
|
|
233
|
+
id: 6,
|
|
234
|
+
token: TOKEN,
|
|
235
|
+
method: BridgeMethod.PendingAdd,
|
|
236
|
+
input: ".serverk-staging/pending-upload/wrong.jar",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
server.stop(true);
|
|
240
|
+
|
|
241
|
+
expect(response).toEqual({
|
|
242
|
+
id: 6,
|
|
243
|
+
ok: true,
|
|
244
|
+
result: rejected,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("names an unknown method so an older agent can degrade", async () => {
|
|
249
|
+
const server = createServer(baseOptions(socket));
|
|
250
|
+
|
|
251
|
+
const response = await call(socket, {
|
|
252
|
+
id: 5,
|
|
253
|
+
token: TOKEN,
|
|
254
|
+
method: "pendingRemove",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
server.stop(true);
|
|
258
|
+
|
|
259
|
+
expect(response).toEqual({
|
|
260
|
+
id: 5,
|
|
261
|
+
ok: false,
|
|
262
|
+
error: 'unknown method "pendingRemove"',
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("error serialisation", () => {
|
|
268
|
+
let dir = "";
|
|
269
|
+
let socket = "";
|
|
270
|
+
|
|
271
|
+
beforeEach(async () => {
|
|
272
|
+
dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
|
|
273
|
+
socket = join(dir, "bridge.sock");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
afterEach(async () => {
|
|
277
|
+
await rm(dir, {
|
|
278
|
+
force: true,
|
|
279
|
+
recursive: true,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const thrown = async (error: unknown) => {
|
|
284
|
+
const server = createServer({
|
|
285
|
+
...baseOptions(socket),
|
|
286
|
+
onPendingAdd: async () => {
|
|
287
|
+
throw error;
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const response = await call(socket, {
|
|
292
|
+
id: 1,
|
|
293
|
+
token: TOKEN,
|
|
294
|
+
method: BridgeMethod.PendingAdd,
|
|
295
|
+
input: ".serverk-staging/pending-upload/patch.jar",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
server.stop(true);
|
|
299
|
+
|
|
300
|
+
return response;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
test("carries the localized text of a user error beside the message", async () => {
|
|
304
|
+
expect(
|
|
305
|
+
await thrown(
|
|
306
|
+
new BridgeUserError({
|
|
307
|
+
ar: "هذا مو الملف اللي ننتظره",
|
|
308
|
+
en: "this is not a file we are waiting for",
|
|
309
|
+
}),
|
|
310
|
+
),
|
|
311
|
+
).toEqual({
|
|
312
|
+
id: 1,
|
|
313
|
+
ok: false,
|
|
314
|
+
error: "this is not a file we are waiting for",
|
|
315
|
+
text: {
|
|
316
|
+
ar: "هذا مو الملف اللي ننتظره",
|
|
317
|
+
en: "this is not a file we are waiting for",
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("carries the failure code of a failure error and no text", async () => {
|
|
323
|
+
expect(await thrown(new BridgeFailureError(BridgeFailureCode.CatalogRestricted, "the author blocked it"))).toEqual({
|
|
324
|
+
id: 1,
|
|
325
|
+
ok: false,
|
|
326
|
+
error: "the author blocked it",
|
|
327
|
+
failure: BridgeFailureCode.CatalogRestricted,
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("carries the message alone for an ordinary error", async () => {
|
|
332
|
+
expect(await thrown(new Error("the file could not be read"))).toEqual({
|
|
333
|
+
id: 1,
|
|
334
|
+
ok: false,
|
|
335
|
+
error: "the file could not be read",
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|