@pagopa/dx-savemoney 0.3.3 → 0.5.0

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 (174) hide show
  1. package/README.md +12 -0
  2. package/dist/__tests__/finding.test.js +22 -0
  3. package/dist/__tests__/finding.test.js.map +1 -1
  4. package/dist/azure/__tests__/analyzer-tags.test.js +28 -1
  5. package/dist/azure/__tests__/analyzer-tags.test.js.map +1 -1
  6. package/dist/azure/__tests__/config.test.js +31 -1
  7. package/dist/azure/__tests__/config.test.js.map +1 -1
  8. package/dist/azure/__tests__/report.test.js +71 -0
  9. package/dist/azure/__tests__/report.test.js.map +1 -1
  10. package/dist/azure/analyzer.d.ts +8 -1
  11. package/dist/azure/analyzer.d.ts.map +1 -1
  12. package/dist/azure/analyzer.js +57 -41
  13. package/dist/azure/analyzer.js.map +1 -1
  14. package/dist/azure/analyzers/registry.d.ts.map +1 -1
  15. package/dist/azure/analyzers/registry.js +4 -4
  16. package/dist/azure/analyzers/registry.js.map +1 -1
  17. package/dist/azure/analyzers/types.d.ts +6 -0
  18. package/dist/azure/analyzers/types.d.ts.map +1 -1
  19. package/dist/azure/config.d.ts.map +1 -1
  20. package/dist/azure/config.js +1 -5
  21. package/dist/azure/config.js.map +1 -1
  22. package/dist/azure/pricing/__tests__/cache.test.d.ts +8 -0
  23. package/dist/azure/pricing/__tests__/cache.test.d.ts.map +1 -0
  24. package/dist/azure/pricing/__tests__/cache.test.js +56 -0
  25. package/dist/azure/pricing/__tests__/cache.test.js.map +1 -0
  26. package/dist/azure/pricing/__tests__/client.test.d.ts +9 -0
  27. package/dist/azure/pricing/__tests__/client.test.d.ts.map +1 -0
  28. package/dist/azure/pricing/__tests__/client.test.js +181 -0
  29. package/dist/azure/pricing/__tests__/client.test.js.map +1 -0
  30. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +6 -0
  31. package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +1 -0
  32. package/dist/azure/pricing/__tests__/pricing-service.test.js +283 -0
  33. package/dist/azure/pricing/__tests__/pricing-service.test.js.map +1 -0
  34. package/dist/azure/pricing/cache.d.ts +45 -0
  35. package/dist/azure/pricing/cache.d.ts.map +1 -0
  36. package/dist/azure/pricing/cache.js +79 -0
  37. package/dist/azure/pricing/cache.js.map +1 -0
  38. package/dist/azure/pricing/client.d.ts +67 -0
  39. package/dist/azure/pricing/client.d.ts.map +1 -0
  40. package/dist/azure/pricing/client.js +96 -0
  41. package/dist/azure/pricing/client.js.map +1 -0
  42. package/dist/azure/pricing/index.d.ts +6 -0
  43. package/dist/azure/pricing/index.d.ts.map +1 -0
  44. package/dist/azure/pricing/index.js +6 -0
  45. package/dist/azure/pricing/index.js.map +1 -0
  46. package/dist/azure/pricing/pricing-service.d.ts +46 -0
  47. package/dist/azure/pricing/pricing-service.d.ts.map +1 -0
  48. package/dist/azure/pricing/pricing-service.js +84 -0
  49. package/dist/azure/pricing/pricing-service.js.map +1 -0
  50. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +5 -0
  51. package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +1 -0
  52. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +137 -0
  53. package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +1 -0
  54. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +5 -0
  55. package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +1 -0
  56. package/dist/azure/pricing/resolvers/__tests__/disk.test.js +160 -0
  57. package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +1 -0
  58. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +5 -0
  59. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +1 -0
  60. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +113 -0
  61. package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +1 -0
  62. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +9 -0
  63. package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +1 -0
  64. package/dist/azure/pricing/resolvers/__tests__/vm.test.js +204 -0
  65. package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +1 -0
  66. package/dist/azure/pricing/resolvers/app-service.d.ts +27 -0
  67. package/dist/azure/pricing/resolvers/app-service.d.ts.map +1 -0
  68. package/dist/azure/pricing/resolvers/app-service.js +67 -0
  69. package/dist/azure/pricing/resolvers/app-service.js.map +1 -0
  70. package/dist/azure/pricing/resolvers/disk.d.ts +59 -0
  71. package/dist/azure/pricing/resolvers/disk.d.ts.map +1 -0
  72. package/dist/azure/pricing/resolvers/disk.js +140 -0
  73. package/dist/azure/pricing/resolvers/disk.js.map +1 -0
  74. package/dist/azure/pricing/resolvers/index.d.ts +12 -0
  75. package/dist/azure/pricing/resolvers/index.d.ts.map +1 -0
  76. package/dist/azure/pricing/resolvers/index.js +8 -0
  77. package/dist/azure/pricing/resolvers/index.js.map +1 -0
  78. package/dist/azure/pricing/resolvers/public-ip.d.ts +32 -0
  79. package/dist/azure/pricing/resolvers/public-ip.d.ts.map +1 -0
  80. package/dist/azure/pricing/resolvers/public-ip.js +56 -0
  81. package/dist/azure/pricing/resolvers/public-ip.js.map +1 -0
  82. package/dist/azure/pricing/resolvers/vm.d.ts +51 -0
  83. package/dist/azure/pricing/resolvers/vm.d.ts.map +1 -0
  84. package/dist/azure/pricing/resolvers/vm.js +89 -0
  85. package/dist/azure/pricing/resolvers/vm.js.map +1 -0
  86. package/dist/azure/pricing/schema.d.ts +88 -0
  87. package/dist/azure/pricing/schema.d.ts.map +1 -0
  88. package/dist/azure/pricing/schema.js +64 -0
  89. package/dist/azure/pricing/schema.js.map +1 -0
  90. package/dist/azure/pricing/test-cache-dir.d.ts +3 -0
  91. package/dist/azure/pricing/test-cache-dir.d.ts.map +1 -0
  92. package/dist/azure/pricing/test-cache-dir.js +17 -0
  93. package/dist/azure/pricing/test-cache-dir.js.map +1 -0
  94. package/dist/azure/report.d.ts.map +1 -1
  95. package/dist/azure/report.js +57 -18
  96. package/dist/azure/report.js.map +1 -1
  97. package/dist/azure/resources/__tests__/app-service.test.d.ts +5 -0
  98. package/dist/azure/resources/__tests__/app-service.test.d.ts.map +1 -0
  99. package/dist/azure/resources/__tests__/app-service.test.js +105 -0
  100. package/dist/azure/resources/__tests__/app-service.test.js.map +1 -0
  101. package/dist/azure/resources/app-service.d.ts +8 -3
  102. package/dist/azure/resources/app-service.d.ts.map +1 -1
  103. package/dist/azure/resources/app-service.js +35 -2
  104. package/dist/azure/resources/app-service.js.map +1 -1
  105. package/dist/azure/resources/disk.d.ts +3 -1
  106. package/dist/azure/resources/disk.d.ts.map +1 -1
  107. package/dist/azure/resources/disk.js +35 -3
  108. package/dist/azure/resources/disk.js.map +1 -1
  109. package/dist/azure/resources/public-ip.d.ts +3 -1
  110. package/dist/azure/resources/public-ip.d.ts.map +1 -1
  111. package/dist/azure/resources/public-ip.js +41 -3
  112. package/dist/azure/resources/public-ip.js.map +1 -1
  113. package/dist/azure/resources/vm.d.ts +4 -1
  114. package/dist/azure/resources/vm.d.ts.map +1 -1
  115. package/dist/azure/resources/vm.js +49 -6
  116. package/dist/azure/resources/vm.js.map +1 -1
  117. package/dist/azure/types.d.ts +18 -3
  118. package/dist/azure/types.d.ts.map +1 -1
  119. package/dist/finding.d.ts +3 -3
  120. package/dist/finding.d.ts.map +1 -1
  121. package/dist/finding.js +7 -0
  122. package/dist/finding.js.map +1 -1
  123. package/dist/index.d.ts +3 -2
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +1 -0
  126. package/dist/index.js.map +1 -1
  127. package/dist/schema.d.ts +5 -1
  128. package/dist/schema.d.ts.map +1 -1
  129. package/dist/schema.js +5 -3
  130. package/dist/schema.js.map +1 -1
  131. package/dist/types.d.ts +8 -0
  132. package/dist/types.d.ts.map +1 -1
  133. package/dist/types.js +2 -0
  134. package/dist/types.js.map +1 -1
  135. package/package.json +1 -1
  136. package/src/__tests__/finding.test.ts +24 -0
  137. package/src/azure/__tests__/analyzer-tags.test.ts +36 -1
  138. package/src/azure/__tests__/config.test.ts +43 -1
  139. package/src/azure/__tests__/fixtures/full-override.yaml +2 -0
  140. package/src/azure/__tests__/report.test.ts +78 -0
  141. package/src/azure/analyzer.ts +70 -52
  142. package/src/azure/analyzers/registry.ts +8 -2
  143. package/src/azure/analyzers/types.ts +6 -0
  144. package/src/azure/config.ts +1 -5
  145. package/src/azure/pricing/__tests__/cache.test.ts +67 -0
  146. package/src/azure/pricing/__tests__/client.test.ts +212 -0
  147. package/src/azure/pricing/__tests__/fixtures/vm-b2s-westeurope.json +51 -0
  148. package/src/azure/pricing/__tests__/pricing-service.test.ts +315 -0
  149. package/src/azure/pricing/cache.ts +92 -0
  150. package/src/azure/pricing/client.ts +137 -0
  151. package/src/azure/pricing/index.ts +6 -0
  152. package/src/azure/pricing/pricing-service.ts +125 -0
  153. package/src/azure/pricing/resolvers/__tests__/app-service.test.ts +154 -0
  154. package/src/azure/pricing/resolvers/__tests__/disk.test.ts +189 -0
  155. package/src/azure/pricing/resolvers/__tests__/public-ip.test.ts +130 -0
  156. package/src/azure/pricing/resolvers/__tests__/vm.test.ts +232 -0
  157. package/src/azure/pricing/resolvers/app-service.ts +102 -0
  158. package/src/azure/pricing/resolvers/disk.ts +170 -0
  159. package/src/azure/pricing/resolvers/index.ts +31 -0
  160. package/src/azure/pricing/resolvers/public-ip.ts +87 -0
  161. package/src/azure/pricing/resolvers/vm.ts +125 -0
  162. package/src/azure/pricing/schema.ts +69 -0
  163. package/src/azure/pricing/test-cache-dir.ts +26 -0
  164. package/src/azure/report.ts +86 -21
  165. package/src/azure/resources/__tests__/app-service.test.ts +165 -0
  166. package/src/azure/resources/app-service.ts +65 -4
  167. package/src/azure/resources/disk.ts +46 -4
  168. package/src/azure/resources/public-ip.ts +61 -2
  169. package/src/azure/resources/vm.ts +89 -11
  170. package/src/azure/types.ts +19 -3
  171. package/src/finding.ts +10 -3
  172. package/src/index.ts +4 -3
  173. package/src/schema.ts +7 -3
  174. package/src/types.ts +12 -0
