@pagopa/dx-savemoney 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/azure/pricing/pricing-service.d.ts.map +1 -1
  2. package/dist/azure/pricing/pricing-service.js +6 -1
  3. package/dist/azure/pricing/pricing-service.js.map +1 -1
  4. package/dist/azure/pricing/schema.d.ts.map +1 -1
  5. package/dist/azure/pricing/schema.js +22 -14
  6. package/dist/azure/pricing/schema.js.map +1 -1
  7. package/dist/azure/resources/app-service.d.ts.map +1 -1
  8. package/dist/azure/resources/app-service.js +2 -2
  9. package/dist/azure/resources/app-service.js.map +1 -1
  10. package/dist/azure/resources/container-app.d.ts.map +1 -1
  11. package/dist/azure/resources/container-app.js +1 -1
  12. package/dist/azure/resources/container-app.js.map +1 -1
  13. package/dist/azure/resources/disk.js +3 -1
  14. package/dist/azure/resources/disk.js.map +1 -1
  15. package/dist/azure/resources/nic.js +3 -1
  16. package/dist/azure/resources/nic.js.map +1 -1
  17. package/dist/azure/resources/private-endpoint.d.ts.map +1 -1
  18. package/dist/azure/resources/private-endpoint.js +1 -1
  19. package/dist/azure/resources/private-endpoint.js.map +1 -1
  20. package/dist/azure/resources/public-ip.d.ts.map +1 -1
  21. package/dist/azure/resources/public-ip.js +4 -2
  22. package/dist/azure/resources/public-ip.js.map +1 -1
  23. package/dist/azure/resources/static-web-app.d.ts.map +1 -1
  24. package/dist/azure/resources/static-web-app.js +1 -1
  25. package/dist/azure/resources/static-web-app.js.map +1 -1
  26. package/dist/azure/resources/vm.js +6 -2
  27. package/dist/azure/resources/vm.js.map +1 -1
  28. package/dist/azure/utils.d.ts.map +1 -1
  29. package/dist/azure/utils.js +7 -2
  30. package/dist/azure/utils.js.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/telemetry/savemoney-command-telemetry.d.ts +20 -0
  36. package/dist/telemetry/savemoney-command-telemetry.d.ts.map +1 -0
  37. package/dist/telemetry/savemoney-command-telemetry.js +42 -0
  38. package/dist/telemetry/savemoney-command-telemetry.js.map +1 -0
  39. package/package.json +1 -8
  40. package/src/__tests__/savemoney-command-telemetry.test.ts +117 -0
  41. package/src/azure/pricing/__tests__/schema.test.ts +30 -0
  42. package/src/azure/pricing/pricing-service.ts +6 -3
  43. package/src/azure/pricing/schema.ts +22 -14
  44. package/src/azure/resources/app-service.ts +4 -2
  45. package/src/azure/resources/container-app.ts +2 -1
  46. package/src/azure/resources/disk.ts +3 -3
  47. package/src/azure/resources/nic.ts +3 -3
  48. package/src/azure/resources/private-endpoint.ts +2 -1
  49. package/src/azure/resources/public-ip.ts +5 -4
  50. package/src/azure/resources/static-web-app.ts +2 -1
  51. package/src/azure/resources/vm.ts +6 -6
  52. package/src/azure/utils.ts +8 -2
  53. package/src/index.ts +7 -0
  54. package/src/telemetry/savemoney-command-telemetry.ts +83 -0
  55. package/dist/__tests__/finding.test.d.ts +0 -17
  56. package/dist/__tests__/finding.test.d.ts.map +0 -1
  57. package/dist/__tests__/finding.test.js +0 -146
  58. package/dist/__tests__/finding.test.js.map +0 -1
  59. package/dist/azure/__tests__/analyzer-tags.test.d.ts +0 -8
  60. package/dist/azure/__tests__/analyzer-tags.test.d.ts.map +0 -1
  61. package/dist/azure/__tests__/analyzer-tags.test.js +0 -70
  62. package/dist/azure/__tests__/analyzer-tags.test.js.map +0 -1
  63. package/dist/azure/__tests__/azqr.test.d.ts +0 -13
  64. package/dist/azure/__tests__/azqr.test.d.ts.map +0 -1
  65. package/dist/azure/__tests__/azqr.test.js +0 -177
  66. package/dist/azure/__tests__/azqr.test.js.map +0 -1
  67. package/dist/azure/__tests__/config.test.d.ts +0 -9
  68. package/dist/azure/__tests__/config.test.d.ts.map +0 -1
  69. package/dist/azure/__tests__/config.test.js +0 -102
  70. package/dist/azure/__tests__/config.test.js.map +0 -1
  71. package/dist/azure/__tests__/report.test.d.ts +0 -9
  72. package/dist/azure/__tests__/report.test.d.ts.map +0 -1
  73. package/dist/azure/__tests__/report.test.js +0 -191
  74. package/dist/azure/__tests__/report.test.js.map +0 -1
  75. package/dist/azure/__tests__/utils.test.d.ts +0 -15
  76. package/dist/azure/__tests__/utils.test.d.ts.map +0 -1
  77. package/dist/azure/__tests__/utils.test.js +0 -181
  78. package/dist/azure/__tests__/utils.test.js.map +0 -1
  79. package/dist/azure/analyzers/__tests__/advisor.test.d.ts +0 -9
  80. package/dist/azure/analyzers/__tests__/advisor.test.d.ts.map +0 -1
  81. package/dist/azure/analyzers/__tests__/advisor.test.js +0 -314
  82. package/dist/azure/analyzers/__tests__/advisor.test.js.map +0 -1
  83. package/dist/azure/pricing/__tests__/cache.test.d.ts +0 -8
  84. package/dist/azure/pricing/__tests__/cache.test.d.ts.map +0 -1
  85. package/dist/azure/pricing/__tests__/cache.test.js +0 -56
  86. package/dist/azure/pricing/__tests__/cache.test.js.map +0 -1
  87. package/dist/azure/pricing/__tests__/client.test.d.ts +0 -9
  88. package/dist/azure/pricing/__tests__/client.test.d.ts.map +0 -1
  89. package/dist/azure/pricing/__tests__/client.test.js +0 -181
  90. package/dist/azure/pricing/__tests__/client.test.js.map +0 -1
  91. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +0 -6
  92. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +0 -1
  93. package/dist/azure/pricing/__tests__/pricing-service.test.js +0 -283
  94. package/dist/azure/pricing/__tests__/pricing-service.test.js.map +0 -1
  95. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +0 -5
  96. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +0 -1
  97. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +0 -137
  98. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +0 -1
  99. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +0 -5
  100. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +0 -1
  101. package/dist/azure/pricing/resolvers/__tests__/disk.test.js +0 -160
  102. package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +0 -1
  103. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +0 -5
  104. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +0 -1
  105. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +0 -113
  106. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +0 -1
  107. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +0 -9
  108. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +0 -1
  109. package/dist/azure/pricing/resolvers/__tests__/vm.test.js +0 -204
  110. package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +0 -1
  111. package/dist/azure/resources/__tests__/app-service.test.d.ts +0 -5
  112. package/dist/azure/resources/__tests__/app-service.test.d.ts.map +0 -1
  113. package/dist/azure/resources/__tests__/app-service.test.js +0 -105
  114. package/dist/azure/resources/__tests__/app-service.test.js.map +0 -1
  115. package/dist/azure/resources/__tests__/storage.test.d.ts +0 -11
  116. package/dist/azure/resources/__tests__/storage.test.d.ts.map +0 -1
  117. package/dist/azure/resources/__tests__/storage.test.js +0 -99
  118. package/dist/azure/resources/__tests__/storage.test.js.map +0 -1
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../../src/azure/__tests__/config.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -1,102 +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
- import path from "path";
9
- import { fileURLToPath } from "url";
10
- import { afterEach, describe, expect, expectTypeOf, it, vi } from "vitest";
11
- import { loadConfig } from "../../index.js";
12
- import { ConfigSchema } from "../../schema.js";
13
- import { DEFAULT_THRESHOLDS } from "../../types.js";
14
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
- /** Absolute paths used in tests */
16
- const FIXTURE_PARTIAL = path.resolve(__dirname, "fixtures/partial-override.yaml");
17
- const FIXTURE_FULL = path.resolve(__dirname, "fixtures/full-override.yaml");
18
- describe("loadConfig", () => {
19
- afterEach(() => {
20
- vi.unstubAllEnvs();
21
- });
22
- it("throws when explicit path does not exist", async () => {
23
- await expect(loadConfig("/nonexistent/config.yaml")).rejects.toThrow("Config file not found");
24
- });
25
- it("loads subscriptionIds and defaults from a partial YAML file", async () => {
26
- const result = await loadConfig(FIXTURE_PARTIAL);
27
- expectTypeOf().toEqualTypeOf();
28
- expect(result.subscriptionIds).toEqual([
29
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
30
- ]);
31
- expect(result.preferredLocation).toBe("italynorth");
32
- expect(result.sources).toEqual(["advisor", "custom"]);
33
- expect(result.timespanDays).toBe(30);
34
- expect(result.azqrReportPath).toBeUndefined();
35
- });
36
- it("applies sources defaults when loading from the environment", async () => {
37
- vi.stubEnv("ARM_SUBSCRIPTION_ID", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
38
- const result = await loadConfig();
39
- expect(result.sources).toEqual(["advisor", "custom"]);
40
- });
41
- it("loads partial threshold overrides and keeps defaults for missing fields", async () => {
42
- const result = await loadConfig(FIXTURE_PARTIAL);
43
- // Overridden values
44
- expect(result.thresholds?.vm.cpuPercent).toBe(5);
45
- expect(result.thresholds?.storage.transactionsPerDay).toBe(50);
46
- // Non-overridden vm field keeps default
47
- expect(result.thresholds?.vm.networkInBytesPerDay).toBe(DEFAULT_THRESHOLDS.vm.networkInBytesPerDay);
48
- // Entire non-overridden sections keep defaults
49
- expect(result.thresholds?.appService).toEqual(DEFAULT_THRESHOLDS.appService);
50
- expect(result.thresholds?.containerApp).toEqual(DEFAULT_THRESHOLDS.containerApp);
51
- expect(result.thresholds?.publicIp).toEqual(DEFAULT_THRESHOLDS.publicIp);
52
- expect(result.thresholds?.staticSite).toEqual(DEFAULT_THRESHOLDS.staticSite);
53
- });
54
- it("loads all values from a full YAML file", async () => {
55
- const result = await loadConfig(FIXTURE_FULL);
56
- expect(result.subscriptionIds).toHaveLength(2);
57
- expect(result.preferredLocation).toBe("westeurope");
58
- expect(result.sources).toEqual(["custom"]);
59
- expect(result.timespanDays).toBe(60);
60
- expect(result.azqrReportPath).toBe("./azqr_action_plan.json");
61
- expect(result.thresholds?.vm.cpuPercent).toBe(5);
62
- expect(result.thresholds?.vm.networkInBytesPerDay).toBe(10485760);
63
- expect(result.thresholds?.appService.cpuPercent).toBe(10);
64
- expect(result.thresholds?.appService.memoryPercent).toBe(20);
65
- expect(result.thresholds?.appService.premiumCpuPercent).toBe(15);
66
- expect(result.thresholds?.containerApp.cpuNanoCores).toBe(5000000);
67
- expect(result.thresholds?.containerApp.memoryBytes).toBe(52428800);
68
- expect(result.thresholds?.containerApp.networkBytes).toBe(100000);
69
- expect(result.thresholds?.storage.transactionsPerDay).toBe(50);
70
- expect(result.thresholds?.publicIp.bytesInDDoS).toBe(1048576);
71
- expect(result.thresholds?.staticSite.siteHits).toBe(500);
72
- expect(result.thresholds?.staticSite.bytesSent).toBe(5242880);
73
- });
74
- it("returns a complete Thresholds object (all required keys present)", async () => {
75
- const result = await loadConfig(FIXTURE_PARTIAL);
76
- expect(result.thresholds).toHaveProperty("vm");
77
- expect(result.thresholds).toHaveProperty("appService");
78
- expect(result.thresholds).toHaveProperty("containerApp");
79
- expect(result.thresholds).toHaveProperty("storage");
80
- expect(result.thresholds).toHaveProperty("publicIp");
81
- expect(result.thresholds).toHaveProperty("staticSite");
82
- });
83
- it("rejects an empty sources list", () => {
84
- const result = ConfigSchema.safeParse({
85
- azure: {
86
- sources: [],
87
- subscriptionIds: ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
88
- },
89
- });
90
- expect(result.success).toBe(false);
91
- });
92
- it("does not expose pricing settings in YAML config", () => {
93
- const result = ConfigSchema.safeParse({
94
- azure: {
95
- pricing: { enabled: false },
96
- subscriptionIds: ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
97
- },
98
- });
99
- expect(result.success).toBe(false);
100
- });
101
- });
102
- //# sourceMappingURL=config.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/azure/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -1,9 +0,0 @@
1
- /**
2
- * Tests for generateReport() with the "lint" format.
3
- *
4
- * Since process.stdout.isTTY is false/undefined in test environments,
5
- * ANSI color codes are empty strings (~no-ops), making the output
6
- * easy to assert on plain text.
7
- */
8
- export {};
9
- //# sourceMappingURL=report.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"report.test.d.ts","sourceRoot":"","sources":["../../../src/azure/__tests__/report.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -1,191 +0,0 @@
1
- /**
2
- * Tests for generateReport() with the "lint" format.
3
- *
4
- * Since process.stdout.isTTY is false/undefined in test environments,
5
- * ANSI color codes are empty strings (~no-ops), making the output
6
- * easy to assert on plain text.
7
- */
8
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
9
- import { generateReport } from "../report.js";
10
- // ── test fixtures ──────────────────────────────────────────────────────────
11
- function makeEntry(id, costRisk, reason, suspectedUnused = true) {
12
- return {
13
- analysis: { costRisk, reason, suspectedUnused },
14
- resource: {
15
- id,
16
- name: id.split("/").pop() ?? "unknown",
17
- type: "Microsoft.Compute/virtualMachines",
18
- },
19
- };
20
- }
21
- const HIGH_ENTRY = makeEntry("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-high", "high", "VM is deallocated. No disk activity detected.");
22
- const MEDIUM_ENTRY = makeEntry("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-medium", "medium", "Low CPU usage.");
23
- const LOW_ENTRY = makeEntry("/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-low", "low", "Possible idle resource.", false);
24
- // ── helpers ────────────────────────────────────────────────────────────────
25
- function allLogs(spy) {
26
- return spy.mock.calls.map((c) => c[0]).join("\n");
27
- }
28
- // ── tests ──────────────────────────────────────────────────────────────────
29
- describe("generateReport — lint format", () => {
30
- let logSpy;
31
- beforeEach(() => {
32
- logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
33
- });
34
- afterEach(() => {
35
- vi.restoreAllMocks();
36
- });
37
- it("prints the resource ID for each entry", async () => {
38
- await generateReport([HIGH_ENTRY], "lint");
39
- const output = allLogs(logSpy);
40
- expect(output).toContain(HIGH_ENTRY.resource.id);
41
- });
42
- it("uses ✖ icon for high-risk resources", async () => {
43
- await generateReport([HIGH_ENTRY], "lint");
44
- const output = allLogs(logSpy);
45
- expect(output).toContain("✖");
46
- });
47
- it("uses ⚠ icon for medium-risk resources", async () => {
48
- await generateReport([MEDIUM_ENTRY], "lint");
49
- const output = allLogs(logSpy);
50
- expect(output).toContain("⚠");
51
- });
52
- it("uses ℹ icon for low-risk resources", async () => {
53
- await generateReport([LOW_ENTRY], "lint");
54
- const output = allLogs(logSpy);
55
- expect(output).toContain("ℹ");
56
- });
57
- it("splits a multi-sentence reason into separate findings", async () => {
58
- // Reason has two sentences separated by '. '
59
- await generateReport([HIGH_ENTRY], "lint");
60
- const calls = logSpy.mock.calls.map((c) => c[0] ?? "");
61
- const findingLines = calls.filter((l) => l.startsWith(" "));
62
- // "VM is deallocated." and "No disk activity detected." → 2 findings
63
- expect(findingLines.length).toBe(2);
64
- });
65
- it("prints a Summary line at the end", async () => {
66
- await generateReport([HIGH_ENTRY, MEDIUM_ENTRY, LOW_ENTRY], "lint");
67
- const output = allLogs(logSpy);
68
- expect(output).toContain("Summary:");
69
- });
70
- it("summary reports correct total issue count", async () => {
71
- await generateReport([HIGH_ENTRY, MEDIUM_ENTRY, LOW_ENTRY], "lint");
72
- const output = allLogs(logSpy);
73
- // HIGH has 2 findings (split reason), MEDIUM and LOW have 1 each → total 4
74
- expect(output).toContain("4 issues found");
75
- });
76
- it("summary reports correctly with a single issue (no plural)", async () => {
77
- await generateReport([MEDIUM_ENTRY], "lint");
78
- const output = allLogs(logSpy);
79
- // "1 issue found" (not "1 issues found")
80
- expect(output).toContain("1 issue found");
81
- });
82
- it("shows risk level label in uppercase for each finding", async () => {
83
- await generateReport([HIGH_ENTRY], "lint");
84
- const output = allLogs(logSpy);
85
- expect(output).toContain("HIGH");
86
- });
87
- it("prints nothing but the summary for an empty report", async () => {
88
- await generateReport([], "lint");
89
- const calls = logSpy.mock.calls.map((c) => c[0]);
90
- expect(calls).toHaveLength(1);
91
- expect(calls[0]).toContain("0 issues found");
92
- });
93
- describe("monetary annotations", () => {
94
- const VM_WITH_COST = {
95
- analysis: {
96
- costRisk: "high",
97
- estimatedMonthlySavings: { amount: 29.94, currency: "EUR" },
98
- reason: "No tags found. VM is deallocated.",
99
- suspectedUnused: true,
100
- },
101
- resource: {
102
- id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-x",
103
- name: "vm-x",
104
- type: "Microsoft.Compute/virtualMachines",
105
- },
106
- };
107
- const ADVISOR_WITH_SAVINGS = {
108
- analysis: {
109
- costRisk: "medium",
110
- reason: "Advisor recommends a reservation.",
111
- suspectedUnused: true,
112
- },
113
- findings: [
114
- {
115
- category: "cost",
116
- code: "advisor.reservation",
117
- estimatedMonthlySavings: { amount: 12.34, currency: "EUR" },
118
- reason: "Advisor recommends a reservation.",
119
- resourceId: "/subscriptions/sub1/providers/Microsoft.Advisor/recommendations/rec-1",
120
- severity: "medium",
121
- source: "advisor",
122
- },
123
- ],
124
- resource: {
125
- id: "/subscriptions/sub1/providers/Microsoft.Advisor/recommendations/rec-1",
126
- name: "rec-1",
127
- type: "Microsoft.Advisor/recommendations",
128
- },
129
- };
130
- it("renders the resource monthly cost ONCE as a header annotation, not on each finding", async () => {
131
- await generateReport([VM_WITH_COST], "lint");
132
- const output = allLogs(logSpy);
133
- // header line contains the cost-at-risk label
134
- expect(output).toMatch(/cost at risk:.*29[.,]94/);
135
- // exactly one occurrence — must NOT appear next to each finding
136
- const matches = output.match(/29[.,]94/g) ?? [];
137
- // 1× header + 1× summary trailer = 2 occurrences; the per-finding line must not carry it
138
- expect(matches.length).toBe(2);
139
- });
140
- it("does not attach the resource cost to the 'No tags found' finding line", async () => {
141
- await generateReport([VM_WITH_COST], "lint");
142
- const findingLines = logSpy.mock.calls
143
- .map((c) => String(c[0]))
144
- .filter((l) => l.includes("No tags found"));
145
- expect(findingLines).toHaveLength(1);
146
- expect(findingLines[0]).not.toMatch(/29[.,]94/);
147
- });
148
- it("uses the 'Estimated monthly cost at risk' label in the summary trailer", async () => {
149
- await generateReport([VM_WITH_COST], "lint");
150
- const output = allLogs(logSpy);
151
- expect(output).toContain("Estimated monthly cost at risk");
152
- // The old, misleading "Estimated monthly savings (custom)" label is gone
153
- expect(output).not.toMatch(/Estimated monthly savings:\s*€/);
154
- });
155
- it("renders Advisor savings separately from custom cost at risk", async () => {
156
- await generateReport([VM_WITH_COST, ADVISOR_WITH_SAVINGS], "lint");
157
- const output = allLogs(logSpy);
158
- expect(output).toMatch(/cost at risk:.*29[.,]94/);
159
- expect(output).toMatch(/saving:.*12[.,]34/);
160
- expect(output).toContain("Estimated monthly cost at risk (custom)");
161
- expect(output).toContain("Estimated monthly savings (advisor)");
162
- });
163
- });
164
- });
165
- describe("generateReport — table format", () => {
166
- let logSpy;
167
- beforeEach(() => {
168
- logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
169
- });
170
- afterEach(() => {
171
- vi.restoreAllMocks();
172
- });
173
- it("renders without throwing for an empty report", async () => {
174
- await expect(generateReport([], "table")).resolves.toBeUndefined();
175
- // Table is always printed; the summary line ("0 issues found") follows.
176
- expect(logSpy.mock.calls.length).toBeGreaterThanOrEqual(1);
177
- const output = logSpy.mock.calls
178
- .map((c) => String(c[0]))
179
- .join("\n");
180
- expect(output).toContain("0 issues found");
181
- });
182
- it("includes resource name and reason in the rendered table", async () => {
183
- await generateReport([HIGH_ENTRY], "table");
184
- const output = logSpy.mock.calls
185
- .map((c) => String(c[0]))
186
- .join("\n");
187
- expect(output).toContain("vm-high");
188
- expect(output).toContain("VM is deallocated");
189
- });
190
- });
191
- //# sourceMappingURL=report.test.js.map