@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.
Files changed (93) hide show
  1. package/package.json +3 -2
  2. package/rules/package.md +60 -0
  3. package/src/blog/articleFrontmatter.test.ts +10 -10
  4. package/src/blog/compile.test.ts +9 -9
  5. package/src/bot/botDeploy.test.ts +16 -16
  6. package/src/bot/botEnvironment.test.ts +8 -8
  7. package/src/bot/botIdentity.test.ts +12 -12
  8. package/src/bot/botPanel.test.ts +13 -13
  9. package/src/bot/botPanel.ts +4 -4
  10. package/src/bot/botState.test.ts +3 -3
  11. package/src/bot/botTerminal.test.ts +5 -5
  12. package/src/bot/botVariables.test.ts +8 -8
  13. package/src/bot/detect/botToken.test.ts +6 -6
  14. package/src/bot/detect/detect.test.ts +21 -21
  15. package/src/bot/detect/detectTable.test.ts +5 -5
  16. package/src/bot/detect/envExample.test.ts +2 -2
  17. package/src/bot/detect/envScan.test.ts +4 -4
  18. package/src/bot/detect/inferBotPlatform.test.ts +4 -4
  19. package/src/bot/detect/keys.test.ts +3 -3
  20. package/src/bot/detect/sensitiveKey.test.ts +5 -5
  21. package/src/bot/detect/serverkJson.test.ts +5 -5
  22. package/src/catalogs/curseforge.test.ts +35 -35
  23. package/src/catalogs/thunderstore.test.ts +9 -9
  24. package/src/cli/detectTable.test.ts +3 -3
  25. package/src/cli/link.test.ts +11 -11
  26. package/src/cli/main.ts +8 -0
  27. package/src/cli/rules.test.ts +198 -0
  28. package/src/cli/rules.ts +188 -0
  29. package/src/cli/validate.test.ts +52 -30
  30. package/src/cli/validate.ts +2 -0
  31. package/src/codecs/ini.test.ts +13 -13
  32. package/src/codecs/javaProperties.test.ts +11 -11
  33. package/src/codecs/sourceCfg.test.ts +14 -14
  34. package/src/codecs/xmlProperties.test.ts +10 -10
  35. package/src/core/bridgePort.test.ts +6 -6
  36. package/src/core/bridgeUserError.test.ts +4 -4
  37. package/src/core/bridgeValue.test.ts +5 -5
  38. package/src/core/describeDownloadFailure.test.ts +12 -12
  39. package/src/core/describeDownloadFailure.ts +10 -10
  40. package/src/guides/markdown.test.ts +31 -31
  41. package/src/install/installStamp.test.ts +9 -9
  42. package/src/manifest/extension.ts +36 -0
  43. package/src/manifest/index.ts +1 -0
  44. package/src/manifest/manifest.test.ts +208 -90
  45. package/src/manifest/manifest.ts +34 -0
  46. package/src/net/loopback.test.ts +10 -10
  47. package/src/presence/rosterSync.test.ts +7 -7
  48. package/src/probes/a2s.test.ts +5 -5
  49. package/src/probes/minecraftPing.test.ts +17 -17
  50. package/src/probes/raknetPing.test.ts +13 -13
  51. package/src/rcon/rconAccess.test.ts +20 -20
  52. package/src/rcon/rconAccess.ts +13 -13
  53. package/src/rcon/rconClient.test.ts +49 -49
  54. package/src/rcon/rconFrame.test.ts +14 -14
  55. package/src/rcon/rconIds.test.ts +4 -4
  56. package/src/rcon/rconWriter.test.ts +6 -6
  57. package/src/runtime/bridgeActivity.test.ts +15 -15
  58. package/src/runtime/bridgeBackupLease.test.ts +9 -9
  59. package/src/runtime/bridgeConfigReader.test.ts +5 -5
  60. package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
  61. package/src/runtime/bridgeContext.test.ts +5 -5
  62. package/src/runtime/bridgeDriver.test.ts +94 -94
  63. package/src/runtime/bridgeEvents.test.ts +15 -15
  64. package/src/runtime/bridgeFiles.test.ts +54 -54
  65. package/src/runtime/bridgeInstall.test.ts +10 -10
  66. package/src/runtime/bridgeLiveConfig.test.ts +6 -6
  67. package/src/runtime/bridgePending.test.ts +6 -6
  68. package/src/runtime/bridgeRedaction.test.ts +16 -16
  69. package/src/runtime/bridgeRegistry.test.ts +52 -52
  70. package/src/runtime/bridgeRegistry.ts +2 -2
  71. package/src/runtime/bridgeServer.test.ts +31 -31
  72. package/src/runtime/bridgeSetup.test.ts +27 -27
  73. package/src/runtime/bridgeSetup.ts +1 -1
  74. package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
  75. package/src/runtime/bridgeSignal.test.ts +7 -7
  76. package/src/runtime/bridgeSupervisor.test.ts +42 -42
  77. package/src/runtime/serve.ts +4 -4
  78. package/src/steam/avatars.test.ts +13 -13
  79. package/src/steam/steamApp.test.ts +8 -8
  80. package/src/steam/steamcmd.test.ts +17 -17
  81. package/src/steam/steamcmd.ts +4 -4
  82. package/src/steam/steamcmdNarrator.test.ts +4 -4
  83. package/src/steam/workshop.test.ts +5 -5
  84. package/src/utils/changedSettingKeys.test.ts +5 -5
  85. package/src/utils/describeExecFailure.test.ts +4 -4
  86. package/src/utils/describeExecFailure.ts +1 -1
  87. package/src/utils/sameSettingValue.test.ts +7 -7
  88. package/src/utils/token.test.ts +7 -7
  89. package/src/wine/protonCommand.test.ts +5 -5
  90. package/src/wine/wineCommand.test.ts +6 -6
  91. package/src/wine/winePrefix.test.ts +6 -6
  92. package/src/wire/activity.test.ts +8 -8
  93. package/src/wire/panel.test.ts +28 -28
