@pagopa/dx-cli 0.22.4 → 0.23.1

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.
Files changed (104) hide show
  1. package/README.md +18 -0
  2. package/dist/adapters/commander/commands/add.d.ts +2 -6
  3. package/dist/adapters/commander/commands/add.js +4 -8
  4. package/dist/adapters/commander/commands/init.d.ts +2 -6
  5. package/dist/adapters/commander/commands/init.js +6 -5
  6. package/dist/adapters/commander/commands/savemoney.d.ts +11 -0
  7. package/dist/adapters/commander/commands/savemoney.js +30 -5
  8. package/dist/adapters/commander/commands/spec.d.ts +10 -0
  9. package/dist/adapters/commander/commands/spec.js +13 -0
  10. package/dist/adapters/commander/index.js +6 -2
  11. package/dist/adapters/commander/spec.d.ts +16 -0
  12. package/dist/adapters/commander/spec.js +54 -0
  13. package/dist/adapters/plop/generators/environment/actions.js +3 -3
  14. package/dist/adapters/plop/index.js +3 -5
  15. package/dist/adapters/plop/templates-path.d.ts +5 -0
  16. package/dist/adapters/plop/templates-path.js +6 -0
  17. package/dist/domain/dependencies.d.ts +15 -2
  18. package/dist/domain/spec.d.ts +50 -0
  19. package/dist/domain/spec.js +8 -0
  20. package/dist/index.js +15 -12
  21. package/package.json +5 -5
  22. package/templates/monorepo/nx.json +0 -1
  23. package/dist/adapters/azure/__tests__/cloud-account-repository.test.d.ts +0 -1
  24. package/dist/adapters/azure/__tests__/cloud-account-repository.test.js +0 -95
  25. package/dist/adapters/azure/__tests__/cloud-account-service.test.d.ts +0 -1
  26. package/dist/adapters/azure/__tests__/cloud-account-service.test.js +0 -378
  27. package/dist/adapters/codemods/__tests__/registry.test.d.ts +0 -1
  28. package/dist/adapters/codemods/__tests__/registry.test.js +0 -56
  29. package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.d.ts +0 -1
  30. package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.js +0 -77
  31. package/dist/adapters/codemods/__tests__/use-pnpm.test.d.ts +0 -1
  32. package/dist/adapters/codemods/__tests__/use-pnpm.test.js +0 -148
  33. package/dist/adapters/commander/__tests__/env.test.d.ts +0 -1
  34. package/dist/adapters/commander/__tests__/env.test.js +0 -45
  35. package/dist/adapters/commander/__tests__/error-reporting.test.d.ts +0 -1
  36. package/dist/adapters/commander/__tests__/error-reporting.test.js +0 -63
  37. package/dist/adapters/commander/__tests__/exit-with-error.test.d.ts +0 -1
  38. package/dist/adapters/commander/__tests__/exit-with-error.test.js +0 -92
  39. package/dist/adapters/commander/__tests__/global-options.test.d.ts +0 -1
  40. package/dist/adapters/commander/__tests__/global-options.test.js +0 -33
  41. package/dist/adapters/commander/commands/__tests__/add.test.d.ts +0 -4
  42. package/dist/adapters/commander/commands/__tests__/add.test.js +0 -167
  43. package/dist/adapters/commander/commands/__tests__/init.test.d.ts +0 -4
  44. package/dist/adapters/commander/commands/__tests__/init.test.js +0 -48
  45. package/dist/adapters/commander/commands/__tests__/preconditions.test.d.ts +0 -1
  46. package/dist/adapters/commander/commands/__tests__/preconditions.test.js +0 -32
  47. package/dist/adapters/commander/presenters/__tests__/index.test.d.ts +0 -1
  48. package/dist/adapters/commander/presenters/__tests__/index.test.js +0 -23
  49. package/dist/adapters/commander/presenters/__tests__/json.test.d.ts +0 -1
  50. package/dist/adapters/commander/presenters/__tests__/json.test.js +0 -108
  51. package/dist/adapters/commander/presenters/__tests__/text.test.d.ts +0 -1
  52. package/dist/adapters/commander/presenters/__tests__/text.test.js +0 -60
  53. package/dist/adapters/github/__tests__/github-repo.spec.d.ts +0 -1
  54. package/dist/adapters/github/__tests__/github-repo.spec.js +0 -67
  55. package/dist/adapters/node/__tests__/data.d.ts +0 -18
  56. package/dist/adapters/node/__tests__/data.js +0 -22
  57. package/dist/adapters/node/__tests__/package-json.test.d.ts +0 -1
  58. package/dist/adapters/node/__tests__/package-json.test.js +0 -86
  59. package/dist/adapters/node/__tests__/repository.test.d.ts +0 -1
  60. package/dist/adapters/node/__tests__/repository.test.js +0 -77
  61. package/dist/adapters/node/fs/__tests__/file-reader.test.d.ts +0 -1
  62. package/dist/adapters/node/fs/__tests__/file-reader.test.js +0 -80
  63. package/dist/adapters/node/json/__tests__/index.test.d.ts +0 -1
  64. package/dist/adapters/node/json/__tests__/index.test.js +0 -14
  65. package/dist/adapters/octokit/__tests__/index.test.d.ts +0 -1
  66. package/dist/adapters/octokit/__tests__/index.test.js +0 -414
  67. package/dist/adapters/pagopa-technology/__tests__/authorization.test.d.ts +0 -4
  68. package/dist/adapters/pagopa-technology/__tests__/authorization.test.js +0 -548
  69. package/dist/adapters/plop/__tests__/run-actions.test.d.ts +0 -1
  70. package/dist/adapters/plop/__tests__/run-actions.test.js +0 -68
  71. package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.d.ts +0 -1
  72. package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +0 -171
  73. package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.d.ts +0 -1
  74. package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +0 -134
  75. package/dist/adapters/plop/generators/environment/__tests__/actions.test.d.ts +0 -2
  76. package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +0 -92
  77. package/dist/adapters/plop/generators/environment/__tests__/prompts.test.d.ts +0 -1
  78. package/dist/adapters/plop/generators/environment/__tests__/prompts.test.js +0 -182
  79. package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.d.ts +0 -1
  80. package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.js +0 -113
  81. package/dist/adapters/plop/helpers/__tests__/terraform-state-key.test.d.ts +0 -1
  82. package/dist/adapters/plop/helpers/__tests__/terraform-state-key.test.js +0 -35
  83. package/dist/adapters/plop/helpers/__tests__/validate-prompt.test.d.ts +0 -1
  84. package/dist/adapters/plop/helpers/__tests__/validate-prompt.test.js +0 -60
  85. package/dist/adapters/yaml/__tests__/index.test.d.ts +0 -1
  86. package/dist/adapters/yaml/__tests__/index.test.js +0 -53
  87. package/dist/domain/__tests__/data.d.ts +0 -19
  88. package/dist/domain/__tests__/data.js +0 -28
  89. package/dist/domain/__tests__/environment.test.d.ts +0 -1
  90. package/dist/domain/__tests__/environment.test.js +0 -332
  91. package/dist/domain/__tests__/info.test.d.ts +0 -1
  92. package/dist/domain/__tests__/info.test.js +0 -77
  93. package/dist/domain/__tests__/package-json.test.d.ts +0 -1
  94. package/dist/domain/__tests__/package-json.test.js +0 -39
  95. package/dist/domain/__tests__/repository.test.d.ts +0 -1
  96. package/dist/domain/__tests__/repository.test.js +0 -111
  97. package/dist/domain/__tests__/workspace.test.d.ts +0 -1
  98. package/dist/domain/__tests__/workspace.test.js +0 -57
  99. package/dist/use-cases/__tests__/apply-codemod.test.d.ts +0 -1
  100. package/dist/use-cases/__tests__/apply-codemod.test.js +0 -71
  101. package/dist/use-cases/__tests__/list-codemods.test.d.ts +0 -1
  102. package/dist/use-cases/__tests__/list-codemods.test.js +0 -37
  103. package/dist/use-cases/__tests__/request-authorization.test.d.ts +0 -4
  104. package/dist/use-cases/__tests__/request-authorization.test.js +0 -43
