@openparachute/hub 0.6.3 → 0.6.4-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- package/src/__tests__/account-home-ui.test.ts +344 -110
- package/src/__tests__/account-mirror.test.ts +156 -0
- package/src/__tests__/account-setup.test.ts +880 -0
- package/src/__tests__/account-usage.test.ts +137 -0
- package/src/__tests__/account-vault-admin-token.test.ts +301 -0
- package/src/__tests__/account-vault-token.test.ts +53 -1
- package/src/__tests__/admin-vault-admin-token.test.ts +17 -0
- package/src/__tests__/admin-vaults.test.ts +20 -0
- package/src/__tests__/api-account.test.ts +236 -4
- package/src/__tests__/api-invites.test.ts +217 -0
- package/src/__tests__/api-mint-token.test.ts +259 -10
- package/src/__tests__/api-modules-ops.test.ts +195 -3
- package/src/__tests__/api-modules.test.ts +40 -4
- package/src/__tests__/api-settings-hub-origin.test.ts +13 -8
- package/src/__tests__/auto-wire.test.ts +101 -1
- package/src/__tests__/cli.test.ts +188 -2
- package/src/__tests__/cloudflare-state.test.ts +104 -0
- package/src/__tests__/expose-2fa-warning.test.ts +11 -8
- package/src/__tests__/expose-cloudflare.test.ts +135 -9
- package/src/__tests__/expose-interactive.test.ts +234 -7
- package/src/__tests__/expose-supervisor-version.test.ts +104 -0
- package/src/__tests__/expose.test.ts +10 -5
- package/src/__tests__/grants.test.ts +197 -8
- package/src/__tests__/hub-origin-resolution.test.ts +179 -25
- package/src/__tests__/hub-server.test.ts +761 -13
- package/src/__tests__/hub-unit.test.ts +185 -0
- package/src/__tests__/init.test.ts +579 -3
- package/src/__tests__/install.test.ts +448 -2
- package/src/__tests__/invites.test.ts +220 -0
- package/src/__tests__/launchctl-guard.test.ts +185 -0
- package/src/__tests__/migrate-cutover.test.ts +33 -0
- package/src/__tests__/module-ops-client.test.ts +68 -0
- package/src/__tests__/scope-explanations.test.ts +16 -0
- package/src/__tests__/serve-boot.test.ts +74 -1
- package/src/__tests__/serve.test.ts +121 -7
- package/src/__tests__/setup-wizard.test.ts +110 -0
- package/src/__tests__/spawn-path.test.ts +191 -0
- package/src/__tests__/status.test.ts +64 -0
- package/src/__tests__/supervisor.test.ts +374 -0
- package/src/__tests__/users.test.ts +66 -0
- package/src/__tests__/well-known.test.ts +25 -0
- package/src/__tests__/wizard.test.ts +72 -1
- package/src/account-home-ui.ts +481 -235
- package/src/account-mirror.ts +126 -0
- package/src/account-setup.ts +381 -0
- package/src/account-usage.ts +118 -0
- package/src/account-vault-admin-token.ts +242 -0
- package/src/account-vault-token.ts +36 -2
- package/src/admin-login-ui.ts +121 -0
- package/src/admin-vault-admin-token.ts +8 -2
- package/src/admin-vaults.ts +137 -29
- package/src/api-account.ts +118 -1
- package/src/api-invites.ts +345 -0
- package/src/api-mint-token.ts +81 -0
- package/src/api-modules-ops.ts +168 -53
- package/src/api-modules.ts +36 -0
- package/src/auto-wire.ts +87 -0
- package/src/cli.ts +128 -34
- package/src/cloudflare/detect.ts +1 -1
- package/src/cloudflare/state.ts +104 -8
- package/src/commands/expose-2fa-warning.ts +17 -13
- package/src/commands/expose-cloudflare.ts +103 -36
- package/src/commands/expose-interactive.ts +163 -17
- package/src/commands/expose-supervisor.ts +45 -0
- package/src/commands/init.ts +183 -4
- package/src/commands/install.ts +321 -3
- package/src/commands/migrate-cutover.ts +12 -5
- package/src/commands/serve-boot.ts +33 -3
- package/src/commands/serve.ts +158 -37
- package/src/commands/status.ts +9 -1
- package/src/commands/wizard.ts +36 -2
- package/src/grants.ts +113 -0
- package/src/help.ts +18 -5
- package/src/hub-db.ts +70 -2
- package/src/hub-server.ts +438 -41
- package/src/hub-settings.ts +3 -3
- package/src/hub-unit.ts +259 -9
- package/src/invites.ts +291 -0
- package/src/launchctl-guard.ts +131 -0
- package/src/managed-unit.ts +13 -3
- package/src/migrate-offer.ts +15 -6
- package/src/module-ops-client.ts +47 -22
- package/src/scope-attenuation.ts +19 -0
- package/src/scope-explanations.ts +9 -1
- package/src/service-spec.ts +17 -4
- package/src/setup-wizard.ts +34 -2
- package/src/spawn-path.ts +148 -0
- package/src/supervisor.ts +232 -7
- package/src/users.ts +54 -8
- package/src/vault-hub-origin-env.ts +28 -0
- package/src/vault-name.ts +13 -1
- package/src/well-known.ts +13 -0
- package/web/ui/dist/assets/{index-mz8XcVPP.css → index-BYYUeLGA.css} +1 -1
- package/web/ui/dist/assets/index-D3cDUOOj.js +61 -0
- package/web/ui/dist/index.html +2 -2
- package/web/ui/dist/assets/index-D_0TRjeo.js +0 -61
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the `/account/` per-vault backup (mirror) status fetch +
|
|
3
|
+
* formatting (`account-mirror.ts`). The fetch mints an admin-scoped token + hits
|
|
4
|
+
* the vault's loopback `/.parachute/mirror` endpoint; it must be fault-tolerant
|
|
5
|
+
* (any failure → null) and shape-strict (a malformed body → null, not a render
|
|
6
|
+
* of `undefined`). Mirrors `account-usage.test.ts`'s posture.
|
|
7
|
+
*/
|
|
8
|
+
import type { Database } from "bun:sqlite";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import {
|
|
14
|
+
type VaultMirrorStat,
|
|
15
|
+
fetchVaultMirrorStatus,
|
|
16
|
+
formatMirrorLine,
|
|
17
|
+
} from "../account-mirror.ts";
|
|
18
|
+
import { hubDbPath, openHubDb } from "../hub-db.ts";
|
|
19
|
+
|
|
20
|
+
interface Harness {
|
|
21
|
+
db: Database;
|
|
22
|
+
cleanup: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeHarness(): Harness {
|
|
26
|
+
const dir = mkdtempSync(join(tmpdir(), "phub-account-mirror-"));
|
|
27
|
+
const db = openHubDb(hubDbPath(dir));
|
|
28
|
+
return {
|
|
29
|
+
db,
|
|
30
|
+
cleanup: () => {
|
|
31
|
+
db.close();
|
|
32
|
+
rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let harness: Harness;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
harness = makeHarness();
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
harness.cleanup();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** A stub signer — no real key needed; the fetch only carries the token string. */
|
|
46
|
+
const stubSign = async () => ({
|
|
47
|
+
token: "stub.jwt.token",
|
|
48
|
+
jti: "jti-1",
|
|
49
|
+
expiresAt: new Date(Date.now() + 60000).toISOString(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function baseDeps(fetchImpl: typeof fetch) {
|
|
53
|
+
return {
|
|
54
|
+
db: harness.db,
|
|
55
|
+
hubOrigin: "https://hub.test",
|
|
56
|
+
vaultPort: 1940,
|
|
57
|
+
userId: "user-1",
|
|
58
|
+
fetchImpl,
|
|
59
|
+
signToken: stubSign as never,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("fetchVaultMirrorStatus", () => {
|
|
64
|
+
test("returns enabled+not-pushing on a backed-up, local-only config", async () => {
|
|
65
|
+
const fetchImpl = (async () =>
|
|
66
|
+
new Response(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
config: { enabled: true, location: "internal", auto_push: false },
|
|
69
|
+
status: { enabled: true, last_commit_sha: "abc", last_error: null },
|
|
70
|
+
}),
|
|
71
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
72
|
+
)) as unknown as typeof fetch;
|
|
73
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
74
|
+
expect(stat).toEqual({ enabled: true, backedUpToRemote: false });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("flags pushing when auto_push is configured", async () => {
|
|
78
|
+
const fetchImpl = (async () =>
|
|
79
|
+
new Response(
|
|
80
|
+
JSON.stringify({ config: { enabled: true, location: "internal", auto_push: true } }),
|
|
81
|
+
{ status: 200 },
|
|
82
|
+
)) as unknown as typeof fetch;
|
|
83
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
84
|
+
expect(stat).toEqual({ enabled: true, backedUpToRemote: true });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("returns enabled:false when backup is off", async () => {
|
|
88
|
+
const fetchImpl = (async () =>
|
|
89
|
+
new Response(JSON.stringify({ config: { enabled: false, auto_push: false } }), {
|
|
90
|
+
status: 200,
|
|
91
|
+
})) as unknown as typeof fetch;
|
|
92
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
93
|
+
expect(stat).toEqual({ enabled: false, backedUpToRemote: false });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("mints an ADMIN-scoped Bearer + hits the vault's loopback mirror endpoint", async () => {
|
|
97
|
+
let seenUrl = "";
|
|
98
|
+
let seenAuth = "";
|
|
99
|
+
let seenScope: string[] = [];
|
|
100
|
+
const captureSign = (async (_db: unknown, opts: { scopes: string[] }) => {
|
|
101
|
+
seenScope = opts.scopes;
|
|
102
|
+
return { token: "stub.jwt.token", jti: "j", expiresAt: new Date().toISOString() };
|
|
103
|
+
}) as never;
|
|
104
|
+
const fetchImpl = (async (url: string, init?: RequestInit) => {
|
|
105
|
+
seenUrl = url;
|
|
106
|
+
seenAuth = (init?.headers as Record<string, string>)?.authorization ?? "";
|
|
107
|
+
return new Response(JSON.stringify({ config: { enabled: true, auto_push: false } }), {
|
|
108
|
+
status: 200,
|
|
109
|
+
});
|
|
110
|
+
}) as unknown as typeof fetch;
|
|
111
|
+
await fetchVaultMirrorStatus("work", { ...baseDeps(fetchImpl), signToken: captureSign });
|
|
112
|
+
expect(seenUrl).toBe("http://127.0.0.1:1940/vault/work/.parachute/mirror");
|
|
113
|
+
expect(seenAuth).toBe("Bearer stub.jwt.token");
|
|
114
|
+
expect(seenScope).toEqual(["vault:work:admin"]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("returns null on a non-2xx response (vault down / 403 / 404)", async () => {
|
|
118
|
+
for (const status of [403, 404, 500]) {
|
|
119
|
+
const fetchImpl = (async () => new Response("nope", { status })) as unknown as typeof fetch;
|
|
120
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
121
|
+
expect(stat).toBeNull();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("returns null when the body is malformed (missing config.enabled)", async () => {
|
|
126
|
+
const fetchImpl = (async () =>
|
|
127
|
+
new Response(JSON.stringify({ config: {} }), { status: 200 })) as unknown as typeof fetch;
|
|
128
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
129
|
+
expect(stat).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("returns null when fetch throws (network error)", async () => {
|
|
133
|
+
const fetchImpl = (async () => {
|
|
134
|
+
throw new Error("ECONNREFUSED");
|
|
135
|
+
}) as unknown as typeof fetch;
|
|
136
|
+
const stat = await fetchVaultMirrorStatus("work", baseDeps(fetchImpl));
|
|
137
|
+
expect(stat).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("formatMirrorLine", () => {
|
|
142
|
+
test("warm plain-language line; GitHub variant when pushing", () => {
|
|
143
|
+
expect(formatMirrorLine({ enabled: true, backedUpToRemote: false } as VaultMirrorStat)).toBe(
|
|
144
|
+
"Backed up — full version history",
|
|
145
|
+
);
|
|
146
|
+
expect(formatMirrorLine({ enabled: true, backedUpToRemote: true } as VaultMirrorStat)).toBe(
|
|
147
|
+
"Backed up — version history + GitHub",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("returns null when backup is off (the tile omits the line, never nags)", () => {
|
|
152
|
+
expect(
|
|
153
|
+
formatMirrorLine({ enabled: false, backedUpToRemote: false } as VaultMirrorStat),
|
|
154
|
+
).toBeNull();
|
|
155
|
+
});
|
|
156
|
+
});
|