@@ -3,6 +3,7 @@ import { PORT_BLOCK_MAX_COUNT, PortJoin, PortProtocol, socketProtocolsOf } from
3
3
  import { BridgeIcon } from "../panel/bridgeIcon";
4
4
  import { BRIDGE_OPTION_VALUE_LENGTH_LIMIT, BRIDGE_VISIBLE_VALUES_LIMIT } from "../protocol/bridgeLimits";
5
5
  import { GameCategory } from "./category";
6
+ import { type ManifestExtension, manifestExtensionsSchema } from "./extension";
6
7
  import { type GuideEntry, type GuideTag, guideEntrySchema, guideTagSchema } from "./guide";
7
8
  import { IMAGE_NAMESPACE } from "./image";
8
9
  import { PackageKind } from "./kind";
@@ -157,10 +158,12 @@ const manifestMetadataSchema = z.object({
157
158
  category: z.enum(GameCategory),
158
159
  logo: zAssetPath,
159
160
  banner: zAssetPath,
161
+ extensions: manifestExtensionsSchema,
160
162
  });
161
163
 
162
164
  const botMetadataSchema = manifestMetadataSchema.omit({
163
165
  category: true,
166
+ extensions: true,
164
167
  });
165
168
 
166
169
  const manifestResourcesSchema = z
@@ -764,6 +767,36 @@ const packageIssues = (manifest: PackageShape): CheckIssue[] => {
764
767
  ];
765
768
  };
766
769
 
