@pagopa/dx-savemoney 0.5.0 → 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.
Files changed (134) hide show
  1. package/README.md +48 -0
  2. package/dist/azure/analyzer.d.ts.map +1 -1
  3. package/dist/azure/analyzer.js +30 -0
  4. package/dist/azure/analyzer.js.map +1 -1
  5. package/dist/azure/analyzers/advisor.d.ts.map +1 -1
  6. package/dist/azure/analyzers/advisor.js.map +1 -1
  7. package/dist/azure/azqr.d.ts +136 -0
  8. package/dist/azure/azqr.d.ts.map +1 -0
  9. package/dist/azure/azqr.js +235 -0
  10. package/dist/azure/azqr.js.map +1 -0
  11. package/dist/azure/config.d.ts.map +1 -1
  12. package/dist/azure/config.js +1 -0
  13. package/dist/azure/config.js.map +1 -1
  14. package/dist/azure/pricing/pricing-service.d.ts.map +1 -1
  15. package/dist/azure/pricing/pricing-service.js +6 -1
  16. package/dist/azure/pricing/pricing-service.js.map +1 -1
  17. package/dist/azure/pricing/schema.d.ts.map +1 -1
  18. package/dist/azure/pricing/schema.js +22 -14
  19. package/dist/azure/pricing/schema.js.map +1 -1
  20. package/dist/azure/resources/app-service.d.ts.map +1 -1
  21. package/dist/azure/resources/app-service.js +2 -2
  22. package/dist/azure/resources/app-service.js.map +1 -1
  23. package/dist/azure/resources/container-app.d.ts.map +1 -1
  24. package/dist/azure/resources/container-app.js +1 -1
  25. package/dist/azure/resources/container-app.js.map +1 -1
  26. package/dist/azure/resources/disk.js +3 -1
  27. package/dist/azure/resources/disk.js.map +1 -1
  28. package/dist/azure/resources/nic.js +3 -1
  29. package/dist/azure/resources/nic.js.map +1 -1
  30. package/dist/azure/resources/private-endpoint.d.ts.map +1 -1
  31. package/dist/azure/resources/private-endpoint.js +1 -1
  32. package/dist/azure/resources/private-endpoint.js.map +1 -1
  33. package/dist/azure/resources/public-ip.js +4 -2
  34. package/dist/azure/resources/public-ip.js.map +1 -1
  35. package/dist/azure/resources/static-web-app.d.ts.map +1 -1
  36. package/dist/azure/resources/static-web-app.js +1 -1
  37. package/dist/azure/resources/static-web-app.js.map +1 -1
  38. package/dist/azure/resources/vm.js +6 -2
  39. package/dist/azure/resources/vm.js.map +1 -1
  40. package/dist/azure/types.d.ts +8 -0
  41. package/dist/azure/types.d.ts.map +1 -1
  42. package/dist/azure/utils.d.ts.map +1 -1
  43. package/dist/azure/utils.js +7 -2
  44. package/dist/azure/utils.js.map +1 -1
  45. package/dist/finding.d.ts +2 -1
  46. package/dist/finding.d.ts.map +1 -1
  47. package/dist/finding.js.map +1 -1
  48. package/dist/schema.d.ts +1 -0
  49. package/dist/schema.d.ts.map +1 -1
  50. package/dist/schema.js +6 -0
  51. package/dist/schema.js.map +1 -1
  52. package/package.json +8 -15
  53. package/src/azure/__tests__/azqr.test.ts +238 -0
  54. package/src/azure/__tests__/config.test.ts +2 -0
  55. package/src/azure/__tests__/fixtures/full-override.yaml +1 -0
  56. package/src/azure/analyzer.ts +47 -0
  57. package/src/azure/analyzers/advisor.ts +1 -2
  58. package/src/azure/azqr.ts +270 -0
  59. package/src/azure/config.ts +1 -0
  60. package/src/azure/pricing/__tests__/schema.test.ts +30 -0
  61. package/src/azure/pricing/pricing-service.ts +6 -3
  62. package/src/azure/pricing/schema.ts +22 -14
  63. package/src/azure/resources/app-service.ts +4 -2
  64. package/src/azure/resources/container-app.ts +2 -1
  65. package/src/azure/resources/disk.ts +3 -3
  66. package/src/azure/resources/nic.ts +3 -3
  67. package/src/azure/resources/private-endpoint.ts +2 -1
  68. package/src/azure/resources/public-ip.ts +6 -6
  69. package/src/azure/resources/static-web-app.ts +2 -1
  70. package/src/azure/resources/vm.ts +6 -6
  71. package/src/azure/types.ts +8 -0
  72. package/src/azure/utils.ts +8 -2
  73. package/src/finding.ts +3 -6
  74. package/src/schema.ts +6 -0
  75. package/dist/__tests__/finding.test.d.ts +0 -17
  76. package/dist/__tests__/finding.test.d.ts.map +0 -1
  77. package/dist/__tests__/finding.test.js +0 -146
  78. package/dist/__tests__/finding.test.js.map +0 -1
  79. package/dist/azure/__tests__/analyzer-tags.test.d.ts +0 -8
  80. package/dist/azure/__tests__/analyzer-tags.test.d.ts.map +0 -1
  81. package/dist/azure/__tests__/analyzer-tags.test.js +0 -70
  82. package/dist/azure/__tests__/analyzer-tags.test.js.map +0 -1
  83. package/dist/azure/__tests__/config.test.d.ts +0 -9
  84. package/dist/azure/__tests__/config.test.d.ts.map +0 -1
  85. package/dist/azure/__tests__/config.test.js +0 -100
  86. package/dist/azure/__tests__/config.test.js.map +0 -1
  87. package/dist/azure/__tests__/report.test.d.ts +0 -9
  88. package/dist/azure/__tests__/report.test.d.ts.map +0 -1
  89. package/dist/azure/__tests__/report.test.js +0 -191
  90. package/dist/azure/__tests__/report.test.js.map +0 -1
  91. package/dist/azure/__tests__/utils.test.d.ts +0 -15
  92. package/dist/azure/__tests__/utils.test.d.ts.map +0 -1
  93. package/dist/azure/__tests__/utils.test.js +0 -181
  94. package/dist/azure/__tests__/utils.test.js.map +0 -1
  95. package/dist/azure/analyzers/__tests__/advisor.test.d.ts +0 -9
  96. package/dist/azure/analyzers/__tests__/advisor.test.d.ts.map +0 -1
  97. package/dist/azure/analyzers/__tests__/advisor.test.js +0 -314
  98. package/dist/azure/analyzers/__tests__/advisor.test.js.map +0 -1
  99. package/dist/azure/pricing/__tests__/cache.test.d.ts +0 -8
  100. package/dist/azure/pricing/__tests__/cache.test.d.ts.map +0 -1
  101. package/dist/azure/pricing/__tests__/cache.test.js +0 -56
  102. package/dist/azure/pricing/__tests__/cache.test.js.map +0 -1
  103. package/dist/azure/pricing/__tests__/client.test.d.ts +0 -9
  104. package/dist/azure/pricing/__tests__/client.test.d.ts.map +0 -1
  105. package/dist/azure/pricing/__tests__/client.test.js +0 -181
  106. package/dist/azure/pricing/__tests__/client.test.js.map +0 -1
  107. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +0 -6
  108. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +0 -1
  109. package/dist/azure/pricing/__tests__/pricing-service.test.js +0 -283
  110. package/dist/azure/pricing/__tests__/pricing-service.test.js.map +0 -1
  111. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +0 -5
  112. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +0 -1
  113. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +0 -137
  114. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +0 -1
  115. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +0 -5
  116. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +0 -1
  117. package/dist/azure/pricing/resolvers/__tests__/disk.test.js +0 -160
  118. package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +0 -1
  119. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +0 -5
  120. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +0 -1
  121. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +0 -113
  122. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +0 -1
  123. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +0 -9
  124. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +0 -1
  125. package/dist/azure/pricing/resolvers/__tests__/vm.test.js +0 -204
  126. package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +0 -1
  127. package/dist/azure/resources/__tests__/app-service.test.d.ts +0 -5
  128. package/dist/azure/resources/__tests__/app-service.test.d.ts.map +0 -1
  129. package/dist/azure/resources/__tests__/app-service.test.js +0 -105
  130. package/dist/azure/resources/__tests__/app-service.test.js.map +0 -1
  131. package/dist/azure/resources/__tests__/storage.test.d.ts +0 -11
  132. package/dist/azure/resources/__tests__/storage.test.d.ts.map +0 -1
  133. package/dist/azure/resources/__tests__/storage.test.js +0 -99
  134. package/dist/azure/resources/__tests__/storage.test.js.map +0 -1
