@serverkgg/bridge 0.1.2 → 0.1.3
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 -2
- package/rules/package.md +60 -0
- package/src/blog/articleFrontmatter.test.ts +10 -10
- package/src/blog/compile.test.ts +9 -9
- package/src/bot/botDeploy.test.ts +16 -16
- package/src/bot/botEnvironment.test.ts +8 -8
- package/src/bot/botIdentity.test.ts +12 -12
- package/src/bot/botPanel.test.ts +13 -13
- package/src/bot/botPanel.ts +4 -4
- package/src/bot/botState.test.ts +3 -3
- package/src/bot/botTerminal.test.ts +5 -5
- package/src/bot/botVariables.test.ts +8 -8
- package/src/bot/detect/botToken.test.ts +6 -6
- package/src/bot/detect/detect.test.ts +21 -21
- package/src/bot/detect/detectTable.test.ts +5 -5
- package/src/bot/detect/envExample.test.ts +2 -2
- package/src/bot/detect/envScan.test.ts +4 -4
- package/src/bot/detect/inferBotPlatform.test.ts +4 -4
- package/src/bot/detect/keys.test.ts +3 -3
- package/src/bot/detect/sensitiveKey.test.ts +5 -5
- package/src/bot/detect/serverkJson.test.ts +5 -5
- package/src/catalogs/curseforge.test.ts +35 -35
- package/src/catalogs/thunderstore.test.ts +9 -9
- package/src/cli/detectTable.test.ts +3 -3
- package/src/cli/link.test.ts +11 -11
- package/src/cli/main.ts +8 -0
- package/src/cli/rules.test.ts +198 -0
- package/src/cli/rules.ts +188 -0
- package/src/cli/validate.test.ts +52 -30
- package/src/cli/validate.ts +2 -0
- package/src/codecs/ini.test.ts +13 -13
- package/src/codecs/javaProperties.test.ts +11 -11
- package/src/codecs/sourceCfg.test.ts +14 -14
- package/src/codecs/xmlProperties.test.ts +10 -10
- package/src/core/bridgePort.test.ts +6 -6
- package/src/core/bridgeUserError.test.ts +4 -4
- package/src/core/bridgeValue.test.ts +5 -5
- package/src/core/describeDownloadFailure.test.ts +12 -12
- package/src/core/describeDownloadFailure.ts +10 -10
- package/src/guides/markdown.test.ts +31 -31
- package/src/install/installStamp.test.ts +9 -9
- package/src/manifest/extension.ts +36 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +208 -90
- package/src/manifest/manifest.ts +34 -0
- package/src/net/loopback.test.ts +10 -10
- package/src/presence/rosterSync.test.ts +7 -7
- package/src/probes/a2s.test.ts +5 -5
- package/src/probes/minecraftPing.test.ts +17 -17
- package/src/probes/raknetPing.test.ts +13 -13
- package/src/rcon/rconAccess.test.ts +20 -20
- package/src/rcon/rconAccess.ts +13 -13
- package/src/rcon/rconClient.test.ts +49 -49
- package/src/rcon/rconFrame.test.ts +14 -14
- package/src/rcon/rconIds.test.ts +4 -4
- package/src/rcon/rconWriter.test.ts +6 -6
- package/src/runtime/bridgeActivity.test.ts +15 -15
- package/src/runtime/bridgeBackupLease.test.ts +9 -9
- package/src/runtime/bridgeConfigReader.test.ts +5 -5
- package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
- package/src/runtime/bridgeContext.test.ts +5 -5
- package/src/runtime/bridgeDriver.test.ts +94 -94
- package/src/runtime/bridgeEvents.test.ts +15 -15
- package/src/runtime/bridgeFiles.test.ts +54 -54
- package/src/runtime/bridgeInstall.test.ts +10 -10
- package/src/runtime/bridgeLiveConfig.test.ts +6 -6
- package/src/runtime/bridgePending.test.ts +6 -6
- package/src/runtime/bridgeRedaction.test.ts +16 -16
- package/src/runtime/bridgeRegistry.test.ts +52 -52
- package/src/runtime/bridgeRegistry.ts +2 -2
- package/src/runtime/bridgeServer.test.ts +31 -31
- package/src/runtime/bridgeSetup.test.ts +27 -27
- package/src/runtime/bridgeSetup.ts +1 -1
- package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
- package/src/runtime/bridgeSignal.test.ts +7 -7
- package/src/runtime/bridgeSupervisor.test.ts +42 -42
- package/src/runtime/serve.ts +4 -4
- package/src/steam/avatars.test.ts +13 -13
- package/src/steam/steamApp.test.ts +8 -8
- package/src/steam/steamcmd.test.ts +17 -17
- package/src/steam/steamcmd.ts +4 -4
- package/src/steam/steamcmdNarrator.test.ts +4 -4
- package/src/steam/workshop.test.ts +5 -5
- package/src/utils/changedSettingKeys.test.ts +5 -5
- package/src/utils/describeExecFailure.test.ts +4 -4
- package/src/utils/describeExecFailure.ts +1 -1
- package/src/utils/sameSettingValue.test.ts +7 -7
- package/src/utils/token.test.ts +7 -7
- package/src/wine/protonCommand.test.ts +5 -5
- package/src/wine/wineCommand.test.ts +6 -6
- package/src/wine/winePrefix.test.ts +6 -6
- package/src/wire/activity.test.ts +8 -8
- package/src/wire/panel.test.ts +28 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -45,7 +45,7 @@ afterEach(async () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
describe("the bridge live config reader", () => {
|
|
48
|
-
|
|
48
|
+
it("answers from the boot snapshot", async () => {
|
|
49
49
|
const config = configWith({
|
|
50
50
|
CURSEFORGE_API_KEY: "boot",
|
|
51
51
|
});
|
|
@@ -58,7 +58,7 @@ describe("the bridge live config reader", () => {
|
|
|
58
58
|
expect(secrets.secret("MODRINTH_API_KEY")).toBeNull();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
it("sees a value the agent rewrote into the config", async () => {
|
|
62
62
|
const config = configWith({
|
|
63
63
|
CURSEFORGE_API_KEY: "boot",
|
|
64
64
|
});
|
|
@@ -80,7 +80,7 @@ describe("the bridge live config reader", () => {
|
|
|
80
80
|
expect(secrets.secret("MODRINTH_API_KEY")).toBe("added");
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
it("keeps the last good values while the config is mid-write", async () => {
|
|
84
84
|
const config = configWith({
|
|
85
85
|
CURSEFORGE_API_KEY: "boot",
|
|
86
86
|
});
|
|
@@ -100,7 +100,7 @@ describe("the bridge live config reader", () => {
|
|
|
100
100
|
expect(secrets.secret("CURSEFORGE_API_KEY")).toBe("boot");
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
it("re-reads the file at most once per refresh window", async () => {
|
|
104
104
|
const config = configWith({
|
|
105
105
|
CURSEFORGE_API_KEY: "boot",
|
|
106
106
|
});
|
|
@@ -120,7 +120,7 @@ describe("the bridge live config reader", () => {
|
|
|
120
120
|
expect(secrets.secret("CURSEFORGE_API_KEY")).toBe("boot");
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
it("sees a variable the customer changed after boot, and the new plan resources", async () => {
|
|
124
124
|
const config = configWith({});
|
|
125
125
|
|
|
126
126
|
await write(config);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { BRIDGE_PENDING_FILES_LIMIT, type BridgePendingFile } from "../protocol";
|
|
3
3
|
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
4
4
|
import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
|
|
@@ -46,7 +46,7 @@ const recorder = () => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
describe("unsupportedPending", () => {
|
|
49
|
-
|
|
49
|
+
it("answers the sentinel a driver without a pending module gets", () => {
|
|
50
50
|
expect(unsupportedPending()).toEqual({
|
|
51
51
|
supported: false,
|
|
52
52
|
staging: "",
|
|
@@ -58,7 +58,7 @@ describe("unsupportedPending", () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
describe("pendingResult", () => {
|
|
61
|
-
|
|
61
|
+
it("carries the staging path, the extensions and the files the driver listed", () => {
|
|
62
62
|
const { onLog, warnings } = recorder();
|
|
63
63
|
|
|
64
64
|
expect(
|
|
@@ -83,7 +83,7 @@ describe("pendingResult", () => {
|
|
|
83
83
|
expect(warnings).toEqual([]);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
it("truncates a list longer than the platform takes and warns instead of throwing", () => {
|
|
87
87
|
const { onLog, warnings } = recorder();
|
|
88
88
|
|
|
89
89
|
const result = pendingResult(
|
|
@@ -104,7 +104,7 @@ describe("pendingResult", () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
describe("rejectedPending", () => {
|
|
107
|
-
|
|
107
|
+
it("names every file still pending as what the upload was expected to be", () => {
|
|
108
108
|
const { onLog } = recorder();
|
|
109
109
|
|
|
110
110
|
const result = rejectedPending(
|
|
@@ -127,7 +127,7 @@ describe("rejectedPending", () => {
|
|
|
127
127
|
expect(result.files).toHaveLength(2);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
it("leaves an empty expectation when the driver has nothing pending left", () => {
|
|
131
131
|
const { onLog } = recorder();
|
|
132
132
|
|
|
133
133
|
expect(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -93,7 +93,7 @@ const sourceOf = (initial: Record<string, string>, secrets: Record<string, strin
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
describe("secretFieldKeys", () => {
|
|
96
|
-
|
|
96
|
+
it("collects every secret form field once and nothing else", () => {
|
|
97
97
|
expect(
|
|
98
98
|
secretFieldKeys(
|
|
99
99
|
driverWith([
|
|
@@ -106,13 +106,13 @@ describe("secretFieldKeys", () => {
|
|
|
106
106
|
]);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
it("finds nothing in a driver with no secret fields", () => {
|
|
110
110
|
expect(secretFieldKeys(driverWith([]))).toEqual([]);
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
describe("createBridgeRedaction", () => {
|
|
115
|
-
|
|
115
|
+
it("is built for every driver and leaves text alone until a secret exists", () => {
|
|
116
116
|
const { source, seal } = sourceOf({});
|
|
117
117
|
const redact = createBridgeRedaction([], source);
|
|
118
118
|
|
|
@@ -125,7 +125,7 @@ describe("createBridgeRedaction", () => {
|
|
|
125
125
|
expect(redact(`token ${TOKEN}`)).toBe(`token ${REDACTED}`);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
it("blanks the platform secrets the manifest declares, with the same length floor", () => {
|
|
129
129
|
const redact = createBridgeRedaction(
|
|
130
130
|
[],
|
|
131
131
|
sourceOf(
|
|
@@ -140,7 +140,7 @@ describe("createBridgeRedaction", () => {
|
|
|
140
140
|
expect(redact("key cf-api-key-0123456789 and short")).toBe(`key ${REDACTED} and short`);
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
it("blanks secret fields and platform secrets together", () => {
|
|
144
144
|
const redact = createBridgeRedaction(
|
|
145
145
|
[
|
|
146
146
|
"BOT_TOKEN",
|
|
@@ -158,7 +158,7 @@ describe("createBridgeRedaction", () => {
|
|
|
158
158
|
expect(redact(`${TOKEN} cf-api-key-0123456789`)).toBe(`${REDACTED} ${REDACTED}`);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
it("replaces every occurrence of a secret value", () => {
|
|
162
162
|
const redact = createBridgeRedaction(
|
|
163
163
|
[
|
|
164
164
|
"BOT_TOKEN",
|
|
@@ -173,7 +173,7 @@ describe("createBridgeRedaction", () => {
|
|
|
173
173
|
);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
it("blanks every line of a multi-line secret the process prints one line at a time", () => {
|
|
177
177
|
const key = [
|
|
178
178
|
"-----BEGIN PRIVATE KEY-----",
|
|
179
179
|
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7",
|
|
@@ -198,7 +198,7 @@ describe("createBridgeRedaction", () => {
|
|
|
198
198
|
expect(redact(`whole ${key}`)).toBe(`whole ${REDACTED}`);
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
it("leaves a value under eight characters alone", () => {
|
|
202
202
|
const redact = createBridgeRedaction(
|
|
203
203
|
[
|
|
204
204
|
"RCON_PASSWORD",
|
|
@@ -211,7 +211,7 @@ describe("createBridgeRedaction", () => {
|
|
|
211
211
|
expect(redact("the password is hunter2")).toBe("the password is hunter2");
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
it("escapes values that look like patterns and prefers the longest overlapping value", () => {
|
|
215
215
|
const redact = createBridgeRedaction(
|
|
216
216
|
[
|
|
217
217
|
"SHORT",
|
|
@@ -226,7 +226,7 @@ describe("createBridgeRedaction", () => {
|
|
|
226
226
|
expect(redact("x a.b+c*d?-and-more y a.b+c*d? z aXb+c*d?")).toBe(`x ${REDACTED} y ${REDACTED} z aXb+c*d?`);
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
it("follows a value saved after it was built and forgets the old one", () => {
|
|
230
230
|
const { source, save } = sourceOf({
|
|
231
231
|
BOT_TOKEN: "old-token-value",
|
|
232
232
|
});
|
|
@@ -250,11 +250,11 @@ describe("createBridgeRedaction", () => {
|
|
|
250
250
|
describe("redacting what the bridge reports", () => {
|
|
251
251
|
const redact = (value: string) => value.split(TOKEN).join(REDACTED);
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
it("covers both languages of a customer-facing message", () => {
|
|
254
254
|
expect(redactLocalized(text(`bad ${TOKEN}`), redact)).toEqual(text(`bad ${REDACTED}`));
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
it("covers the message and every string field of a log line", () => {
|
|
258
258
|
const lines: unknown[] = [];
|
|
259
259
|
const record = (level: string) => (message: string, fields?: Record<string, unknown>) => {
|
|
260
260
|
lines.push({
|
|
@@ -345,7 +345,7 @@ describe("redaction over the live config", () => {
|
|
|
345
345
|
});
|
|
346
346
|
});
|
|
347
347
|
|
|
348
|
-
|
|
348
|
+
it("reads the platform secrets from the config the agent wrote", async () => {
|
|
349
349
|
const boot = {
|
|
350
350
|
...configWith({}),
|
|
351
351
|
secrets: {
|
|
@@ -360,7 +360,7 @@ describe("redaction over the live config", () => {
|
|
|
360
360
|
expect(redact("cf-api-key-0123456789")).toBe(REDACTED);
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
it("starts as the identity with no secret anywhere and blanks a secret written after boot", async () => {
|
|
364
364
|
const boot = configWith({});
|
|
365
365
|
|
|
366
366
|
await write(boot);
|
|
@@ -382,7 +382,7 @@ describe("redaction over the live config", () => {
|
|
|
382
382
|
expect(redact(`key ${TOKEN}`)).toBe(`key ${REDACTED}`);
|
|
383
383
|
});
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
it("picks up a token the agent writes into the config after boot", async () => {
|
|
386
386
|
const boot = configWith({});
|
|
387
387
|
|
|
388
388
|
await write(boot);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import {
|
|
4
4
|
BridgeAction,
|
|
@@ -116,7 +116,7 @@ const registryFor = (driver: BridgeDriver, variables: Record<string, string> = {
|
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
describe("actions modules", () => {
|
|
119
|
-
|
|
119
|
+
it("lists its action ids in the manifest", () => {
|
|
120
120
|
const registry = registryFor({
|
|
121
121
|
...baseDriver(),
|
|
122
122
|
modules: {
|
|
@@ -151,7 +151,7 @@ describe("actions modules", () => {
|
|
|
151
151
|
]);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
it("hands the whole argument bag to the handler", async () => {
|
|
155
155
|
const received: BridgeValues[] = [];
|
|
156
156
|
|
|
157
157
|
const registry = registryFor({
|
|
@@ -179,7 +179,7 @@ describe("actions modules", () => {
|
|
|
179
179
|
]);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
it("refuses to run while the game is stopped", async () => {
|
|
183
183
|
const registry = registryFor({
|
|
184
184
|
...baseDriver(),
|
|
185
185
|
modules: {
|
|
@@ -202,7 +202,7 @@ describe("actions modules", () => {
|
|
|
202
202
|
).rejects.toThrow('module "broadcast" needs the game to be running');
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
it("rejects a driver that reuses a read action id", () => {
|
|
206
206
|
expect(() =>
|
|
207
207
|
registryFor({
|
|
208
208
|
...baseDriver(),
|
|
@@ -222,7 +222,7 @@ describe("actions modules", () => {
|
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
describe("manifest events", () => {
|
|
225
|
-
|
|
225
|
+
it("lists the log patterns and the names the driver raises itself", () => {
|
|
226
226
|
const registry = registryFor({
|
|
227
227
|
...baseDriver(),
|
|
228
228
|
events: {
|
|
@@ -252,7 +252,7 @@ describe("manifest events", () => {
|
|
|
252
252
|
]);
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
it("stays on the log patterns when the driver raises nothing itself", () => {
|
|
256
256
|
const registry = registryFor({
|
|
257
257
|
...baseDriver(),
|
|
258
258
|
events: {
|
|
@@ -273,7 +273,7 @@ describe("manifest events", () => {
|
|
|
273
273
|
});
|
|
274
274
|
|
|
275
275
|
describe("collection row actions", () => {
|
|
276
|
-
|
|
276
|
+
it("passes the row and the arguments without the row id", async () => {
|
|
277
277
|
const seen: {
|
|
278
278
|
row: BridgeRow;
|
|
279
279
|
args: BridgeValues;
|
|
@@ -318,7 +318,7 @@ describe("collection row actions", () => {
|
|
|
318
318
|
]);
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
-
|
|
321
|
+
it("keeps listing the built-in actions beside the row actions", () => {
|
|
322
322
|
const registry = registryFor({
|
|
323
323
|
...baseDriver(),
|
|
324
324
|
modules: {
|
|
@@ -398,7 +398,7 @@ describe("offline row actions", () => {
|
|
|
398
398
|
},
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
-
|
|
401
|
+
it("hands an offline action the id alone when the row is not listed", async () => {
|
|
402
402
|
const seen: BridgeRow[] = [];
|
|
403
403
|
const registry = registryFor(
|
|
404
404
|
rosterDriver(
|
|
@@ -419,7 +419,7 @@ describe("offline row actions", () => {
|
|
|
419
419
|
]);
|
|
420
420
|
});
|
|
421
421
|
|
|
422
|
-
|
|
422
|
+
it("still refuses an unlisted row for an action that is not offline", async () => {
|
|
423
423
|
const registry = registryFor(
|
|
424
424
|
rosterDriver(
|
|
425
425
|
false,
|
|
@@ -437,7 +437,7 @@ describe("offline row actions", () => {
|
|
|
437
437
|
});
|
|
438
438
|
|
|
439
439
|
describe("preview modules", () => {
|
|
440
|
-
|
|
440
|
+
it("lists the preview action in the manifest", () => {
|
|
441
441
|
const registry = registryFor({
|
|
442
442
|
...baseDriver(),
|
|
443
443
|
modules: {
|
|
@@ -466,7 +466,7 @@ describe("preview modules", () => {
|
|
|
466
466
|
]);
|
|
467
467
|
});
|
|
468
468
|
|
|
469
|
-
|
|
469
|
+
it("shows the module the pending values as variables and leaves the rest as they are stored", async () => {
|
|
470
470
|
const seen: {
|
|
471
471
|
version: string | null;
|
|
472
472
|
count: string | null;
|
|
@@ -520,7 +520,7 @@ describe("preview modules", () => {
|
|
|
520
520
|
]);
|
|
521
521
|
});
|
|
522
522
|
|
|
523
|
-
|
|
523
|
+
it("returns what the module answers", async () => {
|
|
524
524
|
const registry = registryFor({
|
|
525
525
|
...baseDriver(),
|
|
526
526
|
modules: {
|
|
@@ -550,7 +550,7 @@ describe("preview modules", () => {
|
|
|
550
550
|
});
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
-
|
|
553
|
+
it("refuses an action a preview module does not answer", async () => {
|
|
554
554
|
const registry = registryFor({
|
|
555
555
|
...baseDriver(),
|
|
556
556
|
modules: {
|
|
@@ -571,7 +571,7 @@ describe("preview modules", () => {
|
|
|
571
571
|
});
|
|
572
572
|
|
|
573
573
|
describe("detail modules", () => {
|
|
574
|
-
|
|
574
|
+
it("lists read and its own action ids in the manifest", () => {
|
|
575
575
|
const registry = registryFor({
|
|
576
576
|
...baseDriver(),
|
|
577
577
|
modules: {
|
|
@@ -602,7 +602,7 @@ describe("detail modules", () => {
|
|
|
602
602
|
]);
|
|
603
603
|
});
|
|
604
604
|
|
|
605
|
-
|
|
605
|
+
it("returns what read answers", async () => {
|
|
606
606
|
const registry = registryFor({
|
|
607
607
|
...baseDriver(),
|
|
608
608
|
modules: {
|
|
@@ -630,7 +630,7 @@ describe("detail modules", () => {
|
|
|
630
630
|
});
|
|
631
631
|
});
|
|
632
632
|
|
|
633
|
-
|
|
633
|
+
it("hands the argument bag to an action and returns its outcome", async () => {
|
|
634
634
|
const received: BridgeValues[] = [];
|
|
635
635
|
|
|
636
636
|
const registry = registryFor({
|
|
@@ -673,7 +673,7 @@ describe("detail modules", () => {
|
|
|
673
673
|
]);
|
|
674
674
|
});
|
|
675
675
|
|
|
676
|
-
|
|
676
|
+
it("refuses an action named after a read", () => {
|
|
677
677
|
expect(() =>
|
|
678
678
|
registryFor({
|
|
679
679
|
...baseDriver(),
|
|
@@ -690,7 +690,7 @@ describe("detail modules", () => {
|
|
|
690
690
|
).toThrow('module "modpackStatus" declares action "read", which is reserved for reads');
|
|
691
691
|
});
|
|
692
692
|
|
|
693
|
-
|
|
693
|
+
it("refuses an action the module does not answer", async () => {
|
|
694
694
|
const registry = registryFor({
|
|
695
695
|
...baseDriver(),
|
|
696
696
|
modules: {
|
|
@@ -708,7 +708,7 @@ describe("detail modules", () => {
|
|
|
708
708
|
});
|
|
709
709
|
|
|
710
710
|
describe("the setup singleton", () => {
|
|
711
|
-
|
|
711
|
+
it("rides the manifest as its own block and never joins the panel modules", () => {
|
|
712
712
|
const steps = [
|
|
713
713
|
{
|
|
714
714
|
kind: BridgeSetupStepKind.Driver as const,
|
|
@@ -736,13 +736,13 @@ describe("the setup singleton", () => {
|
|
|
736
736
|
expect(manifest.modules.some((module) => module.kind === BridgeKind.Setup)).toBe(false);
|
|
737
737
|
});
|
|
738
738
|
|
|
739
|
-
|
|
739
|
+
it("leaves the manifest block empty for a driver that declares no setup", () => {
|
|
740
740
|
expect(registryFor(baseDriver()).manifest().setup).toBeNull();
|
|
741
741
|
});
|
|
742
742
|
});
|
|
743
743
|
|
|
744
744
|
describe("protected action dispatch", () => {
|
|
745
|
-
|
|
745
|
+
it("advertises protection and refuses old callers before touching files", async () => {
|
|
746
746
|
let calls = 0;
|
|
747
747
|
const registry = registryFor({
|
|
748
748
|
...baseDriver(),
|
|
@@ -768,7 +768,7 @@ describe("protected action dispatch", () => {
|
|
|
768
768
|
await registry.invoke("world", "replace", {}, "01993720-0000-7000-8000-000000000001");
|
|
769
769
|
expect(calls).toBe(1);
|
|
770
770
|
});
|
|
771
|
-
|
|
771
|
+
it("rejects protection metadata for unknown actions or running-only modules", () => {
|
|
772
772
|
for (const module of [
|
|
773
773
|
{
|
|
774
774
|
kind: BridgeKind.Actions as const,
|
|
@@ -830,7 +830,7 @@ describe("activity", () => {
|
|
|
830
830
|
},
|
|
831
831
|
});
|
|
832
832
|
|
|
833
|
-
|
|
833
|
+
it("answers an invoke with its result and the entries the handler reported", async () => {
|
|
834
834
|
const { registry, ring } = harnessFor(whitelistDriver());
|
|
835
835
|
|
|
836
836
|
expect(await registry.invoke("whitelist", "add")).toEqual({
|
|
@@ -842,7 +842,7 @@ describe("activity", () => {
|
|
|
842
842
|
expect(ring.drain(0).events).toEqual([]);
|
|
843
843
|
});
|
|
844
844
|
|
|
845
|
-
|
|
845
|
+
it("answers an empty list when the handler reported nothing", async () => {
|
|
846
846
|
const { registry } = harnessFor({
|
|
847
847
|
...baseDriver(),
|
|
848
848
|
modules: {
|
|
@@ -863,7 +863,7 @@ describe("activity", () => {
|
|
|
863
863
|
});
|
|
864
864
|
});
|
|
865
865
|
|
|
866
|
-
|
|
866
|
+
it("sends what a failing handler reported to the events ring", async () => {
|
|
867
867
|
const { registry, ring } = harnessFor(whitelistDriver(true));
|
|
868
868
|
|
|
869
869
|
await expect(registry.invoke("whitelist", "add")).rejects.toThrow("read-only");
|
|
@@ -946,7 +946,7 @@ describe("activity", () => {
|
|
|
946
946
|
},
|
|
947
947
|
});
|
|
948
948
|
|
|
949
|
-
|
|
949
|
+
it("reports the keys whose value changed when the handler reported nothing", async () => {
|
|
950
950
|
const { registry } = harnessFor(
|
|
951
951
|
settingsDriver({
|
|
952
952
|
stored: {
|
|
@@ -980,7 +980,7 @@ describe("activity", () => {
|
|
|
980
980
|
]);
|
|
981
981
|
});
|
|
982
982
|
|
|
983
|
-
|
|
983
|
+
it("finishes the write even when a stored value cannot be compared", async () => {
|
|
984
984
|
const { registry } = harnessFor(
|
|
985
985
|
settingsDriver({
|
|
986
986
|
stored: {},
|
|
@@ -1005,7 +1005,7 @@ describe("activity", () => {
|
|
|
1005
1005
|
});
|
|
1006
1006
|
});
|
|
1007
1007
|
|
|
1008
|
-
|
|
1008
|
+
it("reports nothing when no value changed", async () => {
|
|
1009
1009
|
const { registry } = harnessFor(
|
|
1010
1010
|
settingsDriver({
|
|
1011
1011
|
stored: {
|
|
@@ -1023,7 +1023,7 @@ describe("activity", () => {
|
|
|
1023
1023
|
).toEqual([]);
|
|
1024
1024
|
});
|
|
1025
1025
|
|
|
1026
|
-
|
|
1026
|
+
it("keeps the handler's own entry instead of the default", async () => {
|
|
1027
1027
|
const { registry } = harnessFor(
|
|
1028
1028
|
settingsDriver({
|
|
1029
1029
|
stored: {
|
|
@@ -1042,7 +1042,7 @@ describe("activity", () => {
|
|
|
1042
1042
|
]);
|
|
1043
1043
|
});
|
|
1044
1044
|
|
|
1045
|
-
|
|
1045
|
+
it("reports every written key when the settings could not be read first", async () => {
|
|
1046
1046
|
const { registry } = harnessFor(
|
|
1047
1047
|
settingsDriver({
|
|
1048
1048
|
stored: {},
|
|
@@ -1063,7 +1063,7 @@ describe("activity", () => {
|
|
|
1063
1063
|
});
|
|
1064
1064
|
});
|
|
1065
1065
|
|
|
1066
|
-
|
|
1066
|
+
it("never carries the value of a secret field", async () => {
|
|
1067
1067
|
const secret = "hunter22hunter22";
|
|
1068
1068
|
|
|
1069
1069
|
const { registry } = harnessFor(
|
|
@@ -1085,7 +1085,7 @@ describe("activity", () => {
|
|
|
1085
1085
|
expect(JSON.stringify(activity)).not.toContain(secret);
|
|
1086
1086
|
});
|
|
1087
1087
|
|
|
1088
|
-
|
|
1088
|
+
it("drops a detail a handler keyed by a secret field", async () => {
|
|
1089
1089
|
const { registry } = harnessFor({
|
|
1090
1090
|
...settingsDriver({
|
|
1091
1091
|
stored: {},
|
|
@@ -1126,7 +1126,7 @@ describe("activity", () => {
|
|
|
1126
1126
|
});
|
|
1127
1127
|
});
|
|
1128
1128
|
|
|
1129
|
-
|
|
1129
|
+
it("reports no default for a catalog install", async () => {
|
|
1130
1130
|
const { registry } = harnessFor({
|
|
1131
1131
|
...baseDriver(),
|
|
1132
1132
|
modules: {
|
|
@@ -1234,7 +1234,7 @@ describe("availability", () => {
|
|
|
1234
1234
|
throw new Error("the invoke did not refuse");
|
|
1235
1235
|
};
|
|
1236
1236
|
|
|
1237
|
-
|
|
1237
|
+
it("advertises availability only for a collection that declares the hook", () => {
|
|
1238
1238
|
const registry = registryFor({
|
|
1239
1239
|
...baseDriver(),
|
|
1240
1240
|
modules: {
|
|
@@ -1273,7 +1273,7 @@ describe("availability", () => {
|
|
|
1273
1273
|
]);
|
|
1274
1274
|
});
|
|
1275
1275
|
|
|
1276
|
-
|
|
1276
|
+
it("answers the reasons per row and leaves available rows out", async () => {
|
|
1277
1277
|
const registry = registryFor(
|
|
1278
1278
|
worldsDriver({
|
|
1279
1279
|
listed: 0,
|
|
@@ -1292,7 +1292,7 @@ describe("availability", () => {
|
|
|
1292
1292
|
});
|
|
1293
1293
|
});
|
|
1294
1294
|
|
|
1295
|
-
|
|
1295
|
+
it("narrows the answer to one row when asked for its id", async () => {
|
|
1296
1296
|
const registry = registryFor(
|
|
1297
1297
|
worldsDriver({
|
|
1298
1298
|
listed: 0,
|
|
@@ -1312,7 +1312,7 @@ describe("availability", () => {
|
|
|
1312
1312
|
});
|
|
1313
1313
|
});
|
|
1314
1314
|
|
|
1315
|
-
|
|
1315
|
+
it("refuses an unavailable row action before the driver is entered", async () => {
|
|
1316
1316
|
const spies = {
|
|
1317
1317
|
listed: 0,
|
|
1318
1318
|
deleted: [] as string[],
|
|
@@ -1338,7 +1338,7 @@ describe("availability", () => {
|
|
|
1338
1338
|
]);
|
|
1339
1339
|
});
|
|
1340
1340
|
|
|
1341
|
-
|
|
1341
|
+
it("lets the action run when the hook itself fails", async () => {
|
|
1342
1342
|
const spies = {
|
|
1343
1343
|
listed: 0,
|
|
1344
1344
|
deleted: [] as string[],
|
|
@@ -1355,7 +1355,7 @@ describe("availability", () => {
|
|
|
1355
1355
|
]);
|
|
1356
1356
|
});
|
|
1357
1357
|
|
|
1358
|
-
|
|
1358
|
+
it("lets an availability read join a list already in flight and never reuses a settled one", async () => {
|
|
1359
1359
|
const spies = {
|
|
1360
1360
|
listed: 0,
|
|
1361
1361
|
deleted: [] as string[],
|
|
@@ -1375,7 +1375,7 @@ describe("availability", () => {
|
|
|
1375
1375
|
expect(spies.listed).toBe(3);
|
|
1376
1376
|
});
|
|
1377
1377
|
|
|
1378
|
-
|
|
1378
|
+
it("lists fresh for every list read and every action", async () => {
|
|
1379
1379
|
const spies = {
|
|
1380
1380
|
listed: 0,
|
|
1381
1381
|
deleted: [] as string[],
|
|
@@ -1393,7 +1393,7 @@ describe("availability", () => {
|
|
|
1393
1393
|
expect(spies.listed).toBe(3);
|
|
1394
1394
|
});
|
|
1395
1395
|
|
|
1396
|
-
|
|
1396
|
+
it("forgets a list in flight when any module mutates", async () => {
|
|
1397
1397
|
let listed = 0;
|
|
1398
1398
|
let release = () => {};
|
|
1399
1399
|
const gate = new Promise<void>((resolve) => {
|
|
@@ -1444,7 +1444,7 @@ describe("availability", () => {
|
|
|
1444
1444
|
await first;
|
|
1445
1445
|
});
|
|
1446
1446
|
|
|
1447
|
-
|
|
1447
|
+
it("drops reasons for actions the module cannot refuse and clamps long ones", async () => {
|
|
1448
1448
|
const registry = registryFor({
|
|
1449
1449
|
...baseDriver(),
|
|
1450
1450
|
modules: {
|
|
@@ -1485,7 +1485,7 @@ describe("availability", () => {
|
|
|
1485
1485
|
});
|
|
1486
1486
|
});
|
|
1487
1487
|
|
|
1488
|
-
|
|
1488
|
+
it("drops entries the wire would refuse so the answer always validates", async () => {
|
|
1489
1489
|
const registry = registryFor({
|
|
1490
1490
|
...baseDriver(),
|
|
1491
1491
|
modules: {
|
|
@@ -1528,7 +1528,7 @@ describe("availability", () => {
|
|
|
1528
1528
|
expect(bridgeAvailabilitySchema.safeParse(result).success).toBe(true);
|
|
1529
1529
|
});
|
|
1530
1530
|
|
|
1531
|
-
|
|
1531
|
+
it("enforces the allow-list of a detail and answers its reasons", async () => {
|
|
1532
1532
|
const updates: BridgeValues[] = [];
|
|
1533
1533
|
let pressable: string[] = [];
|
|
1534
1534
|
const stale: LocalizedText = {
|
|
@@ -1572,8 +1572,8 @@ describe("availability", () => {
|
|
|
1572
1572
|
module: {
|
|
1573
1573
|
update: stale,
|
|
1574
1574
|
repair: {
|
|
1575
|
-
ar: "هذا الإجراء
|
|
1576
|
-
en: "This action
|
|
1575
|
+
ar: "هذا الإجراء غير متاح حاليًا",
|
|
1576
|
+
en: "This action isn't available right now",
|
|
1577
1577
|
},
|
|
1578
1578
|
},
|
|
1579
1579
|
subjects: {},
|
|
@@ -1594,7 +1594,7 @@ describe("availability", () => {
|
|
|
1594
1594
|
expect(updates).toHaveLength(1);
|
|
1595
1595
|
});
|
|
1596
1596
|
|
|
1597
|
-
|
|
1597
|
+
it("refuses an actions module, a settings write and a catalog removal through their hooks", async () => {
|
|
1598
1598
|
const closed: LocalizedText = {
|
|
1599
1599
|
ar: "مقفل الحين",
|
|
1600
1600
|
en: "Closed right now",
|
|
@@ -1713,7 +1713,7 @@ describe("availability", () => {
|
|
|
1713
1713
|
});
|
|
1714
1714
|
});
|
|
1715
1715
|
|
|
1716
|
-
|
|
1716
|
+
it("does not answer availability for a module that declares no hook", async () => {
|
|
1717
1717
|
const registry = registryFor({
|
|
1718
1718
|
...baseDriver(),
|
|
1719
1719
|
modules: {
|
|
@@ -1729,7 +1729,7 @@ describe("availability", () => {
|
|
|
1729
1729
|
);
|
|
1730
1730
|
});
|
|
1731
1731
|
|
|
1732
|
-
|
|
1732
|
+
it("refuses a driver that names a row action availability", () => {
|
|
1733
1733
|
expect(() =>
|
|
1734
1734
|
registryFor({
|
|
1735
1735
|
...baseDriver(),
|
|
@@ -1748,7 +1748,7 @@ describe("availability", () => {
|
|
|
1748
1748
|
).toThrow('module "worlds" declares action "availability", which is reserved for reads');
|
|
1749
1749
|
});
|
|
1750
1750
|
|
|
1751
|
-
|
|
1751
|
+
it("turns the gates that never enter the driver into refusals without text", async () => {
|
|
1752
1752
|
const registry = registryFor({
|
|
1753
1753
|
...baseDriver(),
|
|
1754
1754
|
modules: {
|
|
@@ -53,8 +53,8 @@ interface CacheEntry {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const UNAVAILABLE_TEXT: LocalizedText = {
|
|
56
|
-
ar: "هذا الإجراء
|
|
57
|
-
en: "This action
|
|
56
|
+
ar: "هذا الإجراء غير متاح حاليًا",
|
|
57
|
+
en: "This action isn't available right now",
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
const requiresRunning = (module: BridgePanelModule) => {
|