770
+ const extensionIssues = (extensions: ManifestExtension[]): CheckIssue[] => {
771
+ return [
772
+ ...duplicateIssues(
773
+ extensions,
774
+ (extension) => extension.kind,
775
+ (index) => [
776
+ "metadata",
777
+ "extensions",
778
+ index,
779
+ "kind",
780
+ ],
781
+ (key) => `extension "${key}" is declared twice`,
782
+ ),
783
+ ...extensions.flatMap((extension, extensionIndex) =>
784
+ duplicateIssues(
785
+ extension.sources,
786
+ (source) => source,
787
+ (index) => [
788
+ "metadata",
789
+ "extensions",
790
+ extensionIndex,
791
+ "sources",
792
+ index,
793
+ ],
794
+ (key) => `source "${key}" is listed twice for ${extension.kind}`,
795
+ ),
796
+ ),
797
+ ];
798
+ };
799
+
767
800
  const secretIssues = (secrets: ManifestSecret[]): CheckIssue[] => {
768
801
  return duplicateIssues(
769
802
  secrets,
@@ -801,6 +834,7 @@ export const gameManifestSchema = z
801
834
 
802
835
  ctx.issues.push(
803
836
  ...packageIssues(manifest),
837
+ ...extensionIssues(manifest.metadata.extensions),
804
838
  ...secretIssues(manifest.secrets),
805
839
  ...presenceIssues(manifest.presence),
806
840
  );
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { BridgeNetError } from "../core";
3
3
  import { loopbackJson, loopbackText } from "./loopback";
4
4
  import { LoopbackMethod } from "./loopbackMethod";
@@ -12,7 +12,7 @@ const serve = (handler: (request: Request) => Response | Promise<Response>) => {
12
12
  };
13
13
 
14
14
  describe("loopbackJson", () => {
15
- test("reads a json body from a loopback server", async () => {
15
+ it("reads a json body from a loopback server", async () => {
16
16
  const server = serve(() =>
17
17
  Response.json({
18
18
  online: 3,
@@ -32,7 +32,7 @@ describe("loopbackJson", () => {
32
32
  }
33
33
  });
34
34
 
35
- test("sends the method, headers and body it is given", async () => {
35
+ it("sends the method, headers and body it is given", async () => {
36
36
  let seen = "";
37
37
 
38
38
  const server = serve(async (request) => {
@@ -58,7 +58,7 @@ describe("loopbackJson", () => {
58
58
  }
59
59
  });
60
60
 
61
- test("raises a net error carrying the status when the server refuses", async () => {
61
+ it("raises a net error carrying the status when the server refuses", async () => {
62
62
  const server = serve(
63
63
  () =>
64
64
  new Response("nope", {
@@ -76,7 +76,7 @@ describe("loopbackJson", () => {
76
76
  }
77
77
  });
78
78
 
79
- test("raises a net error when the body is not json", async () => {
79
+ it("raises a net error when the body is not json", async () => {
80
80
  const server = serve(() => new Response("not json"));
81
81
 
82
82
  try {
@@ -89,7 +89,7 @@ describe("loopbackJson", () => {
89
89
  }
90
90
  });
91
91
 
92
- test("times out against a server that never answers", async () => {
92
+ it("times out against a server that never answers", async () => {
93
93
  const server = serve(async () => {
94
94
  await Bun.sleep(600);
95
95
 
@@ -109,7 +109,7 @@ describe("loopbackJson", () => {
109
109
  });
110
110
 
111
111
  describe("loopback guard", () => {
112
- test("refuses a host that is not loopback", async () => {
112
+ it("refuses a host that is not loopback", async () => {
113
113
  for (const url of [
114
114
  "https://api.curseforge.com/v1",
115
115
  "http://10.0.0.5:8080/",
@@ -122,14 +122,14 @@ describe("loopback guard", () => {
122
122
  }
123
123
  });
124
124
 
125
- test("refuses a scheme that is not http", async () => {
125
+ it("refuses a scheme that is not http", async () => {
126
126
  const error = await loopbackText("file:///etc/passwd").catch((reason: unknown) => reason);
127
127
 
128
128
  expect(error).toBeInstanceOf(BridgeNetError);
129
129
  expect((error as BridgeNetError).message).toContain("is not an http url");
130
130
  });
131
131
 
132
- test("refuses a header carrying a line break", async () => {
132
+ it("refuses a header carrying a line break", async () => {
133
133
  const error = await loopbackText("http://127.0.0.1:1/", {
134
134
  headers: {
135
135
  "x-token": "a\r\nx-admin: yes",
@@ -140,7 +140,7 @@ describe("loopback guard", () => {
140
140
  expect((error as BridgeNetError).message).toContain("line break");
141
141
  });
142
142
 
143
- test("accepts localhost", async () => {
143
+ it("accepts localhost", async () => {
144
144
  const server = serve(() => new Response("pong"));
145
145
 
146
146
  try {
@@ -1,4 +1,4 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
1
+ import { beforeEach, describe, expect, it } from "bun:test";
2
2
  import type { BridgeContext } from "../context";
3
3
  import type { BridgeValues } from "../core";
4
4
  import { BridgeEventName } from "../protocol";
@@ -42,7 +42,7 @@ beforeEach(() => {
42
42
  });
43
43
 
44
44
  describe("createRosterSync", () => {
45
- test("emits a join for every player of the first sample", () => {
45
+ it("emits a join for every player of the first sample", () => {
46
46
  const sync = roster();
47
47
 
48
48
  sync.sync(context, [
@@ -74,7 +74,7 @@ describe("createRosterSync", () => {
74
74
  ]);
75
75
  });
76
76
 
77
- test("stays quiet while the roster does not change", () => {
77
+ it("stays quiet while the roster does not change", () => {
78
78
  const sync = roster();
79
79
  const rows = [
80
80
  {
@@ -90,7 +90,7 @@ describe("createRosterSync", () => {
90
90
  expect(emitted).toEqual([]);
91
91
  });
92
92
 
93
- test("emits a leave carrying the presence the player had", () => {
93
+ it("emits a leave carrying the presence the player had", () => {
94
94
  const sync = roster();
95
95
 
96
96
  sync.sync(context, [
@@ -113,7 +113,7 @@ describe("createRosterSync", () => {
113
113
  ]);
114
114
  });
115
115
 
116
- test("emits both sides of a swap in one sample", () => {
116
+ it("emits both sides of a swap in one sample", () => {
117
117
  const sync = roster();
118
118
 
119
119
  sync.sync(context, [
@@ -136,7 +136,7 @@ describe("createRosterSync", () => {
136
136
  ]);
137
137
  });
138
138
 
139
- test("reports who it believes is online", () => {
139
+ it("reports who it believes is online", () => {
140
140
  const sync = roster();
141
141
 
142
142
  sync.sync(context, [
@@ -158,7 +158,7 @@ describe("createRosterSync", () => {
158
158
  expect(sync.online()).toEqual([]);
159
159
  });
160
160
 
161
- test("emits a fresh join after a clear", () => {
161
+ it("emits a fresh join after a clear", () => {
162
162
  const sync = roster();
163
163
  const rows = [
164
164
  {
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { BridgeProbeCode, BridgeProbeError } from "../core";
3
3
  import { probeA2s } from "./a2s";
4
4
 
@@ -21,13 +21,13 @@ const listen = async (reply: Uint8Array | null) => {
21
21
  };
22
22
 
23
23
  describe("probeA2s", () => {
24
- test("rejects with an unreachable probe error when nothing listens on the port", async () => {
24
+ it("rejects with an unreachable probe error when nothing listens on the port", async () => {
25
25
  const probe = probeA2s(HOST, CLOSED_PORT, 2000);
26
26
 
27
27
  await expect(probe).rejects.toBeInstanceOf(BridgeProbeError);
28
28
  });
29
29
 
30
- test("survives the port-unreachable the game leaves behind before it binds", async () => {
30
+ it("survives the port-unreachable the game leaves behind before it binds", async () => {
31
31
  for (let attempt = 0; attempt < 3; attempt += 1) {
32
32
  await probeA2s(HOST, CLOSED_PORT, 300).catch(() => null);
33
33
  }
@@ -35,7 +35,7 @@ describe("probeA2s", () => {
35
35
  expect(process.exitCode ?? 0).toBe(0);
36
36
  });
37
37
 
38
- test("times out against a port that never answers", async () => {
38
+ it("times out against a port that never answers", async () => {
39
39
  const silent = await listen(null);
40
40
 
41
41
  try {
@@ -48,7 +48,7 @@ describe("probeA2s", () => {
48
48
  }
49
49
  });
50
50
 
51
- test("rejects a reply that is not an a2s frame", async () => {
51
+ it("rejects a reply that is not an a2s frame", async () => {
52
52
  const noisy = await listen(
53
53
  new Uint8Array([
54
54
  1,
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { BridgeProbeCode, BridgeProbeError } from "../core";
3
3
  import { parseStatus, probeMinecraftPing } from "./minecraftPing";
4
4
 
@@ -105,7 +105,7 @@ const listen = async (reply: Uint8Array | null, onServerName?: (serverName: stri
105
105
  };
106
106
 
107
107
  describe("reading a status response off the wire", () => {
108
- test("a complete response gives the player counts, the version and the motd", () => {
108
+ it("a complete response gives the player counts, the version and the motd", () => {
109
109
  expect(parseStatus(response(status))).toEqual({
110
110
  players: {
111
111
  online: 3,
@@ -116,7 +116,7 @@ describe("reading a status response off the wire", () => {
116
116
  });
117
117
  });
118
118
 
119
- test("a response longer than one packet is still read, because the frame carries its own length", () => {
119
+ it("a response longer than one packet is still read, because the frame carries its own length", () => {
120
120
  const packet = response(status);
121
121
  const padded = new Uint8Array(packet.length + 4);
122
122
 
@@ -128,7 +128,7 @@ describe("reading a status response off the wire", () => {
128
128
  });
129
129
  });
130
130
 
131
- test("a modern description object is read through its text", () => {
131
+ it("a modern description object is read through its text", () => {
132
132
  expect(
133
133
  parseStatus(
134
134
  response({
@@ -141,7 +141,7 @@ describe("reading a status response off the wire", () => {
141
141
  ).toBe("hello");
142
142
  });
143
143
 
144
- test("a description we cannot read is dropped rather than failing the sample", () => {
144
+ it("a description we cannot read is dropped rather than failing the sample", () => {
145
145
  expect(
146
146
  parseStatus(
147
147
  response({
@@ -162,7 +162,7 @@ describe("reading a status response off the wire", () => {
162
162
  ).toBeNull();
163
163
  });
164
164
 
165
- test("a missing version name is dropped rather than failing the sample", () => {
165
+ it("a missing version name is dropped rather than failing the sample", () => {
166
166
  expect(
167
167
  parseStatus(
168
168
  response({
@@ -172,7 +172,7 @@ describe("reading a status response off the wire", () => {
172
172
  ).toBeNull();
173
173
  });
174
174
 
175
- test("fractional or negative counts are clamped to whole players", () => {
175
+ it("fractional or negative counts are clamped to whole players", () => {
176
176
  expect(
177
177
  parseStatus(
178
178
  response({
@@ -188,7 +188,7 @@ describe("reading a status response off the wire", () => {
188
188
  });
189
189
  });
190
190
 
191
- test("a response we have not fully received yet asks for more bytes", () => {
191
+ it("a response we have not fully received yet asks for more bytes", () => {
192
192
  const packet = response(status);
193
193
 
194
194
  expect(parseStatus(new Uint8Array(0))).toBeNull();
@@ -196,7 +196,7 @@ describe("reading a status response off the wire", () => {
196
196
  expect(parseStatus(packet.subarray(0, packet.length - 4))).toBeNull();
197
197
  });
198
198
 
199
- test("a large payload whose length needs two varint bytes is read whole", () => {
199
+ it("a large payload whose length needs two varint bytes is read whole", () => {
200
200
  const long = {
201
201
  players: status.players,
202
202
  description: "n".repeat(400),
@@ -205,11 +205,11 @@ describe("reading a status response off the wire", () => {
205
205
  expect(parseStatus(response(long))?.description).toBe(long.description);
206
206
  });
207
207
 
208
- test("a packet that is not a status response is refused", () => {
208
+ it("a packet that is not a status response is refused", () => {
209
209
  expect(() => parseStatus(response(status, 0x01))).toThrow();
210
210
  });
211
211
 
212
- test("a response with no player counts is refused, because a sample without them is a lie", () => {
212
+ it("a response with no player counts is refused, because a sample without them is a lie", () => {
213
213
  expect(() =>
214
214
  parseStatus(
215
215
  response({
@@ -223,7 +223,7 @@ describe("reading a status response off the wire", () => {
223
223
  });
224
224
 
225
225
  describe("probeMinecraftPing", () => {
226
- test("answers with the status a listening server sends back", async () => {
226
+ it("answers with the status a listening server sends back", async () => {
227
227
  const server = await listen(response(status));
228
228
 
229
229
  try {
@@ -246,7 +246,7 @@ describe("probeMinecraftPing", () => {
246
246
  }
247
247
  });
248
248
 
249
- test("puts the server name in the handshake so a virtual host still answers", async () => {
249
+ it("puts the server name in the handshake so a virtual host still answers", async () => {
250
250
  const seen: string[] = [];
251
251
 
252
252
  const server = await listen(response(status), (serverName) => {
@@ -269,7 +269,7 @@ describe("probeMinecraftPing", () => {
269
269
  }
270
270
  });
271
271
 
272
- test("falls back to the address when no server name is given", async () => {
272
+ it("falls back to the address when no server name is given", async () => {
273
273
  const seen: string[] = [];
274
274
 
275
275
  const server = await listen(response(status), (serverName) => {
@@ -291,7 +291,7 @@ describe("probeMinecraftPing", () => {
291
291
  }
292
292
  });
293
293
 
294
- test("rejects as unreachable when nothing listens on the port", async () => {
294
+ it("rejects as unreachable when nothing listens on the port", async () => {
295
295
  const error = await probeMinecraftPing(
296
296
  {
297
297
  address: HOST,
@@ -304,7 +304,7 @@ describe("probeMinecraftPing", () => {
304
304
  expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.Unreachable);
305
305
  });
306
306
 
307
- test("times out against a port that never answers", async () => {
307
+ it("times out against a port that never answers", async () => {
308
308
  const silent = await listen(null);
309
309
 
310
310
  try {
@@ -323,7 +323,7 @@ describe("probeMinecraftPing", () => {
323
323
  }
324
324
  });
325
325
 
326
- test("rejects a reply that is not a status response", async () => {
326
+ it("rejects a reply that is not a status response", async () => {
327
327
  const noisy = await listen(response(status, 0x01));
328
328
 
329
329
  try {
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import { BridgeProbeCode, BridgeProbeError } from "../core";
3
3
  import { parseUnconnectedPong, probeRaknetPing } from "./raknetPing";
4
4
 
@@ -64,7 +64,7 @@ const listen = async (reply: Uint8Array | null) => {
64
64
  };
65
65
 
66
66
  describe("reading an unconnected pong", () => {
67
- test("a full advertisement gives the players, the version and the motd", () => {
67
+ it("a full advertisement gives the players, the version and the motd", () => {
68
68
  expect(parseUnconnectedPong(pong(ADVERT))).toEqual({
69
69
  players: {
70
70
  online: 4,
@@ -79,7 +79,7 @@ describe("reading an unconnected pong", () => {
79
79
  });
80
80
  });
81
81
 
82
- test("an advertisement that stops after the player counts is still read", () => {
82
+ it("an advertisement that stops after the player counts is still read", () => {
83
83
  expect(parseUnconnectedPong(pong("MCPE;Serverk;800;1.21.60;2;8"))).toEqual({
84
84
  players: {
85
85
  online: 2,
@@ -94,7 +94,7 @@ describe("reading an unconnected pong", () => {
94
94
  });
95
95
  });
96
96
 
97
- test("a pong with another packet id is refused", () => {
97
+ it("a pong with another packet id is refused", () => {
98
98
  expect(() =>
99
99
  parseUnconnectedPong(
100
100
  pong(ADVERT, {
@@ -104,7 +104,7 @@ describe("reading an unconnected pong", () => {
104
104
  ).toThrow();
105
105
  });
106
106
 
107
- test("a pong without the raknet magic is refused", () => {
107
+ it("a pong without the raknet magic is refused", () => {
108
108
  expect(() =>
109
109
  parseUnconnectedPong(
110
110
  pong(ADVERT, {
@@ -114,7 +114,7 @@ describe("reading an unconnected pong", () => {
114
114
  ).toThrow();
115
115
  });
116
116
 
117
- test("a pong that advertises more payload than it carries is refused", () => {
117
+ it("a pong that advertises more payload than it carries is refused", () => {
118
118
  expect(() =>
119
119
  parseUnconnectedPong(
120
120
  pong(ADVERT, {
@@ -124,21 +124,21 @@ describe("reading an unconnected pong", () => {
124
124
  ).toThrow();
125
125
  });
126
126
 
127
- test("a pong without player counts is refused, because a sample without them is a lie", () => {
127
+ it("a pong without player counts is refused, because a sample without them is a lie", () => {
128
128
  expect(() => parseUnconnectedPong(pong("MCPE;Serverk;800;1.21.60"))).toThrow();
129
129
  });
130
130
 
131
- test("a datagram larger than a pong can be is refused", () => {
131
+ it("a datagram larger than a pong can be is refused", () => {
132
132
  expect(() => parseUnconnectedPong(pong("n".repeat(4096)))).toThrow();
133
133
  });
134
134
 
135
- test("a truncated datagram is refused", () => {
135
+ it("a truncated datagram is refused", () => {
136
136
  expect(() => parseUnconnectedPong(new Uint8Array(8))).toThrow();
137
137
  });
138
138
  });
139
139
 
140
140
  describe("probeRaknetPing", () => {
141
- test("answers with the advertisement a listening server sends back", async () => {
141
+ it("answers with the advertisement a listening server sends back", async () => {
142
142
  const server = await listen(pong(ADVERT));
143
143
 
144
144
  try {
@@ -155,13 +155,13 @@ describe("probeRaknetPing", () => {
155
155
  }
156
156
  });
157
157
 
158
- test("rejects with an unreachable probe error when nothing listens on the port", async () => {
158
+ it("rejects with an unreachable probe error when nothing listens on the port", async () => {
159
159
  const probe = probeRaknetPing(HOST, CLOSED_PORT, 2000);
160
160
 
161
161
  await expect(probe).rejects.toBeInstanceOf(BridgeProbeError);
162
162
  });
163
163
 
164
- test("times out against a port that never answers", async () => {
164
+ it("times out against a port that never answers", async () => {
165
165
  const silent = await listen(null);
166
166
 
167
167
  try {
@@ -174,7 +174,7 @@ describe("probeRaknetPing", () => {
174
174
  }
175
175
  });
176
176
 
177
- test("rejects a reply that is not a raknet pong", async () => {
177
+ it("rejects a reply that is not a raknet pong", async () => {
178
178
  const noisy = await listen(
179
179
  new Uint8Array([
180
180
  1,
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { describe, expect, it } from "bun:test";
2
2
  import type { BridgeContext } from "../context";
3
3
  import { BridgeDetailFormat, BridgeDetailTone, BridgeFormTarget, BridgeLayout } from "../panel";
4
4
  import {
@@ -55,7 +55,7 @@ const accessWith = (password: RconAccessPassword | null, rotate?: () => Promise<
55
55
  };
56
56
 
57
57
  describe("rconExposed", () => {
58
- test("only the string true opens remote access", () => {
58
+ it("only the string true opens remote access", () => {
59
59
  expect(
60
60
  rconExposed(
61
61
  stubContext({
@@ -68,7 +68,7 @@ describe("rconExposed", () => {
68
68
  });
69
69
 
70
70
  describe("rconBindHost", () => {
71
- test("an exposed server binds every interface and a closed one stays on loopback", () => {
71
+ it("an exposed server binds every interface and a closed one stays on loopback", () => {
72
72
  expect(
73
73
  rconBindHost(
74
74
  stubContext({
@@ -81,7 +81,7 @@ describe("rconBindHost", () => {
81
81
  });
82
82
 
83
83
  describe("createRconAccess", () => {
84
- test("an exposed server reads as open", async () => {
84
+ it("an exposed server reads as open", async () => {
85
85
  const detail = await accessWith({
86
86
  value: "secret",
87
87
  pending: false,
@@ -94,7 +94,7 @@ describe("createRconAccess", () => {
94
94
 
95
95
  expect(detail?.id).toBe(RCON_ACCESS_MODULE);
96
96
  expect(detail?.badges.map((badge) => badge.label.en)).toEqual([
97
- "Open",
97
+ "On",
98
98
  ]);
99
99
  expect(detail?.badges[0]?.tone).toBe(BridgeDetailTone.Success);
100
100
  expect(detail?.actions).toEqual([
@@ -102,19 +102,19 @@ describe("createRconAccess", () => {
102
102
  ]);
103
103
  });
104
104
 
105
- test("a closed server reads as closed", async () => {
105
+ it("a closed server reads as closed", async () => {
106
106
  const detail = await accessWith({
107
107
  value: "secret",
108
108
  pending: false,
109
109
  }).read(stubContext().context);
110
110
 
111
111
  expect(detail?.badges.map((badge) => badge.label.en)).toEqual([
112
- "Closed",
112
+ "Off",
113
113
  ]);
114
114
  expect(detail?.badges[0]?.tone).toBe(BridgeDetailTone.Neutral);
115
115
  });
116
116
 
117
- test("the port and the password are the two stats", async () => {
117
+ it("the port and the password are the two stats", async () => {
118
118
  const detail = await accessWith({
119
119
  value: "secret",
120
120
  pending: false,
@@ -146,19 +146,19 @@ describe("createRconAccess", () => {
146
146
  ]);
147
147
  });
148
148
 
149
- test("a password waiting for the next restart carries the pending badge", async () => {
149
+ it("a password waiting for the next restart carries the pending badge", async () => {
150
150
  const detail = await accessWith({
151
151
  value: "next",
152
152
  pending: true,
153
153
  }).read(stubContext().context);
154
154
 
155
155
  expect(detail?.badges.map((badge) => badge.label.en)).toEqual([
156
- "Closed",
156
+ "Off",
157
157
  "Applies after restart",
158
158
  ]);
159
159
  });
160
160
 
161
- test("an inactive port leaves the port stat empty instead of failing the read", async () => {
161
+ it("an inactive port leaves the port stat empty instead of failing the read", async () => {
162
162
  const detail = await accessWith({
163
163
  value: "secret",
164
164
  pending: false,
@@ -172,11 +172,11 @@ describe("createRconAccess", () => {
172
172
  expect(detail?.stats.find((stat) => stat.key === "password")?.value).toBe("secret");
173
173
  });
174
174
 
175
- test("a server with no password yet has no detail", async () => {
175
+ it("a server with no password yet has no detail", async () => {
176
176
  expect(await accessWith(null).read(stubContext().context)).toBeNull();
177
177
  });
178
178
 
179
- test("rotating delegates to the driver, logs without the value and answers nothing", async () => {
179
+ it("rotating delegates to the driver, logs without the value and answers nothing", async () => {
180
180
  let rotated = 0;
181
181
 
182
182
  const access = accessWith(
@@ -199,7 +199,7 @@ describe("createRconAccess", () => {
199
199
  ]);
200
200
  });
201
201
 
202
- test("the protocol name names the detail and both stats", async () => {
202
+ it("the protocol name names the detail and both stats", async () => {
203
203
  const detail = await createRconAccess({
204
204
  protocol: {
205
205
  ar: "Telnet",
@@ -232,7 +232,7 @@ describe("createRconAccess", () => {
232
232
  ]);
233
233
  });
234
234
 
235
- test("a tools line joins the security copy", async () => {
235
+ it("a tools line joins the security copy", async () => {
236
236
  const detail = await createRconAccess({
237
237
  tools: {
238
238
  ar: "أدوات مثل RustAdmin.",
@@ -256,7 +256,7 @@ describe("createRconAccess", () => {
256
256
  });
257
257
 
258
258
  describe("rconAccessSections", () => {
259
- test("the form declares the variable the manifest activates the port on", () => {
259
+ it("the form declares the variable the manifest activates the port on", () => {
260
260
  const [form] = rconAccessSections();
261
261
 
262
262
  expect(form?.layout).toBe(BridgeLayout.Form);
@@ -272,7 +272,7 @@ describe("rconAccessSections", () => {
272
272
  ]);
273
273
  });
274
274
 
275
- test("the detail points at the module and offers the rotate action", () => {
275
+ it("the detail points at the module and offers the rotate action", () => {
276
276
  const [, detail] = rconAccessSections();
277
277
 
278
278
  if (detail?.layout !== BridgeLayout.Detail) {
@@ -286,7 +286,7 @@ describe("rconAccessSections", () => {
286
286
  expect(detail.empty).toBeDefined();
287
287
  });
288
288
 
289
- test("the two sections carry different titles so the panel never repeats a heading", () => {
289
+ it("the two sections carry different titles so the panel never repeats a heading", () => {
290
290
  const [form, detail] = rconAccessSections();
291
291
 
292
292
  expect(form?.title).toEqual({
@@ -300,7 +300,7 @@ describe("rconAccessSections", () => {
300
300
  expect(detail?.title).not.toEqual(form?.title);
301
301
  });
302
302
 
303
- test("the detail is visible only while remote access is on", () => {
303
+ it("the detail is visible only while remote access is on", () => {
304
304
  const [, detail] = rconAccessSections();
305
305
 
306
306
  if (detail?.layout !== BridgeLayout.Detail) {
@@ -315,7 +315,7 @@ describe("rconAccessSections", () => {
315
315
  });
316
316
  });
317
317
 
318
- test("a dedicated port opens a port and a shared one only turns access on", () => {
318
+ it("a dedicated port opens a port and a shared one only turns access on", () => {
319
319
  const [dedicated] = rconAccessSections({
320
320
  portKey: RCON_ACCESS_PORT,
321
321
  });