@pagopa/dx-savemoney 0.6.1 → 0.6.2
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/azure/pricing/pricing-service.d.ts.map +1 -1
- package/dist/azure/pricing/pricing-service.js +6 -1
- package/dist/azure/pricing/pricing-service.js.map +1 -1
- package/dist/azure/pricing/schema.d.ts.map +1 -1
- package/dist/azure/pricing/schema.js +22 -14
- package/dist/azure/pricing/schema.js.map +1 -1
- package/dist/azure/resources/app-service.d.ts.map +1 -1
- package/dist/azure/resources/app-service.js +2 -2
- package/dist/azure/resources/app-service.js.map +1 -1
- package/dist/azure/resources/container-app.d.ts.map +1 -1
- package/dist/azure/resources/container-app.js +1 -1
- package/dist/azure/resources/container-app.js.map +1 -1
- package/dist/azure/resources/disk.js +3 -1
- package/dist/azure/resources/disk.js.map +1 -1
- package/dist/azure/resources/nic.js +3 -1
- package/dist/azure/resources/nic.js.map +1 -1
- package/dist/azure/resources/private-endpoint.d.ts.map +1 -1
- package/dist/azure/resources/private-endpoint.js +1 -1
- package/dist/azure/resources/private-endpoint.js.map +1 -1
- package/dist/azure/resources/public-ip.d.ts.map +1 -1
- package/dist/azure/resources/public-ip.js +4 -2
- package/dist/azure/resources/public-ip.js.map +1 -1
- package/dist/azure/resources/static-web-app.d.ts.map +1 -1
- package/dist/azure/resources/static-web-app.js +1 -1
- package/dist/azure/resources/static-web-app.js.map +1 -1
- package/dist/azure/resources/vm.js +6 -2
- package/dist/azure/resources/vm.js.map +1 -1
- package/dist/azure/utils.d.ts.map +1 -1
- package/dist/azure/utils.js +7 -2
- package/dist/azure/utils.js.map +1 -1
- package/package.json +1 -8
- package/src/azure/pricing/__tests__/schema.test.ts +30 -0
- package/src/azure/pricing/pricing-service.ts +6 -3
- package/src/azure/pricing/schema.ts +22 -14
- package/src/azure/resources/app-service.ts +4 -2
- package/src/azure/resources/container-app.ts +2 -1
- package/src/azure/resources/disk.ts +3 -3
- package/src/azure/resources/nic.ts +3 -3
- package/src/azure/resources/private-endpoint.ts +2 -1
- package/src/azure/resources/public-ip.ts +5 -4
- package/src/azure/resources/static-web-app.ts +2 -1
- package/src/azure/resources/vm.ts +6 -6
- package/src/azure/utils.ts +8 -2
- package/dist/__tests__/finding.test.d.ts +0 -17
- package/dist/__tests__/finding.test.d.ts.map +0 -1
- package/dist/__tests__/finding.test.js +0 -146
- package/dist/__tests__/finding.test.js.map +0 -1
- package/dist/azure/__tests__/analyzer-tags.test.d.ts +0 -8
- package/dist/azure/__tests__/analyzer-tags.test.d.ts.map +0 -1
- package/dist/azure/__tests__/analyzer-tags.test.js +0 -70
- package/dist/azure/__tests__/analyzer-tags.test.js.map +0 -1
- package/dist/azure/__tests__/azqr.test.d.ts +0 -13
- package/dist/azure/__tests__/azqr.test.d.ts.map +0 -1
- package/dist/azure/__tests__/azqr.test.js +0 -177
- package/dist/azure/__tests__/azqr.test.js.map +0 -1
- package/dist/azure/__tests__/config.test.d.ts +0 -9
- package/dist/azure/__tests__/config.test.d.ts.map +0 -1
- package/dist/azure/__tests__/config.test.js +0 -102
- package/dist/azure/__tests__/config.test.js.map +0 -1
- package/dist/azure/__tests__/report.test.d.ts +0 -9
- package/dist/azure/__tests__/report.test.d.ts.map +0 -1
- package/dist/azure/__tests__/report.test.js +0 -191
- package/dist/azure/__tests__/report.test.js.map +0 -1
- package/dist/azure/__tests__/utils.test.d.ts +0 -15
- package/dist/azure/__tests__/utils.test.d.ts.map +0 -1
- package/dist/azure/__tests__/utils.test.js +0 -181
- package/dist/azure/__tests__/utils.test.js.map +0 -1
- package/dist/azure/analyzers/__tests__/advisor.test.d.ts +0 -9
- package/dist/azure/analyzers/__tests__/advisor.test.d.ts.map +0 -1
- package/dist/azure/analyzers/__tests__/advisor.test.js +0 -314
- package/dist/azure/analyzers/__tests__/advisor.test.js.map +0 -1
- package/dist/azure/pricing/__tests__/cache.test.d.ts +0 -8
- package/dist/azure/pricing/__tests__/cache.test.d.ts.map +0 -1
- package/dist/azure/pricing/__tests__/cache.test.js +0 -56
- package/dist/azure/pricing/__tests__/cache.test.js.map +0 -1
- package/dist/azure/pricing/__tests__/client.test.d.ts +0 -9
- package/dist/azure/pricing/__tests__/client.test.d.ts.map +0 -1
- package/dist/azure/pricing/__tests__/client.test.js +0 -181
- package/dist/azure/pricing/__tests__/client.test.js.map +0 -1
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +0 -6
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +0 -1
- package/dist/azure/pricing/__tests__/pricing-service.test.js +0 -283
- package/dist/azure/pricing/__tests__/pricing-service.test.js.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +0 -5
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +0 -137
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +0 -5
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js +0 -160
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +0 -5
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +0 -113
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +0 -9
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +0 -1
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js +0 -204
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +0 -1
- package/dist/azure/resources/__tests__/app-service.test.d.ts +0 -5
- package/dist/azure/resources/__tests__/app-service.test.d.ts.map +0 -1
- package/dist/azure/resources/__tests__/app-service.test.js +0 -105
- package/dist/azure/resources/__tests__/app-service.test.js.map +0 -1
- package/dist/azure/resources/__tests__/storage.test.d.ts +0 -11
- package/dist/azure/resources/__tests__/storage.test.d.ts.map +0 -1
- package/dist/azure/resources/__tests__/storage.test.js +0 -99
- package/dist/azure/resources/__tests__/storage.test.js.map +0 -1
|
@@ -139,7 +139,8 @@ export async function analyzeAppServicePlan(
|
|
|
139
139
|
} catch (error) {
|
|
140
140
|
const logger = getLogger(["savemoney", "azure"]);
|
|
141
141
|
logger.warn(
|
|
142
|
-
`Failed to get App Service Plan details for ${planName}:
|
|
142
|
+
`Failed to get App Service Plan details for ${planName}: {message}`,
|
|
143
|
+
{ message: error instanceof Error ? error.message : error },
|
|
143
144
|
);
|
|
144
145
|
reason += "Could not retrieve detailed App Service Plan information. ";
|
|
145
146
|
}
|
|
@@ -184,7 +185,8 @@ async function enrichWithPricing(
|
|
|
184
185
|
} catch (error) {
|
|
185
186
|
const logger = getLogger(["savemoney", "azure"]);
|
|
186
187
|
logger.warn(
|
|
187
|
-
`Failed to enrich App Service Plan ${resource.name ?? "unknown"} with pricing:
|
|
188
|
+
`Failed to enrich App Service Plan ${resource.name ?? "unknown"} with pricing: {message}`,
|
|
189
|
+
{ message: error instanceof Error ? error.message : error },
|
|
188
190
|
);
|
|
189
191
|
return result;
|
|
190
192
|
}
|
|
@@ -100,7 +100,8 @@ export async function analyzeContainerApp(
|
|
|
100
100
|
} catch (error) {
|
|
101
101
|
const logger = getLogger(["savemoney", "azure"]);
|
|
102
102
|
logger.warn(
|
|
103
|
-
`Failed to get Container App details for ${containerAppName}:
|
|
103
|
+
`Failed to get Container App details for ${containerAppName}: {message}`,
|
|
104
|
+
{ message: error instanceof Error ? error.message : error },
|
|
104
105
|
);
|
|
105
106
|
reason += "Could not retrieve detailed Container App information. ";
|
|
106
107
|
}
|
|
@@ -81,9 +81,9 @@ export async function analyzeDisk(
|
|
|
81
81
|
}
|
|
82
82
|
} catch (error) {
|
|
83
83
|
const logger = getLogger(["savemoney", "azure"]);
|
|
84
|
-
logger.warn(
|
|
85
|
-
|
|
86
|
-
);
|
|
84
|
+
logger.warn(`Failed to get disk details for ${diskName}: {message}`, {
|
|
85
|
+
message: error instanceof Error ? error.message : error,
|
|
86
|
+
});
|
|
87
87
|
// Fallback to checking properties if API call fails
|
|
88
88
|
// Note: GenericResource doesn't have diskState property
|
|
89
89
|
// Without API access, we can't determine if disk is unattached
|
|
@@ -77,9 +77,9 @@ export async function analyzeNic(
|
|
|
77
77
|
// The primary checks (attachment to VM and public IP assignment) are sufficient
|
|
78
78
|
} catch (error) {
|
|
79
79
|
const logger = getLogger(["savemoney", "azure"]);
|
|
80
|
-
logger.warn(
|
|
81
|
-
|
|
82
|
-
);
|
|
80
|
+
logger.warn(`Failed to get NIC details for ${nicName}: {message}`, {
|
|
81
|
+
message: error instanceof Error ? error.message : error,
|
|
82
|
+
});
|
|
83
83
|
reason += "Could not retrieve detailed NIC information. ";
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -100,7 +100,8 @@ export async function analyzePrivateEndpoint(
|
|
|
100
100
|
} catch (error) {
|
|
101
101
|
const logger = getLogger(["savemoney", "azure"]);
|
|
102
102
|
logger.warn(
|
|
103
|
-
`Failed to get Private Endpoint details for ${privateEndpointName}:
|
|
103
|
+
`Failed to get Private Endpoint details for ${privateEndpointName}: {message}`,
|
|
104
|
+
{ message: error instanceof Error ? error.message : error },
|
|
104
105
|
);
|
|
105
106
|
reason += "Could not retrieve detailed Private Endpoint information. ";
|
|
106
107
|
}
|
|
@@ -106,7 +106,8 @@ export async function analyzePublicIp(
|
|
|
106
106
|
} catch (error) {
|
|
107
107
|
const logger = getLogger(["savemoney", "azure"]);
|
|
108
108
|
logger.warn(
|
|
109
|
-
`Failed to get Public IP details for ${publicIpName}:
|
|
109
|
+
`Failed to get Public IP details for ${publicIpName}: {message}`,
|
|
110
|
+
{ message: error instanceof Error ? error.message : error },
|
|
110
111
|
);
|
|
111
112
|
reason += "Could not retrieve detailed Public IP information. ";
|
|
112
113
|
}
|
|
@@ -160,9 +161,9 @@ async function enrichWithPricing(
|
|
|
160
161
|
return { ...result, estimatedMonthlySavings };
|
|
161
162
|
} catch (error) {
|
|
162
163
|
const logger = getLogger(["savemoney", "azure"]);
|
|
163
|
-
logger.warn(
|
|
164
|
-
|
|
165
|
-
);
|
|
164
|
+
logger.warn("Failed to enrich Public IP with pricing: {message}", {
|
|
165
|
+
message: error instanceof Error ? error.message : error,
|
|
166
|
+
});
|
|
166
167
|
return result;
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -101,7 +101,8 @@ export async function analyzeStaticSite(
|
|
|
101
101
|
} catch (error) {
|
|
102
102
|
const logger = getLogger(["savemoney", "azure"]);
|
|
103
103
|
logger.warn(
|
|
104
|
-
`Failed to get metrics for Static Web App ${resource.name}:
|
|
104
|
+
`Failed to get metrics for Static Web App ${resource.name}: {message}`,
|
|
105
|
+
{ message: error instanceof Error ? error.message : error },
|
|
105
106
|
);
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -114,9 +114,9 @@ export async function analyzeVM(
|
|
|
114
114
|
}
|
|
115
115
|
} catch (error) {
|
|
116
116
|
const logger = getLogger(["savemoney", "azure"]);
|
|
117
|
-
logger.warn(
|
|
118
|
-
|
|
119
|
-
);
|
|
117
|
+
logger.warn(`Failed to get VM instance view for ${vmName}: {message}`, {
|
|
118
|
+
message: error instanceof Error ? error.message : error,
|
|
119
|
+
});
|
|
120
120
|
// Continue with metric analysis if instance view fails
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -203,9 +203,9 @@ async function enrichWithPricing(
|
|
|
203
203
|
return { ...result, estimatedMonthlySavings };
|
|
204
204
|
} catch (error) {
|
|
205
205
|
const logger = getLogger(["savemoney", "azure"]);
|
|
206
|
-
logger.warn(
|
|
207
|
-
|
|
208
|
-
);
|
|
206
|
+
logger.warn(`Failed to enrich VM ${vmName} with pricing: {message}`, {
|
|
207
|
+
message: error instanceof Error ? error.message : error,
|
|
208
|
+
});
|
|
209
209
|
return result;
|
|
210
210
|
}
|
|
211
211
|
}
|
package/src/azure/utils.ts
CHANGED
|
@@ -219,7 +219,11 @@ export function verboseLog(
|
|
|
219
219
|
if (verbose) {
|
|
220
220
|
const logger = getLogger(["savemoney", "azure", "verbose"]);
|
|
221
221
|
if (object !== undefined) {
|
|
222
|
-
|
|
222
|
+
// Passed as a template property (see pricing-service.ts for why: avoids
|
|
223
|
+
// logtape misparsing literal `{`/`}` in the JSON as placeholders).
|
|
224
|
+
logger.debug(`${message} {object}`, {
|
|
225
|
+
object: JSON.stringify(object, null, 2),
|
|
226
|
+
});
|
|
223
227
|
} else {
|
|
224
228
|
logger.debug(message);
|
|
225
229
|
}
|
|
@@ -308,8 +312,10 @@ async function fetchMetric(
|
|
|
308
312
|
return aggregatedValue;
|
|
309
313
|
} catch (error) {
|
|
310
314
|
const logger = getLogger(["savemoney", "azure", "metrics"]);
|
|
315
|
+
// Passed as a template property (see pricing-service.ts for why).
|
|
311
316
|
logger.error(
|
|
312
|
-
`Failed to fetch metric ${metricName} for resource ${resourceId}:
|
|
317
|
+
`Failed to fetch metric ${metricName} for resource ${resourceId}: {message}`,
|
|
318
|
+
{ message: error instanceof Error ? error.message : String(error) },
|
|
313
319
|
);
|
|
314
320
|
return null;
|
|
315
321
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for findingsFromAnalysisResult — verifies the adapter that converts
|
|
3
|
-
* a legacy `AnalysisResult.reason` string into a structured `Finding[]`.
|
|
4
|
-
*
|
|
5
|
-
* Behaviours covered:
|
|
6
|
-
* 1. Empty / whitespace-only reason → empty array.
|
|
7
|
-
* 2. Single sentence (with or without trailing period) → one finding.
|
|
8
|
-
* 3. Multi-sentence reason (". " separator) → one finding per sentence.
|
|
9
|
-
* 4. Trailing period on the whole string → not duplicated.
|
|
10
|
-
* 5. Custom `code` → propagated to every finding.
|
|
11
|
-
* 6. Omitted `code` → defaults to "custom.unknown".
|
|
12
|
-
* 7. Custom `source` → propagated.
|
|
13
|
-
* 8. Omitted `source` → defaults to "custom".
|
|
14
|
-
* 9. Every finding carries the correct `resourceId`, `severity`, `category`.
|
|
15
|
-
*/
|
|
16
|
-
export {};
|
|
17
|
-
//# sourceMappingURL=finding.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"finding.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/finding.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for findingsFromAnalysisResult — verifies the adapter that converts
|
|
3
|
-
* a legacy `AnalysisResult.reason` string into a structured `Finding[]`.
|
|
4
|
-
*
|
|
5
|
-
* Behaviours covered:
|
|
6
|
-
* 1. Empty / whitespace-only reason → empty array.
|
|
7
|
-
* 2. Single sentence (with or without trailing period) → one finding.
|
|
8
|
-
* 3. Multi-sentence reason (". " separator) → one finding per sentence.
|
|
9
|
-
* 4. Trailing period on the whole string → not duplicated.
|
|
10
|
-
* 5. Custom `code` → propagated to every finding.
|
|
11
|
-
* 6. Omitted `code` → defaults to "custom.unknown".
|
|
12
|
-
* 7. Custom `source` → propagated.
|
|
13
|
-
* 8. Omitted `source` → defaults to "custom".
|
|
14
|
-
* 9. Every finding carries the correct `resourceId`, `severity`, `category`.
|
|
15
|
-
*/
|
|
16
|
-
import { describe, expect, it } from "vitest";
|
|
17
|
-
import { findingsFromAnalysisResult } from "../finding.js";
|
|
18
|
-
const BASE_ARGS = {
|
|
19
|
-
reason: "Low CPU usage.",
|
|
20
|
-
resourceId: "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm",
|
|
21
|
-
severity: "high",
|
|
22
|
-
};
|
|
23
|
-
describe("findingsFromAnalysisResult", () => {
|
|
24
|
-
describe("empty / whitespace input", () => {
|
|
25
|
-
it("returns [] for an empty string", () => {
|
|
26
|
-
expect(findingsFromAnalysisResult({ ...BASE_ARGS, reason: "" })).toEqual([]);
|
|
27
|
-
});
|
|
28
|
-
it("returns [] for a whitespace-only string", () => {
|
|
29
|
-
expect(findingsFromAnalysisResult({ ...BASE_ARGS, reason: " " })).toEqual([]);
|
|
30
|
-
});
|
|
31
|
-
it("returns [] for a bare period", () => {
|
|
32
|
-
expect(findingsFromAnalysisResult({ ...BASE_ARGS, reason: "." })).toEqual([]);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe("single sentence", () => {
|
|
36
|
-
it("returns one finding with a trailing period", () => {
|
|
37
|
-
const result = findingsFromAnalysisResult({
|
|
38
|
-
...BASE_ARGS,
|
|
39
|
-
reason: "VM is deallocated.",
|
|
40
|
-
});
|
|
41
|
-
expect(result).toHaveLength(1);
|
|
42
|
-
expect(result[0].reason).toBe("VM is deallocated.");
|
|
43
|
-
});
|
|
44
|
-
it("appends a trailing period when missing", () => {
|
|
45
|
-
const result = findingsFromAnalysisResult({
|
|
46
|
-
...BASE_ARGS,
|
|
47
|
-
reason: "VM is deallocated",
|
|
48
|
-
});
|
|
49
|
-
expect(result).toHaveLength(1);
|
|
50
|
-
expect(result[0].reason).toBe("VM is deallocated.");
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("multi-sentence reason", () => {
|
|
54
|
-
it("splits on '. ' into separate findings", () => {
|
|
55
|
-
const result = findingsFromAnalysisResult({
|
|
56
|
-
...BASE_ARGS,
|
|
57
|
-
reason: "VM is deallocated. No tags found. Low CPU usage.",
|
|
58
|
-
});
|
|
59
|
-
expect(result).toHaveLength(3);
|
|
60
|
-
expect(result[0].reason).toBe("VM is deallocated.");
|
|
61
|
-
expect(result[1].reason).toBe("No tags found.");
|
|
62
|
-
expect(result[2].reason).toBe("Low CPU usage.");
|
|
63
|
-
});
|
|
64
|
-
it("does not create an extra empty finding for a trailing period", () => {
|
|
65
|
-
const result = findingsFromAnalysisResult({
|
|
66
|
-
...BASE_ARGS,
|
|
67
|
-
reason: "Sentence one. Sentence two.",
|
|
68
|
-
});
|
|
69
|
-
expect(result).toHaveLength(2);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe("code field", () => {
|
|
73
|
-
it("uses the provided code for every finding", () => {
|
|
74
|
-
const result = findingsFromAnalysisResult({
|
|
75
|
-
...BASE_ARGS,
|
|
76
|
-
code: "vm.deallocated",
|
|
77
|
-
reason: "A. B.",
|
|
78
|
-
});
|
|
79
|
-
expect(result.every((f) => f.code === "vm.deallocated")).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
it("defaults code to 'custom.unknown' when omitted", () => {
|
|
82
|
-
const result = findingsFromAnalysisResult(BASE_ARGS);
|
|
83
|
-
expect(result[0].code).toBe("custom.unknown");
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe("source field", () => {
|
|
87
|
-
it("uses the provided source for every finding", () => {
|
|
88
|
-
const result = findingsFromAnalysisResult({
|
|
89
|
-
...BASE_ARGS,
|
|
90
|
-
source: "advisor",
|
|
91
|
-
});
|
|
92
|
-
expect(result.every((f) => f.source === "advisor")).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
it("defaults source to 'custom' when omitted", () => {
|
|
95
|
-
const result = findingsFromAnalysisResult(BASE_ARGS);
|
|
96
|
-
expect(result[0].source).toBe("custom");
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
describe("static fields on every finding", () => {
|
|
100
|
-
it("propagates resourceId to every finding", () => {
|
|
101
|
-
const result = findingsFromAnalysisResult({
|
|
102
|
-
...BASE_ARGS,
|
|
103
|
-
reason: "A. B.",
|
|
104
|
-
});
|
|
105
|
-
expect(result.every((f) => f.resourceId === BASE_ARGS.resourceId)).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
it("propagates severity to every finding", () => {
|
|
108
|
-
const result = findingsFromAnalysisResult({
|
|
109
|
-
...BASE_ARGS,
|
|
110
|
-
reason: "A. B.",
|
|
111
|
-
severity: "medium",
|
|
112
|
-
});
|
|
113
|
-
expect(result.every((f) => f.severity === "medium")).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
it("sets category to 'cost' on every finding", () => {
|
|
116
|
-
const result = findingsFromAnalysisResult({
|
|
117
|
-
...BASE_ARGS,
|
|
118
|
-
reason: "A. B.",
|
|
119
|
-
});
|
|
120
|
-
expect(result.every((f) => f.category === "cost")).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
it("never attaches estimatedMonthlySavings to custom findings", () => {
|
|
123
|
-
// Custom analyzers carry the resource's monthly cost on
|
|
124
|
-
// `AnalysisResult.estimatedMonthlySavings` and the report layer
|
|
125
|
-
// renders it at the resource level. Propagating it down to a
|
|
126
|
-
// single finding sentence (e.g. "No tags found") would falsely
|
|
127
|
-
// suggest that fixing that sentence yields the full saving.
|
|
128
|
-
const result = findingsFromAnalysisResult({
|
|
129
|
-
...BASE_ARGS,
|
|
130
|
-
reason: "Low CPU. Low network. Idle.",
|
|
131
|
-
});
|
|
132
|
-
expect(result).toHaveLength(3);
|
|
133
|
-
for (const f of result) {
|
|
134
|
-
expect(f.estimatedMonthlySavings).toBeUndefined();
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
it("omits estimatedMonthlySavings entirely", () => {
|
|
138
|
-
const result = findingsFromAnalysisResult({
|
|
139
|
-
...BASE_ARGS,
|
|
140
|
-
reason: "Low CPU.",
|
|
141
|
-
});
|
|
142
|
-
expect(result[0].estimatedMonthlySavings).toBeUndefined();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
//# sourceMappingURL=finding.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"finding.test.js","sourceRoot":"","sources":["../../src/__tests__/finding.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=analyzer-tags.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-tags.test.d.ts","sourceRoot":"","sources":["../../../src/azure/__tests__/analyzer-tags.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -1,70 +0,0 @@
|
|
|
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
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
import { mergeFinding, shouldIncludeAdvisorFindingForTags, } from "../analyzer.js";
|
|
9
|
-
function mkFinding(resourceId, source) {
|
|
10
|
-
return {
|
|
11
|
-
category: "cost",
|
|
12
|
-
code: `${source}.test`,
|
|
13
|
-
reason: "Test finding.",
|
|
14
|
-
resourceId,
|
|
15
|
-
severity: "low",
|
|
16
|
-
source,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
describe("shouldIncludeAdvisorFindingForTags", () => {
|
|
20
|
-
it("includes all findings when no tag filter is active", () => {
|
|
21
|
-
const finding = mkFinding("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1", "advisor");
|
|
22
|
-
expect(shouldIncludeAdvisorFindingForTags(finding, new Set(), false)).toBe(true);
|
|
23
|
-
});
|
|
24
|
-
it("keeps subscription-level Advisor findings global even with tag filters", () => {
|
|
25
|
-
const finding = mkFinding("/subscriptions/sub1", "advisor");
|
|
26
|
-
expect(shouldIncludeAdvisorFindingForTags(finding, new Set(), true)).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
it("includes resource-level Advisor findings only when resource id is tag-matched", () => {
|
|
29
|
-
const finding = mkFinding("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1", "advisor");
|
|
30
|
-
const taggedResourceIds = new Set([
|
|
31
|
-
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
32
|
-
]);
|
|
33
|
-
expect(shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true)).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
it("excludes resource-level Advisor findings when resource id is not tag-matched", () => {
|
|
36
|
-
const finding = mkFinding("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm2", "advisor");
|
|
37
|
-
const taggedResourceIds = new Set([
|
|
38
|
-
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
39
|
-
]);
|
|
40
|
-
expect(shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true)).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe("mergeFinding", () => {
|
|
44
|
-
it("preserves custom cost at risk when merging an Advisor finding", () => {
|
|
45
|
-
const resourceId = "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1";
|
|
46
|
-
const existing = {
|
|
47
|
-
analysis: {
|
|
48
|
-
costRisk: "high",
|
|
49
|
-
estimatedMonthlySavings: { amount: 29.94, currency: "EUR" },
|
|
50
|
-
reason: "VM is deallocated.",
|
|
51
|
-
suspectedUnused: true,
|
|
52
|
-
},
|
|
53
|
-
findings: [mkFinding(resourceId, "custom")],
|
|
54
|
-
resource: {
|
|
55
|
-
id: resourceId,
|
|
56
|
-
name: "vm1",
|
|
57
|
-
type: "Microsoft.Compute/virtualMachines",
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
const reports = [existing];
|
|
61
|
-
const reportsById = new Map([[resourceId.toLowerCase(), existing]]);
|
|
62
|
-
mergeFinding(mkFinding(resourceId, "advisor"), reports, reportsById);
|
|
63
|
-
expect(existing.analysis.estimatedMonthlySavings).toEqual({
|
|
64
|
-
amount: 29.94,
|
|
65
|
-
currency: "EUR",
|
|
66
|
-
});
|
|
67
|
-
expect(existing.findings).toHaveLength(2);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
//# sourceMappingURL=analyzer-tags.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-tags.test.js","sourceRoot":"","sources":["../../../src/azure/__tests__/analyzer-tags.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the AZQR ingestion module.
|
|
3
|
-
*
|
|
4
|
-
* Cover the behaviours CES-2192 requires:
|
|
5
|
-
* 1. `parseAzqrReport` validates the JSON shape (accept valid, reject invalid).
|
|
6
|
-
* 2. `isAzqrReportMasked` detects AZQR's default subscription-ID masking.
|
|
7
|
-
* 3. `classifyAzqrRow` / `azqrImpactedToFindings` promote billable waste as
|
|
8
|
-
* `cost`, orphaned free resources as `cleanup` candidates, and drop
|
|
9
|
-
* security / reliability / best-practice noise.
|
|
10
|
-
* 4. Impact → severity mapping and inventory enrichment.
|
|
11
|
-
*/
|
|
12
|
-
export {};
|
|
13
|
-
//# sourceMappingURL=azqr.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"azqr.test.d.ts","sourceRoot":"","sources":["../../../src/azure/__tests__/azqr.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the AZQR ingestion module.
|
|
3
|
-
*
|
|
4
|
-
* Cover the behaviours CES-2192 requires:
|
|
5
|
-
* 1. `parseAzqrReport` validates the JSON shape (accept valid, reject invalid).
|
|
6
|
-
* 2. `isAzqrReportMasked` detects AZQR's default subscription-ID masking.
|
|
7
|
-
* 3. `classifyAzqrRow` / `azqrImpactedToFindings` promote billable waste as
|
|
8
|
-
* `cost`, orphaned free resources as `cleanup` candidates, and drop
|
|
9
|
-
* security / reliability / best-practice noise.
|
|
10
|
-
* 4. Impact → severity mapping and inventory enrichment.
|
|
11
|
-
*/
|
|
12
|
-
import { describe, expect, it } from "vitest";
|
|
13
|
-
import { azqrImpactedToFindings, classifyAzqrRow, isAzqrReportMasked, parseAzqrReport, } from "../azqr.js";
|
|
14
|
-
const REAL_SUB = "ec285037-c673-4f58-b594-d7c480da4e8b";
|
|
15
|
-
const MASKED_SUB = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxx0da4e8b";
|
|
16
|
-
function impactedRow(overrides = {}) {
|
|
17
|
-
return {
|
|
18
|
-
category: "Security",
|
|
19
|
-
impact: "Medium",
|
|
20
|
-
recommendation: "Some best practice",
|
|
21
|
-
resourceId: resourceId(REAL_SUB, "microsoft.network/networksecuritygroups", "nsg"),
|
|
22
|
-
resourceType: "microsoft.network/networksecuritygroups",
|
|
23
|
-
source: "APRL",
|
|
24
|
-
...overrides,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
/** An orphaned (AOR) resource row of the given type. */
|
|
28
|
-
function orphanRow(resourceType, overrides = {}) {
|
|
29
|
-
return impactedRow({
|
|
30
|
-
category: "Governance",
|
|
31
|
-
resourceId: resourceId(REAL_SUB, resourceType, "res"),
|
|
32
|
-
resourceType,
|
|
33
|
-
source: "AOR",
|
|
34
|
-
...overrides,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
function resourceId(sub, type, name) {
|
|
38
|
-
return `/subscriptions/${sub}/resourcegroups/rg/providers/${type}/${name}`;
|
|
39
|
-
}
|
|
40
|
-
describe("parseAzqrReport", () => {
|
|
41
|
-
it("accepts a valid report and defaults missing arrays", () => {
|
|
42
|
-
const report = parseAzqrReport({ impacted: [impactedRow()] });
|
|
43
|
-
expect(report.impacted).toHaveLength(1);
|
|
44
|
-
expect(report.inventory).toEqual([]);
|
|
45
|
-
});
|
|
46
|
-
it("strips unknown top-level sections", () => {
|
|
47
|
-
const report = parseAzqrReport({
|
|
48
|
-
advisor: [{ anything: true }],
|
|
49
|
-
impacted: [],
|
|
50
|
-
recommendations: [{ foo: "bar" }],
|
|
51
|
-
});
|
|
52
|
-
expect(report.impacted).toEqual([]);
|
|
53
|
-
});
|
|
54
|
-
it("throws with a cause when an impacted row misses required fields", () => {
|
|
55
|
-
expect(() => parseAzqrReport({ impacted: [{ impact: "High" }] })).toThrow(/Invalid AZQR report/);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe("isAzqrReportMasked", () => {
|
|
59
|
-
it("detects masked subscription IDs in resource IDs", () => {
|
|
60
|
-
const report = parseAzqrReport({
|
|
61
|
-
impacted: [
|
|
62
|
-
impactedRow({
|
|
63
|
-
resourceId: resourceId(MASKED_SUB, "microsoft.network/publicipaddresses", "pip"),
|
|
64
|
-
}),
|
|
65
|
-
],
|
|
66
|
-
});
|
|
67
|
-
expect(isAzqrReportMasked(report)).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
it("returns false when IDs carry the real subscription GUID", () => {
|
|
70
|
-
const report = parseAzqrReport({ impacted: [impactedRow()] });
|
|
71
|
-
expect(isAzqrReportMasked(report)).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
describe("classifyAzqrRow", () => {
|
|
75
|
-
it("classifies rows AZQR categorises as Cost as cost", () => {
|
|
76
|
-
expect(classifyAzqrRow(impactedRow({ category: "Cost" }))).toBe("cost");
|
|
77
|
-
});
|
|
78
|
-
it("classifies orphaned billable resources as cost", () => {
|
|
79
|
-
expect(classifyAzqrRow(orphanRow("microsoft.network/publicipaddresses", {
|
|
80
|
-
recommendation: "Public IPs not attached to any resource",
|
|
81
|
-
}))).toBe("cost");
|
|
82
|
-
});
|
|
83
|
-
it("classifies orphaned free resources (NSG, subnet, …) as cleanup", () => {
|
|
84
|
-
expect(classifyAzqrRow(orphanRow("microsoft.network/networksecuritygroups", {
|
|
85
|
-
recommendation: "Network Security Groups not attached to any network interface or subnet",
|
|
86
|
-
}))).toBe("cleanup");
|
|
87
|
-
});
|
|
88
|
-
it("drops non-orphan security / reliability best-practice rows", () => {
|
|
89
|
-
expect(classifyAzqrRow(impactedRow({
|
|
90
|
-
category: "Security",
|
|
91
|
-
recommendation: "Enable diagnostic settings",
|
|
92
|
-
resourceType: "microsoft.web/serverfarms",
|
|
93
|
-
}))).toBeNull();
|
|
94
|
-
});
|
|
95
|
-
it("does not promote orphan-sounding text unless the AOR source tags it", () => {
|
|
96
|
-
expect(classifyAzqrRow(impactedRow({
|
|
97
|
-
category: "Governance",
|
|
98
|
-
recommendation: "Public IPs not attached to any resource",
|
|
99
|
-
resourceType: "microsoft.network/publicipaddresses",
|
|
100
|
-
source: "APRL",
|
|
101
|
-
}))).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe("azqrImpactedToFindings", () => {
|
|
105
|
-
it("maps billable orphans to cost findings and drops non-orphan noise", () => {
|
|
106
|
-
const report = parseAzqrReport({
|
|
107
|
-
impacted: [
|
|
108
|
-
orphanRow("microsoft.web/serverfarms", {
|
|
109
|
-
impact: "High",
|
|
110
|
-
recommendation: "App Service plans without hosting Apps",
|
|
111
|
-
recommendationId: "app-service-empty",
|
|
112
|
-
resourceId: resourceId(REAL_SUB, "microsoft.web/serverfarms", "asp"),
|
|
113
|
-
}),
|
|
114
|
-
impactedRow(), // non-orphan APRL noise → dropped
|
|
115
|
-
],
|
|
116
|
-
});
|
|
117
|
-
const findings = azqrImpactedToFindings(report);
|
|
118
|
-
expect(findings).toHaveLength(1);
|
|
119
|
-
expect(findings[0]).toMatchObject({
|
|
120
|
-
category: "cost",
|
|
121
|
-
code: "azqr.app-service-empty",
|
|
122
|
-
severity: "high",
|
|
123
|
-
source: "azqr",
|
|
124
|
-
});
|
|
125
|
-
expect(findings[0].estimatedMonthlySavings).toBeUndefined();
|
|
126
|
-
expect(findings[0].recommendedAction).toContain("Review the resource");
|
|
127
|
-
});
|
|
128
|
-
it("maps orphaned free resources to low-severity cleanup candidates", () => {
|
|
129
|
-
const report = parseAzqrReport({
|
|
130
|
-
impacted: [
|
|
131
|
-
orphanRow("microsoft.network/virtualnetworks", {
|
|
132
|
-
impact: "Medium",
|
|
133
|
-
recommendation: "Subnets without Connected Devices or Delegation",
|
|
134
|
-
recommendationId: "empty-subnet",
|
|
135
|
-
}),
|
|
136
|
-
],
|
|
137
|
-
});
|
|
138
|
-
const [finding] = azqrImpactedToFindings(report);
|
|
139
|
-
expect(finding).toMatchObject({
|
|
140
|
-
category: "operationalExcellence",
|
|
141
|
-
code: "azqr.empty-subnet",
|
|
142
|
-
severity: "low",
|
|
143
|
-
source: "azqr",
|
|
144
|
-
});
|
|
145
|
-
expect(finding.recommendedAction).toContain("no direct cost");
|
|
146
|
-
});
|
|
147
|
-
it("enriches the reason with inventory SKU / location when available", () => {
|
|
148
|
-
const id = resourceId(REAL_SUB, "microsoft.network/natgateways", "nat");
|
|
149
|
-
const report = parseAzqrReport({
|
|
150
|
-
impacted: [
|
|
151
|
-
orphanRow("microsoft.network/natgateways", {
|
|
152
|
-
recommendation: "NAT Gateways not attached to any subnet",
|
|
153
|
-
resourceId: id,
|
|
154
|
-
}),
|
|
155
|
-
],
|
|
156
|
-
inventory: [
|
|
157
|
-
{ location: "italynorth", resourceId: id, skuName: "Standard" },
|
|
158
|
-
],
|
|
159
|
-
});
|
|
160
|
-
const [finding] = azqrImpactedToFindings(report);
|
|
161
|
-
expect(finding.reason).toContain("Standard");
|
|
162
|
-
expect(finding.reason).toContain("italynorth");
|
|
163
|
-
});
|
|
164
|
-
it("defaults severity to low for unknown impact on cost rows", () => {
|
|
165
|
-
const report = parseAzqrReport({
|
|
166
|
-
impacted: [
|
|
167
|
-
impactedRow({
|
|
168
|
-
category: "Cost",
|
|
169
|
-
impact: undefined,
|
|
170
|
-
resourceType: "microsoft.compute/disks",
|
|
171
|
-
}),
|
|
172
|
-
],
|
|
173
|
-
});
|
|
174
|
-
expect(azqrImpactedToFindings(report)[0].severity).toBe("low");
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
//# sourceMappingURL=azqr.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"azqr.test.js","sourceRoot":"","sources":["../../../src/azure/__tests__/azqr.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for loadConfig() — verifies that:
|
|
3
|
-
* 1. Returns default thresholds and prompts for subscriptionIds when no file is given.
|
|
4
|
-
* 2. Loads subscriptionIds, location, timespanDays and thresholds from a YAML file.
|
|
5
|
-
* 3. Partial threshold overrides keep defaults for non-overridden fields.
|
|
6
|
-
* 4. Throws a clear error for a non-existent explicit path.
|
|
7
|
-
*/
|
|
8
|
-
export {};
|
|
9
|
-
//# sourceMappingURL=config.test.d.ts.map
|