Skip to content

Commit 2837b91

Browse files
authored
feat: add support for ignoring teams, repositories and users (#172)
* chore: extract locals out of state * feat: add support for ignoring teams, repositories and users * test: test ignoring entities via locals * chore: move members filter
1 parent 2912553 commit 2837b91

9 files changed

Lines changed: 467 additions & 65 deletions

File tree

scripts/__tests__/__resources__/terraform/locals_override.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ locals {
1010
"github_repository_file",
1111
"github_issue_label"
1212
]
13+
ignore = {
14+
"repositories" = ["ignored"]
15+
"teams" = ["ignored"]
16+
"users" = ["ignored"]
17+
}
1318
}

scripts/__tests__/github.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import 'reflect-metadata'
2+
3+
import {before, describe, it} from 'node:test'
4+
import assert from 'node:assert'
5+
import {mockGitHub} from './github.js'
6+
import {GitHub} from '../src/github.js'
7+
8+
describe('github', () => {
9+
let github: GitHub
10+
11+
before(async () => {
12+
mockGitHub({
13+
invitations: [
14+
{
15+
login: 'ignored'
16+
},
17+
{
18+
login: 'unignored'
19+
}
20+
],
21+
members: [
22+
{
23+
login: 'ignored'
24+
},
25+
{
26+
login: 'unignored'
27+
}
28+
],
29+
repositories: [
30+
{
31+
name: 'ignored',
32+
branchProtectionRules: [
33+
{
34+
pattern: 'ignored'
35+
},
36+
{
37+
pattern: 'unignored'
38+
}
39+
],
40+
collaborators: [
41+
{
42+
login: 'ignored'
43+
},
44+
{
45+
login: 'unignored'
46+
}
47+
],
48+
invitations: [
49+
{
50+
login: 'ignored'
51+
},
52+
{
53+
login: 'unignored'
54+
}
55+
],
56+
labels: [
57+
{
58+
name: 'ignored'
59+
},
60+
{
61+
name: 'unignored'
62+
}
63+
]
64+
},
65+
{
66+
name: 'unignored',
67+
branchProtectionRules: [
68+
{
69+
pattern: 'ignored'
70+
},
71+
{
72+
pattern: 'unignored'
73+
}
74+
],
75+
collaborators: [
76+
{
77+
login: 'ignored'
78+
},
79+
{
80+
login: 'unignored'
81+
}
82+
],
83+
invitations: [
84+
{
85+
login: 'ignored'
86+
},
87+
{
88+
login: 'unignored'
89+
}
90+
],
91+
labels: [
92+
{
93+
name: 'ignored'
94+
},
95+
{
96+
name: 'unignored'
97+
}
98+
]
99+
}
100+
],
101+
teams: [
102+
{
103+
name: 'ignored',
104+
members: [
105+
{
106+
login: 'ignored'
107+
},
108+
{
109+
login: 'unignored'
110+
}
111+
],
112+
invitations: [
113+
{
114+
login: 'ignored'
115+
},
116+
{
117+
login: 'unignored'
118+
}
119+
],
120+
repositories: [
121+
{
122+
name: 'ignored'
123+
},
124+
{
125+
name: 'unignored'
126+
}
127+
]
128+
},
129+
{
130+
name: 'unignored',
131+
members: [
132+
{
133+
login: 'ignored'
134+
},
135+
{
136+
login: 'unignored'
137+
}
138+
],
139+
invitations: [
140+
{
141+
login: 'ignored'
142+
},
143+
{
144+
login: 'unignored'
145+
}
146+
],
147+
repositories: [
148+
{
149+
name: 'ignored'
150+
},
151+
{
152+
name: 'unignored'
153+
}
154+
]
155+
}
156+
]
157+
})
158+
github = await GitHub.getGitHub()
159+
})
160+
161+
it('listInvitations', async () => {
162+
const invitations = await github.listInvitations()
163+
assert.ok(invitations.length > 0)
164+
assert.ok(!invitations.some(i => i.login === 'ignored'))
165+
})
166+
167+
it('listMembers', async () => {
168+
const members = await github.listMembers()
169+
assert.ok(members.length > 0)
170+
assert.ok(!members.some(m => m.user?.login === 'ignored'))
171+
})
172+
173+
it('listRepositories', async () => {
174+
const repositories = await github.listRepositories()
175+
assert.ok(repositories.length > 0)
176+
assert.ok(!repositories.some(r => r.name === 'ignored'))
177+
})
178+
179+
it('listRepositoryBranchProtectionRules', async () => {
180+
const rules = await github.listRepositoryBranchProtectionRules()
181+
assert.ok(rules.length > 0)
182+
assert.ok(!rules.some(r => r.repository.name === 'ignored'))
183+
// NOTE: Ignoring rules by pattern is not supported yet
184+
assert.ok(rules.some(r => r.branchProtectionRule.pattern === 'ignored'))
185+
})
186+
187+
it('listRepositoryCollaborators', async () => {
188+
const collaborators = await github.listRepositoryCollaborators()
189+
assert.ok(collaborators.length > 0)
190+
assert.ok(!collaborators.some(c => c.repository.name === 'ignored'))
191+
assert.ok(!collaborators.some(c => c.collaborator.login === 'ignored'))
192+
})
193+
194+
it('listRepositoryInvitations', async () => {
195+
const invitations = await github.listRepositoryInvitations()
196+
assert.ok(invitations.length > 0)
197+
assert.ok(!invitations.some(i => i.repository.name === 'ignored'))
198+
assert.ok(!invitations.some(i => i.invitee?.login === 'ignored'))
199+
})
200+
201+
it('listRepositoryLabels', async () => {
202+
const labels = await github.listRepositoryLabels()
203+
assert.ok(labels.length > 0)
204+
assert.ok(!labels.some(l => l.repository.name === 'ignored'))
205+
// NOTE: Ignoring labels by name is not supported yet
206+
assert.ok(labels.some(l => l.label.name === 'ignored'))
207+
})
208+
209+
it('listTeams', async () => {
210+
const teams = await github.listTeams()
211+
assert.ok(teams.length > 0)
212+
assert.ok(!teams.some(t => t.name === 'ignored'))
213+
})
214+
215+
it('listTeamInvitations', async () => {
216+
const invitations = await github.listTeamInvitations()
217+
assert.ok(invitations.length > 0)
218+
assert.ok(!invitations.some(i => i.team.name === 'ignored'))
219+
assert.ok(!invitations.some(i => i.invitation.login === 'ignored'))
220+
})
221+
222+
it('listTeamMembers', async () => {
223+
const members = await github.listTeamMembers()
224+
assert.ok(members.length > 0)
225+
assert.ok(!members.some(m => m.team.name === 'ignored'))
226+
assert.ok(!members.some(m => m.member.login === 'ignored'))
227+
})
228+
229+
it('listTeamRepositories', async () => {
230+
const repositories = await github.listTeamRepositories()
231+
assert.ok(repositories.length > 0)
232+
assert.ok(!repositories.some(r => r.team.name === 'ignored'))
233+
assert.ok(!repositories.some(r => r.repository.name === 'ignored'))
234+
})
235+
})

