@serverkgg/bridge 0.0.9 → 0.0.11
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 +1 -1
- package/src/cli/validate.test.ts +116 -1
- package/src/cli/validate.ts +50 -3
- package/src/context/bridgeContext.ts +8 -1
- package/src/core/bridgeKind.ts +1 -0
- package/src/core/bridgeProbeError.ts +16 -0
- package/src/core/bridgeSetup.ts +20 -0
- package/src/core/bridgeValue.test.ts +29 -0
- package/src/core/bridgeValue.ts +12 -0
- package/src/core/index.ts +2 -0
- package/src/guides/blocks.ts +8 -0
- package/src/guides/markdown.test.ts +18 -0
- package/src/guides/markdown.ts +7 -1
- package/src/manifest/guide.ts +1 -0
- package/src/manifest/manifest.test.ts +49 -2
- package/src/manifest/presence.ts +9 -1
- package/src/modules/bridge.ts +20 -0
- package/src/modules/bridgeDriver.ts +2 -0
- package/src/modules/bridgeModule.ts +7 -0
- package/src/panel/bridgeControl.ts +1 -0
- package/src/panel/bridgeDetail.ts +6 -5
- package/src/panel/bridgePanel.ts +2 -0
- package/src/probes/a2s.test.ts +71 -0
- package/src/probes/a2s.ts +38 -11
- package/src/protocol/bridgeFrame.ts +5 -0
- package/src/protocol/bridgeLimits.ts +10 -0
- package/src/protocol/bridgeManifest.ts +2 -0
- package/src/protocol/bridgeSetup.ts +97 -0
- package/src/protocol/index.ts +1 -0
- package/src/runtime/bridgeConfigReader.ts +12 -8
- package/src/runtime/bridgeContext.ts +28 -2
- package/src/runtime/bridgeDriver.test.ts +512 -2
- package/src/runtime/bridgeDriver.ts +219 -1
- package/src/runtime/bridgeEvents.test.ts +96 -0
- package/src/runtime/bridgeEvents.ts +4 -1
- package/src/runtime/bridgeRegistry.test.ts +38 -1
- package/src/runtime/bridgeRegistry.ts +14 -1
- package/src/runtime/bridgeSecrets.test.ts +121 -0
- package/src/runtime/bridgeSecrets.ts +43 -0
- package/src/runtime/bridgeServer.test.ts +262 -2
- package/src/runtime/bridgeServer.ts +17 -0
- package/src/runtime/bridgeSetup.test.ts +575 -0
- package/src/runtime/bridgeSetup.ts +303 -0
- package/src/runtime/bridgeSetupHandlers.test.ts +303 -0
- package/src/runtime/bridgeSetupHandlers.ts +71 -0
- package/src/runtime/bridgeSignal.test.ts +114 -0
- package/src/runtime/bridgeSignal.ts +31 -0
- package/src/runtime/bridgeSupervisor.test.ts +111 -0
- package/src/runtime/bridgeSupervisor.ts +37 -5
- package/src/runtime/serve.ts +19 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/stripAnsi.ts +12 -0
- package/src/wire/panel.test.ts +155 -0
- package/src/wire/panel.ts +43 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { BridgeKind, PortJoin, PortProtocol } from "../core";
|
|
2
|
+
import { BridgeKind, BridgeSetupStepKind, PortJoin, PortProtocol } from "../core";
|
|
3
|
+
import { GuideOpenTab } from "../guides";
|
|
3
4
|
import type { ManifestPort, ManifestPortActivation } from "../manifest";
|
|
4
|
-
import type { BridgeDriver, BridgePendingModule } from "../modules";
|
|
5
|
+
import type { BridgeDriver, BridgePendingModule, BridgeSetupModule } from "../modules";
|
|
5
6
|
import {
|
|
6
7
|
BridgeConfirm,
|
|
7
8
|
BridgeControl,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
BridgeUploadMode,
|
|
14
15
|
type BridgeVisibleWhen,
|
|
15
16
|
} from "../panel";
|
|
17
|
+
import type { BridgeSetupStep } from "../protocol";
|
|
16
18
|
import { validateDriver, validatePortActivation } from "./bridgeDriver";
|
|
17
19
|
|
|
18
20
|
const text = (value: string) => ({
|
|
@@ -1125,3 +1127,511 @@ describe("the pending module", () => {
|
|
|
1125
1127
|
).toThrow('module id "pending" is reserved');
|
|
1126
1128
|
});
|
|
1127
1129
|
});
|
|
1130
|
+
|
|
1131
|
+
const driverWithSetup = (setup: BridgeSetupModule): BridgeDriver => ({
|
|
1132
|
+
install: {
|
|
1133
|
+
kind: BridgeKind.Install,
|
|
1134
|
+
run: async () => {
|
|
1135
|
+
return;
|
|
1136
|
+
},
|
|
1137
|
+
},
|
|
1138
|
+
lifecycle: {
|
|
1139
|
+
kind: BridgeKind.Lifecycle,
|
|
1140
|
+
command: async () => [
|
|
1141
|
+
"run",
|
|
1142
|
+
],
|
|
1143
|
+
},
|
|
1144
|
+
setup,
|
|
1145
|
+
panel: {
|
|
1146
|
+
tabs: [
|
|
1147
|
+
{
|
|
1148
|
+
id: "settings",
|
|
1149
|
+
title: text("Settings"),
|
|
1150
|
+
sections: [
|
|
1151
|
+
{
|
|
1152
|
+
layout: BridgeLayout.Form,
|
|
1153
|
+
id: "server",
|
|
1154
|
+
target: BridgeFormTarget.Variables,
|
|
1155
|
+
fields: [
|
|
1156
|
+
field("SERVER_NAME"),
|
|
1157
|
+
field("MOTD"),
|
|
1158
|
+
{
|
|
1159
|
+
key: "ADMIN_PASSWORD",
|
|
1160
|
+
control: BridgeControl.Secret,
|
|
1161
|
+
label: text("Admin password"),
|
|
1162
|
+
},
|
|
1163
|
+
],
|
|
1164
|
+
},
|
|
1165
|
+
],
|
|
1166
|
+
},
|
|
1167
|
+
],
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
const driverStep = (id: string) => ({
|
|
1172
|
+
kind: BridgeSetupStepKind.Driver as const,
|
|
1173
|
+
id,
|
|
1174
|
+
title: text(id),
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
const setupWith = (steps: BridgeSetupStep[]): BridgeSetupModule => ({
|
|
1178
|
+
kind: BridgeKind.Setup,
|
|
1179
|
+
steps,
|
|
1180
|
+
submit: async () => {
|
|
1181
|
+
return;
|
|
1182
|
+
},
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
const setupWithoutSubmit = (steps: BridgeSetupStep[]): BridgeSetupModule => ({
|
|
1186
|
+
kind: BridgeKind.Setup,
|
|
1187
|
+
steps,
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
const driverWithFields = (fields: BridgeField[]): BridgeDriver => ({
|
|
1191
|
+
install: {
|
|
1192
|
+
kind: BridgeKind.Install,
|
|
1193
|
+
run: async () => {
|
|
1194
|
+
return;
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
lifecycle: {
|
|
1198
|
+
kind: BridgeKind.Lifecycle,
|
|
1199
|
+
command: async () => [
|
|
1200
|
+
"run",
|
|
1201
|
+
],
|
|
1202
|
+
},
|
|
1203
|
+
panel: {
|
|
1204
|
+
tabs: [
|
|
1205
|
+
{
|
|
1206
|
+
id: "settings",
|
|
1207
|
+
title: text("Settings"),
|
|
1208
|
+
sections: [
|
|
1209
|
+
formSection("server", fields),
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
},
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
describe("the setup singleton", () => {
|
|
1217
|
+
test("takes a driver step with both locales", () => {
|
|
1218
|
+
expect(() =>
|
|
1219
|
+
validateDriver(
|
|
1220
|
+
driverWithSetup(
|
|
1221
|
+
setupWith([
|
|
1222
|
+
driverStep("sign-in"),
|
|
1223
|
+
]),
|
|
1224
|
+
),
|
|
1225
|
+
),
|
|
1226
|
+
).not.toThrow();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
test("rejects a setup that declares no steps", () => {
|
|
1230
|
+
expect(() => validateDriver(driverWithSetup(setupWith([])))).toThrow("the setup module declares no steps");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
test("rejects more steps than the platform takes", () => {
|
|
1234
|
+
expect(() =>
|
|
1235
|
+
validateDriver(
|
|
1236
|
+
driverWithSetup(
|
|
1237
|
+
setupWith(
|
|
1238
|
+
Array.from({
|
|
1239
|
+
length: 9,
|
|
1240
|
+
}).map((_entry, index) => driverStep(`step-${index}`)),
|
|
1241
|
+
),
|
|
1242
|
+
),
|
|
1243
|
+
),
|
|
1244
|
+
).toThrow("the setup module declares 9 steps — the platform takes at most 8");
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
test("rejects a step id that is not kebab-case", () => {
|
|
1248
|
+
expect(() =>
|
|
1249
|
+
validateDriver(
|
|
1250
|
+
driverWithSetup(
|
|
1251
|
+
setupWith([
|
|
1252
|
+
driverStep("signIn"),
|
|
1253
|
+
]),
|
|
1254
|
+
),
|
|
1255
|
+
),
|
|
1256
|
+
).toThrow('the setup module step id "signIn" must be kebab-case');
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
test("rejects the same step id twice", () => {
|
|
1260
|
+
expect(() =>
|
|
1261
|
+
validateDriver(
|
|
1262
|
+
driverWithSetup(
|
|
1263
|
+
setupWith([
|
|
1264
|
+
driverStep("sign-in"),
|
|
1265
|
+
driverStep("sign-in"),
|
|
1266
|
+
]),
|
|
1267
|
+
),
|
|
1268
|
+
),
|
|
1269
|
+
).toThrow('the setup module declares step "sign-in" more than once');
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
test("rejects a title that is missing a locale", () => {
|
|
1273
|
+
expect(() =>
|
|
1274
|
+
validateDriver(
|
|
1275
|
+
driverWithSetup(
|
|
1276
|
+
setupWith([
|
|
1277
|
+
{
|
|
1278
|
+
kind: BridgeSetupStepKind.Driver,
|
|
1279
|
+
id: "sign-in",
|
|
1280
|
+
title: {
|
|
1281
|
+
ar: "",
|
|
1282
|
+
en: "Sign in",
|
|
1283
|
+
},
|
|
1284
|
+
},
|
|
1285
|
+
]),
|
|
1286
|
+
),
|
|
1287
|
+
),
|
|
1288
|
+
).toThrow('the setup module step "sign-in" title needs both arabic and english text');
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
test("takes a form step that names an existing form section and its own fields", () => {
|
|
1292
|
+
expect(() =>
|
|
1293
|
+
validateDriver(
|
|
1294
|
+
driverWithSetup(
|
|
1295
|
+
setupWith([
|
|
1296
|
+
{
|
|
1297
|
+
kind: BridgeSetupStepKind.Form,
|
|
1298
|
+
id: "name",
|
|
1299
|
+
title: text("Name"),
|
|
1300
|
+
tab: "settings",
|
|
1301
|
+
section: "server",
|
|
1302
|
+
fields: [
|
|
1303
|
+
"SERVER_NAME",
|
|
1304
|
+
],
|
|
1305
|
+
},
|
|
1306
|
+
]),
|
|
1307
|
+
),
|
|
1308
|
+
),
|
|
1309
|
+
).not.toThrow();
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
test("rejects a form step that names a section the panel does not declare", () => {
|
|
1313
|
+
expect(() =>
|
|
1314
|
+
validateDriver(
|
|
1315
|
+
driverWithSetup(
|
|
1316
|
+
setupWith([
|
|
1317
|
+
{
|
|
1318
|
+
kind: BridgeSetupStepKind.Form,
|
|
1319
|
+
id: "name",
|
|
1320
|
+
title: text("Name"),
|
|
1321
|
+
tab: "settings",
|
|
1322
|
+
section: "missing",
|
|
1323
|
+
},
|
|
1324
|
+
]),
|
|
1325
|
+
),
|
|
1326
|
+
),
|
|
1327
|
+
).toThrow(
|
|
1328
|
+
'the setup module step "name" names tab "settings" section "missing", which is not a form section of this panel',
|
|
1329
|
+
);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
test("rejects a form step that names a field the section does not declare", () => {
|
|
1333
|
+
expect(() =>
|
|
1334
|
+
validateDriver(
|
|
1335
|
+
driverWithSetup(
|
|
1336
|
+
setupWith([
|
|
1337
|
+
{
|
|
1338
|
+
kind: BridgeSetupStepKind.Form,
|
|
1339
|
+
id: "name",
|
|
1340
|
+
title: text("Name"),
|
|
1341
|
+
tab: "settings",
|
|
1342
|
+
section: "server",
|
|
1343
|
+
fields: [
|
|
1344
|
+
"PASSWORD",
|
|
1345
|
+
],
|
|
1346
|
+
},
|
|
1347
|
+
]),
|
|
1348
|
+
),
|
|
1349
|
+
),
|
|
1350
|
+
).toThrow('the setup module step "name" names field "PASSWORD", which section "server" does not declare');
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
test("takes an open step that names an existing panel tab", () => {
|
|
1354
|
+
expect(() =>
|
|
1355
|
+
validateDriver(
|
|
1356
|
+
driverWithSetup(
|
|
1357
|
+
setupWith([
|
|
1358
|
+
{
|
|
1359
|
+
kind: BridgeSetupStepKind.Open,
|
|
1360
|
+
id: "tune",
|
|
1361
|
+
title: text("Tune"),
|
|
1362
|
+
target: {
|
|
1363
|
+
tab: GuideOpenTab.Panel,
|
|
1364
|
+
tabId: "settings",
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
]),
|
|
1368
|
+
),
|
|
1369
|
+
),
|
|
1370
|
+
).not.toThrow();
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
test("rejects an open step that names a panel tab the panel does not declare", () => {
|
|
1374
|
+
expect(() =>
|
|
1375
|
+
validateDriver(
|
|
1376
|
+
driverWithSetup(
|
|
1377
|
+
setupWith([
|
|
1378
|
+
{
|
|
1379
|
+
kind: BridgeSetupStepKind.Open,
|
|
1380
|
+
id: "tune",
|
|
1381
|
+
title: text("Tune"),
|
|
1382
|
+
target: {
|
|
1383
|
+
tab: GuideOpenTab.Panel,
|
|
1384
|
+
tabId: "world",
|
|
1385
|
+
},
|
|
1386
|
+
},
|
|
1387
|
+
]),
|
|
1388
|
+
),
|
|
1389
|
+
),
|
|
1390
|
+
).toThrow('the setup module step "tune" opens panel tab "world", which the panel does not declare');
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
test("takes an open step that leaves the panel altogether", () => {
|
|
1394
|
+
expect(() =>
|
|
1395
|
+
validateDriver(
|
|
1396
|
+
driverWithSetup(
|
|
1397
|
+
setupWith([
|
|
1398
|
+
{
|
|
1399
|
+
kind: BridgeSetupStepKind.Open,
|
|
1400
|
+
id: "invite",
|
|
1401
|
+
title: text("Invite"),
|
|
1402
|
+
target: {
|
|
1403
|
+
tab: GuideOpenTab.Access,
|
|
1404
|
+
},
|
|
1405
|
+
},
|
|
1406
|
+
]),
|
|
1407
|
+
),
|
|
1408
|
+
),
|
|
1409
|
+
).not.toThrow();
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
test("takes a form step over a secret field", () => {
|
|
1413
|
+
expect(() =>
|
|
1414
|
+
validateDriver(
|
|
1415
|
+
driverWithSetup(
|
|
1416
|
+
setupWith([
|
|
1417
|
+
{
|
|
1418
|
+
kind: BridgeSetupStepKind.Form,
|
|
1419
|
+
id: "password",
|
|
1420
|
+
title: text("Password"),
|
|
1421
|
+
tab: "settings",
|
|
1422
|
+
section: "server",
|
|
1423
|
+
fields: [
|
|
1424
|
+
"ADMIN_PASSWORD",
|
|
1425
|
+
],
|
|
1426
|
+
},
|
|
1427
|
+
]),
|
|
1428
|
+
),
|
|
1429
|
+
),
|
|
1430
|
+
).not.toThrow();
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
test("rejects a driver step while the setup exports no submit", () => {
|
|
1434
|
+
expect(() =>
|
|
1435
|
+
validateDriver(
|
|
1436
|
+
driverWithSetup(
|
|
1437
|
+
setupWithoutSubmit([
|
|
1438
|
+
driverStep("sign-in"),
|
|
1439
|
+
]),
|
|
1440
|
+
),
|
|
1441
|
+
),
|
|
1442
|
+
).toThrow("the setup module declares a driver step but exports no submit");
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
test("takes a setup with no submit while every step is answered elsewhere", () => {
|
|
1446
|
+
expect(() =>
|
|
1447
|
+
validateDriver(
|
|
1448
|
+
driverWithSetup(
|
|
1449
|
+
setupWithoutSubmit([
|
|
1450
|
+
{
|
|
1451
|
+
kind: BridgeSetupStepKind.Open,
|
|
1452
|
+
id: "invite",
|
|
1453
|
+
title: text("Invite"),
|
|
1454
|
+
target: {
|
|
1455
|
+
tab: GuideOpenTab.Access,
|
|
1456
|
+
},
|
|
1457
|
+
},
|
|
1458
|
+
]),
|
|
1459
|
+
),
|
|
1460
|
+
),
|
|
1461
|
+
).not.toThrow();
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
test("reserves the setup module id for the singleton", () => {
|
|
1465
|
+
expect(() =>
|
|
1466
|
+
validateDriver({
|
|
1467
|
+
install: {
|
|
1468
|
+
kind: BridgeKind.Install,
|
|
1469
|
+
run: async () => {
|
|
1470
|
+
return;
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
lifecycle: {
|
|
1474
|
+
kind: BridgeKind.Lifecycle,
|
|
1475
|
+
command: async () => [
|
|
1476
|
+
"run",
|
|
1477
|
+
],
|
|
1478
|
+
},
|
|
1479
|
+
modules: {
|
|
1480
|
+
setup: {
|
|
1481
|
+
kind: BridgeKind.Collection,
|
|
1482
|
+
list: async () => [],
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
}),
|
|
1486
|
+
).toThrow('module id "setup" is reserved');
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
describe("field patterns", () => {
|
|
1491
|
+
test("takes a pattern on a text field and on a secret one", () => {
|
|
1492
|
+
expect(() =>
|
|
1493
|
+
validateDriver(
|
|
1494
|
+
driverWithFields([
|
|
1495
|
+
{
|
|
1496
|
+
key: "LICENSE_KEY",
|
|
1497
|
+
control: BridgeControl.Secret,
|
|
1498
|
+
label: text("Licence key"),
|
|
1499
|
+
pattern: "^cfxk_\\w{1,60}_\\w{1,20}$",
|
|
1500
|
+
patternHint: text("A licence key starts with cfxk_"),
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
key: "SERVER_NAME",
|
|
1504
|
+
control: BridgeControl.Text,
|
|
1505
|
+
label: text("Name"),
|
|
1506
|
+
pattern: "^.{1,32}$",
|
|
1507
|
+
},
|
|
1508
|
+
]),
|
|
1509
|
+
),
|
|
1510
|
+
).not.toThrow();
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
test("rejects a pattern that is not a regular expression", () => {
|
|
1514
|
+
expect(() =>
|
|
1515
|
+
validateDriver(
|
|
1516
|
+
driverWithFields([
|
|
1517
|
+
{
|
|
1518
|
+
key: "LICENSE_KEY",
|
|
1519
|
+
control: BridgeControl.Text,
|
|
1520
|
+
label: text("Licence key"),
|
|
1521
|
+
pattern: "^cfxk_(",
|
|
1522
|
+
},
|
|
1523
|
+
]),
|
|
1524
|
+
),
|
|
1525
|
+
).toThrow('declares pattern "^cfxk_(", which is not a valid regular expression');
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
test("takes the FiveM licence pattern, whose repeats are all bounded", () => {
|
|
1529
|
+
expect(() =>
|
|
1530
|
+
validateDriver(
|
|
1531
|
+
driverWithFields([
|
|
1532
|
+
{
|
|
1533
|
+
key: "LICENSE_KEY",
|
|
1534
|
+
control: BridgeControl.Secret,
|
|
1535
|
+
label: text("Licence key"),
|
|
1536
|
+
pattern: "^(?:cfxk_\\w{1,60}_\\w{1,20}|\\w{32})$",
|
|
1537
|
+
},
|
|
1538
|
+
]),
|
|
1539
|
+
),
|
|
1540
|
+
).not.toThrow();
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
test("rejects a pattern that repeats a group which already repeats", () => {
|
|
1544
|
+
expect(() =>
|
|
1545
|
+
validateDriver(
|
|
1546
|
+
driverWithFields([
|
|
1547
|
+
{
|
|
1548
|
+
key: "LICENSE_KEY",
|
|
1549
|
+
control: BridgeControl.Text,
|
|
1550
|
+
label: text("Licence key"),
|
|
1551
|
+
pattern: "^(a+)+$",
|
|
1552
|
+
},
|
|
1553
|
+
]),
|
|
1554
|
+
),
|
|
1555
|
+
).toThrow('declares pattern "^(a+)+$", which repeats a group that already repeats');
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
test("rejects a starred group whose body repeats without a bound", () => {
|
|
1559
|
+
expect(() =>
|
|
1560
|
+
validateDriver(
|
|
1561
|
+
driverWithFields([
|
|
1562
|
+
{
|
|
1563
|
+
key: "SERVER_NAME",
|
|
1564
|
+
control: BridgeControl.Text,
|
|
1565
|
+
label: text("Name"),
|
|
1566
|
+
pattern: "^(\\w+\\s?)*$",
|
|
1567
|
+
},
|
|
1568
|
+
]),
|
|
1569
|
+
),
|
|
1570
|
+
).toThrow("repeats a group that already repeats");
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
test("rejects a pattern longer than the platform takes", () => {
|
|
1574
|
+
expect(() =>
|
|
1575
|
+
validateDriver(
|
|
1576
|
+
driverWithFields([
|
|
1577
|
+
{
|
|
1578
|
+
key: "LICENSE_KEY",
|
|
1579
|
+
control: BridgeControl.Text,
|
|
1580
|
+
label: text("Licence key"),
|
|
1581
|
+
pattern: `^${"a".repeat(256)}$`,
|
|
1582
|
+
},
|
|
1583
|
+
]),
|
|
1584
|
+
),
|
|
1585
|
+
).toThrow("pattern is 258 characters — the platform takes at most 256");
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
test("rejects a pattern hint with no pattern to explain", () => {
|
|
1589
|
+
expect(() =>
|
|
1590
|
+
validateDriver(
|
|
1591
|
+
driverWithFields([
|
|
1592
|
+
{
|
|
1593
|
+
key: "LICENSE_KEY",
|
|
1594
|
+
control: BridgeControl.Text,
|
|
1595
|
+
label: text("Licence key"),
|
|
1596
|
+
patternHint: text("A licence key starts with cfxk_"),
|
|
1597
|
+
},
|
|
1598
|
+
]),
|
|
1599
|
+
),
|
|
1600
|
+
).toThrow("declares a pattern hint with no pattern to explain");
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
test("rejects a pattern on a control that never matches one", () => {
|
|
1604
|
+
expect(() =>
|
|
1605
|
+
validateDriver(
|
|
1606
|
+
driverWithFields([
|
|
1607
|
+
{
|
|
1608
|
+
key: "WHITELIST",
|
|
1609
|
+
control: BridgeControl.Boolean,
|
|
1610
|
+
label: text("Whitelist"),
|
|
1611
|
+
pattern: "^true$",
|
|
1612
|
+
},
|
|
1613
|
+
]),
|
|
1614
|
+
),
|
|
1615
|
+
).toThrow('declares a pattern on a "boolean" control — only text and secret controls match one');
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
test("rejects a secret control that offers options", () => {
|
|
1619
|
+
expect(() =>
|
|
1620
|
+
validateDriver(
|
|
1621
|
+
driverWithFields([
|
|
1622
|
+
{
|
|
1623
|
+
key: "ADMIN_PASSWORD",
|
|
1624
|
+
control: BridgeControl.Secret,
|
|
1625
|
+
label: text("Admin password"),
|
|
1626
|
+
options: [
|
|
1627
|
+
{
|
|
1628
|
+
value: "letmein",
|
|
1629
|
+
label: text("letmein"),
|
|
1630
|
+
},
|
|
1631
|
+
],
|
|
1632
|
+
},
|
|
1633
|
+
]),
|
|
1634
|
+
),
|
|
1635
|
+
).toThrow("is a secret control with options — a secret is typed, never chosen from a list");
|
|
1636
|
+
});
|
|
1637
|
+
});
|