@serfab/cadre-host 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +301 -505
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/join-output.d.ts +56 -0
- package/dist/bin/join-output.js +92 -0
- package/dist/bin/join-output.js.map +1 -0
- package/dist/bin/nat-output.d.ts +49 -0
- package/dist/bin/nat-output.js +127 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +44 -0
- package/dist/hosted/claim-details.js +66 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +318 -0
- package/dist/hosted/hosted-node-service.js +666 -0
- package/dist/hosted/hosted-node-service.js.map +1 -0
- package/dist/hosted/hosted-node-store.d.ts +40 -0
- package/dist/hosted/hosted-node-store.js +126 -0
- package/dist/hosted/hosted-node-store.js.map +1 -0
- package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
- package/dist/hosted/hosted-node-supervisor.js +396 -0
- package/dist/hosted/hosted-node-supervisor.js.map +1 -0
- package/dist/hosted/hosted-node-watcher.d.ts +83 -0
- package/dist/hosted/hosted-node-watcher.js +224 -0
- package/dist/hosted/hosted-node-watcher.js.map +1 -0
- package/dist/hosted/index.d.ts +16 -0
- package/dist/hosted/index.js +13 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/hosted/node-status.d.ts +35 -0
- package/dist/hosted/node-status.js +61 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +134 -0
- package/dist/hosted/types.js +22 -0
- package/dist/hosted/types.js.map +1 -0
- package/dist/index.d.ts +12 -24
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/installer/config.d.ts +4 -34
- package/dist/installer/config.js +3 -18
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +7 -19
- package/dist/installer/index.js +14 -69
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/paths.d.ts +0 -2
- package/dist/installer/paths.js +0 -4
- package/dist/installer/paths.js.map +1 -1
- package/dist/installer/wizard.d.ts +0 -7
- package/dist/installer/wizard.js +1 -8
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/address-resolver.d.ts +18 -17
- package/dist/nat/address-resolver.js +79 -25
- package/dist/nat/address-resolver.js.map +1 -1
- package/dist/nat/address-watch.d.ts +37 -0
- package/dist/nat/address-watch.js +84 -0
- package/dist/nat/address-watch.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +2 -1
- package/dist/nat/ddns/duckdns.js +5 -1
- package/dist/nat/ddns/duckdns.js.map +1 -1
- package/dist/nat/ddns/index.d.ts +6 -0
- package/dist/nat/ddns/index.js.map +1 -1
- package/dist/nat/index.d.ts +12 -9
- package/dist/nat/index.js +6 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +203 -77
- package/dist/nat/nat-service.js +602 -237
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +114 -30
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/port-mapper.d.ts +83 -79
- package/dist/nat/port-mapper.js +148 -189
- package/dist/nat/port-mapper.js.map +1 -1
- package/dist/nat/reachability.d.ts +31 -15
- package/dist/nat/reachability.js +97 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +78 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
- package/dist/orchestrator/host-process-orchestrator.js +78 -218
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
- package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
- package/dist/orchestrator/identity-file.js.map +1 -0
- package/dist/orchestrator/index.d.ts +2 -3
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +6 -6
- package/dist/orchestrator/node-identity.js +7 -7
- package/dist/orchestrator/node-identity.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +5 -6
- package/dist/orchestrator/port-allocator.js +9 -11
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +3 -5
- package/dist/orchestrator/state-store.js +2 -10
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +33 -22
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/error-handler.d.ts +5 -25
- package/dist/server/error-handler.js +11 -74
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +7 -11
- package/dist/server/events/types.js +2 -1
- package/dist/server/events/types.js.map +1 -1
- package/dist/server/index.d.ts +11 -37
- package/dist/server/index.js +30 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/hosted-nodes.d.ts +28 -0
- package/dist/server/routes/hosted-nodes.js +68 -0
- package/dist/server/routes/hosted-nodes.js.map +1 -0
- package/dist/server/routes/nat.d.ts +4 -1
- package/dist/server/routes/nat.js +13 -2
- package/dist/server/routes/nat.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +6 -20
- package/dist/server/routes/nodes.js +7 -86
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/routes/settings.d.ts +2 -7
- package/dist/server/routes/settings.js +3 -7
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +1 -11
- package/dist/server/routes/status.js.map +1 -1
- package/dist/server/server.js +8 -8
- package/dist/server/settings-store.d.ts +2 -2
- package/dist/server/settings-store.js +2 -2
- package/dist/server/static.js +1 -2
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-4FWuU8ye.js +19 -0
- package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
- package/dist/ui/assets/index-Di9Q4d01.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/store.d.ts +1 -1
- package/dist/update/store.js +1 -1
- package/dist/update/types.d.ts +1 -1
- package/dist/update/types.js +1 -1
- package/package.json +5 -6
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
- package/src/__tests__/orchestrator-ports.test.ts +124 -143
- package/src/__tests__/orchestrator-push.test.ts +138 -146
- package/src/__tests__/orchestrator.test.ts +1019 -1063
- package/src/bin/host.ts +368 -553
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +177 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
- package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +96 -0
- package/src/hosted/hosted-node-service.ts +795 -0
- package/src/hosted/hosted-node-store.ts +144 -0
- package/src/hosted/hosted-node-supervisor.ts +439 -0
- package/src/hosted/hosted-node-watcher.ts +250 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +71 -0
- package/src/hosted/types.ts +153 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +5 -86
- package/src/installer/__tests__/wizard.test.ts +56 -64
- package/src/installer/config.ts +135 -175
- package/src/installer/index.ts +15 -83
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +87 -103
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +433 -408
- package/src/nat/__tests__/nat-store.test.ts +127 -93
- package/src/nat/__tests__/port-mapper.test.ts +30 -191
- package/src/nat/__tests__/reachability.test.ts +96 -13
- package/src/nat/address-resolver.ts +94 -52
- package/src/nat/address-watch.ts +92 -0
- package/src/nat/ddns/duckdns.ts +5 -1
- package/src/nat/ddns/index.ts +6 -0
- package/src/nat/index.ts +87 -71
- package/src/nat/nat-service.ts +889 -501
- package/src/nat/nat-store.ts +237 -154
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +134 -25
- package/src/nat/types.ts +209 -159
- package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +81 -243
- package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
- package/src/orchestrator/index.ts +9 -10
- package/src/orchestrator/node-identity.ts +41 -41
- package/src/orchestrator/port-allocator.ts +71 -73
- package/src/orchestrator/state-store.ts +76 -90
- package/src/orchestrator/types.ts +34 -23
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +81 -118
- package/src/server/__tests__/fakes.ts +40 -44
- package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
- package/src/server/__tests__/nodes-route.test.ts +114 -283
- package/src/server/__tests__/publishers.test.ts +193 -180
- package/src/server/__tests__/server.smoke.test.ts +134 -223
- package/src/server/__tests__/settings-route.test.ts +179 -195
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/status-route.test.ts +82 -121
- package/src/server/error-handler.ts +112 -178
- package/src/server/events/types.ts +20 -24
- package/src/server/index.ts +223 -267
- package/src/server/routes/hosted-nodes.ts +83 -0
- package/src/server/routes/nat.ts +75 -63
- package/src/server/routes/nodes.ts +96 -188
- package/src/server/routes/settings.ts +115 -123
- package/src/server/routes/status.ts +102 -134
- package/src/server/server.ts +8 -8
- package/src/server/settings-store.ts +2 -2
- package/src/server/static.ts +1 -2
- package/src/update/index.ts +2 -2
- package/src/update/store.ts +1 -1
- package/src/update/types.ts +1 -1
- package/dist/auth/duration.d.ts +0 -1
- package/dist/auth/duration.js +0 -32
- package/dist/auth/duration.js.map +0 -1
- package/dist/auth/index.d.ts +0 -11
- package/dist/auth/index.js +0 -9
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/self-label.d.ts +0 -36
- package/dist/auth/self-label.js +0 -57
- package/dist/auth/self-label.js.map +0 -1
- package/dist/auth/trust-circle-store.d.ts +0 -26
- package/dist/auth/trust-circle-store.js +0 -129
- package/dist/auth/trust-circle-store.js.map +0 -1
- package/dist/auth/trust-circle.d.ts +0 -156
- package/dist/auth/trust-circle.js +0 -307
- package/dist/auth/trust-circle.js.map +0 -1
- package/dist/auth/types.d.ts +0 -84
- package/dist/auth/types.js +0 -24
- package/dist/auth/types.js.map +0 -1
- package/dist/donation/donation-service.d.ts +0 -389
- package/dist/donation/donation-service.js +0 -828
- package/dist/donation/donation-service.js.map +0 -1
- package/dist/donation/donation-store.d.ts +0 -51
- package/dist/donation/donation-store.js +0 -152
- package/dist/donation/donation-store.js.map +0 -1
- package/dist/donation/donation-supervisor.js +0 -364
- package/dist/donation/donation-supervisor.js.map +0 -1
- package/dist/donation/grant-service.d.ts +0 -80
- package/dist/donation/grant-service.js +0 -210
- package/dist/donation/grant-service.js.map +0 -1
- package/dist/donation/grant-store.d.ts +0 -38
- package/dist/donation/grant-store.js +0 -135
- package/dist/donation/grant-store.js.map +0 -1
- package/dist/donation/index.d.ts +0 -20
- package/dist/donation/index.js +0 -17
- package/dist/donation/index.js.map +0 -1
- package/dist/donation/types.d.ts +0 -224
- package/dist/donation/types.js +0 -40
- package/dist/donation/types.js.map +0 -1
- package/dist/installer/identity.js.map +0 -1
- package/dist/owner/index.d.ts +0 -11
- package/dist/owner/index.js +0 -11
- package/dist/owner/index.js.map +0 -1
- package/dist/owner/owner-node-client.d.ts +0 -110
- package/dist/owner/owner-node-client.js +0 -184
- package/dist/owner/owner-node-client.js.map +0 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
- package/dist/server/routes/bootstrap-node-validation.js +0 -197
- package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
- package/dist/server/routes/grants-admin.d.ts +0 -29
- package/dist/server/routes/grants-admin.js +0 -58
- package/dist/server/routes/grants-admin.js.map +0 -1
- package/dist/server/routes/grants.d.ts +0 -28
- package/dist/server/routes/grants.js +0 -165
- package/dist/server/routes/grants.js.map +0 -1
- package/dist/server/routes/provision-request-validation.d.ts +0 -45
- package/dist/server/routes/provision-request-validation.js +0 -97
- package/dist/server/routes/provision-request-validation.js.map +0 -1
- package/dist/server/routes/strands.d.ts +0 -20
- package/dist/server/routes/strands.js +0 -38
- package/dist/server/routes/strands.js.map +0 -1
- package/dist/server/routes/trust-circle.d.ts +0 -17
- package/dist/server/routes/trust-circle.js +0 -35
- package/dist/server/routes/trust-circle.js.map +0 -1
- package/dist/strands/index.d.ts +0 -7
- package/dist/strands/index.js +0 -6
- package/dist/strands/index.js.map +0 -1
- package/dist/strands/strand-service.d.ts +0 -50
- package/dist/strands/strand-service.js +0 -103
- package/dist/strands/strand-service.js.map +0 -1
- package/dist/strands/types.d.ts +0 -93
- package/dist/strands/types.js +0 -23
- package/dist/strands/types.js.map +0 -1
- package/dist/ui/assets/index-C6nELNfC.js +0 -23
- package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
- package/dist/ui/assets/index-TtIYSeZO.css +0 -1
- package/src/__tests__/cli-invite.smoke.test.ts +0 -192
- package/src/__tests__/host-owner.smoke.test.ts +0 -123
- package/src/__tests__/orchestrator-owner.test.ts +0 -234
- package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
- package/src/auth/__tests__/duration.test.ts +0 -28
- package/src/auth/__tests__/self-label.test.ts +0 -85
- package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
- package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
- package/src/auth/__tests__/trust-circle.test.ts +0 -445
- package/src/auth/duration.ts +0 -33
- package/src/auth/index.ts +0 -21
- package/src/auth/self-label.ts +0 -74
- package/src/auth/trust-circle-store.ts +0 -155
- package/src/auth/trust-circle.ts +0 -395
- package/src/auth/types.ts +0 -99
- package/src/donation/__tests__/donation-service.test.ts +0 -1193
- package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
- package/src/donation/__tests__/grant-service.test.ts +0 -213
- package/src/donation/__tests__/grant-store.test.ts +0 -113
- package/src/donation/donation-service.ts +0 -1033
- package/src/donation/donation-store.ts +0 -176
- package/src/donation/donation-supervisor.ts +0 -411
- package/src/donation/grant-service.ts +0 -252
- package/src/donation/grant-store.ts +0 -152
- package/src/donation/index.ts +0 -65
- package/src/donation/types.ts +0 -260
- package/src/owner/__tests__/owner-node-client.test.ts +0 -194
- package/src/owner/index.ts +0 -12
- package/src/owner/owner-node-client.ts +0 -259
- package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
- package/src/server/__tests__/grants-admin-route.test.ts +0 -171
- package/src/server/__tests__/grants-route.test.ts +0 -394
- package/src/server/__tests__/strands-route.test.ts +0 -234
- package/src/server/routes/bootstrap-node-validation.ts +0 -206
- package/src/server/routes/grants-admin.ts +0 -74
- package/src/server/routes/grants.ts +0 -193
- package/src/server/routes/provision-request-validation.ts +0 -105
- package/src/server/routes/strands.ts +0 -54
- package/src/server/routes/trust-circle.ts +0 -49
- package/src/strands/__tests__/strand-service.test.ts +0 -165
- package/src/strands/index.ts +0 -14
- package/src/strands/strand-service.ts +0 -135
- package/src/strands/types.ts +0 -102
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.dot.svelte-1qa2kn2{display:inline-flex;align-items:center;gap:.5rem;font-weight:500}.bulb.svelte-1qa2kn2{display:inline-block;border-radius:999px;flex:0 0 auto;background:var(--color-text-soft);box-shadow:0 0 0 3px #0000000f}.dot-ok.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-success);box-shadow:0 0 0 3px var(--color-success-bg)}.dot-warn.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-warn);box-shadow:0 0 0 3px var(--color-warn-bg)}.dot-err.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-danger);box-shadow:0 0 0 3px var(--color-danger-bg)}.dot-muted.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-text-soft)}.dot-ok.svelte-1qa2kn2{color:var(--color-success)}.dot-warn.svelte-1qa2kn2{color:var(--color-warn)}.dot-err.svelte-1qa2kn2{color:var(--color-danger)}.row.svelte-lwwmgz{display:inline-flex;gap:.4rem;flex-wrap:wrap}.banner.svelte-15mtls8{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info)}.summary.svelte-15mtls8{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}.kv.svelte-15mtls8{margin:var(--space-3) 0 0 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-15mtls8>div:where(.svelte-15mtls8){display:contents}.kv.svelte-15mtls8 dt:where(.svelte-15mtls8){color:var(--color-text-muted)}.kv.svelte-15mtls8 dd:where(.svelte-15mtls8){margin:0}.big.svelte-15mtls8{font-size:1.75rem;font-weight:600;margin:var(--space-2) 0}.big.svelte-15mtls8 .muted:where(.svelte-15mtls8){font-size:.95rem;font-weight:400}.link.svelte-15mtls8{display:inline-block;margin-top:var(--space-2);font-weight:500}.backdrop.svelte-80vbit{position:fixed;inset:0;background:#0f111673;z-index:1000}.dialog.svelte-80vbit{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:var(--space-5);min-width:min(24rem,calc(100vw - 2rem));max-width:min(32rem,calc(100vw - 2rem));overflow-wrap:anywhere;z-index:1010}.note.svelte-80vbit{background:var(--color-warn-bg);color:var(--color-warn);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:.9rem}.gate.svelte-80vbit{margin-top:var(--space-3);overflow-wrap:anywhere}.actions.svelte-80vbit{display:flex;justify-content:flex-end;gap:.5rem;margin-top:var(--space-4)}.qr.svelte-1uqetyj{background:#fff;border-radius:var(--radius);padding:.5rem;display:inline-block;line-height:0}.qr.svelte-1uqetyj svg{width:100%;height:100%;display:block}.placeholder.svelte-1uqetyj{background:var(--color-surface-alt)}.qr-wrap.svelte-sddtr9{display:flex;justify-content:center;margin:var(--space-3) 0}.token.svelte-sddtr9{display:flex;flex-direction:column;gap:.5rem}.token.svelte-sddtr9 textarea:where(.svelte-sddtr9){font-family:var(--font-mono);font-size:.78rem;word-break:break-all;resize:vertical}.token.svelte-sddtr9 button:where(.svelte-sddtr9){align-self:flex-end;display:inline-flex;align-items:center;gap:.375rem}.backdrop.svelte-1d46kin{position:fixed;inset:0;background:#0f111673;z-index:1000}.dialog.svelte-1d46kin{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:var(--space-5);width:min(28rem,calc(100vw - 2rem));max-height:calc(100vh - 2rem);overflow:auto;z-index:1010}.actions.svelte-1d46kin{display:flex;justify-content:flex-end;gap:.5rem;margin-top:var(--space-4)}.small.svelte-1d46kin{font-size:.85rem}.page-header.svelte-3alf51{display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.list.svelte-3alf51{list-style:none;padding:0;margin:var(--space-3) 0 0 0;display:flex;flex-direction:column;gap:.5rem}.list.svelte-3alf51 li:where(.svelte-3alf51){display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius);background:var(--color-surface-alt)}.member-main.svelte-3alf51{display:flex;flex-direction:column;gap:.125rem;min-width:0}.member-label.svelte-3alf51{font-weight:500}.small.svelte-3alf51{font-size:.85rem}.grid.two.svelte-hh3bto{grid-template-columns:repeat(auto-fit,minmax(20rem,1fr))}.kv.svelte-hh3bto{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-hh3bto>div:where(.svelte-hh3bto){display:contents}.kv.svelte-hh3bto dt:where(.svelte-hh3bto){color:var(--color-text-muted)}.kv.svelte-hh3bto dd:where(.svelte-hh3bto){margin:0}.row.inline.svelte-hh3bto{gap:.5rem}.row.inline.svelte-hh3bto input[type=checkbox]:where(.svelte-hh3bto){width:auto}.warning.svelte-hh3bto{background:var(--color-warn-bg);border-color:var(--color-warn)}.warning.svelte-hh3bto ul:where(.svelte-hh3bto){margin:0;padding-left:1.25rem}.actions.svelte-hh3bto{display:flex;justify-content:flex-end;gap:.5rem}.small.svelte-hh3bto{font-size:.85rem}table.svelte-7tcksg{width:100%;border-collapse:collapse;font-size:.92rem}th.svelte-7tcksg,td.svelte-7tcksg{padding:.5rem .75rem;text-align:left;border-bottom:1px solid var(--color-border)}th.svelte-7tcksg{font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-muted);border-bottom-color:var(--color-border-strong)}tr.svelte-7tcksg:last-child td:where(.svelte-7tcksg){border-bottom:none}.log-card.svelte-15ufwrh{display:flex;flex-direction:column;gap:var(--space-3)}.header.svelte-15ufwrh{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3)}.logs.svelte-15ufwrh{max-height:24rem;font-size:.78rem;line-height:1.45;white-space:pre}.small.svelte-15ufwrh{font-size:.85rem}button.svelte-15ufwrh{display:inline-flex;align-items:center;gap:.375rem}.back.svelte-1eq4g96{font-size:.92rem}.page-header.svelte-1eq4g96{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.kv.svelte-1eq4g96{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1eq4g96>div:where(.svelte-1eq4g96){display:contents}.kv.svelte-1eq4g96 dt:where(.svelte-1eq4g96){color:var(--color-text-muted)}.kv.svelte-1eq4g96 dd:where(.svelte-1eq4g96){margin:0}.actions.svelte-1eq4g96{display:flex;gap:.5rem;flex-wrap:wrap}.row.inline.svelte-1cl3wo6{gap:.5rem}.row.inline.svelte-1cl3wo6 input[type=checkbox]:where(.svelte-1cl3wo6){width:auto}.banner.svelte-1cl3wo6{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info);flex-wrap:wrap}.kv.svelte-1cl3wo6{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1cl3wo6>div:where(.svelte-1cl3wo6){display:contents}.kv.svelte-1cl3wo6 dt:where(.svelte-1cl3wo6){color:var(--color-text-muted)}.kv.svelte-1cl3wo6 dd:where(.svelte-1cl3wo6){margin:0}.copy-row.svelte-1cl3wo6{display:flex;align-items:center;gap:.375rem}.actions.svelte-1cl3wo6{display:flex;justify-content:flex-end}.small.svelte-1cl3wo6{font-size:.85rem}.error.svelte-1cl3wo6{color:var(--color-danger)}.list.svelte-ngb3vw{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}.list.svelte-ngb3vw li:where(.svelte-ngb3vw){display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius);background:var(--color-surface-alt)}.strand-main.svelte-ngb3vw{display:flex;flex-direction:column;gap:.25rem;min-width:0}.strand-id.svelte-ngb3vw{overflow-wrap:anywhere;user-select:text}.error.svelte-ngb3vw{color:var(--color-danger)}.meta.svelte-ngb3vw{gap:.5rem}.small.svelte-ngb3vw{font-size:.85rem}.warning.svelte-ngb3vw{background:var(--color-warn-bg);border-color:var(--color-warn)}.warning.svelte-ngb3vw code:where(.svelte-ngb3vw){overflow-wrap:anywhere}.actions.svelte-ngb3vw{display:flex;justify-content:flex-end;gap:.5rem}.backdrop.svelte-1gylbi3{position:fixed;inset:0;background:#0f111673;z-index:1000}.dialog.svelte-1gylbi3{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:var(--space-5);width:min(28rem,calc(100vw - 2rem));max-height:calc(100vh - 2rem);overflow:auto;overflow-wrap:anywhere;z-index:1010}.actions.svelte-1gylbi3{display:flex;justify-content:flex-end;gap:.5rem;margin-top:var(--space-4)}.small.svelte-1gylbi3{font-size:.85rem}.page-header.svelte-1jncsno{display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.list.svelte-1jncsno{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}.list.svelte-1jncsno>li:where(.svelte-1jncsno){display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius);background:var(--color-surface-alt)}.grant-main.svelte-1jncsno{display:flex;flex-direction:column;gap:.25rem;min-width:0;overflow-wrap:anywhere}.grant-label.svelte-1jncsno{font-weight:500}.meta.svelte-1jncsno{gap:.5rem}.donations.svelte-1jncsno{list-style:none;padding:0;margin:.25rem 0 0;display:flex;flex-direction:column;gap:.125rem}.donations.svelte-1jncsno li:where(.svelte-1jncsno){display:flex;flex-wrap:wrap;gap:.5rem}.actions.svelte-1jncsno{display:flex;gap:.5rem}.keep.svelte-1jncsno{display:flex;align-items:center;gap:.5rem;margin-top:var(--space-3);font-size:.9rem;font-weight:400;color:var(--color-text)}.error.svelte-1jncsno{color:var(--color-danger)}.small.svelte-1jncsno{font-size:.85rem}.toast-stack.svelte-g7yjw8{position:fixed;right:1rem;bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:min(30rem,calc(100vw - 2rem));z-index:1100}.toast.svelte-g7yjw8{background:var(--color-surface);color:var(--color-text);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:.625rem .75rem;display:flex;align-items:center;gap:.5rem;box-shadow:var(--shadow);font-size:.92rem}.toast-error.svelte-g7yjw8{border-color:var(--color-danger);background:var(--color-danger-bg);color:var(--color-danger)}.toast-success.svelte-g7yjw8{border-color:var(--color-success);background:var(--color-success-bg);color:var(--color-success)}.text.svelte-g7yjw8{flex:1 1 auto}button.svelte-g7yjw8{font-size:1.1rem;line-height:1;padding:.125rem .5rem;color:currentColor}.topbar.svelte-1r8vfx3{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-3) var(--space-5);background:var(--color-surface);border-bottom:1px solid var(--color-border);backdrop-filter:saturate(180%) blur(8px)}.brand.svelte-1r8vfx3{display:flex;align-items:center;gap:.6rem;font-weight:600}.logo.svelte-1r8vfx3{color:var(--color-accent);font-size:1.1rem}nav.svelte-1r8vfx3{display:flex;gap:.125rem}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3){font-size:.92rem;color:var(--color-text-muted);padding:.375rem .75rem;border-radius:var(--radius);text-decoration:none}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3):hover{background:var(--color-surface-alt);color:var(--color-text)}nav.svelte-1r8vfx3 a.active:where(.svelte-1r8vfx3){color:var(--color-accent-on);background:var(--color-accent)}main.svelte-1r8vfx3{flex:1 1 auto;width:min(64rem,100%);margin:0 auto;padding:var(--space-5)}@media(max-width:640px){.topbar.svelte-1r8vfx3{flex-direction:column;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3)}nav.svelte-1r8vfx3{width:100%;overflow-x:auto}main.svelte-1r8vfx3{padding:var(--space-3)}}:root{color-scheme:light dark;--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, monospace;--radius-sm: .25rem;--radius: .5rem;--radius-lg: .75rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.5rem;--space-6: 2rem;--space-7: 3rem;--color-bg: #f7f8fa;--color-surface: #ffffff;--color-surface-alt: #f0f2f6;--color-border: #d9dde3;--color-border-strong: #b8bec7;--color-text: #1d1f24;--color-text-muted: #5b626d;--color-text-soft: #8a8f99;--color-accent: #1d4ed8;--color-accent-strong: #1e3a8a;--color-accent-on: #ffffff;--color-accent-soft: #e0e7ff;--color-success: #15803d;--color-success-bg: #dcfce7;--color-warn: #b45309;--color-warn-bg: #fef3c7;--color-danger: #b91c1c;--color-danger-bg: #fee2e2;--color-info: #1d4ed8;--color-info-bg: #dbeafe;--shadow-sm: 0 1px 2px rgba(15, 23, 42, .05);--shadow: 0 4px 12px rgba(15, 23, 42, .08);--shadow-lg: 0 12px 32px rgba(15, 23, 42, .16)}@media(prefers-color-scheme:dark){:root{--color-bg: #0f1116;--color-surface: #181b22;--color-surface-alt: #20242d;--color-border: #2c313c;--color-border-strong: #3b424f;--color-text: #e8eaf0;--color-text-muted: #a4abb8;--color-text-soft: #777e8b;--color-accent: #93c5fd;--color-accent-strong: #bfdbfe;--color-accent-on: #0f1116;--color-accent-soft: #1e293b;--color-success: #4ade80;--color-success-bg: #14532d;--color-warn: #fbbf24;--color-warn-bg: #78350f;--color-danger: #f87171;--color-danger-bg: #7f1d1d;--color-info: #93c5fd;--color-info-bg: #1e3a8a;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow: 0 4px 12px rgba(0, 0, 0, .45);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .6)}}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;background:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}#app{min-height:100vh;display:flex;flex-direction:column}h1,h2,h3,h4,h5,h6{margin:0 0 var(--space-2) 0;font-weight:600;line-height:1.25;letter-spacing:-.01em}h1{font-size:1.5rem}h2{font-size:1.25rem}h3{font-size:1.05rem}p{margin:0 0 var(--space-3) 0}a{color:var(--color-accent);text-decoration:none}a:hover{text-decoration:underline}button{font:inherit;cursor:pointer;border:1px solid var(--color-border-strong);background:var(--color-surface);color:var(--color-text);padding:var(--space-2) var(--space-3);border-radius:var(--radius);transition:background-color .12s ease,border-color .12s ease}button:hover:not(:disabled){background:var(--color-surface-alt);border-color:var(--color-text-muted)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}button:disabled{opacity:.55;cursor:not-allowed}button.primary{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-accent-on)}button.primary:hover:not(:disabled){background:var(--color-accent-strong);border-color:var(--color-accent-strong)}button.danger{background:var(--color-danger);border-color:var(--color-danger);color:#fff}button.ghost{background:transparent;border-color:transparent;color:var(--color-text-muted);padding:var(--space-1) var(--space-2)}button.ghost:hover:not(:disabled){color:var(--color-text);background:var(--color-surface-alt)}input[type=text],input[type=url],input[type=number],input[type=password],select,textarea{font:inherit;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:var(--space-2) var(--space-3);width:100%}label{display:block;font-size:.85rem;color:var(--color-text-muted);font-weight:500;margin-bottom:var(--space-1)}code,pre{font-family:var(--font-mono);font-size:.85rem}pre{background:var(--color-surface-alt);padding:var(--space-3);border-radius:var(--radius);overflow:auto;margin:0}hr{border:none;border-top:1px solid var(--color-border);margin:var(--space-5) 0}.card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:var(--shadow-sm)}.muted{color:var(--color-text-muted)}.row{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap}.stack{display:flex;flex-direction:column;gap:var(--space-3)}.grid{display:grid;gap:var(--space-4)}.badge{display:inline-flex;align-items:center;gap:var(--space-1);font-size:.78rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;padding:.125rem .5rem;border-radius:999px;background:var(--color-surface-alt);color:var(--color-text-muted)}.badge.ok{background:var(--color-success-bg);color:var(--color-success)}.badge.warn{background:var(--color-warn-bg);color:var(--color-warn)}.badge.err{background:var(--color-danger-bg);color:var(--color-danger)}.badge.info{background:var(--color-info-bg);color:var(--color-info)}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI smoke test for the `invite` subcommand. Spawns a stub HTTP server
|
|
3
|
-
* playing the role of cadre-host's management API and asserts the CLI:
|
|
4
|
-
* - POSTs the expected body to /auth/invites
|
|
5
|
-
* - Prints the encodedInvite on stdout
|
|
6
|
-
* - Exits 0 on success / non-zero on connect failure
|
|
7
|
-
*
|
|
8
|
-
* Relies on `yarn build` having produced dist/bin/host.js.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { spawn } from 'node:child_process';
|
|
12
|
-
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
13
|
-
import { existsSync } from 'node:fs';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { dirname, resolve } from 'node:path';
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
-
|
|
18
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const binPath = resolve(here, '../../dist/bin/host.js');
|
|
20
|
-
|
|
21
|
-
// These smoke tests cold-start the built CLI as a child process (Node boot +,
|
|
22
|
-
// for some, an HTTP server). That comfortably exceeds vitest's 5s default under
|
|
23
|
-
// heavy parallel load, so give the subprocess generous headroom.
|
|
24
|
-
vi.setConfig({ testTimeout: 30000 });
|
|
25
|
-
|
|
26
|
-
interface StubServer {
|
|
27
|
-
server: Server;
|
|
28
|
-
port: number;
|
|
29
|
-
requests: Array<{ method: string; url: string; body: string }>;
|
|
30
|
-
setResponse(handler: (req: IncomingMessage, res: ServerResponse) => void): void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function startStub(): Promise<StubServer> {
|
|
34
|
-
const requests: StubServer['requests'] = [];
|
|
35
|
-
let handler: (req: IncomingMessage, res: ServerResponse) => void = (_req, res) => {
|
|
36
|
-
res.statusCode = 200;
|
|
37
|
-
res.setHeader('content-type', 'application/json');
|
|
38
|
-
res.end(JSON.stringify({
|
|
39
|
-
encodedInvite: 'fake-encoded-invite',
|
|
40
|
-
token: 'fake-token',
|
|
41
|
-
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
42
|
-
}));
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const server = createServer((req, res) => {
|
|
46
|
-
const chunks: Buffer[] = [];
|
|
47
|
-
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
48
|
-
req.on('end', () => {
|
|
49
|
-
requests.push({ method: req.method ?? '', url: req.url ?? '', body: Buffer.concat(chunks).toString('utf8') });
|
|
50
|
-
handler(req, res);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await new Promise<void>((resolveStart) => server.listen(0, '127.0.0.1', () => resolveStart()));
|
|
55
|
-
const addr = server.address();
|
|
56
|
-
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
57
|
-
return {
|
|
58
|
-
server,
|
|
59
|
-
port,
|
|
60
|
-
requests,
|
|
61
|
-
setResponse: (h) => { handler = h; },
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function runCli(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
66
|
-
return new Promise((resolveRun) => {
|
|
67
|
-
const child = spawn(process.execPath, [binPath, ...args], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
68
|
-
let stdout = '';
|
|
69
|
-
let stderr = '';
|
|
70
|
-
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8'); });
|
|
71
|
-
child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8'); });
|
|
72
|
-
child.on('close', (code) => resolveRun({ code: code ?? -1, stdout, stderr }));
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let stub: StubServer;
|
|
77
|
-
|
|
78
|
-
beforeEach(async () => {
|
|
79
|
-
if (!existsSync(binPath)) {
|
|
80
|
-
throw new Error(`cadre-host bin not built at ${binPath}; run \`yarn build\` first`);
|
|
81
|
-
}
|
|
82
|
-
stub = await startStub();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
afterEach(async () => {
|
|
86
|
-
await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('cadre-host invite CLI', () => {
|
|
90
|
-
it('POSTs to /auth/invites with label + ttlMs and prints encoded invite', async () => {
|
|
91
|
-
const result = await runCli(['invite', "Mom's phone", '--ttl', '12h', '--port', String(stub.port)]);
|
|
92
|
-
|
|
93
|
-
expect(result.code).toBe(0);
|
|
94
|
-
expect(result.stdout.trim()).toBe('fake-encoded-invite');
|
|
95
|
-
expect(stub.requests).toHaveLength(1);
|
|
96
|
-
expect(stub.requests[0]!.method).toBe('POST');
|
|
97
|
-
expect(stub.requests[0]!.url).toBe('/auth/invites');
|
|
98
|
-
const body = JSON.parse(stub.requests[0]!.body);
|
|
99
|
-
expect(body.label).toBe("Mom's phone");
|
|
100
|
-
expect(body.ttlMs).toBe(12 * 60 * 60 * 1000);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('exits non-zero when cadre-host returns an error', async () => {
|
|
104
|
-
stub.setResponse((_req, res) => {
|
|
105
|
-
res.statusCode = 500;
|
|
106
|
-
res.end('boom');
|
|
107
|
-
});
|
|
108
|
-
const result = await runCli(['invite', 'Test', '--port', String(stub.port)]);
|
|
109
|
-
expect(result.code).not.toBe(0);
|
|
110
|
-
expect(result.stderr).toContain('500');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('exits 2 (no service) when cadre-host is not reachable', async () => {
|
|
114
|
-
// Use a port the stub server occupied; close it first so connections fail.
|
|
115
|
-
const port = stub.port;
|
|
116
|
-
await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
|
|
117
|
-
const result = await runCli(['invite', 'Test', '--port', String(port)]);
|
|
118
|
-
expect(result.code).toBe(2);
|
|
119
|
-
expect(result.stderr).toMatch(/cadre-host start|Failed to reach/);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('rejects invalid --ttl', async () => {
|
|
123
|
-
const result = await runCli(['invite', 'Test', '--ttl', 'eternity', '--port', String(stub.port)]);
|
|
124
|
-
expect(result.code).toBe(1);
|
|
125
|
-
expect(result.stderr).toMatch(/Invalid --ttl/);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('cadre-host trust list CLI', () => {
|
|
130
|
-
it('renders members and pending invites', async () => {
|
|
131
|
-
stub.setResponse((_req, res) => {
|
|
132
|
-
res.statusCode = 200;
|
|
133
|
-
res.setHeader('content-type', 'application/json');
|
|
134
|
-
res.end(JSON.stringify({
|
|
135
|
-
members: [
|
|
136
|
-
{ peerId: '12D3KooWAlice', label: 'Alice', addedAt: 't1' },
|
|
137
|
-
{ peerId: '12D3KooWHost', label: 'Host', addedAt: 't2', self: true },
|
|
138
|
-
],
|
|
139
|
-
pending: [
|
|
140
|
-
{ token: 'tok1', label: 'Mom', createdAt: 't3', expiresAt: 't4' },
|
|
141
|
-
],
|
|
142
|
-
}));
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const result = await runCli(['trust', 'list', '--port', String(stub.port)]);
|
|
146
|
-
expect(result.code).toBe(0);
|
|
147
|
-
expect(result.stdout).toContain('12D3KooWAlice');
|
|
148
|
-
expect(result.stdout).toContain('Alice');
|
|
149
|
-
expect(result.stdout).toContain('[self]');
|
|
150
|
-
expect(result.stdout).toContain('tok1');
|
|
151
|
-
expect(result.stdout).toContain('Mom');
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('cadre-host trust revoke CLI', () => {
|
|
156
|
-
it('DELETEs /auth/invites/<token> by default', async () => {
|
|
157
|
-
stub.setResponse((_req, res) => {
|
|
158
|
-
res.statusCode = 204;
|
|
159
|
-
res.end();
|
|
160
|
-
});
|
|
161
|
-
const result = await runCli(['trust', 'revoke', 'some-token', '--port', String(stub.port)]);
|
|
162
|
-
expect(result.code).toBe(0);
|
|
163
|
-
expect(stub.requests[0]!.method).toBe('DELETE');
|
|
164
|
-
expect(stub.requests[0]!.url).toBe('/auth/invites/some-token');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('DELETEs /auth/members/<peerId> when id looks like a peerId', async () => {
|
|
168
|
-
stub.setResponse((_req, res) => {
|
|
169
|
-
res.statusCode = 204;
|
|
170
|
-
res.end();
|
|
171
|
-
});
|
|
172
|
-
const result = await runCli(['trust', 'revoke', '12D3KooWAlice', '--port', String(stub.port)]);
|
|
173
|
-
expect(result.code).toBe(0);
|
|
174
|
-
expect(stub.requests[0]!.url).toBe('/auth/members/12D3KooWAlice');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('treats secp256k1 peer IDs (16Uiu2HAm…) as members', async () => {
|
|
178
|
-
stub.setResponse((_req, res) => {
|
|
179
|
-
res.statusCode = 204;
|
|
180
|
-
res.end();
|
|
181
|
-
});
|
|
182
|
-
const result = await runCli(['trust', 'revoke', '16Uiu2HAmExample', '--port', String(stub.port)]);
|
|
183
|
-
expect(result.code).toBe(0);
|
|
184
|
-
expect(stub.requests[0]!.url).toBe('/auth/members/16Uiu2HAmExample');
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('rejects invalid --port', async () => {
|
|
188
|
-
const result = await runCli(['trust', 'revoke', 'some-token', '--port', 'garbage']);
|
|
189
|
-
expect(result.code).toBe(1);
|
|
190
|
-
expect(result.stderr).toMatch(/Invalid --port/);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-level smoke: the /auth/* HTTP contract over the owner-node
|
|
3
|
-
* channel adapter. Validates the two behaviours the realignment must
|
|
4
|
-
* preserve:
|
|
5
|
-
* 1. With the node up, POST /auth/invites returns an encodedInvite (no 500).
|
|
6
|
-
* 2. With the node refusing connections, GET /auth/trust-circle still lists
|
|
7
|
-
* local labels, and POST /auth/invites returns 503 (not a raw 500).
|
|
8
|
-
*
|
|
9
|
-
* The owner node is represented by a stub HTTP server matching the 6.6
|
|
10
|
-
* admin contract (the spawn path itself is covered by
|
|
11
|
-
* orchestrator-owner.test.ts).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
15
|
-
import Fastify from 'fastify';
|
|
16
|
-
import http from 'node:http';
|
|
17
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
18
|
-
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
|
|
21
|
-
import { TrustCircleService, TrustCircleStore, createTrustCircleHandlers } from '../auth/index.js';
|
|
22
|
-
import { OwnerNodeClient } from '../owner/index.js';
|
|
23
|
-
import { registerErrorHandler } from '../server/error-handler.js';
|
|
24
|
-
import { registerTrustCircleRoutes } from '../server/routes/trust-circle.js';
|
|
25
|
-
import { EventBus } from '../server/events/bus.js';
|
|
26
|
-
|
|
27
|
-
const TOKEN = 'tok';
|
|
28
|
-
|
|
29
|
-
let tmpRoot: string;
|
|
30
|
-
beforeEach(() => { tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-smoke-')); });
|
|
31
|
-
afterEach(() => { try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ } });
|
|
32
|
-
|
|
33
|
-
function buildApp(service: TrustCircleService) {
|
|
34
|
-
const app = Fastify();
|
|
35
|
-
registerErrorHandler(app);
|
|
36
|
-
registerTrustCircleRoutes(app, { handlers: createTrustCircleHandlers(service), events: new EventBus() });
|
|
37
|
-
return app;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
describe('host /auth delegation — node up', () => {
|
|
41
|
-
let server: http.Server;
|
|
42
|
-
let baseUrl: string;
|
|
43
|
-
|
|
44
|
-
beforeEach(async () => {
|
|
45
|
-
server = http.createServer((req, res) => {
|
|
46
|
-
const chunks: Buffer[] = [];
|
|
47
|
-
req.on('data', (c) => chunks.push(c as Buffer));
|
|
48
|
-
req.on('end', () => {
|
|
49
|
-
if (req.headers['authorization'] !== `Bearer ${TOKEN}`) {
|
|
50
|
-
res.writeHead(401); res.end(JSON.stringify({ ok: false, error: { code: 'not_authorized', message: 'x' } })); return;
|
|
51
|
-
}
|
|
52
|
-
if (req.url === '/admin/invites' && req.method === 'POST') {
|
|
53
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
54
|
-
res.end(JSON.stringify({ ok: true, data: { invite: { partyId: 'p', ownerAddrs: [], token: 't', createdAt: 1 }, encodedInvite: 'ENC' } }));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (req.url === '/admin/members' && req.method === 'GET') {
|
|
58
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
59
|
-
res.end(JSON.stringify({ ok: true, data: { members: [] } }));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
res.writeHead(400); res.end(JSON.stringify({ ok: false, error: { code: 'bad_request', message: req.url } }));
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
|
|
66
|
-
const a = server.address();
|
|
67
|
-
baseUrl = `http://127.0.0.1:${typeof a === 'object' && a ? a.port : 0}`;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
afterEach(async () => { await new Promise<void>((resolve) => server.close(() => resolve())); });
|
|
71
|
-
|
|
72
|
-
it('POST /auth/invites returns an encodedInvite (no 500)', async () => {
|
|
73
|
-
const client = new OwnerNodeClient({ baseUrl, token: TOKEN });
|
|
74
|
-
const service = new TrustCircleService({ cadreNode: client, store: new TrustCircleStore(tmpRoot) });
|
|
75
|
-
const app = buildApp(service);
|
|
76
|
-
try {
|
|
77
|
-
const res = await app.inject({ method: 'POST', url: '/auth/invites', payload: { label: 'Mom' } });
|
|
78
|
-
expect(res.statusCode).toBe(200);
|
|
79
|
-
const body = res.json() as { encodedInvite: string };
|
|
80
|
-
expect(body.encodedInvite).toBe('ENC');
|
|
81
|
-
} finally {
|
|
82
|
-
await app.close();
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('host /auth delegation — node refusing connections', () => {
|
|
88
|
-
let closedBaseUrl: string;
|
|
89
|
-
|
|
90
|
-
beforeEach(async () => {
|
|
91
|
-
const tmp = http.createServer();
|
|
92
|
-
const port = await new Promise<number>((resolve) => {
|
|
93
|
-
tmp.listen(0, '127.0.0.1', () => {
|
|
94
|
-
const a = tmp.address();
|
|
95
|
-
resolve(typeof a === 'object' && a ? a.port : 0);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
await new Promise<void>((resolve) => tmp.close(() => resolve()));
|
|
99
|
-
closedBaseUrl = `http://127.0.0.1:${port}`;
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('GET /auth/trust-circle lists local labels and POST /auth/invites returns 503', async () => {
|
|
103
|
-
const store = new TrustCircleStore(tmpRoot);
|
|
104
|
-
store.addMember({ peerId: '12D3KooWLocal', label: 'My laptop', addedAt: '2025-01-01T00:00:00Z' });
|
|
105
|
-
|
|
106
|
-
const client = new OwnerNodeClient({ baseUrl: closedBaseUrl, token: TOKEN });
|
|
107
|
-
const service = new TrustCircleService({ cadreNode: client, store });
|
|
108
|
-
const app = buildApp(service);
|
|
109
|
-
try {
|
|
110
|
-
const list = await app.inject({ method: 'GET', url: '/auth/trust-circle' });
|
|
111
|
-
expect(list.statusCode).toBe(200);
|
|
112
|
-
const snap = list.json() as { members: Array<{ peerId: string; label: string }> };
|
|
113
|
-
expect(snap.members.some((m) => m.peerId === '12D3KooWLocal' && m.label === 'My laptop')).toBe(true);
|
|
114
|
-
|
|
115
|
-
const invite = await app.inject({ method: 'POST', url: '/auth/invites', payload: { label: 'Dad' } });
|
|
116
|
-
expect(invite.statusCode).toBe(503);
|
|
117
|
-
const body = invite.json() as { error: { code: string } };
|
|
118
|
-
expect(body.error.code).toBe('node_unavailable');
|
|
119
|
-
} finally {
|
|
120
|
-
await app.close();
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HostProcessOrchestrator owner-node lifecycle tests.
|
|
3
|
-
*
|
|
4
|
-
* Uses the `spawn.entrypoint` hook with a fake CLI that writes its startup
|
|
5
|
-
* token and binds a minimal `/admin/identity` endpoint on the admin port —
|
|
6
|
-
* enough to validate spawn, idempotency, admin-port allocation/persistence,
|
|
7
|
-
* the end-to-end OwnerNodeClient round-trip, and init() re-attach.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
-
import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
-
import { tmpdir } from 'node:os';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
|
|
15
|
-
import { HostProcessOrchestrator, OWNER_CONTAINER_ID } from '../orchestrator/host-process-orchestrator.js';
|
|
16
|
-
import { StateStore } from '../orchestrator/state-store.js';
|
|
17
|
-
import { isPidAlive } from '../orchestrator/pid-liveness.js';
|
|
18
|
-
import { decodeDockerId } from '../orchestrator/types.js';
|
|
19
|
-
import { OwnerNodeClient } from '../owner/owner-node-client.js';
|
|
20
|
-
import { removeAllNodes } from './orchestrator-teardown.js';
|
|
21
|
-
|
|
22
|
-
const FAKE_OWNER_CLI = `
|
|
23
|
-
import fs from 'node:fs';
|
|
24
|
-
import http from 'node:http';
|
|
25
|
-
|
|
26
|
-
const args = process.argv.slice(2);
|
|
27
|
-
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
28
|
-
|
|
29
|
-
const tokenPath = get('--startup-token-file');
|
|
30
|
-
const adminPort = get('--admin-port');
|
|
31
|
-
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
32
|
-
|
|
33
|
-
if (tokenPath && token) {
|
|
34
|
-
try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (err) { console.error('token write failed', err); }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (adminPort && token) {
|
|
38
|
-
const server = http.createServer((req, res) => {
|
|
39
|
-
const auth = req.headers['authorization'];
|
|
40
|
-
if (auth !== 'Bearer ' + token) {
|
|
41
|
-
res.writeHead(401, { 'content-type': 'application/json' });
|
|
42
|
-
res.end(JSON.stringify({ ok: false, error: { code: 'not_authorized', message: 'bad token' } }));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (req.url === '/admin/identity' && req.method === 'GET') {
|
|
46
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
47
|
-
res.end(JSON.stringify({ ok: true, data: { peerId: 'FAKEPEER', partyId: 'fake-party' } }));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
res.writeHead(400, { 'content-type': 'application/json' });
|
|
51
|
-
res.end(JSON.stringify({ ok: false, error: { code: 'bad_request', message: req.url } }));
|
|
52
|
-
});
|
|
53
|
-
server.listen(parseInt(adminPort, 10), '127.0.0.1', () => {
|
|
54
|
-
console.log('admin listening on ' + adminPort);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
59
|
-
console.log('fake owner up');
|
|
60
|
-
setInterval(() => {}, 1 << 30);
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
let tmpRoot: string;
|
|
64
|
-
let scriptPath: string;
|
|
65
|
-
const orchestrators: HostProcessOrchestrator[] = [];
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-auth-'));
|
|
69
|
-
scriptPath = join(tmpRoot, 'fake-owner.mjs');
|
|
70
|
-
writeFileSync(scriptPath, FAKE_OWNER_CLI, 'utf8');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
afterEach(async () => {
|
|
74
|
-
try {
|
|
75
|
-
await removeAllNodes(orchestrators);
|
|
76
|
-
} finally {
|
|
77
|
-
await sleep(50);
|
|
78
|
-
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
function makeOrchestrator(rootDir: string): HostProcessOrchestrator {
|
|
83
|
-
mkdirSync(rootDir, { recursive: true });
|
|
84
|
-
const orch = new HostProcessOrchestrator({
|
|
85
|
-
rootDir,
|
|
86
|
-
portRange: { start: 18500, end: 18999 },
|
|
87
|
-
stopTimeoutMs: 1500,
|
|
88
|
-
spawn: { entrypoint: scriptPath },
|
|
89
|
-
});
|
|
90
|
-
orchestrators.push(orch);
|
|
91
|
-
return orch;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function waitFor(predicate: () => boolean | Promise<boolean>, timeoutMs = 4000, intervalMs = 50): Promise<void> {
|
|
95
|
-
const deadline = Date.now() + timeoutMs;
|
|
96
|
-
while (Date.now() < deadline) {
|
|
97
|
-
if (await predicate()) return;
|
|
98
|
-
await sleep(intervalMs);
|
|
99
|
-
}
|
|
100
|
-
throw new Error('waitFor: timed out');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function sleep(ms: number): Promise<void> {
|
|
104
|
-
return new Promise((res) => setTimeout(res, ms));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const CFG = { identityPath: 'C:/fake/identity.key', partyId: 'install-id-123', libp2pPort: 4555 };
|
|
108
|
-
|
|
109
|
-
describe('HostProcessOrchestrator.ensureOwnerNode', () => {
|
|
110
|
-
it('spawns the owner node, allocates+persists an admin port', async () => {
|
|
111
|
-
const orch = makeOrchestrator(join(tmpRoot, 'a'));
|
|
112
|
-
await orch.init();
|
|
113
|
-
const node = await orch.ensureOwnerNode(CFG);
|
|
114
|
-
|
|
115
|
-
expect(node.id).toBe(OWNER_CONTAINER_ID);
|
|
116
|
-
expect(node.owner).toBe(true);
|
|
117
|
-
expect(node.ports.admin).toBeGreaterThanOrEqual(18500);
|
|
118
|
-
expect(node.ports.p2p).toBe(4555);
|
|
119
|
-
|
|
120
|
-
const endpoint = orch.getOwnerAdminEndpoint();
|
|
121
|
-
expect(endpoint).toBeDefined();
|
|
122
|
-
expect(endpoint!.baseUrl).toBe(`http://127.0.0.1:${node.ports.admin}`);
|
|
123
|
-
|
|
124
|
-
// Persisted with admin port + owner flag + ownerConfig.
|
|
125
|
-
const persisted = new StateStore(join(tmpRoot, 'a')).load();
|
|
126
|
-
expect(persisted.ownerConfig).toMatchObject({ partyId: 'install-id-123', libp2pPort: 4555 });
|
|
127
|
-
const ph = persisted.handles.find((h) => h.containerId === OWNER_CONTAINER_ID);
|
|
128
|
-
expect(ph?.owner).toBe(true);
|
|
129
|
-
expect(typeof ph?.ports.admin).toBe('number');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('is idempotent — a second call does not spawn a second child', async () => {
|
|
133
|
-
const orch = makeOrchestrator(join(tmpRoot, 'b'));
|
|
134
|
-
await orch.init();
|
|
135
|
-
const first = await orch.ensureOwnerNode(CFG);
|
|
136
|
-
await waitFor(() => orch.isRunning(first.dockerId));
|
|
137
|
-
|
|
138
|
-
const second = await orch.ensureOwnerNode(CFG);
|
|
139
|
-
expect(second.dockerId).toBe(first.dockerId);
|
|
140
|
-
expect(orch.listNodes()).toHaveLength(1);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('the OwnerNodeClient reaches the spawned admin channel', async () => {
|
|
144
|
-
const orch = makeOrchestrator(join(tmpRoot, 'c'));
|
|
145
|
-
await orch.init();
|
|
146
|
-
const node = await orch.ensureOwnerNode(CFG);
|
|
147
|
-
await waitFor(() => orch.isRunning(node.dockerId));
|
|
148
|
-
|
|
149
|
-
const client = new OwnerNodeClient(() => orch.getOwnerAdminEndpoint());
|
|
150
|
-
await waitFor(async () => {
|
|
151
|
-
try { return (await client.getPeerId()) === 'FAKEPEER'; } catch { return false; }
|
|
152
|
-
});
|
|
153
|
-
expect(await client.getPeerId()).toBe('FAKEPEER');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('getOwnerAdminEndpoint() survives an init() re-attach', async () => {
|
|
157
|
-
const rootDir = join(tmpRoot, 'd');
|
|
158
|
-
const a = makeOrchestrator(rootDir);
|
|
159
|
-
await a.init();
|
|
160
|
-
const node = await a.ensureOwnerNode(CFG);
|
|
161
|
-
// Not `a.isRunning`: that answers from the ChildProcess at once, but `b`
|
|
162
|
-
// below can only recognise the node once it has written its startup token.
|
|
163
|
-
await waitFor(() => existsSync(join(rootDir, OWNER_CONTAINER_ID, '.startup-token')));
|
|
164
|
-
const before = a.getOwnerAdminEndpoint();
|
|
165
|
-
|
|
166
|
-
const b = makeOrchestrator(rootDir);
|
|
167
|
-
await b.init();
|
|
168
|
-
expect(b.isOwnerNode(OWNER_CONTAINER_ID)).toBe(true);
|
|
169
|
-
expect(b.hasOwnerConfig()).toBe(true);
|
|
170
|
-
const after = b.getOwnerAdminEndpoint();
|
|
171
|
-
expect(after).toEqual(before);
|
|
172
|
-
expect(await b.isRunning(node.dockerId)).toBe(true);
|
|
173
|
-
|
|
174
|
-
const { pid } = decodeDockerId(node.dockerId);
|
|
175
|
-
expect(isPidAlive(pid)).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// The owner's own guard arm: its admin channel binds on loopback, so the probe
|
|
179
|
-
// must check 127.0.0.1 too — the fake owner binds nothing else.
|
|
180
|
-
it('refuses to re-spawn over a re-attached owner whose admin port is still bound', async () => {
|
|
181
|
-
const rootDir = join(tmpRoot, 'g');
|
|
182
|
-
const a = makeOrchestrator(rootDir);
|
|
183
|
-
await a.init();
|
|
184
|
-
const node = await a.ensureOwnerNode(CFG);
|
|
185
|
-
const tokenPath = join(rootDir, OWNER_CONTAINER_ID, '.startup-token');
|
|
186
|
-
await waitFor(async () => (await a.getLogs(node.dockerId)).includes('admin listening'));
|
|
187
|
-
|
|
188
|
-
// Unverified as far as `b` can tell: live pid, token not (yet) matching.
|
|
189
|
-
writeFileSync(tokenPath, 'not-yet', 'utf8');
|
|
190
|
-
const b = makeOrchestrator(rootDir);
|
|
191
|
-
await b.init();
|
|
192
|
-
expect(await b.isRunning(node.dockerId)).toBe(false);
|
|
193
|
-
|
|
194
|
-
await expect(b.ensureOwnerNode(CFG)).rejects.toThrow(
|
|
195
|
-
new RegExp(`owner may still be running: port ${node.ports.admin} on 127\\.0\\.0\\.1 is unavailable`),
|
|
196
|
-
);
|
|
197
|
-
expect(b.listNodes().map((n) => n.dockerId)).toEqual([node.dockerId]);
|
|
198
|
-
expect(isPidAlive(decodeDockerId(node.dockerId).pid)).toBe(true);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('restartOwnerNode preserves the workdir and re-spawns', async () => {
|
|
202
|
-
const orch = makeOrchestrator(join(tmpRoot, 'e'));
|
|
203
|
-
await orch.init();
|
|
204
|
-
const first = await orch.ensureOwnerNode(CFG);
|
|
205
|
-
await waitFor(() => orch.isRunning(first.dockerId));
|
|
206
|
-
const { pid: firstPid } = decodeDockerId(first.dockerId);
|
|
207
|
-
|
|
208
|
-
const restarted = await orch.restartOwnerNode();
|
|
209
|
-
await waitFor(() => orch.isRunning(restarted.dockerId));
|
|
210
|
-
|
|
211
|
-
// New child (different pid/token), still exactly one node, workdir reused.
|
|
212
|
-
expect(restarted.dockerId).not.toBe(first.dockerId);
|
|
213
|
-
expect(orch.listNodes()).toHaveLength(1);
|
|
214
|
-
await waitFor(() => !isPidAlive(firstPid));
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Like a lent node, the owner node comes back on its previous ports even when lower
|
|
218
|
-
// ones are free. Only its TCP port differs: the configured libp2pPort wins over the
|
|
219
|
-
// previous handle's, because the NAT mapping points at the configured one.
|
|
220
|
-
it('re-spawns on its previous ports, taking p2p from the current config', async () => {
|
|
221
|
-
const orch = makeOrchestrator(join(tmpRoot, 'f'));
|
|
222
|
-
await orch.init();
|
|
223
|
-
const lower = await orch.createContainer({ containerId: 'c0', partyId: 'party-c0', bootstrapNodes: [], profile: 'transaction' });
|
|
224
|
-
const first = await orch.ensureOwnerNode(CFG);
|
|
225
|
-
await waitFor(() => orch.isRunning(first.dockerId));
|
|
226
|
-
|
|
227
|
-
await orch.removeContainer(lower.dockerId);
|
|
228
|
-
await orch.stopOwnerNode();
|
|
229
|
-
const respawned = await orch.ensureOwnerNode({ ...CFG, libp2pPort: 4556 });
|
|
230
|
-
|
|
231
|
-
expect(respawned.dockerId).not.toBe(first.dockerId);
|
|
232
|
-
expect(respawned.ports).toEqual({ ...first.ports, p2p: 4556 });
|
|
233
|
-
});
|
|
234
|
-
});
|