scripts/__tests__/github.ts

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
11
import {mock} from 'node:test'
22

3-
export function mockGitHub(): void {
3+
export interface GitHubConfig {
4+
repositories?: {
5+
name: string
6+
collaborators?: {
7+
login: string
8+
}[]
9+
branchProtectionRules?: {
10+
pattern: string
11+
}[]
12+
invitations?: {
13+
login: string
14+
}[]
15+
labels?: {
16+
name: string
17+
}[]
18+
}[]
19+
teams?: {
20+
name: string
21+
members?: {
22+
login: string
23+
}[]
24+
invitations?: {
25+
login: string
26+
}[]
27+
repositories?: {
28+
name: string
29+
}[]
30+
}[]
31+
invitations?: {
32+
login: string
33+
}[]
34+
members?: {
35+
login: string
36+
}[]
37+
}
38+
39+
export function mockGitHub(config: GitHubConfig = {}): void {
440
mock.module('@octokit/auth-app', {
541
namedExports: {
642
createAppAuth: () => () => ({token: undefined})
@@ -16,13 +52,16 @@ export function mockGitHub(): void {
1652
issues = {
1753
listCommentsForRepo: async () => [],
1854
listForRepo: async () => [],
19-
listLabelsForRepo: async () => []
55+
listLabelsForRepo: async (opts: {repo: string}) =>
56+
config?.repositories?.find(r => r.name === opts.repo)?.labels ??
57+
[]
2058
}
2159
orgs = {
22-
getMembershipForUser: async () => {},
23-
listInvitationTeams: async () => [],
24-
listMembers: async () => [],
25-
listPendingInvitations: async () => []
60+
getMembershipForUser: async () => ({
61+
data: {}
62+
}),
63+
listMembers: async () => config?.members ?? [],
64+
listPendingInvitations: async () => config?.invitations ?? []
2665
}
2766
pulls = {
2867
listReviewCommentsForRepo: async () => []
@@ -50,24 +89,64 @@ export function mockGitHub(): void {
5089
}
5190
}),
5291
listActivities: async () => [],
53-
listCollaborators: async () => [],
92+
listCollaborators: async (opts: {repo: string}) =>
93+
config?.repositories?.find(r => r.name === opts.repo)
94+
?.collaborators ?? [],
5495
listCommitCommentsForRepo: async () => [],
55-
listForOrg: async () => [],
56-
listInvitations: async () => []
96+
listForOrg: async () => config?.repositories ?? [],
97+
listInvitations: async (opts: {repo: string}) => {
98+
const repository = config?.repositories?.find(
99+
r => r.name === opts.repo
100+
)
101+
102+
return repository?.invitations?.map(invitee => ({
103+
repository,
104+
invitee
105+
}))
106+
}
57107
}
58108
teams = {
59-
getMembershipForUserInOrg: async () => {},
60-
list: async () => [],
61-
listMembersInOrg: async () => [],
62-
listPendingInvitationsInOrg: async () => [],
63-
listReposInOrg: async () => []
109+
getMembershipForUserInOrg: async () => ({
110+
data: {}
111+
}),
112+
list: async () =>
113+
config?.teams?.map(t => ({slug: t.name, ...t})) ?? [],
114+
listMembersInOrg: async (opts: {team_slug: string}) =>
115+
config?.teams?.find(t => t.name === opts.team_slug)?.members ??
116+
[],
117+
listPendingInvitationsInOrg: async (opts: {team_slug: string}) =>
118+
config?.teams?.find(t => t.name === opts.team_slug)
119+
?.invitations ?? [],
120+
listReposInOrg: async (opts: {team_slug: string}) =>
121+
config?.teams?.find(t => t.name === opts.team_slug)
122+
?.repositories ?? []
64123
}
65124
async paginate<T, K>(
66125
f: (opts: K) => Promise<T>,
67126
opts: K
68127
): Promise<T> {
69128
return f(opts)
70129
}
130+
async graphql(query: string): Promise<unknown> {
131+
// extract owner and repo from query using repository\(owner: \"([^\"]+)\", name: \"([^\"]+)\"\)
132+
const match = query.match(
133+
/repository\(owner: "([^"]+)", name: "([^"]+)"\)/
134+
)
135+
if (match === null) {
136+
throw new Error(`Could not find repository in query: ${query}`)
137+
}
138+
const [, , repo] = match
139+
const nodes =
140+
config.repositories?.find(r => r.name === repo)
141+
?.branchProtectionRules ?? []
142+
return {
143+
repository: {
144+
branchProtectionRules: {
145+
nodes
146+
}
147+
}
148+
}
149+
}
71150
}
72151
}
73152
}

0 commit comments

Comments
 (0)