-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub-ui-authz.test.ts
More file actions
123 lines (112 loc) · 3.89 KB
/
Copy pathgithub-ui-authz.test.ts
File metadata and controls
123 lines (112 loc) · 3.89 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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { GitHubUI } from "../src/github-ui";
describe("GitHubUI comment AuthZ", () => {
let githubUI: GitHubUI;
let octokit: any;
let context: any;
beforeEach(() => {
githubUI = new GitHubUI();
octokit = {
pulls: {
get: jest.fn().mockResolvedValue({
data: {
number: 42,
user: { login: "pr-author" },
head: { ref: "feature", sha: "abc123" },
},
}),
listCommits: jest.fn().mockResolvedValue({
data: [{ author: { login: "committer-user" }, committer: { login: "committer-user" } }],
}),
createReplyForReviewComment: jest.fn().mockResolvedValue({ id: 1 }),
},
issues: {
createComment: jest.fn().mockResolvedValue({ id: 1 }),
},
repos: {
getCollaboratorPermissionLevel: jest.fn().mockResolvedValue({
data: { permission: "read" },
}),
getContent: jest.fn().mockRejectedValue({ status: 404 }),
createOrUpdateFileContents: jest.fn().mockResolvedValue({
data: { content: { sha: "x" } },
}),
},
};
context = {
payload: {
sender: { login: "random-reader" },
repository: {
owner: { login: "acme" },
name: "app",
},
issue: { number: 42, pull_request: {} },
pull_request: { number: 42 },
},
octokit,
};
});
test("authorizeSpecMutation allows PR author", async () => {
context.payload.sender.login = "pr-author";
const result = await githubUI.authorizeSpecMutation(context, {
body: "/specsync accept",
user: { login: "pr-author" },
});
expect(result.allowed).toBe(true);
expect(result.reason).toBe("pr_author");
});
test("authorizeSpecMutation allows write collaborators", async () => {
octokit.repos.getCollaboratorPermissionLevel.mockResolvedValue({
data: { permission: "write" },
});
const result = await githubUI.authorizeSpecMutation(context, {
body: "/specsync ignore",
user: { login: "random-reader" },
});
expect(result.allowed).toBe(true);
expect(result.reason).toBe("permission:write");
});
test("authorizeSpecMutation allows PR committers", async () => {
context.payload.sender.login = "committer-user";
const result = await githubUI.authorizeSpecMutation(context, {
body: "/specsync edit",
user: { login: "committer-user" },
});
expect(result.allowed).toBe(true);
expect(result.reason).toBe("pr_committer");
});
test("authorizeSpecMutation denies readers without commits", async () => {
const result = await githubUI.authorizeSpecMutation(context, {
body: "/specsync accept",
user: { login: "random-reader" },
});
expect(result.allowed).toBe(false);
});
test("handleSpecCommentAction rejects unauthorized accept", async () => {
await githubUI.handleSpecCommentAction(context, {
id: 9,
body: "/specsync accept\n## Spec for `foo`",
user: { login: "random-reader" },
path: "src/a.ts",
});
expect(octokit.pulls.createReplyForReviewComment).toHaveBeenCalled();
const reply = octokit.pulls.createReplyForReviewComment.mock.calls[0][0];
expect(reply.body).toContain("not allowed");
expect(octokit.repos.createOrUpdateFileContents).not.toHaveBeenCalled();
});
test("formatSpecComment escapes HTML in user-controlled fields", () => {
const body = githubUI.formatSpecComment({
functionName: '<script>alert(1)</script>',
filePath: 'src/<evil>.ts',
lineNumber: 3,
preconditions: ["x < 0 && y > 1"],
postconditions: ['ret == "<ok>"'],
invariants: [],
confidence: 0.9,
reasoning: "Uses <b>tags</b>",
});
expect(body).toContain("<script>");
expect(body).not.toContain("<script>");
expect(body).toContain("src/<evil>.ts");
expect(body).toContain("Uses <b>tags</b>");
});
});