@pagopa/dx-savemoney 0.3.0 → 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/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/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/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/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/package.json +3 -3
- package/src/azure/__tests__/report.test.ts +10 -8
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for matchesTags() — verifies AND-logic tag filtering:
|
|
3
|
+
* 1. No filter (undefined/empty) → always include the resource.
|
|
4
|
+
* 2. Exact key-value match → include.
|
|
5
|
+
* 3. Key missing on resource → exclude.
|
|
6
|
+
* 4. Key present but wrong value → exclude.
|
|
7
|
+
* 5. Multiple tags: all match → include; any mismatch → exclude.
|
|
8
|
+
*
|
|
9
|
+
* Tests for getMetric() cache behaviour:
|
|
10
|
+
* 6. resetMetricsCache() clears state between runs.
|
|
11
|
+
* 7. Concurrent calls for the same key coalesce into one network call.
|
|
12
|
+
* 8. A failed call is cached as a fulfilled null result (not retried silently).
|
|
13
|
+
*/
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { _metricsCacheSize, getMetric, matchesTags, resetMetricsCache, } from "../utils.js";
|
|
16
|
+
function makeResource(tags) {
|
|
17
|
+
return { id: "r1", name: "res", tags };
|
|
18
|
+
}
|
|
19
|
+
describe("matchesTags", () => {
|
|
20
|
+
describe("when no filter is provided", () => {
|
|
21
|
+
it("returns true for undefined filterTags", () => {
|
|
22
|
+
expect(matchesTags(makeResource({ env: "prod" }), undefined)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it("returns true for empty Map", () => {
|
|
25
|
+
expect(matchesTags(makeResource({ env: "prod" }), new Map())).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it("returns true even when resource has no tags", () => {
|
|
28
|
+
expect(matchesTags(makeResource(), undefined)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("single tag filter", () => {
|
|
32
|
+
it("returns true when tag key and value match exactly", () => {
|
|
33
|
+
expect(matchesTags(makeResource({ env: "prod" }), new Map([["env", "prod"]]))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it("returns false when tag key is missing from resource", () => {
|
|
36
|
+
expect(matchesTags(makeResource({ app: "myapp" }), new Map([["env", "prod"]]))).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it("returns false when tag key exists but value differs", () => {
|
|
39
|
+
expect(matchesTags(makeResource({ env: "dev" }), new Map([["env", "prod"]]))).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
it("returns false when resource has no tags at all", () => {
|
|
42
|
+
expect(matchesTags(makeResource(), new Map([["env", "prod"]]))).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("is case-sensitive for tag values", () => {
|
|
45
|
+
expect(matchesTags(makeResource({ env: "Prod" }), new Map([["env", "prod"]]))).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("multiple tag filters (AND logic)", () => {
|
|
49
|
+
it("returns true when all filter tags match", () => {
|
|
50
|
+
const resource = makeResource({
|
|
51
|
+
env: "prod",
|
|
52
|
+
region: "italy",
|
|
53
|
+
team: "dx",
|
|
54
|
+
});
|
|
55
|
+
expect(matchesTags(resource, new Map([
|
|
56
|
+
["env", "prod"],
|
|
57
|
+
["team", "dx"],
|
|
58
|
+
]))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("returns false when one of the filter tags is missing", () => {
|
|
61
|
+
const resource = makeResource({ env: "prod" });
|
|
62
|
+
expect(matchesTags(resource, new Map([
|
|
63
|
+
["env", "prod"],
|
|
64
|
+
["team", "dx"],
|
|
65
|
+
]))).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it("returns false when one of the filter tags has the wrong value", () => {
|
|
68
|
+
const resource = makeResource({ env: "prod", team: "backend" });
|
|
69
|
+
expect(matchesTags(resource, new Map([
|
|
70
|
+
["env", "prod"],
|
|
71
|
+
["team", "dx"],
|
|
72
|
+
]))).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
it("returns false when both filter tags have wrong values", () => {
|
|
75
|
+
const resource = makeResource({ env: "dev", team: "backend" });
|
|
76
|
+
expect(matchesTags(resource, new Map([
|
|
77
|
+
["env", "prod"],
|
|
78
|
+
["team", "dx"],
|
|
79
|
+
]))).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// ── metrics cache ──────────────────────────────────────────────────────────
|
|
84
|
+
function makeFailingMonitorClient(calls = []) {
|
|
85
|
+
return {
|
|
86
|
+
metrics: {
|
|
87
|
+
list: vi.fn().mockImplementation(async () => {
|
|
88
|
+
calls.push(1);
|
|
89
|
+
throw new Error("network error");
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function makeMonitorClient(returnValue, calls = []) {
|
|
95
|
+
return {
|
|
96
|
+
metrics: {
|
|
97
|
+
list: vi.fn().mockImplementation(async () => {
|
|
98
|
+
calls.push(1);
|
|
99
|
+
if (returnValue === null) {
|
|
100
|
+
return { value: [] };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
value: [
|
|
104
|
+
{
|
|
105
|
+
timeseries: [{ data: [{ average: returnValue }] }],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
describe("getMetric — in-memory cache", () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
resetMetricsCache();
|
|
116
|
+
});
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
vi.restoreAllMocks();
|
|
119
|
+
});
|
|
120
|
+
it("resetMetricsCache clears all cached entries", async () => {
|
|
121
|
+
const calls = [];
|
|
122
|
+
const client = makeMonitorClient(42, calls);
|
|
123
|
+
await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
|
|
124
|
+
expect(_metricsCacheSize()).toBe(1);
|
|
125
|
+
resetMetricsCache();
|
|
126
|
+
expect(_metricsCacheSize()).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
it("does not call the API a second time for the same key", async () => {
|
|
129
|
+
const calls = [];
|
|
130
|
+
const client = makeMonitorClient(10, calls);
|
|
131
|
+
await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
|
|
132
|
+
await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
|
|
133
|
+
expect(calls.length).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
it("concurrent calls for the same key coalesce into one network call", async () => {
|
|
136
|
+
const calls = [];
|
|
137
|
+
const client = makeMonitorClient(5, calls);
|
|
138
|
+
await Promise.all([
|
|
139
|
+
getMetric(client, "/res/2", "Network In Total", "Average", 30),
|
|
140
|
+
getMetric(client, "/res/2", "Network In Total", "Average", 30),
|
|
141
|
+
getMetric(client, "/res/2", "Network In Total", "Average", 30),
|
|
142
|
+
]);
|
|
143
|
+
expect(calls.length).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
it("different keys result in separate network calls", async () => {
|
|
146
|
+
const calls = [];
|
|
147
|
+
const client = makeMonitorClient(1, calls);
|
|
148
|
+
await getMetric(client, "/res/a", "Percentage CPU", "Average", 7);
|
|
149
|
+
await getMetric(client, "/res/b", "Percentage CPU", "Average", 7);
|
|
150
|
+
expect(calls.length).toBe(2);
|
|
151
|
+
});
|
|
152
|
+
it("returns null and caches the null result when no data points are available", async () => {
|
|
153
|
+
const calls = [];
|
|
154
|
+
const client = makeMonitorClient(null, calls);
|
|
155
|
+
const first = await getMetric(client, "/res/3", "Percentage CPU", "Average", 7);
|
|
156
|
+
const second = await getMetric(client, "/res/3", "Percentage CPU", "Average", 7);
|
|
157
|
+
expect(first).toBeNull();
|
|
158
|
+
expect(second).toBeNull();
|
|
159
|
+
expect(calls.length).toBe(1);
|
|
160
|
+
});
|
|
161
|
+
it("a failing call is cached and returns null on retry", async () => {
|
|
162
|
+
const calls = [];
|
|
163
|
+
const client = makeFailingMonitorClient(calls);
|
|
164
|
+
const first = await getMetric(client, "/res/4", "Percentage CPU", "Average", 7);
|
|
165
|
+
const second = await getMetric(client, "/res/4", "Percentage CPU", "Average", 7);
|
|
166
|
+
// Both calls return null (error is swallowed by getMetric)
|
|
167
|
+
expect(first).toBeNull();
|
|
168
|
+
expect(second).toBeNull();
|
|
169
|
+
// Only one actual network call despite two getMetric calls
|
|
170
|
+
expect(calls.length).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
it("after resetMetricsCache the API is called again", async () => {
|
|
173
|
+
const calls = [];
|
|
174
|
+
const client = makeMonitorClient(7, calls);
|
|
175
|
+
await getMetric(client, "/res/5", "Percentage CPU", "Average", 7);
|
|
176
|
+
resetMetricsCache();
|
|
177
|
+
await getMetric(client, "/res/5", "Percentage CPU", "Average", 7);
|
|
178
|
+
expect(calls.length).toBe(2);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=utils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.js","sourceRoot":"","sources":["../../../src/azure/__tests__/utils.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Azure Advisor subscription-level analyzer.
|
|
3
|
+
*
|
|
4
|
+
* We never touch a real Azure subscription: the analyzer is constructed
|
|
5
|
+
* with an injected client factory that returns a fake exposing only the
|
|
6
|
+
* `recommendations.list()` async iterator the analyzer relies on.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=advisor.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisor.test.d.ts","sourceRoot":"","sources":["../../../../src/azure/analyzers/__tests__/advisor.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Azure Advisor subscription-level analyzer.
|
|
3
|
+
*
|
|
4
|
+
* We never touch a real Azure subscription: the analyzer is constructed
|
|
5
|
+
* with an injected client factory that returns a fake exposing only the
|
|
6
|
+
* `recommendations.list()` async iterator the analyzer relies on.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { createAdvisorAnalyzer } from "../advisor.js";
|
|
10
|
+
function makeCtx() {
|
|
11
|
+
const credential = {
|
|
12
|
+
getToken: async () => null,
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
credential,
|
|
16
|
+
subscriptionId: "00000000-0000-0000-0000-000000000000",
|
|
17
|
+
verbose: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Reusable ARM-style resource IDs used across tests
|
|
21
|
+
const RID1 = "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-1";
|
|
22
|
+
const RID2 = "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-2";
|
|
23
|
+
function makeFakeClient(recs) {
|
|
24
|
+
return {
|
|
25
|
+
recommendations: {
|
|
26
|
+
async *list() {
|
|
27
|
+
for (const r of recs)
|
|
28
|
+
yield r;
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
describe("createAdvisorAnalyzer", () => {
|
|
34
|
+
it("exposes a stable id", () => {
|
|
35
|
+
const analyzer = createAdvisorAnalyzer({
|
|
36
|
+
build: () => makeFakeClient([]),
|
|
37
|
+
});
|
|
38
|
+
expect(analyzer.id).toBe("azure.advisor");
|
|
39
|
+
});
|
|
40
|
+
it("returns no findings for an empty list", async () => {
|
|
41
|
+
const analyzer = createAdvisorAnalyzer({
|
|
42
|
+
build: () => makeFakeClient([]),
|
|
43
|
+
});
|
|
44
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
45
|
+
expect(findings).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("createAdvisorAnalyzer — recommendation mapping", () => {
|
|
49
|
+
it("maps a Cost recommendation to a Finding with savings", async () => {
|
|
50
|
+
const analyzer = createAdvisorAnalyzer({
|
|
51
|
+
build: () => makeFakeClient([
|
|
52
|
+
{
|
|
53
|
+
category: "Cost",
|
|
54
|
+
extendedProperties: {
|
|
55
|
+
savingsAmount: "42.50",
|
|
56
|
+
savingsCurrency: "EUR",
|
|
57
|
+
},
|
|
58
|
+
impact: "High",
|
|
59
|
+
recommendationTypeId: "right-size-vm",
|
|
60
|
+
resourceMetadata: {
|
|
61
|
+
resourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm",
|
|
62
|
+
},
|
|
63
|
+
shortDescription: {
|
|
64
|
+
problem: "Right-size your VM",
|
|
65
|
+
solution: "Switch to a smaller SKU",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
]),
|
|
69
|
+
});
|
|
70
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
71
|
+
expect(findings).toHaveLength(1);
|
|
72
|
+
const finding = findings[0];
|
|
73
|
+
expect(finding.source).toBe("advisor");
|
|
74
|
+
expect(finding.code).toBe("advisor.right-size-vm");
|
|
75
|
+
expect(finding.severity).toBe("high");
|
|
76
|
+
expect(finding.category).toBe("cost");
|
|
77
|
+
expect(finding.estimatedMonthlySavings).toEqual({
|
|
78
|
+
amount: 42.5,
|
|
79
|
+
currency: "EUR",
|
|
80
|
+
});
|
|
81
|
+
expect(finding.recommendedAction).toBe("Switch to a smaller SKU");
|
|
82
|
+
expect(finding.reason).toBe("Right-size your VM.");
|
|
83
|
+
expect(finding.resourceId).toBe("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm");
|
|
84
|
+
});
|
|
85
|
+
it("maps `Medium` impact to severity `medium`", async () => {
|
|
86
|
+
const analyzer = createAdvisorAnalyzer({
|
|
87
|
+
build: () => makeFakeClient([
|
|
88
|
+
{
|
|
89
|
+
category: "Cost",
|
|
90
|
+
impact: "Medium",
|
|
91
|
+
recommendationTypeId: "x",
|
|
92
|
+
resourceMetadata: { resourceId: RID1 },
|
|
93
|
+
shortDescription: { problem: "p" },
|
|
94
|
+
},
|
|
95
|
+
]),
|
|
96
|
+
});
|
|
97
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
98
|
+
expect(findings[0].severity).toBe("medium");
|
|
99
|
+
});
|
|
100
|
+
it("falls back to `low` for unknown impact values", async () => {
|
|
101
|
+
const analyzer = createAdvisorAnalyzer({
|
|
102
|
+
build: () => makeFakeClient([
|
|
103
|
+
{
|
|
104
|
+
category: "Cost",
|
|
105
|
+
impact: "Bogus",
|
|
106
|
+
recommendationTypeId: "x",
|
|
107
|
+
resourceMetadata: { resourceId: RID1 },
|
|
108
|
+
shortDescription: { problem: "p" },
|
|
109
|
+
},
|
|
110
|
+
]),
|
|
111
|
+
});
|
|
112
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
113
|
+
expect(findings[0].severity).toBe("low");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe("createAdvisorAnalyzer — savings parsing", () => {
|
|
117
|
+
it("omits estimatedMonthlySavings when savingsAmount is missing", async () => {
|
|
118
|
+
const analyzer = createAdvisorAnalyzer({
|
|
119
|
+
build: () => makeFakeClient([
|
|
120
|
+
{
|
|
121
|
+
category: "Cost",
|
|
122
|
+
impact: "Low",
|
|
123
|
+
recommendationTypeId: "x",
|
|
124
|
+
resourceMetadata: { resourceId: RID1 },
|
|
125
|
+
shortDescription: { problem: "p" },
|
|
126
|
+
},
|
|
127
|
+
]),
|
|
128
|
+
});
|
|
129
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
130
|
+
expect(findings[0].estimatedMonthlySavings).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
it("omits estimatedMonthlySavings when savingsAmount is not a number", async () => {
|
|
133
|
+
const analyzer = createAdvisorAnalyzer({
|
|
134
|
+
build: () => makeFakeClient([
|
|
135
|
+
{
|
|
136
|
+
category: "Cost",
|
|
137
|
+
extendedProperties: { savingsAmount: "n/a" },
|
|
138
|
+
impact: "Low",
|
|
139
|
+
recommendationTypeId: "x",
|
|
140
|
+
resourceMetadata: { resourceId: RID1 },
|
|
141
|
+
shortDescription: { problem: "p" },
|
|
142
|
+
},
|
|
143
|
+
]),
|
|
144
|
+
});
|
|
145
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
146
|
+
expect(findings[0].estimatedMonthlySavings).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
it("defaults the currency to USD when only savingsAmount is present", async () => {
|
|
149
|
+
const analyzer = createAdvisorAnalyzer({
|
|
150
|
+
build: () => makeFakeClient([
|
|
151
|
+
{
|
|
152
|
+
category: "Cost",
|
|
153
|
+
extendedProperties: { savingsAmount: "10" },
|
|
154
|
+
impact: "Low",
|
|
155
|
+
recommendationTypeId: "x",
|
|
156
|
+
resourceMetadata: { resourceId: RID1 },
|
|
157
|
+
shortDescription: { problem: "p" },
|
|
158
|
+
},
|
|
159
|
+
]),
|
|
160
|
+
});
|
|
161
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
162
|
+
expect(findings[0].estimatedMonthlySavings).toEqual({
|
|
163
|
+
amount: 10,
|
|
164
|
+
currency: "USD",
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe("createAdvisorAnalyzer — filtering", () => {
|
|
169
|
+
it("skips non-Cost recommendations", async () => {
|
|
170
|
+
const analyzer = createAdvisorAnalyzer({
|
|
171
|
+
build: () => makeFakeClient([
|
|
172
|
+
{
|
|
173
|
+
category: "Security",
|
|
174
|
+
impact: "High",
|
|
175
|
+
recommendationTypeId: "x",
|
|
176
|
+
resourceMetadata: { resourceId: RID1 },
|
|
177
|
+
shortDescription: { problem: "p" },
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
category: "Cost",
|
|
181
|
+
impact: "Low",
|
|
182
|
+
recommendationTypeId: "y",
|
|
183
|
+
resourceMetadata: { resourceId: RID2 },
|
|
184
|
+
shortDescription: { problem: "p" },
|
|
185
|
+
},
|
|
186
|
+
]),
|
|
187
|
+
});
|
|
188
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
189
|
+
expect(findings).toHaveLength(1);
|
|
190
|
+
expect(findings[0].code).toBe("advisor.y");
|
|
191
|
+
});
|
|
192
|
+
it("attributes subscription as fallback resource when resourceId is absent", async () => {
|
|
193
|
+
const ctx = makeCtx();
|
|
194
|
+
const analyzer = createAdvisorAnalyzer({
|
|
195
|
+
build: () => makeFakeClient([
|
|
196
|
+
{
|
|
197
|
+
category: "Cost",
|
|
198
|
+
impact: "Low",
|
|
199
|
+
recommendationTypeId: "x",
|
|
200
|
+
resourceMetadata: {},
|
|
201
|
+
shortDescription: { problem: "p" },
|
|
202
|
+
},
|
|
203
|
+
]),
|
|
204
|
+
});
|
|
205
|
+
const findings = await analyzer.analyze(ctx);
|
|
206
|
+
expect(findings).toHaveLength(1);
|
|
207
|
+
expect(findings[0].resourceId).toBe(`/subscriptions/${ctx.subscriptionId}`);
|
|
208
|
+
});
|
|
209
|
+
it("includes subscription-scoped recommendations using their own resource ID", async () => {
|
|
210
|
+
const SUB_URI = "/subscriptions/00000000-0000-0000-0000-000000000000";
|
|
211
|
+
const analyzer = createAdvisorAnalyzer({
|
|
212
|
+
build: () => makeFakeClient([
|
|
213
|
+
{
|
|
214
|
+
category: "Cost",
|
|
215
|
+
impact: "High",
|
|
216
|
+
recommendationTypeId: "reserved-instance",
|
|
217
|
+
resourceMetadata: { resourceId: SUB_URI },
|
|
218
|
+
shortDescription: { problem: "Consider reserved instances" },
|
|
219
|
+
},
|
|
220
|
+
]),
|
|
221
|
+
});
|
|
222
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
223
|
+
expect(findings).toHaveLength(1);
|
|
224
|
+
expect(findings[0].resourceId).toBe(SUB_URI);
|
|
225
|
+
expect(findings[0].severity).toBe("high");
|
|
226
|
+
});
|
|
227
|
+
it("aggregates subscription-scoped findings with the same recommendationTypeId", async () => {
|
|
228
|
+
const SUB_URI = "/subscriptions/00000000-0000-0000-0000-000000000000";
|
|
229
|
+
const analyzer = createAdvisorAnalyzer({
|
|
230
|
+
build: () => makeFakeClient([
|
|
231
|
+
{
|
|
232
|
+
category: "Cost",
|
|
233
|
+
extendedProperties: {
|
|
234
|
+
savingsAmount: "121",
|
|
235
|
+
savingsCurrency: "EUR",
|
|
236
|
+
},
|
|
237
|
+
id: "/subscriptions/sub1/advisorRecommendations/r1",
|
|
238
|
+
impact: "High",
|
|
239
|
+
recommendationTypeId: "postgresql-ri",
|
|
240
|
+
resourceMetadata: { resourceId: SUB_URI },
|
|
241
|
+
shortDescription: {
|
|
242
|
+
problem: "Consider PostgreSQL reserved instance",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
category: "Cost",
|
|
247
|
+
extendedProperties: { savingsAmount: "71", savingsCurrency: "EUR" },
|
|
248
|
+
id: "/subscriptions/sub1/advisorRecommendations/r2",
|
|
249
|
+
impact: "High",
|
|
250
|
+
recommendationTypeId: "postgresql-ri",
|
|
251
|
+
resourceMetadata: { resourceId: SUB_URI },
|
|
252
|
+
shortDescription: {
|
|
253
|
+
problem: "Consider PostgreSQL reserved instance",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
]),
|
|
257
|
+
});
|
|
258
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
259
|
+
// Two API entries with the same type → one aggregated finding.
|
|
260
|
+
expect(findings).toHaveLength(1);
|
|
261
|
+
expect(findings[0].code).toBe("advisor.postgresql-ri");
|
|
262
|
+
expect(findings[0].estimatedMonthlySavings).toEqual({
|
|
263
|
+
amount: 121,
|
|
264
|
+
currency: "EUR",
|
|
265
|
+
});
|
|
266
|
+
expect(findings[0].reason).toContain("2 options");
|
|
267
|
+
});
|
|
268
|
+
it("deduplicates subscription-scoped entries with the same recommendation ARM ID", async () => {
|
|
269
|
+
const SUB_URI = "/subscriptions/00000000-0000-0000-0000-000000000000";
|
|
270
|
+
const dupId = "/subscriptions/sub1/providers/microsoft.advisor/recommendations/dup";
|
|
271
|
+
const analyzer = createAdvisorAnalyzer({
|
|
272
|
+
build: () => makeFakeClient([
|
|
273
|
+
{
|
|
274
|
+
category: "Cost",
|
|
275
|
+
extendedProperties: { savingsAmount: "50", savingsCurrency: "EUR" },
|
|
276
|
+
id: dupId,
|
|
277
|
+
impact: "High",
|
|
278
|
+
recommendationTypeId: "vm-ri",
|
|
279
|
+
resourceMetadata: { resourceId: SUB_URI },
|
|
280
|
+
shortDescription: { problem: "Buy VM reserved instance" },
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
// Same ARM ID returned again by the API — should be counted only once.
|
|
284
|
+
category: "Cost",
|
|
285
|
+
extendedProperties: { savingsAmount: "50", savingsCurrency: "EUR" },
|
|
286
|
+
id: dupId,
|
|
287
|
+
impact: "High",
|
|
288
|
+
recommendationTypeId: "vm-ri",
|
|
289
|
+
resourceMetadata: { resourceId: SUB_URI },
|
|
290
|
+
shortDescription: { problem: "Buy VM reserved instance" },
|
|
291
|
+
},
|
|
292
|
+
]),
|
|
293
|
+
});
|
|
294
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
295
|
+
expect(findings).toHaveLength(1);
|
|
296
|
+
// Savings must NOT be doubled.
|
|
297
|
+
expect(findings[0].estimatedMonthlySavings?.amount).toBe(50);
|
|
298
|
+
});
|
|
299
|
+
it("uses a fallback reason when shortDescription is missing", async () => {
|
|
300
|
+
const analyzer = createAdvisorAnalyzer({
|
|
301
|
+
build: () => makeFakeClient([
|
|
302
|
+
{
|
|
303
|
+
category: "Cost",
|
|
304
|
+
impact: "Low",
|
|
305
|
+
recommendationTypeId: "x",
|
|
306
|
+
resourceMetadata: { resourceId: RID1 },
|
|
307
|
+
},
|
|
308
|
+
]),
|
|
309
|
+
});
|
|
310
|
+
const findings = await analyzer.analyze(makeCtx());
|
|
311
|
+
expect(findings[0].reason).toBe("Azure Advisor cost recommendation.");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
//# sourceMappingURL=advisor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisor.test.js","sourceRoot":"","sources":["../../../../src/azure/analyzers/__tests__/advisor.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for analyzeStorageAccount() — verifies that custom thresholds
|
|
3
|
+
* actually change the analysis outcome.
|
|
4
|
+
*
|
|
5
|
+
* Key scenario:
|
|
6
|
+
* - Metric: 30 avg transactions/day
|
|
7
|
+
* - Default threshold: 10 → 30 >= 10 → NOT flagged
|
|
8
|
+
* - Custom threshold: 50 → 30 < 50 → IS flagged ✓ proves the feature works
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=storage.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.test.d.ts","sourceRoot":"","sources":["../../../../src/azure/resources/__tests__/storage.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for analyzeStorageAccount() — verifies that custom thresholds
|
|
3
|
+
* actually change the analysis outcome.
|
|
4
|
+
*
|
|
5
|
+
* Key scenario:
|
|
6
|
+
* - Metric: 30 avg transactions/day
|
|
7
|
+
* - Default threshold: 10 → 30 >= 10 → NOT flagged
|
|
8
|
+
* - Custom threshold: 50 → 30 < 50 → IS flagged ✓ proves the feature works
|
|
9
|
+
*/
|
|
10
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { DEFAULT_THRESHOLDS } from "../../../types.js";
|
|
12
|
+
// Mock the utils module so we control getMetric without real Azure calls
|
|
13
|
+
vi.mock("../../utils.js", () => ({
|
|
14
|
+
getMetric: vi.fn(),
|
|
15
|
+
verboseLog: vi.fn(),
|
|
16
|
+
verboseLogAnalysisResult: vi.fn(),
|
|
17
|
+
verboseLogResourceStart: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
// Import after vi.mock so the module resolves the mock
|
|
20
|
+
import { getMetric } from "../../utils.js";
|
|
21
|
+
import { analyzeStorageAccount } from "../storage.js";
|
|
22
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
23
|
+
// Minimal stub: analyzeStorageAccount only needs monitorClient to be passed
|
|
24
|
+
// through to getMetric, which is fully mocked. We provide only the shape that
|
|
25
|
+
// TypeScript requires without unsafe type assertions.
|
|
26
|
+
const FAKE_CLIENT = {
|
|
27
|
+
metrics: { list: vi.fn() },
|
|
28
|
+
};
|
|
29
|
+
const FAKE_RESOURCE = {
|
|
30
|
+
id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Storage/storageAccounts/st1",
|
|
31
|
+
name: "st1",
|
|
32
|
+
type: "Microsoft.Storage/storageAccounts",
|
|
33
|
+
};
|
|
34
|
+
const mockGetMetric = vi.mocked(getMetric);
|
|
35
|
+
// ── tests ──────────────────────────────────────────────────────────────────
|
|
36
|
+
describe("analyzeStorageAccount — threshold sensitivity", () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
mockGetMetric.mockReset();
|
|
39
|
+
});
|
|
40
|
+
describe("with DEFAULT threshold (transactionsPerDay = 10)", () => {
|
|
41
|
+
it("does NOT flag a resource with 30 transactions/day (30 ≥ 10)", async () => {
|
|
42
|
+
mockGetMetric.mockResolvedValue(30);
|
|
43
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, DEFAULT_THRESHOLDS);
|
|
44
|
+
expect(result.suspectedUnused).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
it("flags a resource with 5 transactions/day (5 < 10)", async () => {
|
|
47
|
+
mockGetMetric.mockResolvedValue(5);
|
|
48
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, DEFAULT_THRESHOLDS);
|
|
49
|
+
expect(result.suspectedUnused).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("does not flag when metric is exactly at the threshold (10)", async () => {
|
|
52
|
+
mockGetMetric.mockResolvedValue(10);
|
|
53
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, DEFAULT_THRESHOLDS);
|
|
54
|
+
// 10 < 10 is false → not flagged
|
|
55
|
+
expect(result.suspectedUnused).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe("with CUSTOM threshold (transactionsPerDay = 50)", () => {
|
|
59
|
+
const customThresholds = {
|
|
60
|
+
...DEFAULT_THRESHOLDS,
|
|
61
|
+
storage: { transactionsPerDay: 50 },
|
|
62
|
+
};
|
|
63
|
+
it("DOES flag a resource with 30 transactions/day (30 < 50) — proves override works", async () => {
|
|
64
|
+
mockGetMetric.mockResolvedValue(30);
|
|
65
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, customThresholds);
|
|
66
|
+
expect(result.suspectedUnused).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("does NOT flag a resource with 60 transactions/day (60 ≥ 50)", async () => {
|
|
69
|
+
mockGetMetric.mockResolvedValue(60);
|
|
70
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, customThresholds);
|
|
71
|
+
expect(result.suspectedUnused).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it("flags at threshold boundary (49 < 50)", async () => {
|
|
74
|
+
mockGetMetric.mockResolvedValue(49);
|
|
75
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, customThresholds);
|
|
76
|
+
expect(result.suspectedUnused).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe("edge cases", () => {
|
|
80
|
+
it("returns suspectedUnused: false when metric is null (data unavailable)", async () => {
|
|
81
|
+
mockGetMetric.mockResolvedValue(null);
|
|
82
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30, DEFAULT_THRESHOLDS);
|
|
83
|
+
expect(result.suspectedUnused).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
it("returns suspectedUnused: false when resource id is missing", async () => {
|
|
86
|
+
const resourceWithoutId = { ...FAKE_RESOURCE, id: undefined };
|
|
87
|
+
const result = await analyzeStorageAccount(resourceWithoutId, FAKE_CLIENT, 30, DEFAULT_THRESHOLDS);
|
|
88
|
+
expect(result.suspectedUnused).toBe(false);
|
|
89
|
+
expect(mockGetMetric).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
it("applies default thresholds when the parameter is omitted", async () => {
|
|
92
|
+
// 5 < DEFAULT 10 → flagged even without explicit thresholds argument
|
|
93
|
+
mockGetMetric.mockResolvedValue(5);
|
|
94
|
+
const result = await analyzeStorageAccount(FAKE_RESOURCE, FAKE_CLIENT, 30);
|
|
95
|
+
expect(result.suspectedUnused).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=storage.test.js.map
|