@oneuptime/common 12.0.17 → 12.0.19

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 (220) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +67 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -0,0 +1,794 @@
1
+ import { JSONObject } from "../../../Types/JSON";
2
+ import {
3
+ parseCronJobObject,
4
+ parseDaemonSetObject,
5
+ parseDeploymentObject,
6
+ parseHPAObject,
7
+ parseJobObject,
8
+ parseNamespaceObject,
9
+ parseNodeObject,
10
+ parsePVCObject,
11
+ parsePVObject,
12
+ parsePodObject,
13
+ parseStatefulSetObject,
14
+ } from "../../../Types/Kubernetes/KubernetesObjectParser";
15
+ import { describe, expect, test } from "@jest/globals";
16
+
17
+ /*
18
+ * The parse<Kind>Object functions turn the OTLP kvlistValue an agent ships a
19
+ * Kubernetes object in into a typed inventory record. The low-level kv helpers
20
+ * they build on are covered by KubernetesObjectParser.test.ts; this suite
21
+ * covers the object-level parsers themselves, which had none.
22
+ *
23
+ * These parsers are defensive by contract: each is wrapped in try/catch and
24
+ * returns null on a malformed payload rather than throwing, because they run in
25
+ * the ingest hot path over untrusted agent data. That contract — null on
26
+ * garbage, correct decode on well-formed input, and no crash on partial input —
27
+ * is exactly what is pinned here, alongside the numeric parseInt fallbacks and
28
+ * the camelCase/snake_case dual encoding the wire format uses.
29
+ */
30
+
31
+ // --- OTLP value-wrapper builders (mirror the wire encoding) ---
32
+
33
+ function kv(entries: Array<[string, JSONObject]>): JSONObject {
34
+ return {
35
+ values: entries.map(([key, value]: [string, JSONObject]) => {
36
+ return { key, value };
37
+ }),
38
+ };
39
+ }
40
+
41
+ function str(value: string): JSONObject {
42
+ return { stringValue: value };
43
+ }
44
+
45
+ // Wrap a kvlist as the value of a key.
46
+ function obj(kvList: JSONObject): JSONObject {
47
+ return { kvlistValue: kvList };
48
+ }
49
+
50
+ // An array of kvlist items (e.g. containers, conditions, addresses).
51
+ function arrOfObjects(items: Array<JSONObject>): JSONObject {
52
+ return {
53
+ arrayValue: {
54
+ values: items.map((item: JSONObject) => {
55
+ return { kvlistValue: item };
56
+ }),
57
+ },
58
+ };
59
+ }
60
+
61
+ // An array of strings (e.g. accessModes, command, args).
62
+ function arrOfStrings(items: Array<string>): JSONObject {
63
+ return {
64
+ arrayValue: {
65
+ values: items.map((s: string) => {
66
+ return { stringValue: s };
67
+ }),
68
+ },
69
+ };
70
+ }
71
+
72
+ describe("parsePodObject", () => {
73
+ test("returns null when metadata is absent", () => {
74
+ expect(parsePodObject(kv([["spec", obj(kv([]))]]))).toBeNull();
75
+ });
76
+
77
+ test("returns null when metadata is a flat string, not a kvlist", () => {
78
+ expect(parsePodObject(kv([["metadata", str("oops")]]))).toBeNull();
79
+ });
80
+
81
+ test("decodes a fully-specified pod", () => {
82
+ const pod: JSONObject = kv([
83
+ [
84
+ "metadata",
85
+ obj(
86
+ kv([
87
+ ["name", str("web-1")],
88
+ ["namespace", str("prod")],
89
+ ["uid", str("uid-123")],
90
+ ["labels", obj(kv([["app", str("web")]]))],
91
+ ]),
92
+ ),
93
+ ],
94
+ [
95
+ "spec",
96
+ obj(
97
+ kv([
98
+ ["serviceAccountName", str("web-sa")],
99
+ ["nodeName", str("node-a")],
100
+ ["nodeSelector", obj(kv([["disk", str("ssd")]]))],
101
+ [
102
+ "containers",
103
+ arrOfObjects([
104
+ kv([
105
+ ["name", str("app")],
106
+ ["image", str("nginx:1.25")],
107
+ ["command", arrOfStrings(["nginx", "-g", "daemon off;"])],
108
+ [
109
+ "ports",
110
+ arrOfObjects([
111
+ kv([
112
+ ["name", str("http")],
113
+ ["containerPort", str("8080")],
114
+ ["protocol", str("TCP")],
115
+ ]),
116
+ ]),
117
+ ],
118
+ ]),
119
+ ]),
120
+ ],
121
+ [
122
+ "tolerations",
123
+ arrOfObjects([
124
+ kv([
125
+ ["key", str("node.kubernetes.io/not-ready")],
126
+ ["operator", str("Exists")],
127
+ ["effect", str("NoExecute")],
128
+ ]),
129
+ ]),
130
+ ],
131
+ ]),
132
+ ),
133
+ ],
134
+ [
135
+ "status",
136
+ obj(
137
+ kv([
138
+ ["phase", str("Running")],
139
+ ["podIP", str("10.0.0.5")],
140
+ ["hostIP", str("10.0.0.1")],
141
+ ["qosClass", str("Burstable")],
142
+ [
143
+ "conditions",
144
+ arrOfObjects([
145
+ kv([
146
+ ["type", str("Ready")],
147
+ ["status", str("True")],
148
+ ]),
149
+ ]),
150
+ ],
151
+ [
152
+ "containerStatuses",
153
+ arrOfObjects([
154
+ kv([
155
+ ["name", str("app")],
156
+ ["ready", str("true")],
157
+ ["restartCount", str("2")],
158
+ ["image", str("nginx:1.25")],
159
+ ["state", obj(kv([["running", obj(kv([]))]]))],
160
+ ]),
161
+ ]),
162
+ ],
163
+ ]),
164
+ ),
165
+ ],
166
+ ]);
167
+
168
+ const result: ReturnType<typeof parsePodObject> = parsePodObject(pod);
169
+ expect(result).not.toBeNull();
170
+ expect(result!.metadata.name).toBe("web-1");
171
+ expect(result!.metadata.namespace).toBe("prod");
172
+ expect(result!.metadata.labels).toEqual({ app: "web" });
173
+ expect(result!.spec.serviceAccountName).toBe("web-sa");
174
+ expect(result!.spec.nodeName).toBe("node-a");
175
+ expect(result!.spec.nodeSelector).toEqual({ disk: "ssd" });
176
+ expect(result!.spec.containers).toHaveLength(1);
177
+ expect(result!.spec.containers[0]!.name).toBe("app");
178
+ expect(result!.spec.containers[0]!.image).toBe("nginx:1.25");
179
+ expect(result!.spec.containers[0]!.command).toEqual([
180
+ "nginx",
181
+ "-g",
182
+ "daemon off;",
183
+ ]);
184
+ expect(result!.spec.containers[0]!.ports[0]!.containerPort).toBe(8080);
185
+ expect(result!.spec.tolerations[0]!.operator).toBe("Exists");
186
+ expect(result!.status.phase).toBe("Running");
187
+ expect(result!.status.podIP).toBe("10.0.0.5");
188
+ expect(result!.status.conditions[0]!.type).toBe("Ready");
189
+ expect(result!.status.containerStatuses[0]!.ready).toBe(true);
190
+ expect(result!.status.containerStatuses[0]!.restartCount).toBe(2);
191
+ expect(result!.status.containerStatuses[0]!.state).toBe("running");
192
+ });
193
+
194
+ test("extracts a container-status waiting reason (e.g. CrashLoopBackOff)", () => {
195
+ const pod: JSONObject = kv([
196
+ ["metadata", obj(kv([["name", str("crasher")]]))],
197
+ [
198
+ "status",
199
+ obj(
200
+ kv([
201
+ [
202
+ "containerStatuses",
203
+ arrOfObjects([
204
+ kv([
205
+ ["name", str("app")],
206
+ ["ready", str("false")],
207
+ [
208
+ "state",
209
+ obj(
210
+ kv([
211
+ [
212
+ "waiting",
213
+ obj(kv([["reason", str("CrashLoopBackOff")]])),
214
+ ],
215
+ ]),
216
+ ),
217
+ ],
218
+ ]),
219
+ ]),
220
+ ],
221
+ ]),
222
+ ),
223
+ ],
224
+ ]);
225
+
226
+ const result: ReturnType<typeof parsePodObject> = parsePodObject(pod);
227
+ expect(result!.status.containerStatuses[0]!.state).toBe("waiting");
228
+ expect(result!.status.containerStatuses[0]!.reason).toBe(
229
+ "CrashLoopBackOff",
230
+ );
231
+ expect(result!.status.containerStatuses[0]!.ready).toBe(false);
232
+ });
233
+
234
+ test("a metadata-only pod decodes with empty spec/status collections", () => {
235
+ const result: ReturnType<typeof parsePodObject> = parsePodObject(
236
+ kv([["metadata", obj(kv([["name", str("bare")]]))]]),
237
+ );
238
+ expect(result!.metadata.name).toBe("bare");
239
+ expect(result!.spec.containers).toEqual([]);
240
+ expect(result!.spec.nodeSelector).toEqual({});
241
+ expect(result!.status.phase).toBe("");
242
+ expect(result!.status.conditions).toEqual([]);
243
+ });
244
+
245
+ test("resolves an env var sourced from a secret to a redacted marker", () => {
246
+ const pod: JSONObject = kv([
247
+ ["metadata", obj(kv([["name", str("with-secret")]]))],
248
+ [
249
+ "spec",
250
+ obj(
251
+ kv([
252
+ [
253
+ "containers",
254
+ arrOfObjects([
255
+ kv([
256
+ ["name", str("app")],
257
+ [
258
+ "env",
259
+ arrOfObjects([
260
+ kv([
261
+ ["name", str("DB_PASSWORD")],
262
+ [
263
+ "valueFrom",
264
+ obj(
265
+ kv([
266
+ [
267
+ "secretKeyRef",
268
+ obj(
269
+ kv([
270
+ ["name", str("db-creds")],
271
+ ["key", str("password")],
272
+ ]),
273
+ ),
274
+ ],
275
+ ]),
276
+ ),
277
+ ],
278
+ ]),
279
+ ]),
280
+ ],
281
+ ]),
282
+ ]),
283
+ ],
284
+ ]),
285
+ ),
286
+ ],
287
+ ]);
288
+
289
+ const result: ReturnType<typeof parsePodObject> = parsePodObject(pod);
290
+ const env: { name: string; value: string } =
291
+ result!.spec.containers[0]!.env[0]!;
292
+ expect(env.name).toBe("DB_PASSWORD");
293
+ // The real secret value is never on the wire; the marker must not leak one.
294
+ expect(env.value).toBe("<Secret: db-creds/password>");
295
+ });
296
+ });
297
+
298
+ describe("parseNodeObject", () => {
299
+ test("returns null without metadata", () => {
300
+ expect(parseNodeObject(kv([]))).toBeNull();
301
+ });
302
+
303
+ test("decodes node info, capacity, and addresses", () => {
304
+ const node: JSONObject = kv([
305
+ ["metadata", obj(kv([["name", str("node-a")]]))],
306
+ [
307
+ "status",
308
+ obj(
309
+ kv([
310
+ [
311
+ "nodeInfo",
312
+ obj(
313
+ kv([
314
+ ["osImage", str("Ubuntu 22.04")],
315
+ ["kubeletVersion", str("v1.29.0")],
316
+ ["architecture", str("amd64")],
317
+ ]),
318
+ ),
319
+ ],
320
+ [
321
+ "capacity",
322
+ obj(
323
+ kv([
324
+ ["cpu", str("4")],
325
+ ["memory", str("8Gi")],
326
+ ]),
327
+ ),
328
+ ],
329
+ ["allocatable", obj(kv([["cpu", str("3800m")]]))],
330
+ [
331
+ "addresses",
332
+ arrOfObjects([
333
+ kv([
334
+ ["type", str("InternalIP")],
335
+ ["address", str("10.0.0.1")],
336
+ ]),
337
+ kv([
338
+ ["type", str("Hostname")],
339
+ ["address", str("node-a")],
340
+ ]),
341
+ ]),
342
+ ],
343
+ [
344
+ "conditions",
345
+ arrOfObjects([
346
+ kv([
347
+ ["type", str("Ready")],
348
+ ["status", str("True")],
349
+ ]),
350
+ ]),
351
+ ],
352
+ ]),
353
+ ),
354
+ ],
355
+ ]);
356
+
357
+ const result: ReturnType<typeof parseNodeObject> = parseNodeObject(node);
358
+ expect(result!.metadata.name).toBe("node-a");
359
+ expect(result!.status.nodeInfo.osImage).toBe("Ubuntu 22.04");
360
+ expect(result!.status.nodeInfo.kubeletVersion).toBe("v1.29.0");
361
+ expect(result!.status.capacity).toEqual({ cpu: "4", memory: "8Gi" });
362
+ expect(result!.status.allocatable).toEqual({ cpu: "3800m" });
363
+ expect(result!.status.addresses).toHaveLength(2);
364
+ expect(result!.status.addresses[0]).toEqual({
365
+ type: "InternalIP",
366
+ address: "10.0.0.1",
367
+ });
368
+ expect(result!.status.conditions[0]!.status).toBe("True");
369
+ });
370
+ });
371
+
372
+ describe("parseDeploymentObject", () => {
373
+ test("returns null without metadata", () => {
374
+ expect(parseDeploymentObject(kv([]))).toBeNull();
375
+ });
376
+
377
+ test("decodes replicas, strategy, selector, and status counters", () => {
378
+ const dep: JSONObject = kv([
379
+ ["metadata", obj(kv([["name", str("api")]]))],
380
+ [
381
+ "spec",
382
+ obj(
383
+ kv([
384
+ ["replicas", str("3")],
385
+ ["strategy", obj(kv([["type", str("RollingUpdate")]]))],
386
+ [
387
+ "selector",
388
+ obj(kv([["matchLabels", obj(kv([["app", str("api")]]))]])),
389
+ ],
390
+ ]),
391
+ ),
392
+ ],
393
+ [
394
+ "status",
395
+ obj(
396
+ kv([
397
+ ["replicas", str("3")],
398
+ ["readyReplicas", str("2")],
399
+ ["availableReplicas", str("2")],
400
+ ["unavailableReplicas", str("1")],
401
+ ]),
402
+ ),
403
+ ],
404
+ ]);
405
+
406
+ const result: ReturnType<typeof parseDeploymentObject> =
407
+ parseDeploymentObject(dep);
408
+ expect(result!.spec.replicas).toBe(3);
409
+ expect(result!.spec.strategy).toBe("RollingUpdate");
410
+ expect(result!.spec.selector).toEqual({ app: "api" });
411
+ expect(result!.status.readyReplicas).toBe(2);
412
+ expect(result!.status.unavailableReplicas).toBe(1);
413
+ });
414
+
415
+ test("non-numeric replica counts fall back to 0, never NaN", () => {
416
+ const dep: JSONObject = kv([
417
+ ["metadata", obj(kv([["name", str("api")]]))],
418
+ ["spec", obj(kv([["replicas", str("not-a-number")]]))],
419
+ ["status", obj(kv([["readyReplicas", str("")]]))],
420
+ ]);
421
+ const result: ReturnType<typeof parseDeploymentObject> =
422
+ parseDeploymentObject(dep);
423
+ expect(result!.spec.replicas).toBe(0);
424
+ expect(result!.status.readyReplicas).toBe(0);
425
+ expect(Number.isNaN(result!.spec.replicas)).toBe(false);
426
+ });
427
+ });
428
+
429
+ describe("parseJobObject", () => {
430
+ test("decodes spec counters and boolean-free status", () => {
431
+ const job: JSONObject = kv([
432
+ ["metadata", obj(kv([["name", str("backup")]]))],
433
+ [
434
+ "spec",
435
+ obj(
436
+ kv([
437
+ ["completions", str("1")],
438
+ ["parallelism", str("2")],
439
+ ["backoffLimit", str("6")],
440
+ ]),
441
+ ),
442
+ ],
443
+ [
444
+ "status",
445
+ obj(
446
+ kv([
447
+ ["active", str("0")],
448
+ ["succeeded", str("1")],
449
+ ["failed", str("0")],
450
+ ["startTime", str("2026-01-01T00:00:00Z")],
451
+ ]),
452
+ ),
453
+ ],
454
+ ]);
455
+ const result: ReturnType<typeof parseJobObject> = parseJobObject(job);
456
+ expect(result!.spec.completions).toBe(1);
457
+ expect(result!.spec.parallelism).toBe(2);
458
+ expect(result!.spec.backoffLimit).toBe(6);
459
+ expect(result!.status.succeeded).toBe(1);
460
+ expect(result!.status.startTime).toBe("2026-01-01T00:00:00Z");
461
+ });
462
+
463
+ test("a job with no spec/status yields zeroed counters", () => {
464
+ const result: ReturnType<typeof parseJobObject> = parseJobObject(
465
+ kv([["metadata", obj(kv([["name", str("empty")]]))]]),
466
+ );
467
+ expect(result!.spec.completions).toBe(0);
468
+ expect(result!.status.active).toBe(0);
469
+ expect(result!.status.conditions).toEqual([]);
470
+ });
471
+ });
472
+
473
+ describe("parseCronJobObject", () => {
474
+ test("decodes schedule and coerces suspend to a real boolean", () => {
475
+ const cron: JSONObject = kv([
476
+ ["metadata", obj(kv([["name", str("nightly")]]))],
477
+ [
478
+ "spec",
479
+ obj(
480
+ kv([
481
+ ["schedule", str("0 2 * * *")],
482
+ ["suspend", str("true")],
483
+ ["concurrencyPolicy", str("Forbid")],
484
+ ["successfulJobsHistoryLimit", str("3")],
485
+ ]),
486
+ ),
487
+ ],
488
+ ["status", obj(kv([["active", str("1")]]))],
489
+ ]);
490
+ const result: ReturnType<typeof parseCronJobObject> =
491
+ parseCronJobObject(cron);
492
+ expect(result!.spec.schedule).toBe("0 2 * * *");
493
+ expect(result!.spec.suspend).toBe(true);
494
+ expect(result!.spec.concurrencyPolicy).toBe("Forbid");
495
+ expect(result!.spec.successfulJobsHistoryLimit).toBe(3);
496
+ expect(result!.status.activeCount).toBe(1);
497
+ });
498
+
499
+ test('suspend is false for any non-"true" string', () => {
500
+ const cron: JSONObject = kv([
501
+ ["metadata", obj(kv([["name", str("nightly")]]))],
502
+ ["spec", obj(kv([["suspend", str("false")]]))],
503
+ ]);
504
+ expect(parseCronJobObject(cron)!.spec.suspend).toBe(false);
505
+ });
506
+ });
507
+
508
+ describe("parseNamespaceObject", () => {
509
+ test("decodes the phase", () => {
510
+ const ns: JSONObject = kv([
511
+ ["metadata", obj(kv([["name", str("prod")]]))],
512
+ ["status", obj(kv([["phase", str("Active")]]))],
513
+ ]);
514
+ const result: ReturnType<typeof parseNamespaceObject> =
515
+ parseNamespaceObject(ns);
516
+ expect(result!.metadata.name).toBe("prod");
517
+ expect(result!.status.phase).toBe("Active");
518
+ });
519
+
520
+ test("returns null without metadata", () => {
521
+ expect(parseNamespaceObject(kv([["status", obj(kv([]))]]))).toBeNull();
522
+ });
523
+ });
524
+
525
+ describe("parsePVCObject", () => {
526
+ test("returns null when metadata carries no name", () => {
527
+ /*
528
+ * PVC/PV parsers additionally reject a nameless object — a claim with no
529
+ * name cannot be keyed into inventory, so it is dropped rather than stored
530
+ * as a blank-named row.
531
+ */
532
+ const pvc: JSONObject = kv([
533
+ ["metadata", obj(kv([["namespace", str("prod")]]))],
534
+ ["spec", obj(kv([["storageClassName", str("gp3")]]))],
535
+ ]);
536
+ expect(parsePVCObject(pvc)).toBeNull();
537
+ });
538
+
539
+ test("decodes access modes, storage class, and requested/actual storage", () => {
540
+ const pvc: JSONObject = kv([
541
+ ["metadata", obj(kv([["name", str("data-0")]]))],
542
+ [
543
+ "spec",
544
+ obj(
545
+ kv([
546
+ ["storageClassName", str("gp3")],
547
+ ["volumeName", str("pv-abc")],
548
+ ["accessModes", arrOfStrings(["ReadWriteOnce"])],
549
+ [
550
+ "resources",
551
+ obj(kv([["requests", obj(kv([["storage", str("10Gi")]]))]])),
552
+ ],
553
+ ]),
554
+ ),
555
+ ],
556
+ [
557
+ "status",
558
+ obj(
559
+ kv([
560
+ ["phase", str("Bound")],
561
+ ["capacity", obj(kv([["storage", str("10Gi")]]))],
562
+ ]),
563
+ ),
564
+ ],
565
+ ]);
566
+ const result: ReturnType<typeof parsePVCObject> = parsePVCObject(pvc);
567
+ expect(result!.metadata.name).toBe("data-0");
568
+ expect(result!.spec.accessModes).toEqual(["ReadWriteOnce"]);
569
+ expect(result!.spec.storageClassName).toBe("gp3");
570
+ expect(result!.spec.volumeName).toBe("pv-abc");
571
+ expect(result!.spec.resources.requests.storage).toBe("10Gi");
572
+ expect(result!.status.phase).toBe("Bound");
573
+ expect(result!.status.capacity.storage).toBe("10Gi");
574
+ });
575
+ });
576
+
577
+ describe("parseStatefulSetObject", () => {
578
+ test("decodes replicas, service name, and update strategy", () => {
579
+ const sts: JSONObject = kv([
580
+ ["metadata", obj(kv([["name", str("pg")]]))],
581
+ [
582
+ "spec",
583
+ obj(
584
+ kv([
585
+ ["replicas", str("3")],
586
+ ["serviceName", str("pg-headless")],
587
+ ["podManagementPolicy", str("OrderedReady")],
588
+ ["updateStrategy", obj(kv([["type", str("RollingUpdate")]]))],
589
+ ]),
590
+ ),
591
+ ],
592
+ [
593
+ "status",
594
+ obj(
595
+ kv([
596
+ ["replicas", str("3")],
597
+ ["readyReplicas", str("3")],
598
+ ["currentReplicas", str("2")],
599
+ ]),
600
+ ),
601
+ ],
602
+ ]);
603
+ const result: ReturnType<typeof parseStatefulSetObject> =
604
+ parseStatefulSetObject(sts);
605
+ expect(result!.spec.replicas).toBe(3);
606
+ expect(result!.spec.serviceName).toBe("pg-headless");
607
+ expect(result!.spec.updateStrategy).toBe("RollingUpdate");
608
+ expect(result!.status.readyReplicas).toBe(3);
609
+ expect(result!.status.currentReplicas).toBe(2);
610
+ });
611
+
612
+ test("returns null without metadata", () => {
613
+ expect(parseStatefulSetObject(kv([["spec", obj(kv([]))]]))).toBeNull();
614
+ });
615
+ });
616
+
617
+ describe("parseDaemonSetObject", () => {
618
+ test("decodes the scheduling counters", () => {
619
+ const ds: JSONObject = kv([
620
+ ["metadata", obj(kv([["name", str("fluentd")]]))],
621
+ [
622
+ "spec",
623
+ obj(
624
+ kv([["updateStrategy", obj(kv([["type", str("RollingUpdate")]]))]]),
625
+ ),
626
+ ],
627
+ [
628
+ "status",
629
+ obj(
630
+ kv([
631
+ ["desiredNumberScheduled", str("5")],
632
+ ["currentNumberScheduled", str("5")],
633
+ ["numberReady", str("4")],
634
+ ["numberMisscheduled", str("0")],
635
+ ["numberAvailable", str("4")],
636
+ ]),
637
+ ),
638
+ ],
639
+ ]);
640
+ const result: ReturnType<typeof parseDaemonSetObject> =
641
+ parseDaemonSetObject(ds);
642
+ expect(result!.spec.updateStrategy).toBe("RollingUpdate");
643
+ expect(result!.status.desiredNumberScheduled).toBe(5);
644
+ expect(result!.status.numberReady).toBe(4);
645
+ expect(result!.status.numberMisscheduled).toBe(0);
646
+ });
647
+ });
648
+
649
+ describe("parsePVObject", () => {
650
+ test("returns null when metadata carries no name", () => {
651
+ const pv: JSONObject = kv([
652
+ ["metadata", obj(kv([["uid", str("uid-1")]]))],
653
+ ["spec", obj(kv([["storageClassName", str("gp3")]]))],
654
+ ]);
655
+ expect(parsePVObject(pv)).toBeNull();
656
+ });
657
+
658
+ test("decodes capacity, reclaim policy, and claim reference", () => {
659
+ const pv: JSONObject = kv([
660
+ ["metadata", obj(kv([["name", str("pv-abc")]]))],
661
+ [
662
+ "spec",
663
+ obj(
664
+ kv([
665
+ ["capacity", obj(kv([["storage", str("100Gi")]]))],
666
+ ["storageClassName", str("gp3")],
667
+ ["persistentVolumeReclaimPolicy", str("Retain")],
668
+ ["accessModes", arrOfStrings(["ReadWriteOnce", "ReadOnlyMany"])],
669
+ [
670
+ "claimRef",
671
+ obj(
672
+ kv([
673
+ ["name", str("data-0")],
674
+ ["namespace", str("prod")],
675
+ ]),
676
+ ),
677
+ ],
678
+ ]),
679
+ ),
680
+ ],
681
+ ["status", obj(kv([["phase", str("Bound")]]))],
682
+ ]);
683
+ const result: ReturnType<typeof parsePVObject> = parsePVObject(pv);
684
+ expect(result!.spec.capacity.storage).toBe("100Gi");
685
+ expect(result!.spec.persistentVolumeReclaimPolicy).toBe("Retain");
686
+ expect(result!.spec.accessModes).toEqual(["ReadWriteOnce", "ReadOnlyMany"]);
687
+ expect(result!.spec.claimRef.name).toBe("data-0");
688
+ expect(result!.spec.claimRef.namespace).toBe("prod");
689
+ expect(result!.status.phase).toBe("Bound");
690
+ });
691
+ });
692
+
693
+ describe("parseHPAObject", () => {
694
+ test("decodes replica bounds, target ref, and a resource metric", () => {
695
+ const hpa: JSONObject = kv([
696
+ ["metadata", obj(kv([["name", str("api-hpa")]]))],
697
+ [
698
+ "spec",
699
+ obj(
700
+ kv([
701
+ ["minReplicas", str("2")],
702
+ ["maxReplicas", str("10")],
703
+ [
704
+ "scaleTargetRef",
705
+ obj(
706
+ kv([
707
+ ["kind", str("Deployment")],
708
+ ["name", str("api")],
709
+ ]),
710
+ ),
711
+ ],
712
+ [
713
+ "metrics",
714
+ arrOfObjects([
715
+ kv([
716
+ ["type", str("Resource")],
717
+ [
718
+ "resource",
719
+ obj(
720
+ kv([
721
+ ["name", str("cpu")],
722
+ [
723
+ "target",
724
+ obj(
725
+ kv([
726
+ ["type", str("Utilization")],
727
+ ["averageUtilization", str("80")],
728
+ ]),
729
+ ),
730
+ ],
731
+ ]),
732
+ ),
733
+ ],
734
+ ]),
735
+ ]),
736
+ ],
737
+ ]),
738
+ ),
739
+ ],
740
+ [
741
+ "status",
742
+ obj(
743
+ kv([
744
+ ["currentReplicas", str("3")],
745
+ ["desiredReplicas", str("4")],
746
+ ]),
747
+ ),
748
+ ],
749
+ ]);
750
+ const result: ReturnType<typeof parseHPAObject> = parseHPAObject(hpa);
751
+ expect(result!.spec.minReplicas).toBe(2);
752
+ expect(result!.spec.maxReplicas).toBe(10);
753
+ expect(result!.spec.scaleTargetRef).toEqual({
754
+ kind: "Deployment",
755
+ name: "api",
756
+ });
757
+ expect(result!.spec.metrics).toHaveLength(1);
758
+ expect(result!.spec.metrics[0]!.type).toBe("Resource");
759
+ expect(result!.spec.metrics[0]!.resourceName).toBe("cpu");
760
+ expect(result!.spec.metrics[0]!.targetType).toBe("Utilization");
761
+ // averageUtilization wins over the other target-value shapes.
762
+ expect(result!.spec.metrics[0]!.targetValue).toBe("80");
763
+ expect(result!.status.currentReplicas).toBe(3);
764
+ expect(result!.status.desiredReplicas).toBe(4);
765
+ });
766
+
767
+ test("returns null without metadata", () => {
768
+ expect(parseHPAObject(kv([["spec", obj(kv([]))]]))).toBeNull();
769
+ });
770
+ });
771
+
772
+ describe("snake_case (protobufjs) wire encoding", () => {
773
+ test("parsePodObject decodes metadata delivered as string_value", () => {
774
+ /*
775
+ * The protobufjs transport delivers the identical field under
776
+ * string_value. A parser that only read stringValue would silently drop
777
+ * every object from agents on that transport.
778
+ */
779
+ const pod: JSONObject = kv([
780
+ [
781
+ "metadata",
782
+ obj(
783
+ kv([
784
+ ["name", { string_value: "snake-pod" }],
785
+ ["namespace", { string_value: "kube-system" }],
786
+ ]),
787
+ ),
788
+ ],
789
+ ]);
790
+ const result: ReturnType<typeof parsePodObject> = parsePodObject(pod);
791
+ expect(result!.metadata.name).toBe("snake-pod");
792
+ expect(result!.metadata.namespace).toBe("kube-system");
793
+ });
794
+ });