@serfab/cadre-host 0.9.0 → 0.11.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/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/self-label.d.ts +36 -0
- package/dist/auth/self-label.js +57 -0
- package/dist/auth/self-label.js.map +1 -0
- package/dist/auth/trust-circle.d.ts +32 -5
- package/dist/auth/trust-circle.js +31 -11
- package/dist/auth/trust-circle.js.map +1 -1
- package/dist/bin/host.js +41 -5
- package/dist/bin/host.js.map +1 -1
- package/dist/donation/__tests__/fake-orchestrator.d.ts +103 -0
- package/dist/donation/__tests__/fake-orchestrator.js +180 -0
- package/dist/donation/__tests__/fake-orchestrator.js.map +1 -0
- package/dist/donation/donation-service.d.ts +237 -12
- package/dist/donation/donation-service.js +491 -34
- package/dist/donation/donation-service.js.map +1 -1
- package/dist/donation/donation-store.d.ts +7 -0
- package/dist/donation/donation-store.js +7 -0
- package/dist/donation/donation-store.js.map +1 -1
- package/dist/donation/donation-supervisor.d.ts +167 -0
- package/dist/donation/donation-supervisor.js +341 -0
- package/dist/donation/donation-supervisor.js.map +1 -0
- package/dist/donation/index.d.ts +4 -2
- package/dist/donation/index.js +2 -1
- package/dist/donation/index.js.map +1 -1
- package/dist/donation/types.d.ts +26 -0
- package/dist/donation/types.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +109 -5
- package/dist/orchestrator/host-process-orchestrator.js +341 -98
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +26 -0
- package/dist/orchestrator/node-identity.js +36 -0
- package/dist/orchestrator/node-identity.js.map +1 -0
- package/dist/orchestrator/port-allocator.d.ts +14 -0
- package/dist/orchestrator/port-allocator.js +45 -0
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/owner/owner-node-client.d.ts +20 -5
- package/dist/owner/owner-node-client.js +21 -4
- package/dist/owner/owner-node-client.js.map +1 -1
- package/dist/server/error-handler.d.ts +7 -1
- package/dist/server/error-handler.js +23 -1
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +3 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +9 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/grants.js +22 -3
- package/dist/server/routes/grants.js.map +1 -1
- package/dist/server/routes/strands.d.ts +20 -0
- package/dist/server/routes/strands.js +38 -0
- package/dist/server/routes/strands.js.map +1 -0
- package/dist/server/static.js +38 -38
- package/dist/strands/index.d.ts +7 -0
- package/dist/strands/index.js +6 -0
- package/dist/strands/index.js.map +1 -0
- package/dist/strands/strand-service.d.ts +50 -0
- package/dist/strands/strand-service.js +103 -0
- package/dist/strands/strand-service.js.map +1 -0
- package/dist/strands/types.d.ts +93 -0
- package/dist/strands/types.js +23 -0
- package/dist/strands/types.js.map +1 -0
- package/dist/ui/assets/index-BVoqLjyX.css +1 -0
- package/dist/ui/assets/index-C-iks41M.js +17 -0
- package/dist/ui/assets/index-C-iks41M.js.map +1 -0
- package/dist/ui/index.html +14 -14
- package/dist/update/release-key.d.ts +1 -1
- package/dist/update/release-key.js +1 -1
- package/package.json +5 -5
- package/service/cadre-host.service.tmpl +16 -16
- package/service/com.serfab.cadre-host.plist.tmpl +35 -35
- package/service/install-service.ps1 +98 -98
- package/service/uninstall-service.ps1 +19 -19
- package/src/__tests__/build-targets.test.ts +26 -0
- package/src/__tests__/cli-invite.smoke.test.ts +192 -192
- package/src/__tests__/cli-nat.smoke.test.ts +212 -212
- package/src/__tests__/cli.smoke.test.ts +46 -46
- package/src/__tests__/global-setup.ts +51 -0
- package/src/__tests__/host-owner.smoke.test.ts +123 -123
- package/src/__tests__/orchestrator-env-scrub.test.ts +140 -0
- package/src/__tests__/orchestrator-node-identity.test.ts +204 -0
- package/src/__tests__/orchestrator-owner.test.ts +198 -198
- package/src/__tests__/orchestrator-pin-keys.test.ts +153 -153
- package/src/__tests__/orchestrator-push.test.ts +145 -145
- package/src/__tests__/orchestrator.test.ts +951 -570
- package/src/auth/__tests__/duration.test.ts +28 -28
- package/src/auth/__tests__/self-label.test.ts +85 -0
- package/src/auth/__tests__/trust-circle-integration.test.ts +152 -147
- package/src/auth/__tests__/trust-circle-store.test.ts +115 -115
- package/src/auth/__tests__/trust-circle.test.ts +445 -401
- package/src/auth/duration.ts +33 -33
- package/src/auth/index.ts +21 -19
- package/src/auth/self-label.ts +74 -0
- package/src/auth/trust-circle-store.ts +155 -155
- package/src/auth/trust-circle.ts +45 -13
- package/src/auth/types.ts +99 -99
- package/src/bin/host.ts +1299 -1258
- package/src/donation/__tests__/donation-service.test.ts +1068 -290
- package/src/donation/__tests__/donation-supervisor.test.ts +510 -0
- package/src/donation/__tests__/fake-orchestrator.test.ts +168 -0
- package/src/donation/__tests__/fake-orchestrator.ts +225 -0
- package/src/donation/__tests__/grant-service.test.ts +213 -213
- package/src/donation/__tests__/grant-store.test.ts +113 -113
- package/src/donation/donation-service.ts +962 -392
- package/src/donation/donation-store.ts +171 -164
- package/src/donation/donation-supervisor.ts +388 -0
- package/src/donation/grant-service.ts +200 -200
- package/src/donation/grant-store.ts +152 -152
- package/src/donation/index.ts +64 -49
- package/src/donation/types.ts +243 -220
- package/src/index.ts +14 -0
- package/src/installer/__tests__/identity.test.ts +34 -34
- package/src/installer/__tests__/paths.test.ts +47 -47
- package/src/installer/__tests__/service-host.test.ts +62 -62
- package/src/installer/__tests__/wizard.test.ts +64 -64
- package/src/installer/identity.ts +49 -49
- package/src/installer/paths.ts +109 -109
- package/src/installer/platform.ts +27 -27
- package/src/installer/service-host/index.ts +21 -21
- package/src/installer/service-host/template.ts +21 -21
- package/src/installer/wizard.ts +103 -103
- package/src/nat/__tests__/address-resolver.test.ts +82 -82
- package/src/nat/__tests__/ddns-duckdns.test.ts +83 -83
- package/src/nat/__tests__/ddns-updater.test.ts +253 -253
- package/src/nat/__tests__/external-ip.test.ts +124 -124
- package/src/nat/__tests__/nat-service.test.ts +497 -497
- package/src/nat/__tests__/nat-store.test.ts +93 -93
- package/src/nat/__tests__/port-mapper.test.ts +191 -191
- package/src/nat/__tests__/reachability.test.ts +24 -24
- package/src/nat/__tests__/secrets.test.ts +178 -178
- package/src/nat/address-resolver.ts +51 -51
- package/src/nat/ddns/duckdns.ts +76 -76
- package/src/nat/ddns/index.ts +55 -55
- package/src/nat/ddns/updater.ts +243 -243
- package/src/nat/external-ip.ts +154 -154
- package/src/nat/index.ts +71 -71
- package/src/nat/nat-service.ts +501 -501
- package/src/nat/nat-store.ts +154 -154
- package/src/nat/port-mapper.ts +329 -329
- package/src/nat/reachability.ts +34 -34
- package/src/nat/secrets/file-store.ts +130 -130
- package/src/nat/secrets/index.ts +112 -112
- package/src/nat/secrets/keytar-store.ts +67 -67
- package/src/nat/types.ts +159 -159
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -0
- package/src/orchestrator/host-process-orchestrator.ts +1133 -894
- package/src/orchestrator/index.ts +10 -10
- package/src/orchestrator/log-rotator.ts +53 -53
- package/src/orchestrator/node-identity.ts +40 -0
- package/src/orchestrator/pid-liveness.ts +24 -24
- package/src/orchestrator/port-allocator.ts +91 -43
- package/src/orchestrator/state-store.ts +90 -90
- package/src/orchestrator/types.ts +141 -141
- package/src/owner/__tests__/owner-node-client.test.ts +194 -194
- package/src/owner/index.ts +12 -12
- package/src/owner/owner-node-client.ts +259 -233
- package/src/push/__tests__/push.test.ts +90 -90
- package/src/push/index.ts +146 -146
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +118 -118
- package/src/server/__tests__/grants-admin-route.test.ts +73 -73
- package/src/server/__tests__/grants-route.test.ts +263 -189
- package/src/server/__tests__/nodes-route.test.ts +259 -259
- package/src/server/__tests__/origin-guard.test.ts +72 -72
- package/src/server/__tests__/publishers.test.ts +216 -216
- package/src/server/__tests__/server.smoke.test.ts +253 -253
- package/src/server/__tests__/settings-route.test.ts +194 -194
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/static.test.ts +145 -145
- package/src/server/__tests__/status-route.test.ts +134 -134
- package/src/server/__tests__/strands-route.test.ts +233 -0
- package/src/server/error-handler.ts +178 -155
- package/src/server/events/bus.ts +42 -42
- package/src/server/events/sse-route.ts +81 -81
- package/src/server/events/types.ts +23 -22
- package/src/server/index.ts +267 -254
- package/src/server/origin-guard.ts +84 -84
- package/src/server/routes/grants-admin.ts +41 -41
- package/src/server/routes/grants.ts +210 -186
- package/src/server/routes/nat.ts +63 -63
- package/src/server/routes/nodes.ts +170 -170
- package/src/server/routes/settings.ts +123 -123
- package/src/server/routes/status.ts +118 -118
- package/src/server/routes/strands.ts +54 -0
- package/src/server/routes/trust-circle.ts +49 -49
- package/src/server/routes/update.ts +43 -43
- package/src/server/server.ts +122 -122
- package/src/server/settings-store.ts +37 -37
- package/src/server/static.ts +207 -207
- package/src/strands/__tests__/strand-service.test.ts +165 -0
- package/src/strands/index.ts +14 -0
- package/src/strands/strand-service.ts +135 -0
- package/src/strands/types.ts +102 -0
- package/src/update/__tests__/apply.test.ts +158 -158
- package/src/update/__tests__/manifest.test.ts +128 -128
- package/src/update/__tests__/release-key.test.ts +42 -42
- package/src/update/__tests__/sign.test.ts +78 -78
- package/src/update/__tests__/store.test.ts +81 -81
- package/src/update/__tests__/update-service.integration.test.ts +256 -256
- package/src/update/__tests__/version.test.ts +31 -31
- package/src/update/apply.ts +190 -190
- package/src/update/index.ts +341 -341
- package/src/update/release-key.ts +71 -71
- package/src/update/sign.ts +74 -74
- package/src/update/store.ts +101 -101
- package/src/update/types.ts +109 -109
- package/src/update/version.ts +80 -80
- package/dist/ui/assets/index-BlMXQPSw.js +0 -14
- package/dist/ui/assets/index-BlMXQPSw.js.map +0 -1
- package/dist/ui/assets/index-BwU5mJGF.css +0 -1
|
@@ -1,123 +1,123 @@
|
|
|
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
|
+
/**
|
|
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
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostProcessOrchestrator env-scrub test — the manager's own `process.env` must
|
|
3
|
+
* not leak `CADRE_*` config-override vars into spawned children.
|
|
4
|
+
*
|
|
5
|
+
* `launchChild` used to build each child's environment as
|
|
6
|
+
* `{ ...process.env, ...extraEnv, <fixed per-child vars> }`, so ANY `CADRE_*`
|
|
7
|
+
* var set on the manager (not just the ones the orchestrator deliberately
|
|
8
|
+
* sets) reached every child. Because `@serfab/cadre-cli` treats an inherited
|
|
9
|
+
* `CADRE_*` var as a config override that beats the per-child config file, a
|
|
10
|
+
* `CADRE_PARTY_ID` set on the manager could silently pull every child into
|
|
11
|
+
* that party.
|
|
12
|
+
*
|
|
13
|
+
* A fake CLI records the vars under test from its environment to a file so
|
|
14
|
+
* the scrub is observable without a real cadre node.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
18
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
import { HostProcessOrchestrator } from '../orchestrator/host-process-orchestrator.js';
|
|
23
|
+
|
|
24
|
+
// Writes the vars under test (as seen in the child env) next to the startup token.
|
|
25
|
+
const FAKE_CLI = `
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
30
|
+
const tokenPath = get('--startup-token-file');
|
|
31
|
+
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
32
|
+
if (tokenPath) {
|
|
33
|
+
const dir = path.dirname(tokenPath);
|
|
34
|
+
const record = {
|
|
35
|
+
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID ?? null,
|
|
36
|
+
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH ?? null,
|
|
37
|
+
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT ?? null,
|
|
38
|
+
DEBUG: process.env.DEBUG ?? null,
|
|
39
|
+
};
|
|
40
|
+
try { fs.writeFileSync(path.join(dir, 'env-seen.json'), JSON.stringify(record), 'utf8'); } catch (e) { console.error(e); }
|
|
41
|
+
if (token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
|
|
42
|
+
}
|
|
43
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
44
|
+
setInterval(() => {}, 1 << 30);
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
let tmpRoot: string;
|
|
48
|
+
let scriptPath: string;
|
|
49
|
+
const orchestrators: HostProcessOrchestrator[] = [];
|
|
50
|
+
let savedEnv: Record<string, string | undefined>;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-env-scrub-'));
|
|
54
|
+
scriptPath = join(tmpRoot, 'fake-cli.mjs');
|
|
55
|
+
writeFileSync(scriptPath, FAKE_CLI, 'utf8');
|
|
56
|
+
savedEnv = {
|
|
57
|
+
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID,
|
|
58
|
+
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH,
|
|
59
|
+
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT,
|
|
60
|
+
DEBUG: process.env.DEBUG,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(async () => {
|
|
65
|
+
for (const orch of orchestrators) {
|
|
66
|
+
for (const n of orch.listNodes()) {
|
|
67
|
+
try { await orch.removeContainer(n.dockerId); } catch { /* ignore */ }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
orchestrators.length = 0;
|
|
71
|
+
await sleep(50);
|
|
72
|
+
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
73
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
74
|
+
if (value === undefined) delete process.env[key];
|
|
75
|
+
else process.env[key] = value;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function makeOrchestrator(rootDir: string): HostProcessOrchestrator {
|
|
80
|
+
mkdirSync(rootDir, { recursive: true });
|
|
81
|
+
const orch = new HostProcessOrchestrator({
|
|
82
|
+
rootDir,
|
|
83
|
+
portRange: { start: 18000, end: 18499 },
|
|
84
|
+
stopTimeoutMs: 1500,
|
|
85
|
+
spawn: { entrypoint: scriptPath },
|
|
86
|
+
});
|
|
87
|
+
orchestrators.push(orch);
|
|
88
|
+
return orch;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function waitForFile(path: string, timeoutMs = 5000): Promise<string> {
|
|
92
|
+
const deadline = Date.now() + timeoutMs;
|
|
93
|
+
while (Date.now() < deadline) {
|
|
94
|
+
if (existsSync(path)) return readFileSync(path, 'utf8');
|
|
95
|
+
await sleep(50);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`file never appeared: ${path}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sleep(ms: number): Promise<void> {
|
|
101
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe('HostProcessOrchestrator child env scrub', () => {
|
|
105
|
+
it('does not leak manager CADRE_* config-override vars into children, but passes through non-CADRE vars', async () => {
|
|
106
|
+
process.env.CADRE_PARTY_ID = 'conflicting-party';
|
|
107
|
+
process.env.CADRE_STORAGE_PATH = '/tmp/should-not-leak';
|
|
108
|
+
// Not an ENV_MAPPINGS config override — read directly by the cli's `start`
|
|
109
|
+
// command — so it only stays scrubbed if the scrub is prefix-wide.
|
|
110
|
+
process.env.CADRE_ADMIN_PORT = '19999';
|
|
111
|
+
process.env.DEBUG = 'some:value';
|
|
112
|
+
|
|
113
|
+
const rootDir = join(tmpRoot, 'a');
|
|
114
|
+
const orch = makeOrchestrator(rootDir);
|
|
115
|
+
await orch.init();
|
|
116
|
+
|
|
117
|
+
await orch.createContainer({
|
|
118
|
+
containerId: 'child-1',
|
|
119
|
+
partyId: 'party-1',
|
|
120
|
+
bootstrapNodes: [],
|
|
121
|
+
profile: 'storage',
|
|
122
|
+
});
|
|
123
|
+
await orch.createContainer({
|
|
124
|
+
containerId: 'child-2',
|
|
125
|
+
partyId: 'party-2',
|
|
126
|
+
bootstrapNodes: [],
|
|
127
|
+
profile: 'storage',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const seen1 = JSON.parse(await waitForFile(join(rootDir, 'child-1', 'env-seen.json')));
|
|
131
|
+
const seen2 = JSON.parse(await waitForFile(join(rootDir, 'child-2', 'env-seen.json')));
|
|
132
|
+
|
|
133
|
+
for (const seen of [seen1, seen2]) {
|
|
134
|
+
expect(seen.CADRE_PARTY_ID).toBeNull();
|
|
135
|
+
expect(seen.CADRE_STORAGE_PATH).toBeNull();
|
|
136
|
+
expect(seen.CADRE_ADMIN_PORT).toBeNull();
|
|
137
|
+
expect(seen.DEBUG).toBe('some:value');
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostProcessOrchestrator per-node identity tests.
|
|
3
|
+
*
|
|
4
|
+
* A donated node must keep the peer id its host cadre approved across restarts,
|
|
5
|
+
* and needs a durable on-disk home for its node-local stores (`cadre-cli start`
|
|
6
|
+
* opens the file-backed bootstrap-peer / trusted-owner stores only when a
|
|
7
|
+
* protobuf identity key file is configured). The orchestrator therefore writes
|
|
8
|
+
* `<workdir>/identity.key` once per container and passes it as
|
|
9
|
+
* `--identity-protobuf`.
|
|
10
|
+
*
|
|
11
|
+
* A fake CLI records the `--identity-protobuf` argument it was launched with, so
|
|
12
|
+
* the spawn wiring is observable without a real cadre node.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
16
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
import { HostProcessOrchestrator, OWNER_CONTAINER_ID } from '../orchestrator/host-process-orchestrator.js';
|
|
21
|
+
import { loadIdentity } from '../installer/identity.js';
|
|
22
|
+
|
|
23
|
+
// Writes the --identity-protobuf argument (as seen on its own command line) next
|
|
24
|
+
// to the startup token, then behaves like a minimal long-lived node.
|
|
25
|
+
const FAKE_CLI = `
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
30
|
+
const tokenPath = get('--startup-token-file');
|
|
31
|
+
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
32
|
+
if (tokenPath) {
|
|
33
|
+
const dir = path.dirname(tokenPath);
|
|
34
|
+
try { fs.writeFileSync(path.join(dir, 'identity-arg.txt'), get('--identity-protobuf') ?? '', 'utf8'); } catch (e) { console.error(e); }
|
|
35
|
+
if (token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
|
|
36
|
+
}
|
|
37
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
38
|
+
setInterval(() => {}, 1 << 30);
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
let tmpRoot: string;
|
|
42
|
+
let scriptPath: string;
|
|
43
|
+
const orchestrators: HostProcessOrchestrator[] = [];
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-nodeid-'));
|
|
47
|
+
scriptPath = join(tmpRoot, 'fake-cli.mjs');
|
|
48
|
+
writeFileSync(scriptPath, FAKE_CLI, 'utf8');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
for (const orch of orchestrators) {
|
|
53
|
+
for (const n of orch.listNodes()) {
|
|
54
|
+
try { await orch.removeContainer(n.dockerId); } catch { /* ignore */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
orchestrators.length = 0;
|
|
58
|
+
await sleep(50);
|
|
59
|
+
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function makeOrchestrator(
|
|
63
|
+
rootDir: string,
|
|
64
|
+
portRange: { start: number; end: number } = { start: 17500, end: 17999 },
|
|
65
|
+
): HostProcessOrchestrator {
|
|
66
|
+
mkdirSync(rootDir, { recursive: true });
|
|
67
|
+
const orch = new HostProcessOrchestrator({
|
|
68
|
+
rootDir,
|
|
69
|
+
portRange,
|
|
70
|
+
stopTimeoutMs: 1500,
|
|
71
|
+
spawn: { entrypoint: scriptPath },
|
|
72
|
+
});
|
|
73
|
+
orchestrators.push(orch);
|
|
74
|
+
return orch;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function waitForFile(path: string, timeoutMs = 5000): Promise<string> {
|
|
78
|
+
const deadline = Date.now() + timeoutMs;
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
if (existsSync(path)) return readFileSync(path, 'utf8');
|
|
81
|
+
await sleep(50);
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`file never appeared: ${path}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sleep(ms: number): Promise<void> {
|
|
87
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe('HostProcessOrchestrator node identity', () => {
|
|
91
|
+
it('passes --identity-protobuf pointing at a real key inside the node workdir', async () => {
|
|
92
|
+
const rootDir = join(tmpRoot, 'a');
|
|
93
|
+
const orch = makeOrchestrator(rootDir);
|
|
94
|
+
await orch.init();
|
|
95
|
+
|
|
96
|
+
await orch.createContainer({
|
|
97
|
+
containerId: 'donated-1',
|
|
98
|
+
partyId: 'foreign-P',
|
|
99
|
+
bootstrapNodes: [],
|
|
100
|
+
profile: 'storage',
|
|
101
|
+
pinnedOwnerKeys: ['key-a'],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const workdir = join(rootDir, 'donated-1');
|
|
105
|
+
const arg = await waitForFile(join(workdir, 'identity-arg.txt'));
|
|
106
|
+
expect(arg).toBe(join(workdir, 'identity.key'));
|
|
107
|
+
// Decodes as a libp2p protobuf private key → the child gets a usable identity.
|
|
108
|
+
expect(loadIdentity(arg).peerId).toMatch(/^12D3Koo/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('reuses the same identity when the same containerId is spawned again', async () => {
|
|
112
|
+
const rootDir = join(tmpRoot, 'b');
|
|
113
|
+
const orch = makeOrchestrator(rootDir);
|
|
114
|
+
await orch.init();
|
|
115
|
+
|
|
116
|
+
const first = await orch.createContainer({
|
|
117
|
+
containerId: 'donated-2',
|
|
118
|
+
partyId: 'foreign-P',
|
|
119
|
+
bootstrapNodes: [],
|
|
120
|
+
profile: 'storage',
|
|
121
|
+
pinnedOwnerKeys: ['key-a'],
|
|
122
|
+
});
|
|
123
|
+
const workdir = join(rootDir, 'donated-2');
|
|
124
|
+
await waitForFile(join(workdir, 'identity-arg.txt'));
|
|
125
|
+
const before = loadIdentity(join(workdir, 'identity.key')).peerId;
|
|
126
|
+
|
|
127
|
+
// Stop (not remove — removal deletes the workdir) and re-spawn, as a restart
|
|
128
|
+
// of a live loan would.
|
|
129
|
+
await orch.stopContainer(first.dockerId);
|
|
130
|
+
await orch.createContainer({
|
|
131
|
+
containerId: 'donated-2',
|
|
132
|
+
partyId: 'foreign-P',
|
|
133
|
+
bootstrapNodes: [],
|
|
134
|
+
profile: 'storage',
|
|
135
|
+
pinnedOwnerKeys: ['key-a'],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const after = loadIdentity(join(workdir, 'identity.key')).peerId;
|
|
139
|
+
expect(after).toBe(before);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('destroys the identity with the workdir when the loan is terminated', async () => {
|
|
143
|
+
const rootDir = join(tmpRoot, 'c');
|
|
144
|
+
const orch = makeOrchestrator(rootDir);
|
|
145
|
+
await orch.init();
|
|
146
|
+
|
|
147
|
+
const created = await orch.createContainer({
|
|
148
|
+
containerId: 'donated-3',
|
|
149
|
+
partyId: 'foreign-P',
|
|
150
|
+
bootstrapNodes: [],
|
|
151
|
+
profile: 'storage',
|
|
152
|
+
pinnedOwnerKeys: ['key-a'],
|
|
153
|
+
});
|
|
154
|
+
const identityPath = join(rootDir, 'donated-3', 'identity.key');
|
|
155
|
+
await waitForFile(identityPath);
|
|
156
|
+
|
|
157
|
+
await orch.removeContainer(created.dockerId);
|
|
158
|
+
expect(existsSync(identityPath)).toBe(false);
|
|
159
|
+
expect(existsSync(join(rootDir, 'donated-3'))).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// The failure the identity step can actually produce is a damaged key file,
|
|
163
|
+
// and `DonationService.provision` turns every such throw into an `error`
|
|
164
|
+
// record and lets the grantee retry. If the spawn path reserved its ports
|
|
165
|
+
// before the step that throws, each retry would burn four more ports out of a
|
|
166
|
+
// bounded range until provisioning stopped working altogether.
|
|
167
|
+
it('reserves no ports when the identity step fails', async () => {
|
|
168
|
+
const rootDir = join(tmpRoot, 'd');
|
|
169
|
+
// Exactly one node's worth of ports (health, metrics, p2p, admin).
|
|
170
|
+
const orch = makeOrchestrator(rootDir, { start: 18100, end: 18103 });
|
|
171
|
+
await orch.init();
|
|
172
|
+
|
|
173
|
+
mkdirSync(join(rootDir, 'donated-bad'), { recursive: true });
|
|
174
|
+
writeFileSync(join(rootDir, 'donated-bad', 'identity.key'), 'not a protobuf private key', 'utf8');
|
|
175
|
+
|
|
176
|
+
const request = {
|
|
177
|
+
partyId: 'foreign-P',
|
|
178
|
+
bootstrapNodes: [],
|
|
179
|
+
profile: 'storage' as const,
|
|
180
|
+
pinnedOwnerKeys: ['key-a'],
|
|
181
|
+
};
|
|
182
|
+
await expect(orch.createContainer({ ...request, containerId: 'donated-bad' })).rejects.toThrow();
|
|
183
|
+
|
|
184
|
+
// The whole range is still free, so a healthy container still gets its four.
|
|
185
|
+
const ok = await orch.createContainer({ ...request, containerId: 'donated-good' });
|
|
186
|
+
expect(ok.p2pPort).toBeGreaterThanOrEqual(18100);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// The owner node carries the HOST's installer identity from <dataDir>, not a
|
|
190
|
+
// per-workdir key — a second key in its workdir would mean the host's own
|
|
191
|
+
// cadre node quietly changed peer id.
|
|
192
|
+
it('leaves the owner node on its own installer identity', async () => {
|
|
193
|
+
const rootDir = join(tmpRoot, 'e');
|
|
194
|
+
const orch = makeOrchestrator(rootDir);
|
|
195
|
+
await orch.init();
|
|
196
|
+
|
|
197
|
+
const hostIdentity = join(tmpRoot, 'host-identity.key');
|
|
198
|
+
await orch.ensureOwnerNode({ identityPath: hostIdentity, partyId: 'host-P', libp2pPort: 4101 });
|
|
199
|
+
|
|
200
|
+
const ownerWorkdir = join(rootDir, OWNER_CONTAINER_ID);
|
|
201
|
+
expect(await waitForFile(join(ownerWorkdir, 'identity-arg.txt'))).toBe(hostIdentity);
|
|
202
|
+
expect(existsSync(join(ownerWorkdir, 'identity.key'))).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|