package/dist/finding.d.ts CHANGED
@@ -30,9 +30,9 @@ export type Finding = {
30
30
  */
31
31
  code: string;
32
32
  /**
33
- * Estimated monthly cost that could be recovered by acting on this
34
- * finding. Populated by Advisor and (in later phases) by the Retail
35
- * Prices integration. Absent when the analyzer cannot estimate it.
33
+ * Estimated monthly saving for this specific finding. Populated by Advisor
34
+ * recommendations; custom Retail Prices estimates stay at resource level
35
+ * because they represent cost at risk, not a per-finding saving.
36
36
  */
37
37
  estimatedMonthlySavings?: Money;
38
38
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB;;;OAGG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,KAAK,CAAC;IAChC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,UAAU,CAAC;AAEf;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,CAAC,SAAS,GAAG,OAAO,IAAI;IAChD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,OAAO,EAAE,CAcZ"}
1
+ {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB;;;OAGG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,KAAK,CAAC;IAChC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,UAAU,CAAC;AAEf;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,CAAC,SAAS,GAAG,OAAO,IAAI;IAChD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,OAAO,EAAE,CAqBZ"}
package/dist/finding.js CHANGED
@@ -27,6 +27,13 @@ export function findingsFromAnalysisResult(args) {
27
27
  if (sentences.length === 0) {
28
28
  return [];
29
29
  }
30
+ // We intentionally do NOT propagate any monetary estimate down to the
31
+ // individual findings: a per-resource cost (e.g. €29.94/mo for a VM)
32
+ // attached to a sentence like "No tags found" would falsely suggest
33
+ // "add the missing tags to save €29.94". The resource-level cost is
34
+ // carried instead by `AnalysisResult.estimatedMonthlySavings` and
35
+ // surfaced by the report layer as a per-resource header — see
36
+ // `azure/report.ts`.
30
37
  return sentences.map((sentence) => ({
31
38
  category: "cost",
32
39
  code: code ?? "custom.unknown",
@@ -1 +1 @@
1
- {"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgGH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAM1C;IACC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,SAAS,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,QAAQ,EAAE,MAAe;QACzB,IAAI,EAAE,IAAI,IAAI,gBAAgB;QAC9B,MAAM,EAAE,QAAQ;QAChB,UAAU;QACV,QAAQ;QACR,MAAM;KACP,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO,MAAM;SACV,KAAK,CAAC,WAAW,CAAC;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgGH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAM1C;IACC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,SAAS,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,sEAAsE;IACtE,qEAAqE;IACrE,oEAAoE;IACpE,oEAAoE;IACpE,kEAAkE;IAClE,8DAA8D;IAC9D,qBAAqB;IACrB,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,QAAQ,EAAE,MAAe;QACzB,IAAI,EAAE,IAAI,IAAI,gBAAgB;QAC9B,MAAM,EAAE,QAAQ;QAChB,UAAU;QACV,QAAQ;QACR,MAAM;KACP,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO,MAAM;SACV,KAAK,CAAC,WAAW,CAAC;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -13,11 +13,12 @@
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
15
  export { type Analyzer, type AnalyzerContext, type AzureClients, createDefaultAnalyzers, } from "./azure/analyzers/index.js";
16
- export type { AzureConfig, AzureSource } from "./azure/types.js";
16
+ export type { AzureConfig, AzureSource, PricingConfig } from "./azure/types.js";
17
+ export { type MetricsCache, type MonitorClientLike } from "./azure/utils.js";
17
18
  import * as azureModule from "./azure/index.js";
18
19
  export declare const azure: typeof azureModule;
19
- export { type MetricsCache, type MonitorClientLike } from "./azure/utils.js";
20
20
  export { type Finding, type FindingCategory, findingsFromAnalysisResult, type FindingSource, type Money, type ResourceReport, } from "./finding.js";
21
+ export { AZURE_SOURCE_VALUES } from "./schema.js";
21
22
  export * from "./types.js";
22
23
  import type { AzureConfig } from "./azure/types.js";
23
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGjE,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAEjC,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG7E,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG7E,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAGjC,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,cAAc,YAAY,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE1E"}
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import * as azureModule from "./azure/index.js";
18
18
  export const azure = azureModule;
19
19
  // Phase 0: unified Finding model and analyzer plugin layer
20
20
  export { findingsFromAnalysisResult, } from "./finding.js";
21
+ export { AZURE_SOURCE_VALUES } from "./schema.js";
21
22
  export * from "./types.js";
22
23
  import { loadAzureConfig } from "./azure/config.js";
23
24
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAKpC,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAIjC,2DAA2D;AAC3D,OAAO,EAGL,0BAA0B,GAI3B,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAMpC,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAEjC,2DAA2D;AAC3D,OAAO,EAGL,0BAA0B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -80,6 +80,7 @@ export declare const ThresholdsSchema: z.ZodObject<{
80
80
  networkInBytesPerDay: number;
81
81
  } | undefined>>;
82
82
  }, z.core.$strict>;
83
+ export declare const AZURE_SOURCE_VALUES: readonly ["advisor", "custom"];
83
84
  /**
84
85
  * Single Zod schema representing the entire YAML configuration file.
85
86
  * Use `ConfigSchema.parse(rawYaml)` to validate and apply defaults.
@@ -88,9 +89,12 @@ export declare const ConfigSchema: z.ZodObject<{
88
89
  azure: z.ZodObject<{
89
90
  concurrency: z.ZodOptional<z.ZodNumber>;
90
91
  preferredLocation: z.ZodDefault<z.ZodString>;
91
- sources: z.ZodDefault<z.ZodArray<z.ZodEnum<{
92
+ sources: z.ZodDefault<z.ZodTuple<[z.ZodEnum<{
93
+ advisor: "advisor";
92
94
  custom: "custom";
95
+ }>], z.ZodEnum<{
93
96
  advisor: "advisor";
97
+ custom: "custom";
94
98
  }>>>;
95
99
  subscriptionIds: z.ZodArray<z.ZodString>;
96
100
  thresholds: z.ZodPipe<z.ZodOptional<z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;AAkCZ;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAmD,CAAC;AAI7E,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;AAIZ,eAAO,MAAM,mBAAmB,gCAAiC,CAAC;AAkClE;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAmD,CAAC;AAI7E,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
package/dist/schema.js CHANGED
@@ -79,6 +79,8 @@ export const ThresholdsSchema = z
79
79
  })
80
80
  .strict();
81
81
  // ── top-level config schema ──────────────────────────────────────────────────
82
+ export const AZURE_SOURCE_VALUES = ["advisor", "custom"];
83
+ const AzureSourceSchema = z.enum(AZURE_SOURCE_VALUES);
82
84
  const AzureSectionSchema = z
83
85
  .object({
84
86
  /**
@@ -93,9 +95,9 @@ const AzureSectionSchema = z
93
95
  * Azure Advisor recommendations, or `["custom"]` to skip Advisor.
94
96
  */
95
97
  sources: z
96
- .array(z.enum(["advisor", "custom"]))
97
- .nonempty()
98
- .default(["advisor", "custom"]),
98
+ .tuple([AzureSourceSchema])
99
+ .rest(AzureSourceSchema)
100
+ .default([...AZURE_SOURCE_VALUES]),
99
101
  subscriptionIds: z
100
102
  .array(z.string())
101
103
  .min(1, "Config file must contain at least one entry in 'azure.subscriptionIds'"),
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Common types shared across all cloud providers
3
3
  */
4
+ import type { Money } from "./finding.js";
4
5
  export type AnalysisResult = {
5
6
  costRisk: CostRisk;
7
+ /**
8
+ * Estimated monthly resource cost at risk. Custom pricing-enriched
9
+ * analyzers populate it when a Retail Prices lookup succeeds; the report
10
+ * renders it once per resource instead of treating it as a per-finding
11
+ * saving.
12
+ */
13
+ estimatedMonthlySavings?: Money;
6
14
  reason: string;
7
15
  suspectedUnused: boolean;
8
16
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;;GAGG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,cAAc,GAC7B,cAAc,CAOhB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAI1C,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,KAAK,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;;GAGG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,cAAc,GAC7B,cAAc,CAUhB"}
package/dist/types.js CHANGED
@@ -13,6 +13,8 @@ export const DEFAULT_THRESHOLDS = ThresholdsSchema.parse({});
13
13
  export function mergeResults(baseResult, specificResult) {
14
14
  return {
15
15
  costRisk: specificResult.costRisk,
16
+ estimatedMonthlySavings: specificResult.estimatedMonthlySavings ??
17
+ baseResult.estimatedMonthlySavings,
16
18
  reason: baseResult.reason + specificResult.reason,
17
19
  suspectedUnused: baseResult.suspectedUnused || specificResult.suspectedUnused,
18
20
  };
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAwB/C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA0B,EAC1B,cAA8B;IAE9B,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM;QACjD,eAAe,EACb,UAAU,CAAC,eAAe,IAAI,cAAc,CAAC,eAAe;KAC/D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA+B/C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA0B,EAC1B,cAA8B;IAE9B,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,uBAAuB,EACrB,cAAc,CAAC,uBAAuB;YACtC,UAAU,CAAC,uBAAuB;QACpC,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM;QACjD,eAAe,EACb,UAAU,CAAC,eAAe,IAAI,cAAc,CAAC,eAAe;KAC/D,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-savemoney",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
6
6
  "repository": {
@@ -145,5 +145,29 @@ describe("findingsFromAnalysisResult", () => {
145
145
  });
146
146
  expect(result.every((f) => f.category === "cost")).toBe(true);
147
147
  });
148
+
149
+ it("never attaches estimatedMonthlySavings to custom findings", () => {
150
+ // Custom analyzers carry the resource's monthly cost on
151
+ // `AnalysisResult.estimatedMonthlySavings` and the report layer
152
+ // renders it at the resource level. Propagating it down to a
153
+ // single finding sentence (e.g. "No tags found") would falsely
154
+ // suggest that fixing that sentence yields the full saving.
155
+ const result = findingsFromAnalysisResult({
156
+ ...BASE_ARGS,
157
+ reason: "Low CPU. Low network. Idle.",
158
+ });
159
+ expect(result).toHaveLength(3);
160
+ for (const f of result) {
161
+ expect(f.estimatedMonthlySavings).toBeUndefined();
162
+ }
163
+ });
164
+
165
+ it("omits estimatedMonthlySavings entirely", () => {
166
+ const result = findingsFromAnalysisResult({
167
+ ...BASE_ARGS,
168
+ reason: "Low CPU.",
169
+ });
170
+ expect(result[0].estimatedMonthlySavings).toBeUndefined();
171
+ });
148
172
  });
149
173
  });
@@ -8,8 +8,12 @@
8
8
  import { describe, expect, it } from "vitest";
9
9
 
10
10
  import type { Finding } from "../../finding.js";
11
+ import type { AzureDetailedResourceReport } from "../types.js";
11
12
 
12
- import { shouldIncludeAdvisorFindingForTags } from "../analyzer.js";
13
+ import {
14
+ mergeFinding,
15
+ shouldIncludeAdvisorFindingForTags,
16
+ } from "../analyzer.js";
13
17
 
14
18
  function mkFinding(resourceId: string, source: Finding["source"]): Finding {
15
19
  return {
@@ -72,3 +76,34 @@ describe("shouldIncludeAdvisorFindingForTags", () => {
72
76
  ).toBe(false);
73
77
  });
74
78
  });
79
+
80
+ describe("mergeFinding", () => {
81
+ it("preserves custom cost at risk when merging an Advisor finding", () => {
82
+ const resourceId =
83
+ "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1";
84
+ const existing: AzureDetailedResourceReport = {
85
+ analysis: {
86
+ costRisk: "high",
87
+ estimatedMonthlySavings: { amount: 29.94, currency: "EUR" },
88
+ reason: "VM is deallocated.",
89
+ suspectedUnused: true,
90
+ },
91
+ findings: [mkFinding(resourceId, "custom")],
92
+ resource: {
93
+ id: resourceId,
94
+ name: "vm1",
95
+ type: "Microsoft.Compute/virtualMachines",
96
+ },
97
+ };
98
+ const reports = [existing];
99
+ const reportsById = new Map([[resourceId.toLowerCase(), existing]]);
100
+
101
+ mergeFinding(mkFinding(resourceId, "advisor"), reports, reportsById);
102
+
103
+ expect(existing.analysis.estimatedMonthlySavings).toEqual({
104
+ amount: 29.94,
105
+ currency: "EUR",
106
+ });
107
+ expect(existing.findings).toHaveLength(2);
108
+ });
109
+ });
@@ -8,9 +8,12 @@
8
8
 
9
9
  import path from "path";
10
10
  import { fileURLToPath } from "url";
11
- import { describe, expect, it } from "vitest";
11
+ import { afterEach, describe, expect, expectTypeOf, it, vi } from "vitest";
12
+
13
+ import type { AzureConfig, AzureSource } from "../../index.js";
12
14
 
13
15
  import { loadConfig } from "../../index.js";
16
+ import { ConfigSchema } from "../../schema.js";
14
17
  import { DEFAULT_THRESHOLDS } from "../../types.js";
15
18
 
16
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -23,6 +26,10 @@ const FIXTURE_PARTIAL = path.resolve(
23
26
  const FIXTURE_FULL = path.resolve(__dirname, "fixtures/full-override.yaml");
24
27
 
25
28
  describe("loadConfig", () => {
29
+ afterEach(() => {
30
+ vi.unstubAllEnvs();
31
+ });
32
+
26
33
  it("throws when explicit path does not exist", async () => {
27
34
  await expect(loadConfig("/nonexistent/config.yaml")).rejects.toThrow(
28
35
  "Config file not found",
@@ -32,13 +39,25 @@ describe("loadConfig", () => {
32
39
  it("loads subscriptionIds and defaults from a partial YAML file", async () => {
33
40
  const result = await loadConfig(FIXTURE_PARTIAL);
34
41
 
42
+ expectTypeOf<AzureConfig["sources"]>().toEqualTypeOf<
43
+ [AzureSource, ...AzureSource[]]
44
+ >();
35
45
  expect(result.subscriptionIds).toEqual([
36
46
  "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
37
47
  ]);
38
48
  expect(result.preferredLocation).toBe("italynorth");
49
+ expect(result.sources).toEqual(["advisor", "custom"]);
39
50
  expect(result.timespanDays).toBe(30);
40
51
  });
41
52
 
53
+ it("applies sources defaults when loading from the environment", async () => {
54
+ vi.stubEnv("ARM_SUBSCRIPTION_ID", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
55
+
56
+ const result = await loadConfig();
57
+
58
+ expect(result.sources).toEqual(["advisor", "custom"]);
59
+ });
60
+
42
61
  it("loads partial threshold overrides and keeps defaults for missing fields", async () => {
43
62
  const result = await loadConfig(FIXTURE_PARTIAL);
44
63
 
@@ -69,6 +88,7 @@ describe("loadConfig", () => {
69
88
 
70
89
  expect(result.subscriptionIds).toHaveLength(2);
71
90
  expect(result.preferredLocation).toBe("westeurope");
91
+ expect(result.sources).toEqual(["custom"]);
72
92
  expect(result.timespanDays).toBe(60);
73
93
 
74
94
  expect(result.thresholds?.vm.cpuPercent).toBe(5);
@@ -94,4 +114,26 @@ describe("loadConfig", () => {
94
114
  expect(result.thresholds).toHaveProperty("publicIp");
95
115
  expect(result.thresholds).toHaveProperty("staticSite");
96
116
  });
117
+
118
+ it("rejects an empty sources list", () => {
119
+ const result = ConfigSchema.safeParse({
120
+ azure: {
121
+ sources: [],
122
+ subscriptionIds: ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
123
+ },
124
+ });
125
+
126
+ expect(result.success).toBe(false);
127
+ });
128
+
129
+ it("does not expose pricing settings in YAML config", () => {
130
+ const result = ConfigSchema.safeParse({
131
+ azure: {
132
+ pricing: { enabled: false },
133
+ subscriptionIds: ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
134
+ },
135
+ });
136
+
137
+ expect(result.success).toBe(false);
138
+ });
97
139
  });
@@ -3,6 +3,8 @@ azure:
3
3
  - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
4
4
  - yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
5
5
  preferredLocation: westeurope
6
+ sources:
7
+ - custom
6
8
  timespanDays: 60
7
9
  thresholds:
8
10
  vm:
@@ -133,6 +133,84 @@ describe("generateReport — lint format", () => {
133
133
  expect(calls).toHaveLength(1);
134
134
  expect(calls[0]).toContain("0 issues found");
135
135
  });
136
+
137
+ describe("monetary annotations", () => {
138
+ const VM_WITH_COST: AzureDetailedResourceReport = {
139
+ analysis: {
140
+ costRisk: "high",
141
+ estimatedMonthlySavings: { amount: 29.94, currency: "EUR" },
142
+ reason: "No tags found. VM is deallocated.",
143
+ suspectedUnused: true,
144
+ },
145
+ resource: {
146
+ id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-x",
147
+ name: "vm-x",
148
+ type: "Microsoft.Compute/virtualMachines",
149
+ },
150
+ };
151
+ const ADVISOR_WITH_SAVINGS: AzureDetailedResourceReport = {
152
+ analysis: {
153
+ costRisk: "medium",
154
+ reason: "Advisor recommends a reservation.",
155
+ suspectedUnused: true,
156
+ },
157
+ findings: [
158
+ {
159
+ category: "cost",
160
+ code: "advisor.reservation",
161
+ estimatedMonthlySavings: { amount: 12.34, currency: "EUR" },
162
+ reason: "Advisor recommends a reservation.",
163
+ resourceId:
164
+ "/subscriptions/sub1/providers/Microsoft.Advisor/recommendations/rec-1",
165
+ severity: "medium",
166
+ source: "advisor",
167
+ },
168
+ ],
169
+ resource: {
170
+ id: "/subscriptions/sub1/providers/Microsoft.Advisor/recommendations/rec-1",
171
+ name: "rec-1",
172
+ type: "Microsoft.Advisor/recommendations",
173
+ },
174
+ };
175
+
176
+ it("renders the resource monthly cost ONCE as a header annotation, not on each finding", async () => {
177
+ await generateReport([VM_WITH_COST], "lint");
178
+ const output = allLogs(logSpy);
179
+ // header line contains the cost-at-risk label
180
+ expect(output).toMatch(/cost at risk:.*29[.,]94/);
181
+ // exactly one occurrence — must NOT appear next to each finding
182
+ const matches = output.match(/29[.,]94/g) ?? [];
183
+ // 1× header + 1× summary trailer = 2 occurrences; the per-finding line must not carry it
184
+ expect(matches.length).toBe(2);
185
+ });
186
+
187
+ it("does not attach the resource cost to the 'No tags found' finding line", async () => {
188
+ await generateReport([VM_WITH_COST], "lint");
189
+ const findingLines = logSpy.mock.calls
190
+ .map((c: unknown[]) => String(c[0]))
191
+ .filter((l: string) => l.includes("No tags found"));
192
+ expect(findingLines).toHaveLength(1);
193
+ expect(findingLines[0]).not.toMatch(/29[.,]94/);
194
+ });
195
+
196
+ it("uses the 'Estimated monthly cost at risk' label in the summary trailer", async () => {
197
+ await generateReport([VM_WITH_COST], "lint");
198
+ const output = allLogs(logSpy);
199
+ expect(output).toContain("Estimated monthly cost at risk");
200
+ // The old, misleading "Estimated monthly savings (custom)" label is gone
201
+ expect(output).not.toMatch(/Estimated monthly savings:\s*€/);
202
+ });
203
+
204
+ it("renders Advisor savings separately from custom cost at risk", async () => {
205
+ await generateReport([VM_WITH_COST, ADVISOR_WITH_SAVINGS], "lint");
206
+ const output = allLogs(logSpy);
207
+
208
+ expect(output).toMatch(/cost at risk:.*29[.,]94/);
209
+ expect(output).toMatch(/saving:.*12[.,]34/);
210
+ expect(output).toContain("Estimated monthly cost at risk (custom)");
211
+ expect(output).toContain("Estimated monthly savings (advisor)");
212
+ });
213
+ });
136
214
  });
137
215
 
138
216
  describe("generateReport — table format", () => {