@pagopa/dx-savemoney 0.2.6 → 0.3.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 +33 -27
- package/dist/__tests__/finding.test.d.ts +17 -0
- package/dist/__tests__/finding.test.d.ts.map +1 -0
- package/dist/__tests__/finding.test.js +124 -0
- package/dist/__tests__/finding.test.js.map +1 -0
- package/dist/azure/__tests__/analyzer-tags.test.d.ts +8 -0
- package/dist/azure/__tests__/analyzer-tags.test.d.ts.map +1 -0
- package/dist/azure/__tests__/analyzer-tags.test.js +43 -0
- package/dist/azure/__tests__/analyzer-tags.test.js.map +1 -0
- package/dist/azure/__tests__/config.test.d.ts +9 -0
- package/dist/azure/__tests__/config.test.d.ts.map +1 -0
- package/dist/azure/__tests__/config.test.js +70 -0
- package/dist/azure/__tests__/config.test.js.map +1 -0
- package/dist/azure/__tests__/report.test.d.ts +9 -0
- package/dist/azure/__tests__/report.test.d.ts.map +1 -0
- package/dist/azure/__tests__/report.test.js +120 -0
- package/dist/azure/__tests__/report.test.js.map +1 -0
- package/dist/azure/__tests__/utils.test.d.ts +15 -0
- package/dist/azure/__tests__/utils.test.d.ts.map +1 -0
- package/dist/azure/__tests__/utils.test.js +181 -0
- package/dist/azure/__tests__/utils.test.js.map +1 -0
- package/dist/azure/analyzer.d.ts +18 -5
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +295 -48
- package/dist/azure/analyzer.js.map +1 -1
- package/dist/azure/analyzers/__tests__/advisor.test.d.ts +9 -0
- package/dist/azure/analyzers/__tests__/advisor.test.d.ts.map +1 -0
- package/dist/azure/analyzers/__tests__/advisor.test.js +314 -0
- package/dist/azure/analyzers/__tests__/advisor.test.js.map +1 -0
- package/dist/azure/analyzers/advisor.d.ts +68 -0
- package/dist/azure/analyzers/advisor.d.ts.map +1 -0
- package/dist/azure/analyzers/advisor.js +234 -0
- package/dist/azure/analyzers/advisor.js.map +1 -0
- package/dist/azure/analyzers/index.d.ts +3 -1
- package/dist/azure/analyzers/index.d.ts.map +1 -1
- package/dist/azure/analyzers/index.js +2 -1
- package/dist/azure/analyzers/index.js.map +1 -1
- package/dist/azure/analyzers/registry.d.ts +8 -0
- package/dist/azure/analyzers/registry.d.ts.map +1 -1
- package/dist/azure/analyzers/registry.js +10 -0
- package/dist/azure/analyzers/registry.js.map +1 -1
- package/dist/azure/analyzers/subscription.d.ts +53 -0
- package/dist/azure/analyzers/subscription.d.ts.map +1 -0
- package/dist/azure/analyzers/subscription.js +18 -0
- package/dist/azure/analyzers/subscription.js.map +1 -0
- package/dist/azure/config.d.ts.map +1 -1
- package/dist/azure/config.js +1 -0
- package/dist/azure/config.js.map +1 -1
- package/dist/azure/index.d.ts +1 -0
- package/dist/azure/index.d.ts.map +1 -1
- package/dist/azure/index.js +1 -0
- package/dist/azure/index.js.map +1 -1
- package/dist/azure/report.d.ts.map +1 -1
- package/dist/azure/report.js +178 -29
- package/dist/azure/report.js.map +1 -1
- package/dist/azure/resources/__tests__/storage.test.d.ts +11 -0
- package/dist/azure/resources/__tests__/storage.test.d.ts.map +1 -0
- package/dist/azure/resources/__tests__/storage.test.js +99 -0
- package/dist/azure/resources/__tests__/storage.test.js.map +1 -0
- package/dist/azure/types.d.ts +28 -1
- package/dist/azure/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +78 -0
- package/dist/index.test.js.map +1 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -0
- package/dist/schema.js.map +1 -1
- package/package.json +5 -3
- package/src/azure/__tests__/analyzer-tags.test.ts +74 -0
- package/src/azure/__tests__/report.test.ts +35 -6
- package/src/azure/analyzer.ts +421 -65
- package/src/azure/analyzers/__tests__/advisor.test.ts +367 -0
- package/src/azure/analyzers/advisor.ts +324 -0
- package/src/azure/analyzers/index.ts +9 -1
- package/src/azure/analyzers/registry.ts +12 -0
- package/src/azure/analyzers/subscription.ts +56 -0
- package/src/azure/config.ts +1 -0
- package/src/azure/index.ts +1 -0
- package/src/azure/report.ts +206 -35
- package/src/azure/types.ts +29 -1
- package/src/index.ts +1 -1
- package/src/schema.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagopa/dx-savemoney",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"DX"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@azure/arm-advisor": "^3.2.0",
|
|
30
31
|
"@azure/arm-appcontainers": "^3.0.0",
|
|
31
32
|
"@azure/arm-appservice": "^17.0.0",
|
|
32
33
|
"@azure/arm-compute": "^23.3.0",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"@azure/arm-resources": "^7.0.0",
|
|
36
37
|
"@azure/identity": "^4.13.1",
|
|
37
38
|
"@logtape/logtape": "^1.3.8",
|
|
39
|
+
"cli-table3": "^0.6.5",
|
|
38
40
|
"js-yaml": "^4.1.1",
|
|
39
41
|
"p-limit": "^7.3.0",
|
|
40
42
|
"zod": "^4.4.2"
|
|
@@ -43,11 +45,11 @@
|
|
|
43
45
|
"@tsconfig/node24": "24.0.4",
|
|
44
46
|
"@types/js-yaml": "^4.0.9",
|
|
45
47
|
"@types/node": "^22.19.17",
|
|
46
|
-
"@vitest/coverage-v8": "^
|
|
48
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
47
49
|
"eslint": "^10.3.0",
|
|
48
50
|
"prettier": "3.8.3",
|
|
49
51
|
"typescript": "~5.9.3",
|
|
50
|
-
"vitest": "^
|
|
52
|
+
"vitest": "^4.1.8",
|
|
51
53
|
"@pagopa/eslint-config": "^6.0.4"
|
|
52
54
|
},
|
|
53
55
|
"scripts": {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for tag-filter behavior on Advisor findings.
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the pure helper used by the Azure analyzer
|
|
5
|
+
* orchestrator to keep filtering semantics explicit and stable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import type { Finding } from "../../finding.js";
|
|
11
|
+
|
|
12
|
+
import { shouldIncludeAdvisorFindingForTags } from "../analyzer.js";
|
|
13
|
+
|
|
14
|
+
function mkFinding(resourceId: string, source: Finding["source"]): Finding {
|
|
15
|
+
return {
|
|
16
|
+
category: "cost",
|
|
17
|
+
code: `${source}.test`,
|
|
18
|
+
reason: "Test finding.",
|
|
19
|
+
resourceId,
|
|
20
|
+
severity: "low",
|
|
21
|
+
source,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("shouldIncludeAdvisorFindingForTags", () => {
|
|
26
|
+
it("includes all findings when no tag filter is active", () => {
|
|
27
|
+
const finding = mkFinding(
|
|
28
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
29
|
+
"advisor",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
shouldIncludeAdvisorFindingForTags(finding, new Set<string>(), false),
|
|
34
|
+
).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("keeps subscription-level Advisor findings global even with tag filters", () => {
|
|
38
|
+
const finding = mkFinding("/subscriptions/sub1", "advisor");
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
shouldIncludeAdvisorFindingForTags(finding, new Set<string>(), true),
|
|
42
|
+
).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("includes resource-level Advisor findings only when resource id is tag-matched", () => {
|
|
46
|
+
const finding = mkFinding(
|
|
47
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
48
|
+
"advisor",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const taggedResourceIds = new Set<string>([
|
|
52
|
+
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(
|
|
56
|
+
shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true),
|
|
57
|
+
).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("excludes resource-level Advisor findings when resource id is not tag-matched", () => {
|
|
61
|
+
const finding = mkFinding(
|
|
62
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm2",
|
|
63
|
+
"advisor",
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const taggedResourceIds = new Set<string>([
|
|
67
|
+
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -52,7 +52,7 @@ const LOW_ENTRY = makeEntry(
|
|
|
52
52
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
53
53
|
|
|
54
54
|
function allLogs(spy: ReturnType<typeof vi.spyOn>) {
|
|
55
|
-
return spy.mock.calls.map((c) => c[0]
|
|
55
|
+
return spy.mock.calls.map((c: string[]) => c[0]).join("\n");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// ── tests ──────────────────────────────────────────────────────────────────
|
|
@@ -95,10 +95,8 @@ describe("generateReport — lint format", () => {
|
|
|
95
95
|
it("splits a multi-sentence reason into separate findings", async () => {
|
|
96
96
|
// Reason has two sentences separated by '. '
|
|
97
97
|
await generateReport([HIGH_ENTRY], "lint");
|
|
98
|
-
const calls = logSpy.mock.calls.map(
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
const findingLines = calls.filter((l) => l.startsWith(" "));
|
|
98
|
+
const calls = logSpy.mock.calls.map((c: string[]) => c[0] ?? "");
|
|
99
|
+
const findingLines = calls.filter((l: string) => l.startsWith(" "));
|
|
102
100
|
// "VM is deallocated." and "No disk activity detected." → 2 findings
|
|
103
101
|
expect(findingLines.length).toBe(2);
|
|
104
102
|
});
|
|
@@ -131,8 +129,39 @@ describe("generateReport — lint format", () => {
|
|
|
131
129
|
|
|
132
130
|
it("prints nothing but the summary for an empty report", async () => {
|
|
133
131
|
await generateReport([], "lint");
|
|
134
|
-
const calls = logSpy.mock.calls.map((c) => c[0]
|
|
132
|
+
const calls = logSpy.mock.calls.map((c: string[]) => c[0]);
|
|
135
133
|
expect(calls).toHaveLength(1);
|
|
136
134
|
expect(calls[0]).toContain("0 issues found");
|
|
137
135
|
});
|
|
138
136
|
});
|
|
137
|
+
|
|
138
|
+
describe("generateReport — table format", () => {
|
|
139
|
+
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
vi.restoreAllMocks();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("renders without throwing for an empty report", async () => {
|
|
150
|
+
await expect(generateReport([], "table")).resolves.toBeUndefined();
|
|
151
|
+
// Table is always printed; the summary line ("0 issues found") follows.
|
|
152
|
+
expect(logSpy.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
153
|
+
const output = logSpy.mock.calls
|
|
154
|
+
.map((c: string[]) => String(c[0]))
|
|
155
|
+
.join("\n");
|
|
156
|
+
expect(output).toContain("0 issues found");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("includes resource name and reason in the rendered table", async () => {
|
|
160
|
+
await generateReport([HIGH_ENTRY], "table");
|
|
161
|
+
const output = logSpy.mock.calls
|
|
162
|
+
.map((c: string[]) => String(c[0]))
|
|
163
|
+
.join("\n");
|
|
164
|
+
expect(output).toContain("vm-high");
|
|
165
|
+
expect(output).toContain("VM is deallocated");
|
|
166
|
+
});
|
|
167
|
+
});
|