Repository navigation
Expand file tree
/
Copy pathapi-usage-policy.test.ts
More file actions
62 lines (51 loc) · 2.05 KB
/
Copy pathapi-usage-policy.test.ts
File metadata and controls
62 lines (51 loc) · 2.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
const productionSources = [
"src/github/app-auth.ts",
"src/github/client.ts",
"src/github/installations.ts",
"src/badges/community-health.ts",
"src/badges/default-branch-rules.ts",
"src/badges/immutable-releases.ts",
"src/badges/secret-protection.ts",
"src/badges/sha-pinning-required.ts",
"src/badges/web-commit-signoff.ts"
];
const allowedRoutes = new Set([
"GET /repos/{owner}/{repo}",
"GET /repos/{owner}/{repo}/actions/permissions",
"GET /repos/{owner}/{repo}/community/profile",
"GET /repos/{owner}/{repo}/immutable-releases",
"GET /repos/{owner}/{repo}/rules/branches/{branch}",
"GET /repos/{owner}/{repo}/installation"
]);
describe("GitHub API usage policy", () => {
it("keeps production REST routes on a small audited GET allowlist", async () => {
const routes = await routeTemplatesFromProductionSources();
expect(routes).toEqual([...allowedRoutes].sort());
});
it("does not introduce high-risk GitHub API patterns", async () => {
const source = await productionSourceText();
expect(source).not.toMatch(/\b(?:POST|PUT|PATCH|DELETE) \//);
expect(source).not.toMatch(/paginate\b|\.iterator\b/);
expect(source).not.toMatch(/\/user\/repos|\/orgs\/\{org\}\/repos/);
expect(source).not.toMatch(/\/installation\/repositories/);
expect(source).not.toMatch(/\/search\//);
expect(source).not.toMatch(/\/graphql\b|graphql\s*\(/i);
});
});
async function routeTemplatesFromProductionSources(): Promise<string[]> {
const source = await productionSourceText();
const routes = [...source.matchAll(/["'`]((?:GET|POST|PUT|PATCH|DELETE) [^"'`]+)["'`]/g)]
.map((match) => match[1])
.filter((route): route is string => route !== undefined);
return [...new Set(routes)].sort();
}
async function productionSourceText(): Promise<string> {
const files = await Promise.all(
productionSources.map(async (path) =>
readFile(new URL(`../../${path}`, import.meta.url), "utf8")
)
);
return files.join("\n");
}