@@ -1,548 +0,0 @@
1
- /**
2
- * Tests for the PagoPA technology authorization adapter.
3
- */
4
- import { describe, expect, it } from "vitest";
5
- import { mock } from "vitest-mock-extended";
6
- import { AuthorizationResult, InvalidAuthorizationFileFormatError, requestAuthorizationInputSchema, } from "../../../domain/authorization.js";
7
- import { FileNotFoundError, PullRequest, } from "../../../domain/github.js";
8
- import { DEFAULT_GROUP_SPECS, makeAzureAdGroupName as makeGroupName, } from "../azure-authorization-config.js";
9
- import { makeAzureAuthorizationService as makeAuthorizationService } from "../azure-authorization.js";
10
- const makeEnv = () => {
11
- const gitHubService = mock();
12
- const authorizationService = makeAuthorizationService(gitHubService);
13
- return {
14
- authorizationService,
15
- gitHubService,
16
- };
17
- };
18
- const makeSampleInput = () => requestAuthorizationInputSchema.parse({
19
- bootstrapIdentityId: "test-bootstrap-identity-id",
20
- envShort: "d",
21
- prefix: "test",
22
- repoName: "test-repo",
23
- subscriptionName: "test-subscription",
24
- });
25
- const FILE_PATH = "src/azure-subscriptions/subscriptions/test-subscription/terraform.tfvars.json";
26
- // eslint-disable-next-line max-lines-per-function
27
- describe("PagoPA AuthorizationService", () => {
28
- // eslint-disable-next-line max-lines-per-function
29
- describe("happy path", () => {
30
- it("should create a pull request when all steps succeed", async () => {
31
- const { authorizationService, gitHubService } = makeEnv();
32
- const input = makeSampleInput();
33
- const originalContent = JSON.stringify({ directory_readers: { service_principals_name: [] } }, null, 2);
34
- gitHubService.createBranch.mockResolvedValue(undefined);
35
- gitHubService.getFileContent.mockResolvedValue({
36
- content: originalContent,
37
- sha: "original-sha-123",
38
- });
39
- gitHubService.updateFile.mockResolvedValue(undefined);
40
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/42"));
41
- const result = await authorizationService.requestAuthorization(input);
42
- expect(result.isOk()).toBe(true);
43
- const authResult = result._unsafeUnwrap();
44
- expect(authResult).toBeInstanceOf(AuthorizationResult);
45
- expect(authResult.url).toBe("https://github.com/pagopa/eng-azure-authorization/pull/42");
46
- expect(gitHubService.getFileContent).toHaveBeenCalledWith({
47
- owner: "pagopa",
48
- path: FILE_PATH,
49
- ref: "main",
50
- repo: "eng-azure-authorization",
51
- });
52
- expect(gitHubService.createBranch).toHaveBeenCalledWith({
53
- branchName: "feats/add-test-repo-test-subscription-bootstrap-identity",
54
- fromRef: "main",
55
- owner: "pagopa",
56
- repo: "eng-azure-authorization",
57
- });
58
- expect(gitHubService.updateFile).toHaveBeenCalledWith(expect.objectContaining({
59
- branch: "feats/add-test-repo-test-subscription-bootstrap-identity",
60
- message: "Add bootstrap identity and AD groups for test-subscription",
61
- owner: "pagopa",
62
- path: FILE_PATH,
63
- repo: "eng-azure-authorization",
64
- sha: "original-sha-123",
65
- }));
66
- expect(gitHubService.createPullRequest).toHaveBeenCalledWith({
67
- base: "main",
68
- body: "This PR adds the bootstrap identity `test-bootstrap-identity-id` to the directory readers and configures AD groups for subscription `test-subscription`.",
69
- head: "feats/add-test-repo-test-subscription-bootstrap-identity",
70
- owner: "pagopa",
71
- repo: "eng-azure-authorization",
72
- title: "Add bootstrap identity and AD groups for test-subscription",
73
- });
74
- });
75
- it("should add all default AD groups when none exist", async () => {
76
- const { authorizationService, gitHubService } = makeEnv();
77
- const input = makeSampleInput();
78
- const originalContent = JSON.stringify({ directory_readers: { service_principals_name: [] } }, null, 2);
79
- gitHubService.createBranch.mockResolvedValue(undefined);
80
- gitHubService.getFileContent.mockResolvedValue({
81
- content: originalContent,
82
- sha: "sha-groups-1",
83
- });
84
- gitHubService.updateFile.mockResolvedValue(undefined);
85
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/50"));
86
- const result = await authorizationService.requestAuthorization(input);
87
- expect(result.isOk()).toBe(true);
88
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
89
- const updatedParsed = JSON.parse(updateCall.content);
90
- expect(updatedParsed.groups).toHaveLength(DEFAULT_GROUP_SPECS.length);
91
- for (const spec of DEFAULT_GROUP_SPECS) {
92
- const groupName = makeGroupName("test", "d", spec.groupName);
93
- const found = updatedParsed.groups.find((g) => g.name === groupName);
94
- expect(found).toBeDefined();
95
- expect(found.roles).toEqual(spec.roles);
96
- expect(found.members).toEqual([]);
97
- }
98
- });
99
- it("should preserve existing members and add missing groups", async () => {
100
- const { authorizationService, gitHubService } = makeEnv();
101
- const input = makeSampleInput();
102
- const originalContent = JSON.stringify({
103
- directory_readers: { service_principals_name: [] },
104
- groups: [
105
- {
106
- members: ["alice@pagopa.it"],
107
- name: "test-d-adgroup-admin",
108
- roles: ["Owner"],
109
- },
110
- ],
111
- }, null, 2);
112
- gitHubService.createBranch.mockResolvedValue(undefined);
113
- gitHubService.getFileContent.mockResolvedValue({
114
- content: originalContent,
115
- sha: "sha-groups-2",
116
- });
117
- gitHubService.updateFile.mockResolvedValue(undefined);
118
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/51"));
119
- const result = await authorizationService.requestAuthorization(input);
120
- expect(result.isOk()).toBe(true);
121
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
122
- const updatedParsed = JSON.parse(updateCall.content);
123
- // All default groups should be present
124
- expect(updatedParsed.groups).toHaveLength(DEFAULT_GROUP_SPECS.length);
125
- // Admin group should preserve existing member
126
- const adminGroup = updatedParsed.groups.find((g) => g.name === "test-d-adgroup-admin");
127
- expect(adminGroup.members).toContain("alice@pagopa.it");
128
- });
129
- it("should update roles on existing group while preserving members", async () => {
130
- const { authorizationService, gitHubService } = makeEnv();
131
- const input = makeSampleInput();
132
- const originalContent = JSON.stringify({
133
- directory_readers: { service_principals_name: [] },
134
- groups: [
135
- {
136
- // externals normally gets "Owner" but file has "Reader"
137
- members: ["bob@pagopa.it"],
138
- name: "test-d-adgroup-externals",
139
- roles: ["Reader"],
140
- },
141
- ],
142
- }, null, 2);
143
- gitHubService.createBranch.mockResolvedValue(undefined);
144
- gitHubService.getFileContent.mockResolvedValue({
145
- content: originalContent,
146
- sha: "sha-groups-3",
147
- });
148
- gitHubService.updateFile.mockResolvedValue(undefined);
149
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/52"));
150
- const result = await authorizationService.requestAuthorization(input);
151
- expect(result.isOk()).toBe(true);
152
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
153
- const updatedParsed = JSON.parse(updateCall.content);
154
- const externalsGroup = updatedParsed.groups.find((g) => g.name === "test-d-adgroup-externals");
155
- // Roles updated to default
156
- expect(externalsGroup.roles).toEqual(["Owner"]);
157
- // Members preserved
158
- expect(externalsGroup.members).toContain("bob@pagopa.it");
159
- });
160
- it("should preserve custom (non-default) groups", async () => {
161
- const { authorizationService, gitHubService } = makeEnv();
162
- const input = makeSampleInput();
163
- const originalContent = JSON.stringify({
164
- directory_readers: { service_principals_name: [] },
165
- groups: [
166
- {
167
- members: ["carol@pagopa.it"],
168
- name: "test-d-adgroup-custom-team",
169
- roles: ["Contributor"],
170
- },
171
- ],
172
- }, null, 2);
173
- gitHubService.createBranch.mockResolvedValue(undefined);
174
- gitHubService.getFileContent.mockResolvedValue({
175
- content: originalContent,
176
- sha: "sha-groups-4",
177
- });
178
- gitHubService.updateFile.mockResolvedValue(undefined);
179
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/53"));
180
- const result = await authorizationService.requestAuthorization(input);
181
- expect(result.isOk()).toBe(true);
182
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
183
- const updatedParsed = JSON.parse(updateCall.content);
184
- // Custom group preserved
185
- const customGroup = updatedParsed.groups.find((g) => g.name === "test-d-adgroup-custom-team");
186
- expect(customGroup).toBeDefined();
187
- expect(customGroup.members).toContain("carol@pagopa.it");
188
- // All defaults also present
189
- expect(updatedParsed.groups).toHaveLength(DEFAULT_GROUP_SPECS.length + 1);
190
- });
191
- it("should preserve existing fields in the JSON file", async () => {
192
- const { authorizationService, gitHubService } = makeEnv();
193
- const input = makeSampleInput();
194
- const originalContent = JSON.stringify({
195
- directory_readers: {
196
- service_principals_name: ["existing-identity"],
197
- some_other_field: "keep-me",
198
- },
199
- entra_groups: {
200
- readers: ["reader-group"],
201
- },
202
- other_top_level: true,
203
- }, null, 2);
204
- gitHubService.createBranch.mockResolvedValue(undefined);
205
- gitHubService.getFileContent.mockResolvedValue({
206
- content: originalContent,
207
- sha: "sha-789",
208
- });
209
- gitHubService.updateFile.mockResolvedValue(undefined);
210
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/44"));
211
- const result = await authorizationService.requestAuthorization(input);
212
- expect(result.isOk()).toBe(true);
213
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
214
- const updatedParsed = JSON.parse(updateCall.content);
215
- // Identity added
216
- expect(updatedParsed.directory_readers.service_principals_name).toContain("test-bootstrap-identity-id");
217
- // Extra fields preserved
218
- expect(updatedParsed.directory_readers.some_other_field).toBe("keep-me");
219
- expect(updatedParsed.entra_groups).toEqual({ readers: ["reader-group"] });
220
- expect(updatedParsed.other_top_level).toBe(true);
221
- // All default groups added
222
- expect(updatedParsed.groups).toHaveLength(DEFAULT_GROUP_SPECS.length);
223
- });
224
- it("should append identity to an existing non-empty list", async () => {
225
- const { authorizationService, gitHubService } = makeEnv();
226
- const input = makeSampleInput();
227
- const originalContent = JSON.stringify({
228
- directory_readers: {
229
- service_principals_name: ["existing-identity"],
230
- },
231
- }, null, 2);
232
- gitHubService.createBranch.mockResolvedValue(undefined);
233
- gitHubService.getFileContent.mockResolvedValue({
234
- content: originalContent,
235
- sha: "sha-456",
236
- });
237
- gitHubService.updateFile.mockResolvedValue(undefined);
238
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/43"));
239
- const result = await authorizationService.requestAuthorization(input);
240
- expect(result.isOk()).toBe(true);
241
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
242
- const updatedParsed = JSON.parse(updateCall.content);
243
- expect(updatedParsed.directory_readers.service_principals_name).toContain("test-bootstrap-identity-id");
244
- expect(updatedParsed.directory_readers.service_principals_name).toContain("existing-identity");
245
- });
246
- it("should use identity-only messages when groups are already correct", async () => {
247
- const { authorizationService, gitHubService } = makeEnv();
248
- const input = makeSampleInput();
249
- // Groups already correct, but identity is missing
250
- const allGroups = DEFAULT_GROUP_SPECS.map((spec) => ({
251
- members: [],
252
- name: makeGroupName("test", "d", spec.groupName),
253
- roles: [...spec.roles],
254
- }));
255
- const originalContent = JSON.stringify({
256
- directory_readers: { service_principals_name: [] },
257
- groups: allGroups,
258
- }, null, 2);
259
- gitHubService.getFileContent.mockResolvedValue({
260
- content: originalContent,
261
- sha: "sha-identity-only",
262
- });
263
- gitHubService.createBranch.mockResolvedValue(undefined);
264
- gitHubService.updateFile.mockResolvedValue(undefined);
265
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/60"));
266
- const result = await authorizationService.requestAuthorization(input);
267
- expect(result.isOk()).toBe(true);
268
- expect(gitHubService.updateFile).toHaveBeenCalledWith(expect.objectContaining({
269
- message: "Add bootstrap identity for test-subscription",
270
- }));
271
- expect(gitHubService.createPullRequest).toHaveBeenCalledWith(expect.objectContaining({
272
- body: "This PR adds the bootstrap identity `test-bootstrap-identity-id` to the directory readers for subscription `test-subscription`.",
273
- title: "Add bootstrap identity for test-subscription",
274
- }));
275
- });
276
- it("should use groups-only messages when identity already exists", async () => {
277
- const { authorizationService, gitHubService } = makeEnv();
278
- const input = makeSampleInput();
279
- // Identity present, no groups yet
280
- const originalContent = JSON.stringify({
281
- directory_readers: {
282
- service_principals_name: ["test-bootstrap-identity-id"],
283
- },
284
- }, null, 2);
285
- gitHubService.getFileContent.mockResolvedValue({
286
- content: originalContent,
287
- sha: "sha-groups-only",
288
- });
289
- gitHubService.createBranch.mockResolvedValue(undefined);
290
- gitHubService.updateFile.mockResolvedValue(undefined);
291
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/61"));
292
- const result = await authorizationService.requestAuthorization(input);
293
- expect(result.isOk()).toBe(true);
294
- expect(gitHubService.updateFile).toHaveBeenCalledWith(expect.objectContaining({
295
- message: "Configure AD groups for test-subscription",
296
- }));
297
- expect(gitHubService.createPullRequest).toHaveBeenCalledWith(expect.objectContaining({
298
- body: "This PR configures AD groups for subscription `test-subscription`.",
299
- title: "Configure AD groups for test-subscription",
300
- }));
301
- });
302
- it("should preserve original group order and append missing defaults at end", async () => {
303
- const { authorizationService, gitHubService } = makeEnv();
304
- const input = makeSampleInput();
305
- // Start with custom group + one default group (externals) in deliberate order
306
- const originalContent = JSON.stringify({
307
- directory_readers: { service_principals_name: [] },
308
- groups: [
309
- {
310
- members: ["carol@pagopa.it"],
311
- name: "test-d-adgroup-custom-team",
312
- roles: ["Contributor"],
313
- },
314
- {
315
- members: ["alice@pagopa.it"],
316
- name: "test-d-adgroup-externals",
317
- roles: ["Owner"],
318
- },
319
- ],
320
- }, null, 2);
321
- gitHubService.getFileContent.mockResolvedValue({
322
- content: originalContent,
323
- sha: "sha-order",
324
- });
325
- gitHubService.createBranch.mockResolvedValue(undefined);
326
- gitHubService.updateFile.mockResolvedValue(undefined);
327
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/62"));
328
- const result = await authorizationService.requestAuthorization(input);
329
- expect(result.isOk()).toBe(true);
330
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
331
- const updatedParsed = JSON.parse(updateCall.content);
332
- const groupNames = updatedParsed.groups.map((g) => g.name);
333
- // Original groups preserve their order
334
- expect(groupNames[0]).toBe("test-d-adgroup-custom-team");
335
- expect(groupNames[1]).toBe("test-d-adgroup-externals");
336
- // Missing defaults appended after existing groups
337
- expect(groupNames.length).toBe(DEFAULT_GROUP_SPECS.length + 1);
338
- });
339
- it("should preserve extra fields on group entries through round-trip", async () => {
340
- const { authorizationService, gitHubService } = makeEnv();
341
- const input = makeSampleInput();
342
- // Group with extra metadata field
343
- const originalContent = JSON.stringify({
344
- directory_readers: { service_principals_name: [] },
345
- groups: [
346
- {
347
- members: ["alice@pagopa.it"],
348
- metadata: { created_by: "automation" },
349
- name: "test-d-adgroup-admin",
350
- roles: ["Owner"],
351
- },
352
- ],
353
- }, null, 2);
354
- gitHubService.getFileContent.mockResolvedValue({
355
- content: originalContent,
356
- sha: "sha-extra-fields",
357
- });
358
- gitHubService.createBranch.mockResolvedValue(undefined);
359
- gitHubService.updateFile.mockResolvedValue(undefined);
360
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/63"));
361
- const result = await authorizationService.requestAuthorization(input);
362
- expect(result.isOk()).toBe(true);
363
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
364
- const updatedParsed = JSON.parse(updateCall.content);
365
- const adminGroup = updatedParsed.groups.find((g) => g.name === "test-d-adgroup-admin");
366
- // Extra field preserved
367
- expect(adminGroup.metadata).toEqual({ created_by: "automation" });
368
- // Members preserved
369
- expect(adminGroup.members).toContain("alice@pagopa.it");
370
- });
371
- });
372
- // eslint-disable-next-line max-lines-per-function
373
- describe("error handling", () => {
374
- it("should return error when file is not found", async () => {
375
- const { authorizationService, gitHubService } = makeEnv();
376
- const input = makeSampleInput();
377
- gitHubService.getFileContent.mockRejectedValue(new FileNotFoundError(FILE_PATH));
378
- const result = await authorizationService.requestAuthorization(input);
379
- expect(result.isErr()).toBe(true);
380
- const error = result._unsafeUnwrapErr();
381
- expect(error.message).toContain("Unable to get");
382
- expect(error.message).toContain("terraform.tfvars.json");
383
- expect(gitHubService.createBranch).not.toHaveBeenCalled();
384
- expect(gitHubService.updateFile).not.toHaveBeenCalled();
385
- });
386
- it("should upsert groups and create PR when identity already exists", async () => {
387
- const { authorizationService, gitHubService } = makeEnv();
388
- const input = makeSampleInput();
389
- // Identity already present, no groups yet
390
- const content = JSON.stringify({
391
- directory_readers: {
392
- service_principals_name: ["test-bootstrap-identity-id"],
393
- },
394
- }, null, 2);
395
- gitHubService.createBranch.mockResolvedValue(undefined);
396
- gitHubService.getFileContent.mockResolvedValue({
397
- content,
398
- sha: "sha-123",
399
- });
400
- gitHubService.updateFile.mockResolvedValue(undefined);
401
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/55"));
402
- const result = await authorizationService.requestAuthorization(input);
403
- expect(result.isOk()).toBe(true);
404
- const authResult = result._unsafeUnwrap();
405
- expect(authResult.url).toBe("https://github.com/pagopa/eng-azure-authorization/pull/55");
406
- // Identity must NOT be duplicated
407
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
408
- const updatedParsed = JSON.parse(updateCall.content);
409
- expect(updatedParsed.directory_readers.service_principals_name).toHaveLength(1);
410
- expect(updatedParsed.directory_readers.service_principals_name).toContain("test-bootstrap-identity-id");
411
- // All default groups must be created
412
- expect(updatedParsed.groups).toHaveLength(DEFAULT_GROUP_SPECS.length);
413
- });
414
- it("should skip update and PR when identity exists and groups are already correct", async () => {
415
- const { authorizationService, gitHubService } = makeEnv();
416
- const input = makeSampleInput();
417
- // Build a file where the identity is present and all groups are already correct
418
- const allGroups = DEFAULT_GROUP_SPECS.map((spec) => ({
419
- members: [],
420
- name: makeGroupName("test", "d", spec.groupName),
421
- roles: [...spec.roles],
422
- }));
423
- const content = JSON.stringify({
424
- directory_readers: {
425
- service_principals_name: ["test-bootstrap-identity-id"],
426
- },
427
- groups: allGroups,
428
- }, null, 2);
429
- gitHubService.getFileContent.mockResolvedValue({
430
- content,
431
- sha: "sha-noop",
432
- });
433
- const result = await authorizationService.requestAuthorization(input);
434
- // Should succeed as a no-op (no PR created)
435
- expect(result.isOk()).toBe(true);
436
- expect(result._unsafeUnwrap().url).toBeUndefined();
437
- // Branch, file update, and PR must not be touched
438
- expect(gitHubService.createBranch).not.toHaveBeenCalled();
439
- expect(gitHubService.updateFile).not.toHaveBeenCalled();
440
- expect(gitHubService.createPullRequest).not.toHaveBeenCalled();
441
- });
442
- it("should update group roles and create PR when identity exists with wrong roles", async () => {
443
- const { authorizationService, gitHubService } = makeEnv();
444
- const input = makeSampleInput();
445
- // Identity present, but externals group has wrong roles
446
- const content = JSON.stringify({
447
- directory_readers: {
448
- service_principals_name: ["test-bootstrap-identity-id"],
449
- },
450
- groups: [
451
- {
452
- members: ["bob@pagopa.it"],
453
- name: "test-d-adgroup-externals",
454
- roles: ["Reader"],
455
- },
456
- ],
457
- }, null, 2);
458
- gitHubService.createBranch.mockResolvedValue(undefined);
459
- gitHubService.getFileContent.mockResolvedValue({
460
- content,
461
- sha: "sha-roles",
462
- });
463
- gitHubService.updateFile.mockResolvedValue(undefined);
464
- gitHubService.createPullRequest.mockResolvedValue(new PullRequest("https://github.com/pagopa/eng-azure-authorization/pull/56"));
465
- const result = await authorizationService.requestAuthorization(input);
466
- expect(result.isOk()).toBe(true);
467
- expect(result._unsafeUnwrap().url).toBe("https://github.com/pagopa/eng-azure-authorization/pull/56");
468
- const updateCall = gitHubService.updateFile.mock.calls[0][0];
469
- const updatedParsed = JSON.parse(updateCall.content);
470
- const externalsGroup = updatedParsed.groups.find((g) => g.name === "test-d-adgroup-externals");
471
- expect(externalsGroup.roles).toEqual(["Owner"]);
472
- // Member preserved
473
- expect(externalsGroup.members).toContain("bob@pagopa.it");
474
- // Identity not duplicated
475
- expect(updatedParsed.directory_readers.service_principals_name).toHaveLength(1);
476
- });
477
- it("should return error when file content is not valid JSON", async () => {
478
- const { authorizationService, gitHubService } = makeEnv();
479
- const input = makeSampleInput();
480
- gitHubService.getFileContent.mockResolvedValue({
481
- content: "not valid json {{",
482
- sha: "sha-123",
483
- });
484
- const result = await authorizationService.requestAuthorization(input);
485
- expect(result.isErr()).toBe(true);
486
- expect(result._unsafeUnwrapErr()).toBeInstanceOf(InvalidAuthorizationFileFormatError);
487
- expect(gitHubService.createBranch).not.toHaveBeenCalled();
488
- expect(gitHubService.updateFile).not.toHaveBeenCalled();
489
- });
490
- it("should return error when JSON is missing expected keys", async () => {
491
- const { authorizationService, gitHubService } = makeEnv();
492
- const input = makeSampleInput();
493
- gitHubService.getFileContent.mockResolvedValue({
494
- content: JSON.stringify({ unexpected_key: {} }),
495
- sha: "sha-123",
496
- });
497
- const result = await authorizationService.requestAuthorization(input);
498
- expect(result.isErr()).toBe(true);
499
- expect(result._unsafeUnwrapErr()).toBeInstanceOf(InvalidAuthorizationFileFormatError);
500
- expect(gitHubService.createBranch).not.toHaveBeenCalled();
501
- expect(gitHubService.updateFile).not.toHaveBeenCalled();
502
- });
503
- it("should return error when branch creation fails", async () => {
504
- const { authorizationService, gitHubService } = makeEnv();
505
- const input = makeSampleInput();
506
- const content = JSON.stringify({ directory_readers: { service_principals_name: [] } }, null, 2);
507
- gitHubService.getFileContent.mockResolvedValue({
508
- content,
509
- sha: "sha-123",
510
- });
511
- gitHubService.createBranch.mockRejectedValue(new Error("Failed to create branch: branch already exists"));
512
- const result = await authorizationService.requestAuthorization(input);
513
- expect(result.isErr()).toBe(true);
514
- expect(result._unsafeUnwrapErr().message).toContain("Unable to create branch");
515
- expect(gitHubService.updateFile).not.toHaveBeenCalled();
516
- });
517
- it("should return error when file update fails", async () => {
518
- const { authorizationService, gitHubService } = makeEnv();
519
- const input = makeSampleInput();
520
- const content = JSON.stringify({ directory_readers: { service_principals_name: [] } }, null, 2);
521
- gitHubService.createBranch.mockResolvedValue(undefined);
522
- gitHubService.getFileContent.mockResolvedValue({
523
- content,
524
- sha: "sha-123",
525
- });
526
- gitHubService.updateFile.mockRejectedValue(new Error("Failed to update file: conflict"));
527
- const result = await authorizationService.requestAuthorization(input);
528
- expect(result.isErr()).toBe(true);
529
- expect(result._unsafeUnwrapErr().message).toContain("Unable to update");
530
- expect(gitHubService.createPullRequest).not.toHaveBeenCalled();
531
- });
532
- it("should return error when PR creation fails", async () => {
533
- const { authorizationService, gitHubService } = makeEnv();
534
- const input = makeSampleInput();
535
- const content = JSON.stringify({ directory_readers: { service_principals_name: [] } }, null, 2);
536
- gitHubService.createBranch.mockResolvedValue(undefined);
537
- gitHubService.getFileContent.mockResolvedValue({
538
- content,
539
- sha: "sha-123",
540
- });
541
- gitHubService.updateFile.mockResolvedValue(undefined);
542
- gitHubService.createPullRequest.mockRejectedValue(new Error("Failed to create pull request"));
543
- const result = await authorizationService.requestAuthorization(input);
544
- expect(result.isErr()).toBe(true);
545
- expect(result._unsafeUnwrapErr().message).toContain("Unable to create pull request");
546
- });
547
- });
548
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,68 +0,0 @@
1
- /**
2
- * Tests for the internal `runActions` helper that coordinates plop's
3
- * generator execution and surfaces meaningful error messages (CES-1923).
4
- */
5
- import { describe, expect, it } from "vitest";
6
- import { mock } from "vitest-mock-extended";
7
- import { runActions } from "../index.js";
8
- const makeGenerator = (result) => {
9
- const generator = mock();
10
- generator.runActions.mockResolvedValue(result);
11
- return generator;
12
- };
13
- describe("runActions", () => {
14
- it("resolves silently when there are no failures", async () => {
15
- const generator = makeGenerator({ changes: [], failures: [] });
16
- await expect(runActions(generator, {})).resolves.toBeUndefined();
17
- });
18
- it("ignores 'Aborted due to previous action failure' entries", async () => {
19
- const generator = makeGenerator({
20
- changes: [],
21
- failures: [
22
- {
23
- error: "Aborted due to previous action failure",
24
- path: "",
25
- type: "add",
26
- },
27
- ],
28
- });
29
- await expect(runActions(generator, {})).resolves.toBeUndefined();
30
- });
31
- it("surfaces the original failure message (no more 'undefined')", async () => {
32
- const generator = makeGenerator({
33
- changes: [],
34
- failures: [
35
- {
36
- error: "Failed to create the key vault: quota exceeded",
37
- path: "",
38
- type: "initCloudAccounts",
39
- },
40
- ],
41
- });
42
- await expect(runActions(generator, {})).rejects.toThrow(/initCloudAccounts.*Failed to create the key vault: quota exceeded/);
43
- });
44
- it("aggregates multiple failures into the thrown message", async () => {
45
- const generator = makeGenerator({
46
- changes: [],
47
- failures: [
48
- { error: "Missing template", path: "", type: "add" },
49
- {
50
- error: "Aborted due to previous action failure",
51
- path: "",
52
- type: "modify",
53
- },
54
- { error: "Permission denied", path: "", type: "initCloudAccounts" },
55
- ],
56
- });
57
- await expect(runActions(generator, {})).rejects.toThrow(/add: Missing template.*initCloudAccounts: Permission denied/s);
58
- });
59
- it("falls back to 'unknown error' when plop provides no error string", async () => {
60
- const generator = makeGenerator({
61
- changes: [],
62
- failures: [
63
- { error: undefined, path: "", type: "add" },
64
- ],
65
- });
66
- await expect(runActions(generator, {})).rejects.toThrow(/unknown error/);
67
- });
68
- });