@cldmv/gh-broker 0.7.0 → 0.9.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 +165 -41
- package/config.example.json +38 -9
- package/config.sample.json +45 -0
- package/deploy/README.md +76 -17
- package/deploy/install.mjs +171 -104
- package/deploy/install.sh +0 -0
- package/deploy/lib/config-merge.mjs +174 -16
- package/deploy/lib/credentials.mjs +143 -0
- package/deploy/lib/install-args.mjs +144 -0
- package/deploy/lib/npm-setup.mjs +164 -0
- package/deploy/lib/secret-store.mjs +20 -2
- package/deploy/lib/system.mjs +26 -2
- package/dist/index.mjs +5864 -1809
- package/dist/index.mjs.map +4 -4
- package/package.json +18 -8
- package/.configs/fix-headers.json +0 -3
- package/.configs/vitest.config.mjs +0 -44
- package/.github/dependabot.yml +0 -111
- package/.github/workflows/branch-retention.yml +0 -44
- package/.github/workflows/bundle-size.yml +0 -52
- package/.github/workflows/ci.yml +0 -389
- package/.github/workflows/cla.yml +0 -66
- package/.github/workflows/codeql.yml +0 -87
- package/.github/workflows/dependabot-auto-merge.yml +0 -62
- package/.github/workflows/dependabot-recreate.yml +0 -67
- package/.github/workflows/dependency-review.yml +0 -46
- package/.github/workflows/feature-pr.yml +0 -75
- package/.github/workflows/hotfix-redirector.yml +0 -67
- package/.github/workflows/hotfixes-release.yml +0 -70
- package/.github/workflows/labeler.yml +0 -48
- package/.github/workflows/master-commit-audit.yml +0 -56
- package/.github/workflows/member-auto-merge.yml +0 -75
- package/.github/workflows/next-release.yml +0 -76
- package/.github/workflows/next-reset.yml +0 -50
- package/.github/workflows/pr-notify.yml +0 -44
- package/.github/workflows/pr-title-normalizer.yml +0 -52
- package/.github/workflows/provenance.yml +0 -51
- package/.github/workflows/publish.yml +0 -146
- package/.github/workflows/release-merge.yml +0 -104
- package/.github/workflows/release-notify.yml +0 -41
- package/.github/workflows/scorecard.yml +0 -64
- package/.github/workflows/stale.yml +0 -50
- package/.github/workflows/tag-health.yml +0 -70
- package/.github/workflows/update-major-version-tags.yml +0 -94
- package/.github/workflows/v4-bootstrap.yml +0 -115
- package/.github/workflows/welcome.yml +0 -42
- package/docs/TOOLS.md +0 -347
- package/docs/agent-user-model.md +0 -89
- package/docs/changelog/v0/v0.2.0.md +0 -26
- package/docs/changelog/v0/v0.2.1.md +0 -23
- package/docs/changelog/v0/v0.2.2.md +0 -23
- package/docs/changelog/v0/v0.2.3.md +0 -33
- package/docs/changelog/v0/v0.2.4.md +0 -25
- package/docs/changelog/v0/v0.2.5.md +0 -29
- package/docs/changelog/v0/v0.3.0.md +0 -84
- package/docs/changelog/v0/v0.3.1.md +0 -30
- package/docs/changelog/v0/v0.3.2.md +0 -24
- package/docs/changelog/v0/v0.3.3.md +0 -28
- package/docs/changelog/v0/v0.4.0.md +0 -58
- package/docs/changelog/v0/v0.4.1.md +0 -33
- package/docs/changelog/v0/v0.4.2.md +0 -45
- package/docs/changelog/v0/v0.5.0.md +0 -39
- package/docs/changelog/v0/v0.5.1.md +0 -28
- package/docs/changelog/v0/v0.6.0.md +0 -52
- package/docs/changelog/v0/v0.6.1.md +0 -48
- package/docs/changelog/v0/v0.7.0.md +0 -71
- package/docs/deploy-architecture.md +0 -82
- package/src/appauth.ts +0 -56
- package/src/audit.ts +0 -34
- package/src/catalog.ts +0 -165
- package/src/config.ts +0 -130
- package/src/index.ts +0 -107
- package/src/paths.ts +0 -60
- package/src/router.ts +0 -258
- package/src/secrets.ts +0 -137
- package/src/server.ts +0 -91
- package/src/types.ts +0 -104
- package/src/upstream.ts +0 -85
- package/src/verbs/issue_labels.ts +0 -329
- package/src/verbs/link_issue.ts +0 -84
- package/src/verbs/release-analysis.ts +0 -210
- package/src/verbs/repo-tier.ts +0 -90
- package/src/verbs/report_ci_status.ts +0 -358
- package/src/verbs/report_release_prs.ts +0 -238
- package/src/verbs/report_security_alerts.ts +0 -442
- package/src/verbs/report_unlabeled.ts +0 -194
- package/src/verbs/rest-client.ts +0 -181
- package/src/verbs/review_thread.ts +0 -138
- package/tests/appauth.test.vitest.mts +0 -76
- package/tests/audit.test.vitest.mts +0 -48
- package/tests/catalog.test.vitest.mts +0 -180
- package/tests/config-merge.test.vitest.mts +0 -58
- package/tests/config.test.vitest.mts +0 -314
- package/tests/issue-labels.test.vitest.mts +0 -347
- package/tests/paths.test.vitest.mts +0 -52
- package/tests/release-prs.test.vitest.mts +0 -237
- package/tests/rest-client.test.vitest.mts +0 -115
- package/tests/router.test.vitest.mts +0 -399
- package/tests/run-vitest.mjs +0 -51
- package/tests/secret-store.test.vitest.mts +0 -106
- package/tests/secrets.test.vitest.mts +0 -155
- package/tests/security-alerts.test.vitest.mts +0 -328
- package/tests/server.test.vitest.mts +0 -20
- package/tests/upstream.test.vitest.mts +0 -91
- package/tests/verb-helpers.test.vitest.mts +0 -331
- package/tests/verbs.test.vitest.mts +0 -624
- package/tsconfig.json +0 -21
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { mergeConfig } from "../deploy/lib/config-merge.mjs";
|
|
3
|
-
|
|
4
|
-
const example = {
|
|
5
|
-
listen: "unix:/run/gh-broker/gh-broker.sock",
|
|
6
|
-
upstream: "https://api.githubcopilot.com/mcp/x/all",
|
|
7
|
-
tools: [
|
|
8
|
-
{ name: "get_me", mode: "read" },
|
|
9
|
-
{ name: "list_issues", mode: "read" }
|
|
10
|
-
],
|
|
11
|
-
owners: { cldmv: { read: "cred:cldmv-read", write: "cred:cldmv-write" } },
|
|
12
|
-
defaultOwner: "cldmv"
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
describe("mergeConfig", () => {
|
|
16
|
-
it("uses the example as-is on first install (no current)", () => {
|
|
17
|
-
expect(mergeConfig(example, null)).toEqual(example);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("takes tools from the example so allowlist removals/tightenings apply", () => {
|
|
21
|
-
const current = { ...example, tools: [{ name: "get_me", mode: "read" }, { name: "removed_tool", mode: "write" }] };
|
|
22
|
-
const merged = mergeConfig(example, current);
|
|
23
|
-
expect(merged.tools).toEqual(example.tools);
|
|
24
|
-
expect(merged.tools.find((t: { name: string }) => t.name === "removed_tool")).toBeUndefined();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("preserves current owner data and adds example-only owners on upgrade", () => {
|
|
28
|
-
const current = { ...example, owners: { cldmv: { read: "cred:cldmv-read", write: "cred:cldmv-write", installationId: 123 } } };
|
|
29
|
-
const example2 = {
|
|
30
|
-
...example,
|
|
31
|
-
owners: {
|
|
32
|
-
cldmv: { read: "cred:cldmv-read", write: "cred:cldmv-write" },
|
|
33
|
-
acme: { read: "cred:acme-read", write: "cred:acme-write" }
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const merged = mergeConfig(example2, current);
|
|
37
|
-
expect(merged.owners.cldmv.installationId).toBe(123); // current wins
|
|
38
|
-
expect(merged.owners.acme).toBeDefined(); // new example owner appears
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("lets current win for listen/upstream/defaultOwner/app/secrets", () => {
|
|
42
|
-
const current = { ...example, listen: "unix:/tmp/custom.sock", app: { appId: 1, privateKey: "cred:k" } };
|
|
43
|
-
const merged = mergeConfig(example, current);
|
|
44
|
-
expect(merged.listen).toBe("unix:/tmp/custom.sock");
|
|
45
|
-
expect(merged.app).toEqual({ appId: 1, privateKey: "cred:k" });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("does not mutate its inputs", () => {
|
|
49
|
-
const ex = structuredClone(example);
|
|
50
|
-
mergeConfig(ex, { ...example, listen: "unix:/z" });
|
|
51
|
-
expect(ex).toEqual(example);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("rejects an array as example or current (typeof reports 'object')", () => {
|
|
55
|
-
expect(() => mergeConfig([], null)).toThrow(/example config must be an object/);
|
|
56
|
-
expect(() => mergeConfig(example, [])).toThrow(/current config must be an object/);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { loadConfig, indexTools } from "../src/config.ts";
|
|
5
|
-
|
|
6
|
-
// loadConfig reads a real file off disk, so we materialize each fixture into the
|
|
7
|
-
// repo's gitignored tmp/ and pass the path.
|
|
8
|
-
const DIR = join(process.cwd(), "tmp", "config-tests");
|
|
9
|
-
let counter = 0;
|
|
10
|
-
|
|
11
|
-
/** Write `contents` (string or object) to a unique tmp file and return its path. */
|
|
12
|
-
function writeConfig(contents: unknown): string {
|
|
13
|
-
const path = join(DIR, `cfg-${counter++}.json`);
|
|
14
|
-
writeFileSync(path, typeof contents === "string" ? contents : JSON.stringify(contents));
|
|
15
|
-
return path;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function baseConfig(): Record<string, unknown> {
|
|
19
|
-
return {
|
|
20
|
-
listen: "unix:/run/gh-broker/gh-broker.sock",
|
|
21
|
-
upstream: "https://api.githubcopilot.com/mcp/",
|
|
22
|
-
tools: [{ name: "get_me", mode: "read" }],
|
|
23
|
-
owners: { cldmv: { read: "cred:cldmv-read", write: "cred:cldmv-write" } }
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
beforeAll(() => {
|
|
28
|
-
mkdirSync(DIR, { recursive: true });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
afterAll(() => {
|
|
32
|
-
rmSync(DIR, { recursive: true, force: true });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("loadConfig — file/parse errors", () => {
|
|
36
|
-
it("throws when the file cannot be read", () => {
|
|
37
|
-
expect(() => loadConfig(join(DIR, "does-not-exist.json"))).toThrow(/cannot read\/parse/);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("throws on malformed JSON", () => {
|
|
41
|
-
const p = writeConfig("{ not json ");
|
|
42
|
-
expect(() => loadConfig(p)).toThrow(/cannot read\/parse/);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("throws when the root is not an object", () => {
|
|
46
|
-
const p = writeConfig("123");
|
|
47
|
-
expect(() => loadConfig(p)).toThrow(/root must be an object/);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("throws when the root is JSON null", () => {
|
|
51
|
-
const p = writeConfig("null");
|
|
52
|
-
expect(() => loadConfig(p)).toThrow(/root must be an object/);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("loadConfig — listen / upstream", () => {
|
|
57
|
-
it("rejects a non-string listen", () => {
|
|
58
|
-
const c = baseConfig();
|
|
59
|
-
delete c.listen;
|
|
60
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.listen/);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("rejects a listen without the unix: prefix", () => {
|
|
64
|
-
const c = baseConfig();
|
|
65
|
-
c.listen = "tcp:0.0.0.0:9000";
|
|
66
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.listen/);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("rejects a non-http upstream", () => {
|
|
70
|
-
const c = baseConfig();
|
|
71
|
-
c.upstream = "ftp://example.com";
|
|
72
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.upstream/);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("rejects a non-string upstream", () => {
|
|
76
|
-
const c = baseConfig();
|
|
77
|
-
c.upstream = 42;
|
|
78
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.upstream/);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("loadConfig — tools", () => {
|
|
83
|
-
it("rejects a missing tools array", () => {
|
|
84
|
-
const c = baseConfig();
|
|
85
|
-
delete c.tools;
|
|
86
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.tools: must be a non-empty array/);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("rejects an empty tools array", () => {
|
|
90
|
-
const c = baseConfig();
|
|
91
|
-
c.tools = [];
|
|
92
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.tools: must be a non-empty array/);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("rejects a tool with no name", () => {
|
|
96
|
-
const c = baseConfig();
|
|
97
|
-
c.tools = [{ mode: "read" }];
|
|
98
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.tools\[0\]\.name: required/);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("rejects a tool with a bad mode", () => {
|
|
102
|
-
const c = baseConfig();
|
|
103
|
-
c.tools = [{ name: "get_me", mode: "admin" }];
|
|
104
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.tools\[0\]\.mode: must be read\|write/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("rejects duplicate tool names", () => {
|
|
108
|
-
const c = baseConfig();
|
|
109
|
-
c.tools = [
|
|
110
|
-
{ name: "get_me", mode: "read" },
|
|
111
|
-
{ name: "get_me", mode: "write" }
|
|
112
|
-
];
|
|
113
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/duplicate tool "get_me"/);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("carries the custom flag through, defaulting to false", () => {
|
|
117
|
-
const c = baseConfig();
|
|
118
|
-
c.tools = [
|
|
119
|
-
{ name: "get_me", mode: "read" },
|
|
120
|
-
{ name: "link_issue", mode: "write", custom: true }
|
|
121
|
-
];
|
|
122
|
-
const out = loadConfig(writeConfig(c));
|
|
123
|
-
expect(out.tools).toEqual([
|
|
124
|
-
{ name: "get_me", mode: "read", custom: false, augment: false },
|
|
125
|
-
{ name: "link_issue", mode: "write", custom: true, augment: false }
|
|
126
|
-
]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("carries the augment flag through, defaulting to false", () => {
|
|
130
|
-
const c = baseConfig();
|
|
131
|
-
c.tools = [
|
|
132
|
-
{ name: "get_me", mode: "read" },
|
|
133
|
-
{ name: "pull_request_read", mode: "read", augment: true }
|
|
134
|
-
];
|
|
135
|
-
const out = loadConfig(writeConfig(c));
|
|
136
|
-
expect(out.tools).toEqual([
|
|
137
|
-
{ name: "get_me", mode: "read", custom: false, augment: false },
|
|
138
|
-
{ name: "pull_request_read", mode: "read", custom: false, augment: true }
|
|
139
|
-
]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("rejects a tool marked both custom and augment", () => {
|
|
143
|
-
const c = baseConfig();
|
|
144
|
-
c.tools = [{ name: "weird", mode: "read", custom: true, augment: true }];
|
|
145
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/custom and augment are mutually exclusive/);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("loadConfig — owners", () => {
|
|
150
|
-
it("rejects a non-object owners", () => {
|
|
151
|
-
const c = baseConfig();
|
|
152
|
-
c.owners = "cldmv";
|
|
153
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.owners: must be an object/);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("rejects an owner missing read/write", () => {
|
|
157
|
-
const c = baseConfig();
|
|
158
|
-
c.owners = { cldmv: { read: "cred:x" } };
|
|
159
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.owners\.cldmv: needs "read" and "write"/);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("rejects a non-numeric installationId", () => {
|
|
163
|
-
const c = baseConfig();
|
|
164
|
-
c.owners = { cldmv: { read: "cred:r", write: "cred:w", installationId: "42" } };
|
|
165
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.owners\.cldmv\.installationId: must be a number/);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("rejects an empty owners object", () => {
|
|
169
|
-
const c = baseConfig();
|
|
170
|
-
c.owners = {};
|
|
171
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/at least one owner required/);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("lowercases owner keys and carries installationId", () => {
|
|
175
|
-
const c = baseConfig();
|
|
176
|
-
c.owners = { CLDMV: { read: "cred:r", write: "cred:w", installationId: 99 } };
|
|
177
|
-
const out = loadConfig(writeConfig(c));
|
|
178
|
-
expect(out.owners).toEqual({ cldmv: { read: "cred:r", write: "cred:w", installationId: 99 } });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("omits installationId when absent", () => {
|
|
182
|
-
const out = loadConfig(writeConfig(baseConfig()));
|
|
183
|
-
expect(out.owners.cldmv).toEqual({ read: "cred:cldmv-read", write: "cred:cldmv-write" });
|
|
184
|
-
expect(out.owners.cldmv).not.toHaveProperty("installationId");
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe("loadConfig — defaultOwner", () => {
|
|
189
|
-
it("is undefined when omitted", () => {
|
|
190
|
-
const out = loadConfig(writeConfig(baseConfig()));
|
|
191
|
-
expect(out.defaultOwner).toBeUndefined();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("lowercases a valid defaultOwner and carries it through", () => {
|
|
195
|
-
const c = baseConfig();
|
|
196
|
-
c.defaultOwner = "CLDMV";
|
|
197
|
-
const out = loadConfig(writeConfig(c));
|
|
198
|
-
expect(out.defaultOwner).toBe("cldmv");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("rejects a non-string / empty defaultOwner", () => {
|
|
202
|
-
const c = baseConfig();
|
|
203
|
-
c.defaultOwner = "";
|
|
204
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.defaultOwner: must be a non-empty string/);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("rejects a defaultOwner that is not a configured owner", () => {
|
|
208
|
-
const c = baseConfig();
|
|
209
|
-
c.defaultOwner = "nobody";
|
|
210
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.defaultOwner "nobody" is not a key of config\.owners/);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe("loadConfig — app", () => {
|
|
215
|
-
it("rejects a non-numeric appId", () => {
|
|
216
|
-
const c = baseConfig();
|
|
217
|
-
c.app = { appId: "1", privateKey: "cred:app" };
|
|
218
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.app\.appId: must be a number/);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("rejects a non-string privateKey", () => {
|
|
222
|
-
const c = baseConfig();
|
|
223
|
-
c.app = { appId: 1, privateKey: 5 };
|
|
224
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.app\.privateKey/);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("carries a valid app config through", () => {
|
|
228
|
-
const c = baseConfig();
|
|
229
|
-
c.app = { appId: 12345, privateKey: "cred:gh-app-key" };
|
|
230
|
-
const out = loadConfig(writeConfig(c));
|
|
231
|
-
expect(out.app).toEqual({ appId: 12345, privateKey: "cred:gh-app-key" });
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("omits app when absent", () => {
|
|
235
|
-
const out = loadConfig(writeConfig(baseConfig()));
|
|
236
|
-
expect(out.app).toBeUndefined();
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe("loadConfig — secrets", () => {
|
|
241
|
-
it("rejects an array (which typeof reports as 'object')", () => {
|
|
242
|
-
const c = baseConfig();
|
|
243
|
-
c.secrets = [];
|
|
244
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets: must be an object/);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("rejects null", () => {
|
|
248
|
-
const c = baseConfig();
|
|
249
|
-
c.secrets = null;
|
|
250
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets: must be an object/);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("rejects an unknown provider", () => {
|
|
254
|
-
const c = baseConfig();
|
|
255
|
-
c.secrets = { provider: "vault" };
|
|
256
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets\.provider/);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("carries a valid secrets block through", () => {
|
|
260
|
-
const c = baseConfig();
|
|
261
|
-
c.secrets = { provider: "file", path: "/etc/gh-broker/secrets.json" };
|
|
262
|
-
const out = loadConfig(writeConfig(c));
|
|
263
|
-
expect(out.secrets).toEqual({ provider: "file", path: "/etc/gh-broker/secrets.json" });
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("rejects a path without the file provider (omitted → systemd-creds default)", () => {
|
|
267
|
-
const c = baseConfig();
|
|
268
|
-
c.secrets = { path: "/etc/gh-broker/secrets.json" };
|
|
269
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets\.path is only used by the "file" provider/);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("rejects a path with the systemd-creds provider", () => {
|
|
273
|
-
const c = baseConfig();
|
|
274
|
-
c.secrets = { provider: "systemd-creds", path: "/etc/gh-broker/secrets.json" };
|
|
275
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets\.path is only used by the "file" provider/);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("rejects an empty path (would be silently ignored by resolveSecretsPath)", () => {
|
|
279
|
-
const c = baseConfig();
|
|
280
|
-
c.secrets = { provider: "file", path: "" };
|
|
281
|
-
expect(() => loadConfig(writeConfig(c))).toThrow(/config\.secrets\.path: must be a non-empty string/);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
describe("loadConfig — full valid config", () => {
|
|
286
|
-
it("returns the normalized shape", () => {
|
|
287
|
-
const c = baseConfig();
|
|
288
|
-
c.defaultOwner = "cldmv";
|
|
289
|
-
c.app = { appId: 7, privateKey: "cred:app" };
|
|
290
|
-
const out = loadConfig(writeConfig(c));
|
|
291
|
-
expect(out).toEqual({
|
|
292
|
-
listen: "unix:/run/gh-broker/gh-broker.sock",
|
|
293
|
-
upstream: "https://api.githubcopilot.com/mcp/",
|
|
294
|
-
tools: [{ name: "get_me", mode: "read", custom: false, augment: false }],
|
|
295
|
-
owners: { cldmv: { read: "cred:cldmv-read", write: "cred:cldmv-write" } },
|
|
296
|
-
app: { appId: 7, privateKey: "cred:app" },
|
|
297
|
-
defaultOwner: "cldmv"
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe("indexTools", () => {
|
|
303
|
-
it("builds a name→rule map", () => {
|
|
304
|
-
const rules = [
|
|
305
|
-
{ name: "get_me", mode: "read" as const },
|
|
306
|
-
{ name: "create_pull_request", mode: "write" as const, custom: false }
|
|
307
|
-
];
|
|
308
|
-
const map = indexTools(rules);
|
|
309
|
-
expect(map.size).toBe(2);
|
|
310
|
-
expect(map.get("get_me")).toEqual({ name: "get_me", mode: "read" });
|
|
311
|
-
expect(map.get("create_pull_request")).toEqual({ name: "create_pull_request", mode: "write", custom: false });
|
|
312
|
-
expect(map.get("missing")).toBeUndefined();
|
|
313
|
-
});
|
|
314
|
-
});
|