@@ -116,9 +116,12 @@ export class PricingService {
116
116
  try {
117
117
  return await factory();
118
118
  } catch (error) {
119
- logger.warn(
120
- `Pricing lookup failed: ${error instanceof Error ? error.message : String(error)}`,
121
- );
119
+ // Passed as a template property, not string-interpolated: logtape parses
120
+ // `{...}` in the message itself as placeholders (e.g. a ZodError's
121
+ // JSON-formatted issues would otherwise get swallowed as `undefined`).
122
+ logger.warn("Pricing lookup failed: {message}", {
123
+ message: error instanceof Error ? error.message : String(error),
124
+ });
122
125
  return undefined;
123
126
  }
124
127
  }
@@ -12,6 +12,14 @@
12
12
  import { z } from "zod";
13
13
 
14
14
  const NonEmptyStringSchema = z.string().min(1);
15
+ /**
16
+ * Optional metadata fields. The Retail Prices API returns `""` (not the
17
+ * field omitted) for meters where a given attribute doesn't apply (e.g.
18
+ * `armSkuName` for Bandwidth/Support meters), so these must tolerate empty
19
+ * strings — resolvers already treat empty and missing values the same way
20
+ * (`?.toLowerCase() ?? ""`).
21
+ */
22
+ const OptionalStringSchema = z.string().optional();
15
23
  const PriceTypeSchema = z.enum([
16
24
  "Consumption",
17
25
  "DevTestConsumption",
@@ -31,23 +39,23 @@ const PriceValueSchema = z.number().finite().nonnegative();
31
39
  * want on-demand pricing must filter by `type === "Consumption"`.
32
40
  */
33
41
  export const PriceItemSchema = z.object({
34
- armRegionName: NonEmptyStringSchema.optional(),
35
- armSkuName: NonEmptyStringSchema.optional(),
42
+ armRegionName: OptionalStringSchema,
43
+ armSkuName: OptionalStringSchema,
36
44
  currencyCode: z.string().length(3),
37
- effectiveStartDate: NonEmptyStringSchema.optional(),
45
+ effectiveStartDate: OptionalStringSchema,
38
46
  isPrimaryMeterRegion: z.boolean().optional(),
39
- location: NonEmptyStringSchema.optional(),
40
- meterId: NonEmptyStringSchema.optional(),
41
- meterName: NonEmptyStringSchema.optional(),
42
- productId: NonEmptyStringSchema.optional(),
43
- productName: NonEmptyStringSchema.optional(),
44
- reservationTerm: NonEmptyStringSchema.optional(),
47
+ location: OptionalStringSchema,
48
+ meterId: OptionalStringSchema,
49
+ meterName: OptionalStringSchema,
50
+ productId: OptionalStringSchema,
51
+ productName: OptionalStringSchema,
52
+ reservationTerm: OptionalStringSchema,
45
53
  retailPrice: PriceValueSchema,
46
- serviceFamily: NonEmptyStringSchema.optional(),
47
- serviceId: NonEmptyStringSchema.optional(),
48
- serviceName: NonEmptyStringSchema.optional(),
49
- skuId: NonEmptyStringSchema.optional(),
50
- skuName: NonEmptyStringSchema.optional(),
54
+ serviceFamily: OptionalStringSchema,
55
+ serviceId: OptionalStringSchema,
56
+ serviceName: OptionalStringSchema,
57
+ skuId: OptionalStringSchema,
58
+ skuName: OptionalStringSchema,
51
59
  tierMinimumUnits: PriceValueSchema.optional(),
52
60
  /** "Consumption" | "Reservation" | "DevTestConsumption". */
53
61
  type: PriceTypeSchema,
@@ -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}: ${error instanceof Error ? error.message : error}`,
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: ${error instanceof Error ? error.message : error}`,
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}: ${error instanceof Error ? error.message : error}`,
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
- `Failed to get disk details for ${diskName}: ${error instanceof Error ? error.message : error}`,
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
- `Failed to get NIC details for ${nicName}: ${error instanceof Error ? error.message : error}`,
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}: ${error instanceof Error ? error.message : error}`,
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
  }
@@ -66,8 +66,7 @@ export async function analyzePublicIp(
66
66
  // Capture details outside the try so the pricing enrichment below can
67
67
  // reuse them without an extra API round-trip.
68
68
  let publicIpDetails:
69
- | Awaited<ReturnType<typeof networkClient.publicIPAddresses.get>>
70
- | undefined;
69
+ Awaited<ReturnType<typeof networkClient.publicIPAddresses.get>> | undefined;
71
70
 
72
71
  try {
73
72
  // Get detailed Public IP information
@@ -107,7 +106,8 @@ export async function analyzePublicIp(
107
106
  } catch (error) {
108
107
  const logger = getLogger(["savemoney", "azure"]);
109
108
  logger.warn(
110
- `Failed to get Public IP details for ${publicIpName}: ${error instanceof Error ? error.message : error}`,
109
+ `Failed to get Public IP details for ${publicIpName}: {message}`,
110
+ { message: error instanceof Error ? error.message : error },
111
111
  );
112
112
  reason += "Could not retrieve detailed Public IP information. ";
113
113
  }
@@ -161,9 +161,9 @@ async function enrichWithPricing(
161
161
  return { ...result, estimatedMonthlySavings };
162
162
  } catch (error) {
163
163
  const logger = getLogger(["savemoney", "azure"]);
164
- logger.warn(
165
- `Failed to enrich Public IP with pricing: ${error instanceof Error ? error.message : error}`,
166
- );
164
+ logger.warn("Failed to enrich Public IP with pricing: {message}", {
165
+ message: error instanceof Error ? error.message : error,
166
+ });
167
167
  return result;
168
168
  }
169
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}: ${error instanceof Error ? error.message : error}`,
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
- `Failed to get VM instance view for ${vmName}: ${error instanceof Error ? error.message : error}`,
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
- `Failed to enrich VM ${vmName} with pricing: ${error instanceof Error ? error.message : error}`,
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
  }
@@ -17,6 +17,14 @@ import type {
17
17
  * Azure configuration extending base config
18
18
  */
19
19
  export type AzureConfig = BaseConfig & {
20
+ /**
21
+ * Optional path to an AZQR (`azqr scan --json`) report. When set, the
22
+ * orchestrator ingests its `impacted` resources, filters them down to
23
+ * FinOps-relevant opportunities and merges them into the run output as
24
+ * findings with `source: "azqr"`. Can be set via YAML (`azure.azqrReportPath`)
25
+ * or the CLI `--azqr-report` flag, which takes precedence.
26
+ */
27
+ azqrReportPath?: string;
20
28
  /**
21
29
  * Maximum number of resources analyzed in parallel within a single
22
30
  * subscription. Defaults to 8 when not provided. Set to 1 for a fully
@@ -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
- logger.debug(`${message} ${JSON.stringify(object, null, 2)}`);
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}: ${error instanceof Error ? error.message : String(error)}`,
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
  }
package/src/finding.ts CHANGED
@@ -67,20 +67,17 @@ export type Finding = {
67
67
  * model is open to future Advisor categories.
68
68
  */
69
69
  export type FindingCategory =
70
- | "cost"
71
- | "operationalExcellence"
72
- | "performance"
73
- | "reliability"
74
- | "security";
70
+ "cost" | "operationalExcellence" | "performance" | "reliability" | "security";
75
71
 
76
72
  /**
77
73
  * Where the finding originated from.
78
74
  *
79
75
  * - `custom` → emitted by a savemoney analyzer plugin
80
76
  * - `advisor` → fetched from Azure Advisor recommendations
77
+ * - `azqr` → ingested from an AZQR (`azqr scan --json`) report
81
78
  * - `aws` → reserved for future AWS Trusted Advisor / Compute Optimizer
82
79
  */
83
- export type FindingSource = "advisor" | "aws" | "custom";
80
+ export type FindingSource = "advisor" | "aws" | "azqr" | "custom";
84
81
 
85
82
  /**
86
83
  * Monetary value associated with a finding, when known.
package/src/schema.ts CHANGED
@@ -109,6 +109,12 @@ const AzureSourceSchema = z.enum(AZURE_SOURCE_VALUES);
109
109
 
110
110
  const AzureSectionSchema = z
111
111
  .object({
112
+ /**
113
+ * Optional path to an AZQR (`azqr scan --json`) report. When set, its
114
+ * FinOps-relevant impacted resources are merged into the analysis. The
115
+ * `--azqr-report` CLI flag, when provided, overrides this value.
116
+ */
117
+ azqrReportPath: z.string().optional(),
112
118
  /**
113
119
  * Maximum number of resources analyzed in parallel within a single
114
120
  * subscription. Defaults to 8 when not provided.
@@ -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,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"}