@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.
- package/README.md +18 -0
- package/dist/adapters/commander/commands/add.d.ts +2 -6
- package/dist/adapters/commander/commands/add.js +4 -8
- package/dist/adapters/commander/commands/init.d.ts +2 -6
- package/dist/adapters/commander/commands/init.js +6 -5
- package/dist/adapters/commander/commands/savemoney.d.ts +11 -0
- package/dist/adapters/commander/commands/savemoney.js +30 -5
- package/dist/adapters/commander/commands/spec.d.ts +10 -0
- package/dist/adapters/commander/commands/spec.js +13 -0
- package/dist/adapters/commander/index.js +6 -2
- package/dist/adapters/commander/spec.d.ts +16 -0
- package/dist/adapters/commander/spec.js +54 -0
- package/dist/adapters/plop/generators/environment/actions.js +3 -3
- package/dist/adapters/plop/index.js +3 -5
- package/dist/adapters/plop/templates-path.d.ts +5 -0
- package/dist/adapters/plop/templates-path.js +6 -0
- package/dist/domain/dependencies.d.ts +15 -2
- package/dist/domain/spec.d.ts +50 -0
- package/dist/domain/spec.js +8 -0
- package/dist/index.js +15 -12
- package/package.json +5 -5
- package/templates/monorepo/nx.json +0 -1
- package/dist/adapters/azure/__tests__/cloud-account-repository.test.d.ts +0 -1
- package/dist/adapters/azure/__tests__/cloud-account-repository.test.js +0 -95
- package/dist/adapters/azure/__tests__/cloud-account-service.test.d.ts +0 -1
- package/dist/adapters/azure/__tests__/cloud-account-service.test.js +0 -378
- package/dist/adapters/codemods/__tests__/registry.test.d.ts +0 -1
- package/dist/adapters/codemods/__tests__/registry.test.js +0 -56
- package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.d.ts +0 -1
- package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.js +0 -77
- package/dist/adapters/codemods/__tests__/use-pnpm.test.d.ts +0 -1
- package/dist/adapters/codemods/__tests__/use-pnpm.test.js +0 -148
- package/dist/adapters/commander/__tests__/env.test.d.ts +0 -1
- package/dist/adapters/commander/__tests__/env.test.js +0 -45
- package/dist/adapters/commander/__tests__/error-reporting.test.d.ts +0 -1
- package/dist/adapters/commander/__tests__/error-reporting.test.js +0 -63
- package/dist/adapters/commander/__tests__/exit-with-error.test.d.ts +0 -1
- package/dist/adapters/commander/__tests__/exit-with-error.test.js +0 -92
- package/dist/adapters/commander/__tests__/global-options.test.d.ts +0 -1
- package/dist/adapters/commander/__tests__/global-options.test.js +0 -33
- package/dist/adapters/commander/commands/__tests__/add.test.d.ts +0 -4
- package/dist/adapters/commander/commands/__tests__/add.test.js +0 -167
- package/dist/adapters/commander/commands/__tests__/init.test.d.ts +0 -4
- package/dist/adapters/commander/commands/__tests__/init.test.js +0 -48
- package/dist/adapters/commander/commands/__tests__/preconditions.test.d.ts +0 -1
- package/dist/adapters/commander/commands/__tests__/preconditions.test.js +0 -32
- package/dist/adapters/commander/presenters/__tests__/index.test.d.ts +0 -1
- package/dist/adapters/commander/presenters/__tests__/index.test.js +0 -23
- package/dist/adapters/commander/presenters/__tests__/json.test.d.ts +0 -1
- package/dist/adapters/commander/presenters/__tests__/json.test.js +0 -108
- package/dist/adapters/commander/presenters/__tests__/text.test.d.ts +0 -1
- package/dist/adapters/commander/presenters/__tests__/text.test.js +0 -60
- package/dist/adapters/github/__tests__/github-repo.spec.d.ts +0 -1
- package/dist/adapters/github/__tests__/github-repo.spec.js +0 -67
- package/dist/adapters/node/__tests__/data.d.ts +0 -18
- package/dist/adapters/node/__tests__/data.js +0 -22
- package/dist/adapters/node/__tests__/package-json.test.d.ts +0 -1
- package/dist/adapters/node/__tests__/package-json.test.js +0 -86
- package/dist/adapters/node/__tests__/repository.test.d.ts +0 -1
- package/dist/adapters/node/__tests__/repository.test.js +0 -77
- package/dist/adapters/node/fs/__tests__/file-reader.test.d.ts +0 -1
- package/dist/adapters/node/fs/__tests__/file-reader.test.js +0 -80
- package/dist/adapters/node/json/__tests__/index.test.d.ts +0 -1
- package/dist/adapters/node/json/__tests__/index.test.js +0 -14
- package/dist/adapters/octokit/__tests__/index.test.d.ts +0 -1
- package/dist/adapters/octokit/__tests__/index.test.js +0 -414
- package/dist/adapters/pagopa-technology/__tests__/authorization.test.d.ts +0 -4
- package/dist/adapters/pagopa-technology/__tests__/authorization.test.js +0 -548
- package/dist/adapters/plop/__tests__/run-actions.test.d.ts +0 -1
- package/dist/adapters/plop/__tests__/run-actions.test.js +0 -68
- package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.d.ts +0 -1
- package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +0 -171
- package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.d.ts +0 -1
- package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +0 -134
- package/dist/adapters/plop/generators/environment/__tests__/actions.test.d.ts +0 -2
- package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +0 -92
- package/dist/adapters/plop/generators/environment/__tests__/prompts.test.d.ts +0 -1
- package/dist/adapters/plop/generators/environment/__tests__/prompts.test.js +0 -182
- package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.d.ts +0 -1
- package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.js +0 -113
- package/dist/adapters/plop/helpers/__tests__/terraform-state-key.test.d.ts +0 -1
- package/dist/adapters/plop/helpers/__tests__/terraform-state-key.test.js +0 -35
- package/dist/adapters/plop/helpers/__tests__/validate-prompt.test.d.ts +0 -1
- package/dist/adapters/plop/helpers/__tests__/validate-prompt.test.js +0 -60
- package/dist/adapters/yaml/__tests__/index.test.d.ts +0 -1
- package/dist/adapters/yaml/__tests__/index.test.js +0 -53
- package/dist/domain/__tests__/data.d.ts +0 -19
- package/dist/domain/__tests__/data.js +0 -28
- package/dist/domain/__tests__/environment.test.d.ts +0 -1
- package/dist/domain/__tests__/environment.test.js +0 -332
- package/dist/domain/__tests__/info.test.d.ts +0 -1
- package/dist/domain/__tests__/info.test.js +0 -77
- package/dist/domain/__tests__/package-json.test.d.ts +0 -1
- package/dist/domain/__tests__/package-json.test.js +0 -39
- package/dist/domain/__tests__/repository.test.d.ts +0 -1
- package/dist/domain/__tests__/repository.test.js +0 -111
- package/dist/domain/__tests__/workspace.test.d.ts +0 -1
- package/dist/domain/__tests__/workspace.test.js +0 -57
- package/dist/use-cases/__tests__/apply-codemod.test.d.ts +0 -1
- package/dist/use-cases/__tests__/apply-codemod.test.js +0 -71
- package/dist/use-cases/__tests__/list-codemods.test.d.ts +0 -1
- package/dist/use-cases/__tests__/list-codemods.test.js +0 -37
- package/dist/use-cases/__tests__/request-authorization.test.d.ts +0 -4
- 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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|