Skip to content

Commit 79d5c90

Browse files
authored
Fix operationIds handling in programmatic_access.yaml pipeline (#56176)
1 parent 23e4f98 commit 79d5c90

File tree

2 files changed

+253
-1
lines changed

2 files changed

+253
-1
lines changed

src/github-apps/scripts/sync.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export async function getProgAccessData(progAccessSource, isRest = false) {
236236

237237
const progAccessData = {}
238238
for (const operation of progAccessDataRaw) {
239-
progAccessData[operation.operation_ids] = {
239+
const operationData = {
240240
userToServerRest: operation.user_to_server.enabled,
241241
serverToServer: operation.server_to_server.enabled,
242242
fineGrainedPat: operation.user_to_server.enabled && !operation.disabled_for_patv2,
@@ -247,6 +247,12 @@ export async function getProgAccessData(progAccessSource, isRest = false) {
247247
allowsPublicRead: operation.allows_public_read,
248248
basicAuth: operation.basic_auth,
249249
}
250+
251+
// Handle comma-separated operation IDs
252+
const operationIds = operation.operation_ids.split(',').map((id) => id.trim())
253+
for (const operationId of operationIds) {
254+
progAccessData[operationId] = operationData
255+
}
250256
}
251257

252258
return { progAccessData, progActorResources }

src/github-apps/tests/sync.js

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { getProgAccessData } from '../scripts/sync'
3+
4+
describe('getProgAccessData', () => {
5+
test('handles single operationIds correctly', async () => {
6+
const mockProgAccessDataRaw = [
7+
{
8+
operation_ids: 'single-operation-id',
9+
user_to_server: { enabled: true },
10+
server_to_server: { enabled: true },
11+
disabled_for_patv2: false,
12+
permission_sets: [{ actions: 'read' }],
13+
allows_permissionless_access: false,
14+
allows_public_read: true,
15+
basic_auth: false,
16+
},
17+
]
18+
19+
const mockProgActorResources = {}
20+
21+
const result = await processProgAccessDataMock(mockProgAccessDataRaw, mockProgActorResources)
22+
23+
expect(result.progAccessData).toHaveProperty('single-operation-id')
24+
expect(result.progAccessData['single-operation-id']).toEqual({
25+
userToServerRest: true,
26+
serverToServer: true,
27+
fineGrainedPat: true,
28+
permissions: [{ actions: 'read' }],
29+
allowPermissionlessAccess: false,
30+
allowsPublicRead: true,
31+
basicAuth: false,
32+
})
33+
})
34+
35+
test('handles comma-separated operationIds correctly', async () => {
36+
const mockProgAccessDataRaw = [
37+
{
38+
operation_ids: 'teams/remove-repo-in-org,teams/remove-repo-legacy',
39+
user_to_server: { enabled: true },
40+
server_to_server: { enabled: true },
41+
disabled_for_patv2: false,
42+
permission_sets: [
43+
{
44+
administration: 'write',
45+
members: 'read',
46+
metadata: 'read',
47+
},
48+
],
49+
allows_permissionless_access: false,
50+
allows_public_read: false,
51+
basic_auth: false,
52+
},
53+
]
54+
55+
const mockProgActorResources = {}
56+
57+
const result = await processProgAccessDataMock(mockProgAccessDataRaw, mockProgActorResources)
58+
59+
// Both operation IDs should exist
60+
expect(result.progAccessData).toHaveProperty('teams/remove-repo-in-org')
61+
expect(result.progAccessData).toHaveProperty('teams/remove-repo-legacy')
62+
63+
// Both should have identical data
64+
const expectedData = {
65+
userToServerRest: true,
66+
serverToServer: true,
67+
fineGrainedPat: true,
68+
permissions: [
69+
{
70+
administration: 'write',
71+
members: 'read',
72+
metadata: 'read',
73+
},
74+
],
75+
allowPermissionlessAccess: false,
76+
allowsPublicRead: false,
77+
basicAuth: false,
78+
}
79+
80+
expect(result.progAccessData['teams/remove-repo-in-org']).toEqual(expectedData)
81+
expect(result.progAccessData['teams/remove-repo-legacy']).toEqual(expectedData)
82+
})
83+
84+
test('handles multiple comma-separated operationIds correctly', async () => {
85+
const mockProgAccessDataRaw = [
86+
{
87+
operation_ids: 'operation1,operation2,operation3',
88+
user_to_server: { enabled: false },
89+
server_to_server: { enabled: true },
90+
disabled_for_patv2: true,
91+
permission_sets: [{ metadata: 'read' }],
92+
allows_permissionless_access: true,
93+
allows_public_read: false,
94+
basic_auth: true,
95+
},
96+
]
97+
98+
const mockProgActorResources = {}
99+
100+
const result = await processProgAccessDataMock(mockProgAccessDataRaw, mockProgActorResources)
101+
102+
// All three operation IDs should exist
103+
expect(result.progAccessData).toHaveProperty('operation1')
104+
expect(result.progAccessData).toHaveProperty('operation2')
105+
expect(result.progAccessData).toHaveProperty('operation3')
106+
107+
// All should have identical data
108+
const expectedData = {
109+
userToServerRest: false,
110+
serverToServer: true,
111+
fineGrainedPat: false, // false because user_to_server.enabled is false
112+
permissions: [{ metadata: 'read' }],
113+
allowPermissionlessAccess: true,
114+
allowsPublicRead: false,
115+
basicAuth: true,
116+
}
117+
118+
expect(result.progAccessData['operation1']).toEqual(expectedData)
119+
expect(result.progAccessData['operation2']).toEqual(expectedData)
120+
expect(result.progAccessData['operation3']).toEqual(expectedData)
121+
})
122+
123+
test('handles comma-separated operationIds with whitespace correctly', async () => {
124+
const mockProgAccessDataRaw = [
125+
{
126+
operation_ids: 'operation-a, operation-b , operation-c',
127+
user_to_server: { enabled: true },
128+
server_to_server: { enabled: false },
129+
disabled_for_patv2: false,
130+
permission_sets: [],
131+
allows_permissionless_access: false,
132+
allows_public_read: true,
133+
basic_auth: false,
134+
},
135+
]
136+
137+
const mockProgActorResources = {}
138+
139+
const result = await processProgAccessDataMock(mockProgAccessDataRaw, mockProgActorResources)
140+
141+
// All operation IDs should exist (whitespace should be trimmed)
142+
expect(result.progAccessData).toHaveProperty('operation-a')
143+
expect(result.progAccessData).toHaveProperty('operation-b')
144+
expect(result.progAccessData).toHaveProperty('operation-c')
145+
146+
const expectedData = {
147+
userToServerRest: true,
148+
serverToServer: false,
149+
fineGrainedPat: true,
150+
permissions: [],
151+
allowPermissionlessAccess: false,
152+
allowsPublicRead: true,
153+
basicAuth: false,
154+
}
155+
156+
expect(result.progAccessData['operation-a']).toEqual(expectedData)
157+
expect(result.progAccessData['operation-b']).toEqual(expectedData)
158+
expect(result.progAccessData['operation-c']).toEqual(expectedData)
159+
})
160+
161+
test('handles mixed single and comma-separated operationIds correctly', async () => {
162+
const mockProgAccessDataRaw = [
163+
{
164+
operation_ids: 'single-operation',
165+
user_to_server: { enabled: true },
166+
server_to_server: { enabled: true },
167+
disabled_for_patv2: false,
168+
permission_sets: [{ actions: 'read' }],
169+
allows_permissionless_access: false,
170+
allows_public_read: true,
171+
basic_auth: false,
172+
},
173+
{
174+
operation_ids: 'comma-op1,comma-op2',
175+
user_to_server: { enabled: true },
176+
server_to_server: { enabled: false },
177+
disabled_for_patv2: true,
178+
permission_sets: [{ metadata: 'write' }],
179+
allows_permissionless_access: true,
180+
allows_public_read: false,
181+
basic_auth: true,
182+
},
183+
]
184+
185+
const mockProgActorResources = {}
186+
187+
const result = await processProgAccessDataMock(mockProgAccessDataRaw, mockProgActorResources)
188+
189+
// Should have 3 total entries
190+
expect(Object.keys(result.progAccessData)).toHaveLength(3)
191+
expect(result.progAccessData).toHaveProperty('single-operation')
192+
expect(result.progAccessData).toHaveProperty('comma-op1')
193+
expect(result.progAccessData).toHaveProperty('comma-op2')
194+
195+
// Single operation should have its own data
196+
expect(result.progAccessData['single-operation']).toEqual({
197+
userToServerRest: true,
198+
serverToServer: true,
199+
fineGrainedPat: true,
200+
permissions: [{ actions: 'read' }],
201+
allowPermissionlessAccess: false,
202+
allowsPublicRead: true,
203+
basicAuth: false,
204+
})
205+
206+
// Comma-separated operations should have identical data
207+
const expectedCommaData = {
208+
userToServerRest: true,
209+
serverToServer: false,
210+
fineGrainedPat: false, // false because disabled_for_patv2 is true
211+
permissions: [{ metadata: 'write' }],
212+
allowPermissionlessAccess: true,
213+
allowsPublicRead: false,
214+
basicAuth: true,
215+
}
216+
217+
expect(result.progAccessData['comma-op1']).toEqual(expectedCommaData)
218+
expect(result.progAccessData['comma-op2']).toEqual(expectedCommaData)
219+
})
220+
})
221+
222+
// Helper function to simulate the data processing logic from sync.js
223+
// without needing to set up the full file system or remote API calls
224+
async function processProgAccessDataMock(progAccessDataRaw, progActorResources) {
225+
const progAccessData = {}
226+
227+
for (const operation of progAccessDataRaw) {
228+
const operationData = {
229+
userToServerRest: operation.user_to_server.enabled,
230+
serverToServer: operation.server_to_server.enabled,
231+
fineGrainedPat: operation.user_to_server.enabled && !operation.disabled_for_patv2,
232+
permissions: operation.permission_sets || [],
233+
allowPermissionlessAccess: operation.allows_permissionless_access,
234+
allowsPublicRead: operation.allows_public_read,
235+
basicAuth: operation.basic_auth,
236+
}
237+
238+
// Handle comma-separated operation IDs
239+
const operationIds = operation.operation_ids.split(',').map((id) => id.trim())
240+
for (const operationId of operationIds) {
241+
progAccessData[operationId] = operationData
242+
}
243+
}
244+
245+
return { progAccessData, progActorResources }
246+
}

0 commit comments

Comments
 (0)