unleash-server 7.5.1 → 7.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 (466) hide show
  1. package/dist/lib/addons/feature-event-formatter-md-events.d.ts.map +1 -1
  2. package/dist/lib/addons/feature-event-formatter-md-events.js +16 -1
  3. package/dist/lib/addons/feature-event-formatter-md-events.js.map +1 -1
  4. package/dist/lib/addons/slack-app-definition.d.ts.map +1 -1
  5. package/dist/lib/addons/slack-app-definition.js +4 -1
  6. package/dist/lib/addons/slack-app-definition.js.map +1 -1
  7. package/dist/lib/app.d.ts.map +1 -1
  8. package/dist/lib/app.js +11 -25
  9. package/dist/lib/app.js.map +1 -1
  10. package/dist/lib/create-config.d.ts.map +1 -1
  11. package/dist/lib/create-config.js +3 -0
  12. package/dist/lib/create-config.js.map +1 -1
  13. package/dist/lib/db/api-token-store.d.ts +4 -1
  14. package/dist/lib/db/api-token-store.d.ts.map +1 -1
  15. package/dist/lib/db/api-token-store.js +17 -3
  16. package/dist/lib/db/api-token-store.js.map +1 -1
  17. package/dist/lib/error/bad-gateway-error.d.ts +7 -0
  18. package/dist/lib/error/bad-gateway-error.d.ts.map +1 -0
  19. package/dist/lib/error/bad-gateway-error.js +9 -0
  20. package/dist/lib/error/bad-gateway-error.js.map +1 -0
  21. package/dist/lib/error/gateway-timeout-error.d.ts +7 -0
  22. package/dist/lib/error/gateway-timeout-error.d.ts.map +1 -0
  23. package/dist/lib/error/gateway-timeout-error.js +9 -0
  24. package/dist/lib/error/gateway-timeout-error.js.map +1 -0
  25. package/dist/lib/error/index.d.ts +3 -1
  26. package/dist/lib/error/index.d.ts.map +1 -1
  27. package/dist/lib/error/index.js +3 -1
  28. package/dist/lib/error/index.js.map +1 -1
  29. package/dist/lib/error/unleash-error.d.ts +1 -1
  30. package/dist/lib/error/unleash-error.d.ts.map +1 -1
  31. package/dist/lib/error/unleash-error.js +2 -0
  32. package/dist/lib/error/unleash-error.js.map +1 -1
  33. package/dist/lib/events/index.d.ts +4 -1
  34. package/dist/lib/events/index.d.ts.map +1 -1
  35. package/dist/lib/events/index.js +6 -0
  36. package/dist/lib/events/index.js.map +1 -1
  37. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.d.ts.map +1 -1
  38. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.js +5 -0
  39. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.js.map +1 -1
  40. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.test.d.ts +2 -0
  41. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.test.d.ts.map +1 -0
  42. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.test.js +53 -0
  43. package/dist/lib/features/client-feature-toggles/client-feature-toggle-store.test.js.map +1 -0
  44. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.d.ts.map +1 -1
  45. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.js +5 -0
  46. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.js.map +1 -1
  47. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.test.d.ts +2 -0
  48. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.test.d.ts.map +1 -0
  49. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.test.js +59 -0
  50. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta-read-model.test.js.map +1 -0
  51. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.d.ts +9 -3
  52. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.d.ts.map +1 -1
  53. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.js +165 -92
  54. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.js.map +1 -1
  55. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.test.js +475 -1
  56. package/dist/lib/features/client-feature-toggles/delta/client-feature-toggle-delta.test.js.map +1 -1
  57. package/dist/lib/features/client-feature-toggles/delta/delta-cache.d.ts.map +1 -1
  58. package/dist/lib/features/client-feature-toggles/delta/delta-cache.js +3 -0
  59. package/dist/lib/features/client-feature-toggles/delta/delta-cache.js.map +1 -1
  60. package/dist/lib/features/client-feature-toggles/delta/delta-cache.test.js +15 -0
  61. package/dist/lib/features/client-feature-toggles/delta/delta-cache.test.js.map +1 -1
  62. package/dist/lib/features/client-feature-toggles/delta/visible-revision.d.ts +3 -0
  63. package/dist/lib/features/client-feature-toggles/delta/visible-revision.d.ts.map +1 -0
  64. package/dist/lib/features/client-feature-toggles/delta/visible-revision.js +16 -0
  65. package/dist/lib/features/client-feature-toggles/delta/visible-revision.js.map +1 -0
  66. package/dist/lib/features/client-feature-toggles/delta/visible-revision.test.d.ts +2 -0
  67. package/dist/lib/features/client-feature-toggles/delta/visible-revision.test.d.ts.map +1 -0
  68. package/dist/lib/features/client-feature-toggles/delta/visible-revision.test.js +25 -0
  69. package/dist/lib/features/client-feature-toggles/delta/visible-revision.test.js.map +1 -0
  70. package/dist/lib/features/context/context.d.ts +0 -1
  71. package/dist/lib/features/context/context.d.ts.map +1 -1
  72. package/dist/lib/features/context/context.js +7 -15
  73. package/dist/lib/features/context/context.js.map +1 -1
  74. package/dist/lib/features/edgetokens/edge-token-store.d.ts +2 -0
  75. package/dist/lib/features/edgetokens/edge-token-store.d.ts.map +1 -1
  76. package/dist/lib/features/edgetokens/edge-token-store.js +20 -0
  77. package/dist/lib/features/edgetokens/edge-token-store.js.map +1 -1
  78. package/dist/lib/features/edgetokens/fake-edge-token-store.d.ts +2 -0
  79. package/dist/lib/features/edgetokens/fake-edge-token-store.d.ts.map +1 -1
  80. package/dist/lib/features/edgetokens/fake-edge-token-store.js +6 -0
  81. package/dist/lib/features/edgetokens/fake-edge-token-store.js.map +1 -1
  82. package/dist/lib/features/events/event-store.d.ts +2 -4
  83. package/dist/lib/features/events/event-store.d.ts.map +1 -1
  84. package/dist/lib/features/events/event-store.js +1 -4
  85. package/dist/lib/features/events/event-store.js.map +1 -1
  86. package/dist/lib/features/feature-search/feature-search-controller.d.ts.map +1 -1
  87. package/dist/lib/features/feature-search/feature-search-controller.js +2 -1
  88. package/dist/lib/features/feature-search/feature-search-controller.js.map +1 -1
  89. package/dist/lib/features/feature-search/feature-search-service.d.ts.map +1 -1
  90. package/dist/lib/features/feature-search/feature-search-service.js +5 -0
  91. package/dist/lib/features/feature-search/feature-search-service.js.map +1 -1
  92. package/dist/lib/features/feature-search/feature-search-store.d.ts.map +1 -1
  93. package/dist/lib/features/feature-search/feature-search-store.js +17 -1
  94. package/dist/lib/features/feature-search/feature-search-store.js.map +1 -1
  95. package/dist/lib/features/feature-search/feature.search.e2e.test.js +56 -1
  96. package/dist/lib/features/feature-search/feature.search.e2e.test.js.map +1 -1
  97. package/dist/lib/features/feature-toggle/configuration-revision-service.d.ts +1 -2
  98. package/dist/lib/features/feature-toggle/configuration-revision-service.d.ts.map +1 -1
  99. package/dist/lib/features/feature-toggle/configuration-revision-service.js +2 -10
  100. package/dist/lib/features/feature-toggle/configuration-revision-service.js.map +1 -1
  101. package/dist/lib/features/feature-toggle/feature-toggle-strategies-store.d.ts.map +1 -1
  102. package/dist/lib/features/feature-toggle/feature-toggle-strategies-store.js +3 -2
  103. package/dist/lib/features/feature-toggle/feature-toggle-strategies-store.js.map +1 -1
  104. package/dist/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.d.ts +1 -0
  105. package/dist/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.d.ts.map +1 -1
  106. package/dist/lib/features/frontend-api/frontend-api-service.d.ts.map +1 -1
  107. package/dist/lib/features/frontend-api/frontend-api-service.js +8 -0
  108. package/dist/lib/features/frontend-api/frontend-api-service.js.map +1 -1
  109. package/dist/lib/features/frontend-api/frontend-api.e2e.test.js +43 -0
  110. package/dist/lib/features/frontend-api/frontend-api.e2e.test.js.map +1 -1
  111. package/dist/lib/features/frontend-api/frontend-api.middleware.e2e.test.d.ts +3 -0
  112. package/dist/lib/features/frontend-api/frontend-api.middleware.e2e.test.d.ts.map +1 -0
  113. package/dist/lib/features/frontend-api/frontend-api.middleware.e2e.test.js +1071 -0
  114. package/dist/lib/features/frontend-api/frontend-api.middleware.e2e.test.js.map +1 -0
  115. package/dist/lib/features/index.d.ts +1 -0
  116. package/dist/lib/features/index.d.ts.map +1 -1
  117. package/dist/lib/features/index.js +1 -0
  118. package/dist/lib/features/index.js.map +1 -1
  119. package/dist/lib/features/instance-stats/instance-stats-service.d.ts.map +1 -1
  120. package/dist/lib/features/instance-stats/instance-stats-service.js +1 -3
  121. package/dist/lib/features/instance-stats/instance-stats-service.js.map +1 -1
  122. package/dist/lib/features/metrics/impact/impact-metrics.e2e.test.js +12 -12
  123. package/dist/lib/features/metrics/impact/impact-metrics.e2e.test.js.map +1 -1
  124. package/dist/lib/features/metrics/impact/metrics-translator.d.ts +5 -2
  125. package/dist/lib/features/metrics/impact/metrics-translator.d.ts.map +1 -1
  126. package/dist/lib/features/metrics/impact/metrics-translator.js +86 -102
  127. package/dist/lib/features/metrics/impact/metrics-translator.js.map +1 -1
  128. package/dist/lib/features/metrics/impact/metrics-translator.test.js +70 -112
  129. package/dist/lib/features/metrics/impact/metrics-translator.test.js.map +1 -1
  130. package/dist/lib/features/project/createProjectService.d.ts.map +1 -1
  131. package/dist/lib/features/project/createProjectService.js +6 -0
  132. package/dist/lib/features/project/createProjectService.js.map +1 -1
  133. package/dist/lib/features/project/project-service.d.ts +2 -1
  134. package/dist/lib/features/project/project-service.d.ts.map +1 -1
  135. package/dist/lib/features/project/project-service.js +3 -1
  136. package/dist/lib/features/project/project-service.js.map +1 -1
  137. package/dist/lib/features/project-environments/project-environments.e2e.test.js +37 -0
  138. package/dist/lib/features/project-environments/project-environments.e2e.test.js.map +1 -1
  139. package/dist/lib/features/release-plans/release-plan-milestone-strategy-service.d.ts.map +1 -1
  140. package/dist/lib/features/release-plans/release-plan-milestone-strategy-service.js +2 -5
  141. package/dist/lib/features/release-plans/release-plan-milestone-strategy-service.js.map +1 -1
  142. package/dist/lib/features/release-plans/release-plan-milestone-strategy-service.test.js +2 -2
  143. package/dist/lib/features/release-plans/release-plan-milestone-strategy-service.test.js.map +1 -1
  144. package/dist/lib/features/release-plans/release-plan-milestone-strategy-store.d.ts +2 -1
  145. package/dist/lib/features/release-plans/release-plan-milestone-strategy-store.d.ts.map +1 -1
  146. package/dist/lib/features/release-plans/release-plan-milestone-strategy-store.js +20 -3
  147. package/dist/lib/features/release-plans/release-plan-milestone-strategy-store.js.map +1 -1
  148. package/dist/lib/features/release-plans/release-plan-milestone-strategy.d.ts +2 -0
  149. package/dist/lib/features/release-plans/release-plan-milestone-strategy.d.ts.map +1 -1
  150. package/dist/lib/features/release-plans/release-plan-store.d.ts.map +1 -1
  151. package/dist/lib/features/release-plans/release-plan-store.js +2 -0
  152. package/dist/lib/features/release-plans/release-plan-store.js.map +1 -1
  153. package/dist/lib/features/release-plans/release-plan-template-store.d.ts.map +1 -1
  154. package/dist/lib/features/release-plans/release-plan-template-store.js +4 -3
  155. package/dist/lib/features/release-plans/release-plan-template-store.js.map +1 -1
  156. package/dist/lib/features/release-plans/release-plan-template-store.test.d.ts +2 -0
  157. package/dist/lib/features/release-plans/release-plan-template-store.test.d.ts.map +1 -0
  158. package/dist/lib/features/release-plans/release-plan-template-store.test.js +47 -0
  159. package/dist/lib/features/release-plans/release-plan-template-store.test.js.map +1 -0
  160. package/dist/lib/metric-events.d.ts +3 -2
  161. package/dist/lib/metric-events.d.ts.map +1 -1
  162. package/dist/lib/metric-events.js +2 -1
  163. package/dist/lib/metric-events.js.map +1 -1
  164. package/dist/lib/middleware/api-token-middleware.d.ts.map +1 -1
  165. package/dist/lib/middleware/api-token-middleware.js +1 -5
  166. package/dist/lib/middleware/api-token-middleware.js.map +1 -1
  167. package/dist/lib/middleware/api-token-middleware.test.js +35 -25
  168. package/dist/lib/middleware/api-token-middleware.test.js.map +1 -1
  169. package/dist/lib/middleware/backend-token-middleware.d.ts +5 -0
  170. package/dist/lib/middleware/backend-token-middleware.d.ts.map +1 -0
  171. package/dist/lib/middleware/backend-token-middleware.js +61 -0
  172. package/dist/lib/middleware/backend-token-middleware.js.map +1 -0
  173. package/dist/lib/middleware/backend-token-middleware.test.d.ts +2 -0
  174. package/dist/lib/middleware/backend-token-middleware.test.d.ts.map +1 -0
  175. package/dist/lib/middleware/backend-token-middleware.test.js +73 -0
  176. package/dist/lib/middleware/backend-token-middleware.test.js.map +1 -0
  177. package/dist/lib/middleware/conditional-middleware.d.ts +20 -0
  178. package/dist/lib/middleware/conditional-middleware.d.ts.map +1 -1
  179. package/dist/lib/middleware/conditional-middleware.js +26 -0
  180. package/dist/lib/middleware/conditional-middleware.js.map +1 -1
  181. package/dist/lib/middleware/conditional-middleware.test.d.ts +2 -0
  182. package/dist/lib/middleware/conditional-middleware.test.d.ts.map +1 -0
  183. package/dist/lib/middleware/conditional-middleware.test.js +31 -0
  184. package/dist/lib/middleware/conditional-middleware.test.js.map +1 -0
  185. package/dist/lib/middleware/frontend-token-middleware.d.ts +5 -0
  186. package/dist/lib/middleware/frontend-token-middleware.d.ts.map +1 -0
  187. package/dist/lib/middleware/frontend-token-middleware.js +61 -0
  188. package/dist/lib/middleware/frontend-token-middleware.js.map +1 -0
  189. package/dist/lib/middleware/user-token-client-api-logger-middleware.js +1 -1
  190. package/dist/lib/middleware/user-token-client-api-logger-middleware.js.map +1 -1
  191. package/dist/lib/openapi/spec/event-schema.d.ts +1 -1
  192. package/dist/lib/openapi/spec/event-search-response-schema.d.ts +1 -1
  193. package/dist/lib/openapi/spec/events-schema.d.ts +1 -1
  194. package/dist/lib/openapi/spec/export-result-schema.d.ts +1018 -100
  195. package/dist/lib/openapi/spec/export-result-schema.d.ts.map +1 -1
  196. package/dist/lib/openapi/spec/export-result-schema.js +4 -2
  197. package/dist/lib/openapi/spec/export-result-schema.js.map +1 -1
  198. package/dist/lib/openapi/spec/feature-environment-safeguard-schema.d.ts +168 -0
  199. package/dist/lib/openapi/spec/feature-environment-safeguard-schema.d.ts.map +1 -0
  200. package/dist/lib/openapi/spec/feature-environment-safeguard-schema.js +66 -0
  201. package/dist/lib/openapi/spec/feature-environment-safeguard-schema.js.map +1 -0
  202. package/dist/lib/openapi/spec/feature-environment-schema.d.ts +265 -7
  203. package/dist/lib/openapi/spec/feature-environment-schema.d.ts.map +1 -1
  204. package/dist/lib/openapi/spec/feature-environment-schema.js +13 -0
  205. package/dist/lib/openapi/spec/feature-environment-schema.js.map +1 -1
  206. package/dist/lib/openapi/spec/feature-events-schema.d.ts +1 -1
  207. package/dist/lib/openapi/spec/feature-schema.d.ts +475 -16
  208. package/dist/lib/openapi/spec/feature-schema.d.ts.map +1 -1
  209. package/dist/lib/openapi/spec/feature-schema.js +4 -2
  210. package/dist/lib/openapi/spec/feature-schema.js.map +1 -1
  211. package/dist/lib/openapi/spec/feature-search-environment-schema.d.ts +688 -14
  212. package/dist/lib/openapi/spec/feature-search-environment-schema.d.ts.map +1 -1
  213. package/dist/lib/openapi/spec/feature-search-environment-schema.js +8 -0
  214. package/dist/lib/openapi/spec/feature-search-environment-schema.js.map +1 -1
  215. package/dist/lib/openapi/spec/feature-search-query-parameters.d.ts +10 -1
  216. package/dist/lib/openapi/spec/feature-search-query-parameters.d.ts.map +1 -1
  217. package/dist/lib/openapi/spec/feature-search-query-parameters.js +10 -0
  218. package/dist/lib/openapi/spec/feature-search-query-parameters.js.map +1 -1
  219. package/dist/lib/openapi/spec/feature-search-response-schema.d.ts +8097 -155
  220. package/dist/lib/openapi/spec/feature-search-response-schema.d.ts.map +1 -1
  221. package/dist/lib/openapi/spec/feature-search-response-schema.js +22 -0
  222. package/dist/lib/openapi/spec/feature-search-response-schema.js.map +1 -1
  223. package/dist/lib/openapi/spec/health-overview-schema.d.ts +1336 -210
  224. package/dist/lib/openapi/spec/health-overview-schema.d.ts.map +1 -1
  225. package/dist/lib/openapi/spec/health-overview-schema.js +8 -0
  226. package/dist/lib/openapi/spec/health-overview-schema.js.map +1 -1
  227. package/dist/lib/openapi/spec/health-report-schema.d.ts +1336 -210
  228. package/dist/lib/openapi/spec/health-report-schema.d.ts.map +1 -1
  229. package/dist/lib/openapi/spec/import-toggles-schema.d.ts +2 -2
  230. package/dist/lib/openapi/spec/import-toggles-schema.d.ts.map +1 -1
  231. package/dist/lib/openapi/spec/import-toggles-schema.js +4 -2
  232. package/dist/lib/openapi/spec/import-toggles-schema.js.map +1 -1
  233. package/dist/lib/openapi/spec/index.d.ts +4 -2
  234. package/dist/lib/openapi/spec/index.d.ts.map +1 -1
  235. package/dist/lib/openapi/spec/index.js +4 -2
  236. package/dist/lib/openapi/spec/index.js.map +1 -1
  237. package/dist/lib/openapi/spec/integration-event-schema.d.ts +1 -1
  238. package/dist/lib/openapi/spec/integration-events-schema.d.ts +2 -2
  239. package/dist/lib/openapi/spec/metric-query-schema.d.ts +6 -0
  240. package/dist/lib/openapi/spec/metric-query-schema.d.ts.map +1 -1
  241. package/dist/lib/openapi/spec/metric-query-schema.js +6 -0
  242. package/dist/lib/openapi/spec/metric-query-schema.js.map +1 -1
  243. package/dist/lib/openapi/spec/profile-schema.d.ts +475 -16
  244. package/dist/lib/openapi/spec/profile-schema.d.ts.map +1 -1
  245. package/dist/lib/openapi/spec/project-overview-schema.d.ts +1336 -210
  246. package/dist/lib/openapi/spec/project-overview-schema.d.ts.map +1 -1
  247. package/dist/lib/openapi/spec/project-overview-schema.js +8 -0
  248. package/dist/lib/openapi/spec/project-overview-schema.js.map +1 -1
  249. package/dist/lib/openapi/spec/release-plan-milestone-schema.d.ts +1 -1
  250. package/dist/lib/openapi/spec/release-plan-milestone-strategy-schema.d.ts +1 -1
  251. package/dist/lib/openapi/spec/release-plan-milestone-strategy-schema.js +1 -1
  252. package/dist/lib/openapi/spec/release-plan-milestone-strategy-schema.js.map +1 -1
  253. package/dist/lib/openapi/spec/{safeguard-schema.d.ts → release-plan-safeguard-schema.d.ts} +16 -4
  254. package/dist/lib/openapi/spec/release-plan-safeguard-schema.d.ts.map +1 -0
  255. package/dist/lib/openapi/spec/{safeguard-schema.js → release-plan-safeguard-schema.js} +3 -3
  256. package/dist/lib/openapi/spec/release-plan-safeguard-schema.js.map +1 -0
  257. package/dist/lib/openapi/spec/release-plan-schema.d.ts +23 -5
  258. package/dist/lib/openapi/spec/release-plan-schema.d.ts.map +1 -1
  259. package/dist/lib/openapi/spec/release-plan-schema.js +3 -3
  260. package/dist/lib/openapi/spec/release-plan-schema.js.map +1 -1
  261. package/dist/lib/openapi/spec/release-plan-template-schema.d.ts +2 -2
  262. package/dist/lib/openapi/spec/release-plan-templates-schema.d.ts +2897 -0
  263. package/dist/lib/openapi/spec/release-plan-templates-schema.d.ts.map +1 -0
  264. package/dist/lib/openapi/spec/release-plan-templates-schema.js +29 -0
  265. package/dist/lib/openapi/spec/release-plan-templates-schema.js.map +1 -0
  266. package/dist/lib/openapi/spec/release-plans-schema.d.ts +45 -9
  267. package/dist/lib/openapi/spec/release-plans-schema.d.ts.map +1 -1
  268. package/dist/lib/openapi/spec/release-plans-schema.js +2 -2
  269. package/dist/lib/openapi/spec/release-plans-schema.js.map +1 -1
  270. package/dist/lib/openapi/spec/search-features-schema.d.ts +13694 -5078
  271. package/dist/lib/openapi/spec/search-features-schema.d.ts.map +1 -1
  272. package/dist/lib/routes/admin-api/api-token.js +1 -1
  273. package/dist/lib/routes/admin-api/api-token.js.map +1 -1
  274. package/dist/lib/routes/admin-api/project/api-token.js +1 -1
  275. package/dist/lib/routes/admin-api/project/api-token.js.map +1 -1
  276. package/dist/lib/routes/logout.js +1 -1
  277. package/dist/lib/routes/logout.js.map +1 -1
  278. package/dist/lib/routes/logout.test.js +4 -4
  279. package/dist/lib/routes/logout.test.js.map +1 -1
  280. package/dist/lib/schema/api-token-schema.d.ts.map +1 -1
  281. package/dist/lib/schema/api-token-schema.js +1 -0
  282. package/dist/lib/schema/api-token-schema.js.map +1 -1
  283. package/dist/lib/server-impl.d.ts +5 -5
  284. package/dist/lib/server-impl.d.ts.map +1 -1
  285. package/dist/lib/server-impl.js +3 -3
  286. package/dist/lib/server-impl.js.map +1 -1
  287. package/dist/lib/services/api-token-service.d.ts +2 -2
  288. package/dist/lib/services/api-token-service.d.ts.map +1 -1
  289. package/dist/lib/services/api-token-service.js +5 -3
  290. package/dist/lib/services/api-token-service.js.map +1 -1
  291. package/dist/lib/services/api-token-service.test.js +5 -4
  292. package/dist/lib/services/api-token-service.test.js.map +1 -1
  293. package/dist/lib/services/edge-service.d.ts +1 -0
  294. package/dist/lib/services/edge-service.d.ts.map +1 -1
  295. package/dist/lib/services/edge-service.js +5 -2
  296. package/dist/lib/services/edge-service.js.map +1 -1
  297. package/dist/lib/services/user-service.d.ts.map +1 -1
  298. package/dist/lib/services/user-service.js +0 -4
  299. package/dist/lib/services/user-service.js.map +1 -1
  300. package/dist/lib/types/experimental.d.ts +1 -1
  301. package/dist/lib/types/experimental.d.ts.map +1 -1
  302. package/dist/lib/types/experimental.js +7 -8
  303. package/dist/lib/types/experimental.js.map +1 -1
  304. package/dist/lib/types/model.d.ts +2 -1
  305. package/dist/lib/types/model.d.ts.map +1 -1
  306. package/dist/lib/types/model.js.map +1 -1
  307. package/dist/lib/types/option.d.ts +2 -0
  308. package/dist/lib/types/option.d.ts.map +1 -1
  309. package/dist/lib/types/stores/api-token-store.d.ts +2 -1
  310. package/dist/lib/types/stores/api-token-store.d.ts.map +1 -1
  311. package/dist/lib/types/stores/edge-store.d.ts +2 -0
  312. package/dist/lib/types/stores/edge-store.d.ts.map +1 -1
  313. package/dist/lib/types/stores/event-store.d.ts +1 -1
  314. package/dist/lib/types/stores/event-store.d.ts.map +1 -1
  315. package/dist/lib/util/clone-db-config.d.ts +3 -0
  316. package/dist/lib/util/clone-db-config.d.ts.map +1 -0
  317. package/dist/lib/util/clone-db-config.js +18 -0
  318. package/dist/lib/util/clone-db-config.js.map +1 -0
  319. package/dist/lib/util/clone-db-config.test.d.ts +2 -0
  320. package/dist/lib/util/clone-db-config.test.d.ts.map +1 -0
  321. package/dist/lib/util/clone-db-config.test.js +40 -0
  322. package/dist/lib/util/clone-db-config.test.js.map +1 -0
  323. package/dist/lib/util/db-lock.d.ts.map +1 -1
  324. package/dist/lib/util/db-lock.js +2 -3
  325. package/dist/lib/util/db-lock.js.map +1 -1
  326. package/dist/lib/util/sortStrategies.d.ts +1 -1
  327. package/dist/lib/util/sortStrategies.d.ts.map +1 -1
  328. package/dist/lib/util/sortStrategies.js +6 -2
  329. package/dist/lib/util/sortStrategies.js.map +1 -1
  330. package/dist/lib/util/sortStrategies.test.js +21 -0
  331. package/dist/lib/util/sortStrategies.test.js.map +1 -1
  332. package/dist/migrations/20260309125735-drop-cascade-for-edge-api-token.d.ts +3 -0
  333. package/dist/migrations/20260309125735-drop-cascade-for-edge-api-token.d.ts.map +1 -0
  334. package/dist/migrations/20260309125735-drop-cascade-for-edge-api-token.js +15 -0
  335. package/dist/migrations/20260309125735-drop-cascade-for-edge-api-token.js.map +1 -0
  336. package/dist/migrations/20260310134100-split-safeguards-table.d.ts +3 -0
  337. package/dist/migrations/20260310134100-split-safeguards-table.d.ts.map +1 -0
  338. package/dist/migrations/20260310134100-split-safeguards-table.js +31 -0
  339. package/dist/migrations/20260310134100-split-safeguards-table.js.map +1 -0
  340. package/dist/migrations/20260311073041-migrate-safeguards-data.d.ts +3 -0
  341. package/dist/migrations/20260311073041-migrate-safeguards-data.d.ts.map +1 -0
  342. package/dist/migrations/20260311073041-migrate-safeguards-data.js +42 -0
  343. package/dist/migrations/20260311073041-migrate-safeguards-data.js.map +1 -0
  344. package/dist/migrations/20260312100000-release-plans-fix-cascade-on-env-disconnect.d.ts +3 -0
  345. package/dist/migrations/20260312100000-release-plans-fix-cascade-on-env-disconnect.d.ts.map +1 -0
  346. package/dist/migrations/20260312100000-release-plans-fix-cascade-on-env-disconnect.js +31 -0
  347. package/dist/migrations/20260312100000-release-plans-fix-cascade-on-env-disconnect.js.map +1 -0
  348. package/dist/migrations/20260316113111-update-memory-usage-to-big-int.d.ts +3 -0
  349. package/dist/migrations/20260316113111-update-memory-usage-to-big-int.d.ts.map +1 -0
  350. package/dist/migrations/20260316113111-update-memory-usage-to-big-int.js +8 -0
  351. package/dist/migrations/20260316113111-update-memory-usage-to-big-int.js.map +1 -0
  352. package/dist/migrations/20260316140000-safeguards-fix-cascade-on-env-disconnect.d.ts +3 -0
  353. package/dist/migrations/20260316140000-safeguards-fix-cascade-on-env-disconnect.d.ts.map +1 -0
  354. package/dist/migrations/20260316140000-safeguards-fix-cascade-on-env-disconnect.js +54 -0
  355. package/dist/migrations/20260316140000-safeguards-fix-cascade-on-env-disconnect.js.map +1 -0
  356. package/dist/migrations/20260317140000-safeguards-drop-project-not-null.d.ts +3 -0
  357. package/dist/migrations/20260317140000-safeguards-drop-project-not-null.d.ts.map +1 -0
  358. package/dist/migrations/20260317140000-safeguards-drop-project-not-null.js +21 -0
  359. package/dist/migrations/20260317140000-safeguards-drop-project-not-null.js.map +1 -0
  360. package/dist/migrations/20260318100000-safeguards-drop-project-column.d.ts +3 -0
  361. package/dist/migrations/20260318100000-safeguards-drop-project-column.d.ts.map +1 -0
  362. package/dist/migrations/20260318100000-safeguards-drop-project-column.js +14 -0
  363. package/dist/migrations/20260318100000-safeguards-drop-project-column.js.map +1 -0
  364. package/dist/migrations/20260406132537-user-access-requests.d.ts +3 -0
  365. package/dist/migrations/20260406132537-user-access-requests.d.ts.map +1 -0
  366. package/dist/migrations/20260406132537-user-access-requests.js +16 -0
  367. package/dist/migrations/20260406132537-user-access-requests.js.map +1 -0
  368. package/dist/migrations/20260406161629-user-access-requests-unique-email.d.ts +3 -0
  369. package/dist/migrations/20260406161629-user-access-requests-unique-email.d.ts.map +1 -0
  370. package/dist/migrations/20260406161629-user-access-requests-unique-email.js +14 -0
  371. package/dist/migrations/20260406161629-user-access-requests-unique-email.js.map +1 -0
  372. package/dist/migrator.d.ts.map +1 -1
  373. package/dist/migrator.js +4 -3
  374. package/dist/migrator.js.map +1 -1
  375. package/dist/server-dev.js +4 -7
  376. package/dist/server-dev.js.map +1 -1
  377. package/dist/test/e2e/api/admin/api-token.auth.e2e.test.js +5 -5
  378. package/dist/test/e2e/api/admin/api-token.auth.e2e.test.js.map +1 -1
  379. package/dist/test/e2e/api/admin/api-token.e2e.test.js +3 -2
  380. package/dist/test/e2e/api/admin/api-token.e2e.test.js.map +1 -1
  381. package/dist/test/e2e/api/admin/conditional-middleware.e2e.test.js +106 -38
  382. package/dist/test/e2e/api/admin/conditional-middleware.e2e.test.js.map +1 -1
  383. package/dist/test/e2e/api/admin/context-api-include-query-params.e2e.test.js +1 -7
  384. package/dist/test/e2e/api/admin/context-api-include-query-params.e2e.test.js.map +1 -1
  385. package/dist/test/e2e/api/admin/project/api-token.e2e.test.js +2 -1
  386. package/dist/test/e2e/api/admin/project/api-token.e2e.test.js.map +1 -1
  387. package/dist/test/e2e/api/admin/project/project.api.tokens.e2e.test.js +5 -5
  388. package/dist/test/e2e/api/admin/project/project.api.tokens.e2e.test.js.map +1 -1
  389. package/dist/test/e2e/api/admin/project-context.e2e.test.js +1 -7
  390. package/dist/test/e2e/api/admin/project-context.e2e.test.js.map +1 -1
  391. package/dist/test/e2e/api/client/feature.token.access.e2e.test.js +34 -1
  392. package/dist/test/e2e/api/client/feature.token.access.e2e.test.js.map +1 -1
  393. package/dist/test/e2e/services/api-token-service.e2e.test.js +21 -6
  394. package/dist/test/e2e/services/api-token-service.e2e.test.js.map +1 -1
  395. package/dist/test/e2e/services/edge-service.e2e.test.js +4 -3
  396. package/dist/test/e2e/services/edge-service.e2e.test.js.map +1 -1
  397. package/dist/test/e2e/stores/api-token-store.e2e.test.js +15 -14
  398. package/dist/test/e2e/stores/api-token-store.e2e.test.js.map +1 -1
  399. package/dist/test/e2e/stores/event-store.e2e.test.js +8 -9
  400. package/dist/test/e2e/stores/event-store.e2e.test.js.map +1 -1
  401. package/dist/test/fixtures/fake-api-token-store.d.ts +2 -1
  402. package/dist/test/fixtures/fake-api-token-store.d.ts.map +1 -1
  403. package/dist/test/fixtures/fake-api-token-store.js +4 -1
  404. package/dist/test/fixtures/fake-api-token-store.js.map +1 -1
  405. package/dist/test/fixtures/fake-event-store.d.ts +1 -1
  406. package/dist/test/fixtures/fake-event-store.d.ts.map +1 -1
  407. package/dist/test/fixtures/fake-event-store.js +1 -1
  408. package/dist/test/fixtures/fake-event-store.js.map +1 -1
  409. package/dist/test/fixtures/fake-release-plan-milestone-strategy-store.d.ts +2 -2
  410. package/dist/test/fixtures/fake-release-plan-milestone-strategy-store.d.ts.map +1 -1
  411. package/dist/test/fixtures/fake-release-plan-milestone-strategy-store.js +3 -2
  412. package/dist/test/fixtures/fake-release-plan-milestone-strategy-store.js.map +1 -1
  413. package/frontend/build/index.html +1 -1
  414. package/frontend/build/static/AdvancedPlayground-BTPrG6RW.js +3 -0
  415. package/frontend/build/static/BackendConnections-CRh8Vxoe.js +1 -0
  416. package/frontend/build/static/{CreateProject-DV-Ad8Sn.js → CreateProject-DEMalH1j.js} +1 -1
  417. package/frontend/build/static/EnterpriseEdge-Cp-pGMa0.js +6 -0
  418. package/frontend/build/static/FeatureMetricsChart-DQOE71LA.js +2 -0
  419. package/frontend/build/static/FeatureStaleDialog-B6BfnDiQ.js +689 -0
  420. package/frontend/build/static/{FeatureViewLazyExport-Bsw7-knb.js → FeatureViewLazyExport-BkNTNFqK.js} +4 -7
  421. package/frontend/build/static/FlagMetricsChart-zYDF9bet.js +2 -0
  422. package/frontend/build/static/FrontendNetworkTrafficUsage-CbxdFXH7.js +1 -0
  423. package/frontend/build/static/GridLayoutWrapper-Cq6Bzm36.js +3 -0
  424. package/frontend/build/static/ImpactMetricsPage-CFYXYXPb.js +1 -0
  425. package/frontend/build/static/LazyAdminExport-ByTh4iFa.js +38 -0
  426. package/frontend/build/static/LazyProjectExport-K12Xy3w9.js +46 -0
  427. package/frontend/build/static/{LifecycleChartComponent-cxR-TN3V.js → LifecycleChartComponent-DHvlFNd2.js} +3 -3
  428. package/frontend/build/static/LineChartComponent-BlhKm3cw.js +1 -0
  429. package/frontend/build/static/MarkCompletedDialogue-apmlFjVY.js +1 -0
  430. package/frontend/build/static/{NetworkOverview-DxaPXUzZ.js → NetworkOverview-BiLOuQ4c.js} +1 -1
  431. package/frontend/build/static/{NetworkPrometheusAPIWarning-ltyTlXFF.js → NetworkPrometheusAPIWarning-CYo3j2Tj.js} +1 -1
  432. package/frontend/build/static/{NetworkTraffic-Dhw4yIBs.js → NetworkTraffic-DONL5oIx.js} +1 -1
  433. package/frontend/build/static/NetworkTrafficUsage-DjQiYq0S.js +1 -0
  434. package/frontend/build/static/{PercentageDonut-JGNh2FVZ.js → PercentageDonut-CA9hYg3v.js} +1 -1
  435. package/frontend/build/static/{ReactJSONEditor-GHl7KvdS.js → ReactJSONEditor-9cJL1vJY.js} +31 -31
  436. package/frontend/build/static/{RoleCell-CjVCxBgl.js → RoleCell-ZzU5Oqy8.js} +1 -1
  437. package/frontend/build/static/Safeguard-DY1N4aPz.js +1 -0
  438. package/frontend/build/static/aggregateFeatureMetrics-D6bHft_C.js +1 -0
  439. package/frontend/build/static/{formatTickValue-BZb4ZIL9.js → formatTickValue-BcLsqREh.js} +2 -2
  440. package/frontend/build/static/{index-DcWBU_a4.js → index-B87DWXbl.js} +1 -1
  441. package/frontend/build/static/index-CoLhlfXP.js +565 -0
  442. package/frontend/build/static/loginBackground-CQx0IRYc.png +0 -0
  443. package/frontend/build/static/{networkTrafficUsageHighlightPlugin-BbfAyDiQ.js → networkTrafficUsageHighlightPlugin-CqEM-cK_.js} +1 -1
  444. package/frontend/build/static/passwordSuccess-fcvTEYOw.png +0 -0
  445. package/frontend/build/static/useApiTokens-ClWvohC9.js +1 -0
  446. package/frontend/package.json +50 -42
  447. package/package.json +11 -10
  448. package/dist/lib/openapi/spec/safeguard-schema.d.ts.map +0 -1
  449. package/dist/lib/openapi/spec/safeguard-schema.js.map +0 -1
  450. package/frontend/build/static/AdvancedPlayground-CF0hkpiE.js +0 -3
  451. package/frontend/build/static/BackendConnections-DhZczar-.js +0 -1
  452. package/frontend/build/static/EnterpriseEdge-D1To2ugP.js +0 -3
  453. package/frontend/build/static/FeatureMetricsChart-D8sRgx5P.js +0 -2
  454. package/frontend/build/static/FeatureStaleDialog-DOkhgvaj.js +0 -7
  455. package/frontend/build/static/FlagMetricsChart-CF_kEyQa.js +0 -2
  456. package/frontend/build/static/FrontendNetworkTrafficUsage-C8zJSvoc.js +0 -1
  457. package/frontend/build/static/GridLayoutWrapper-DpJcUbQB.js +0 -3
  458. package/frontend/build/static/ImpactMetricsPage-KLVv-tP4.js +0 -1
  459. package/frontend/build/static/LazyAdminExport-CWQiknO-.js +0 -38
  460. package/frontend/build/static/LazyProjectExport-DsMn4Yxm.js +0 -728
  461. package/frontend/build/static/LineChartComponent-_yZEp3dd.js +0 -1
  462. package/frontend/build/static/MarkCompletedDialogue-DFtLyDN0.js +0 -1
  463. package/frontend/build/static/NetworkTrafficUsage-deKWRhIS.js +0 -1
  464. package/frontend/build/static/aggregateFeatureMetrics-YEdZNNoV.js +0 -1
  465. package/frontend/build/static/index-DMSjiFEm.js +0 -583
  466. package/frontend/build/static/useApiTokens-DBB3NvXL.js +0 -1
@@ -0,0 +1,1071 @@
1
+ import { setupAppWithAuth, } from '../../../test/e2e/helpers/test-helper.js';
2
+ import dbInit from '../../../test/e2e/helpers/database-init.js';
3
+ import getLogger from '../../../test/fixtures/no-logger.js';
4
+ import { DEFAULT_ENV, randomId } from '../../util/index.js';
5
+ import { ApiTokenType, } from '../../types/model.js';
6
+ import { startOfHour } from 'date-fns';
7
+ import { SYSTEM_USER_AUDIT, TEST_AUDIT_USER, } from '../../types/index.js';
8
+ import { vi } from 'vitest';
9
+ let app;
10
+ let db;
11
+ let frontendApiService;
12
+ beforeAll(async () => {
13
+ db = await dbInit('frontend_api_with_new_middleware', getLogger);
14
+ app = await setupAppWithAuth(db.stores, {
15
+ frontendApiOrigins: ['https://example.com'],
16
+ experimental: {
17
+ flags: {
18
+ onlyFeatureTokensWithFeatureAPIs: true,
19
+ },
20
+ },
21
+ }, db.rawDatabase);
22
+ frontendApiService = app.services.frontendApiService;
23
+ });
24
+ afterEach(() => {
25
+ app.services.frontendApiService.stopAll();
26
+ vi.clearAllMocks();
27
+ });
28
+ afterAll(async () => {
29
+ await app.destroy();
30
+ await db.destroy();
31
+ });
32
+ beforeEach(async () => {
33
+ await db.stores.segmentStore.deleteAll();
34
+ await db.stores.featureToggleStore.deleteAll();
35
+ await db.stores.clientMetricsStoreV2.deleteAll();
36
+ await db.stores.apiTokenStore.deleteAll();
37
+ });
38
+ export const createApiToken = (type, overrides = {}) => {
39
+ return app.services.apiTokenService.createApiTokenWithProjects({
40
+ type,
41
+ projects: ['*'],
42
+ environment: DEFAULT_ENV,
43
+ tokenName: `${type}-token-${randomId()}`,
44
+ ...overrides,
45
+ });
46
+ };
47
+ const createFeatureToggle = async ({ name, project = 'default', environment = DEFAULT_ENV, strategies, enabled, }) => {
48
+ const createdFeature = await app.services.featureToggleService.createFeatureToggle(project, { name }, TEST_AUDIT_USER, true);
49
+ const createdStrategies = await Promise.all((strategies ?? []).map(async (s) => app.services.featureToggleService.createStrategy(s, { projectId: project, featureName: name, environment }, TEST_AUDIT_USER)));
50
+ await app.services.featureToggleService.updateEnabled(project, name, environment, enabled, TEST_AUDIT_USER);
51
+ return [createdFeature, createdStrategies];
52
+ };
53
+ const createProject = async (id, name) => {
54
+ const user = await db.stores.userStore.insert({
55
+ name: randomId(),
56
+ email: `${randomId()}@example.com`,
57
+ });
58
+ await app.services.projectService.createProject({ id, name, mode: 'open', defaultStickiness: 'default' }, user, TEST_AUDIT_USER);
59
+ };
60
+ test('should require a frontend token or an admin token', async () => {
61
+ await app.request
62
+ .get('/api/frontend')
63
+ .expect('Content-Type', /json/)
64
+ .expect(401);
65
+ });
66
+ test('should not allow requests with a client token', async () => {
67
+ const clientToken = await createApiToken(ApiTokenType.CLIENT);
68
+ await app.request
69
+ .get('/api/frontend')
70
+ .set('Authorization', clientToken.secret)
71
+ .expect('Content-Type', /json/)
72
+ .expect(403);
73
+ });
74
+ test('should allow requests with a token secret alias', async () => {
75
+ const featureA = randomId();
76
+ const featureB = randomId();
77
+ const envA = randomId();
78
+ const envB = randomId();
79
+ await db.stores.environmentStore.create({ name: envA, type: 'test' });
80
+ await db.stores.environmentStore.create({ name: envB, type: 'test' });
81
+ await db.stores.projectStore.addEnvironmentToProject('default', envA);
82
+ await db.stores.projectStore.addEnvironmentToProject('default', envB);
83
+ await createFeatureToggle({
84
+ name: featureA,
85
+ enabled: true,
86
+ environment: envA,
87
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
88
+ });
89
+ await createFeatureToggle({
90
+ name: featureB,
91
+ enabled: true,
92
+ environment: envB,
93
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
94
+ });
95
+ const tokenA = await createApiToken(ApiTokenType.FRONTEND, {
96
+ alias: randomId(),
97
+ environment: envA,
98
+ });
99
+ const tokenB = await createApiToken(ApiTokenType.FRONTEND, {
100
+ alias: randomId(),
101
+ environment: envB,
102
+ });
103
+ await frontendApiService.refreshData();
104
+ await app.request
105
+ .get('/api/frontend')
106
+ .expect('Content-Type', /json/)
107
+ .expect(401);
108
+ await app.request
109
+ .get('/api/frontend')
110
+ .set('Authorization', '')
111
+ .expect('Content-Type', /json/)
112
+ .expect(401);
113
+ await app.request
114
+ .get('/api/frontend')
115
+ .set('Authorization', 'null')
116
+ .expect('Content-Type', /json/)
117
+ .expect(401);
118
+ await app.request
119
+ .get('/api/frontend')
120
+ .set('Authorization', randomId())
121
+ .expect('Content-Type', /json/)
122
+ .expect(401);
123
+ await app.request
124
+ .get('/api/frontend')
125
+ .set('Authorization', tokenA.secret.slice(0, -1))
126
+ .expect('Content-Type', /json/)
127
+ .expect(401);
128
+ await app.request
129
+ .get('/api/frontend')
130
+ .set('Authorization', tokenA.secret)
131
+ .expect('Content-Type', /json/)
132
+ .expect(200)
133
+ .expect((res) => expect(res.body.toggles).toHaveLength(1))
134
+ .expect((res) => expect(res.body.toggles[0].name).toEqual(featureA));
135
+ await app.request
136
+ .get('/api/frontend')
137
+ .set('Authorization', tokenB.secret)
138
+ .expect('Content-Type', /json/)
139
+ .expect(200)
140
+ .expect((res) => expect(res.body.toggles).toHaveLength(1))
141
+ .expect((res) => expect(res.body.toggles[0].name).toEqual(featureB));
142
+ await app.request
143
+ .get('/api/frontend')
144
+ .set('Authorization', tokenA.alias)
145
+ .expect('Content-Type', /json/)
146
+ .expect(200)
147
+ .expect((res) => expect(res.body.toggles).toHaveLength(1))
148
+ .expect((res) => expect(res.body.toggles[0].name).toEqual(featureA));
149
+ await app.request
150
+ .get('/api/frontend')
151
+ .set('Authorization', tokenB.alias)
152
+ .expect('Content-Type', /json/)
153
+ .expect(200)
154
+ .expect((res) => expect(res.body.toggles).toHaveLength(1))
155
+ .expect((res) => expect(res.body.toggles[0].name).toEqual(featureB));
156
+ });
157
+ test('should not allow requests with an admin token', async () => {
158
+ const featureA = randomId();
159
+ await createFeatureToggle({
160
+ name: featureA,
161
+ enabled: true,
162
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
163
+ });
164
+ const adminToken = await createApiToken(ApiTokenType.ADMIN, {
165
+ projects: ['*'],
166
+ environment: '*',
167
+ });
168
+ await frontendApiService.refreshData();
169
+ await app.request
170
+ .get('/api/frontend')
171
+ .set('Authorization', adminToken.secret)
172
+ .expect('Content-Type', /json/)
173
+ .expect(403);
174
+ });
175
+ test('should not allow admin requests with a frontend token', async () => {
176
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
177
+ await app.request
178
+ .get('/api/admin/projects')
179
+ .set('Authorization', frontendToken.secret)
180
+ .expect('Content-Type', /json/)
181
+ .expect(403);
182
+ });
183
+ test('should not allow client requests with a frontend token', async () => {
184
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
185
+ await app.request
186
+ .get('/api/client/features')
187
+ .set('Authorization', frontendToken.secret)
188
+ .expect('Content-Type', /json/)
189
+ .expect(403);
190
+ });
191
+ test('should not allow requests with an invalid frontend token', async () => {
192
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
193
+ await app.request
194
+ .get('/api/frontend')
195
+ .set('Authorization', frontendToken.secret.slice(0, -1))
196
+ .expect('Content-Type', /json/)
197
+ .expect(401);
198
+ });
199
+ test('should allow requests with a frontend token', async () => {
200
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
201
+ await frontendApiService.refreshData();
202
+ await app.request
203
+ .get('/api/frontend')
204
+ .set('Authorization', frontendToken.secret)
205
+ .expect('Content-Type', /json/)
206
+ .expect(200)
207
+ .expect((res) => expect(res.body).toEqual({ toggles: [] }));
208
+ });
209
+ test('should return 405 from unimplemented endpoints', async () => {
210
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
211
+ await app.request
212
+ .get('/api/frontend/client/features')
213
+ .set('Authorization', frontendToken.secret)
214
+ .expect('Content-Type', /json/)
215
+ .expect(405);
216
+ await app.request
217
+ .get('/api/frontend/health')
218
+ .set('Authorization', frontendToken.secret)
219
+ .expect('Content-Type', /json/)
220
+ .expect(405);
221
+ await app.request
222
+ .get('/api/frontend/internal-backstage/prometheus')
223
+ .set('Authorization', frontendToken.secret)
224
+ .expect('Content-Type', /json/)
225
+ .expect(405);
226
+ });
227
+ test('should enforce frontend API CORS config', async () => {
228
+ const allowedOrigin = 'https://example.com';
229
+ const unknownOrigin = 'https://example.org';
230
+ const origin = 'access-control-allow-origin';
231
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
232
+ await app.request
233
+ .options('/api/frontend')
234
+ .set('Origin', unknownOrigin)
235
+ .set('Authorization', frontendToken.secret)
236
+ .expect((res) => expect(res.headers[origin]).toBeUndefined());
237
+ await app.request
238
+ .options('/api/frontend')
239
+ .set('Origin', allowedOrigin)
240
+ .set('Authorization', frontendToken.secret)
241
+ .expect((res) => expect(res.headers[origin]).toEqual(allowedOrigin));
242
+ await app.request
243
+ .get('/api/frontend')
244
+ .set('Origin', unknownOrigin)
245
+ .set('Authorization', frontendToken.secret)
246
+ .expect((res) => expect(res.headers[origin]).toBeUndefined());
247
+ await app.request
248
+ .get('/api/frontend')
249
+ .set('Origin', allowedOrigin)
250
+ .set('Authorization', frontendToken.secret)
251
+ .expect((res) => expect(res.headers[origin]).toEqual(allowedOrigin));
252
+ });
253
+ test('should accept client registration requests', async () => {
254
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
255
+ await app.request
256
+ .post('/api/frontend/client/register')
257
+ .set('Authorization', frontendToken.secret)
258
+ .send({})
259
+ .expect('Content-Type', /json/)
260
+ .expect(400);
261
+ await app.request
262
+ .post('/api/frontend/client/register')
263
+ .set('Authorization', frontendToken.secret)
264
+ .send({
265
+ appName: randomId(),
266
+ instanceId: randomId(),
267
+ sdkVersion: randomId(),
268
+ environment: DEFAULT_ENV,
269
+ interval: 10000,
270
+ started: new Date(),
271
+ strategies: ['default'],
272
+ })
273
+ .expect(200)
274
+ .expect((res) => expect(res.text).toEqual('OK'));
275
+ });
276
+ test('should store frontend api client metrics', async () => {
277
+ const now = new Date();
278
+ const appName = randomId();
279
+ const instanceId = randomId();
280
+ const featureName = randomId();
281
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
282
+ const adminToken = await createApiToken(ApiTokenType.ADMIN, {
283
+ projects: ['*'],
284
+ environment: '*',
285
+ });
286
+ // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2
287
+ app.services.clientMetricsServiceV2.cachedFeatureNames = vi
288
+ .fn()
289
+ .mockResolvedValue([featureName]);
290
+ await app.request
291
+ .get(`/api/admin/client-metrics/features/${featureName}`)
292
+ .set('Authorization', adminToken.secret)
293
+ .expect('Content-Type', /json/)
294
+ .expect(200)
295
+ .then((res) => {
296
+ expect(res.body).toEqual({
297
+ featureName,
298
+ lastHourUsage: [],
299
+ maturity: 'stable',
300
+ seenApplications: [],
301
+ version: 1,
302
+ });
303
+ });
304
+ await app.request
305
+ .post('/api/frontend/client/metrics')
306
+ .set('Authorization', frontendToken.secret)
307
+ .send({
308
+ appName,
309
+ instanceId,
310
+ bucket: {
311
+ start: now,
312
+ stop: now,
313
+ toggles: { [featureName]: { yes: 1, no: 10 } },
314
+ },
315
+ })
316
+ .expect(200)
317
+ .expect((res) => expect(res.text).toEqual('OK'));
318
+ await app.request
319
+ .post('/api/frontend/client/metrics')
320
+ .set('Authorization', frontendToken.secret)
321
+ .send({
322
+ appName,
323
+ instanceId,
324
+ bucket: {
325
+ start: now,
326
+ stop: now,
327
+ toggles: { [featureName]: { yes: 2, no: 20 } },
328
+ },
329
+ })
330
+ .expect(200)
331
+ .expect((res) => expect(res.text).toEqual('OK'));
332
+ await app.services.clientMetricsServiceV2.bulkAdd();
333
+ await app.request
334
+ .get(`/api/admin/client-metrics/features/${featureName}`)
335
+ .set('Authorization', adminToken.secret)
336
+ .expect('Content-Type', /json/)
337
+ .expect(200)
338
+ .then((res) => {
339
+ expect(res.body).toEqual({
340
+ featureName,
341
+ lastHourUsage: [
342
+ {
343
+ environment: DEFAULT_ENV,
344
+ timestamp: startOfHour(now).toISOString(),
345
+ yes: 3,
346
+ no: 30,
347
+ },
348
+ ],
349
+ maturity: 'stable',
350
+ seenApplications: [appName],
351
+ version: 1,
352
+ });
353
+ });
354
+ });
355
+ test('should filter features by strategies', async () => {
356
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
357
+ await createFeatureToggle({
358
+ name: 'featureWithoutStrategies',
359
+ enabled: false,
360
+ strategies: [],
361
+ });
362
+ await createFeatureToggle({
363
+ name: 'featureWithMultipleStrategies',
364
+ enabled: true,
365
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
366
+ });
367
+ await frontendApiService.refreshData();
368
+ await app.request
369
+ .get('/api/frontend')
370
+ .set('Authorization', frontendToken.secret)
371
+ .expect('Content-Type', /json/)
372
+ .expect(200)
373
+ .expect((res) => {
374
+ expect(res.body).toEqual({
375
+ toggles: [
376
+ {
377
+ name: 'featureWithMultipleStrategies',
378
+ enabled: true,
379
+ impressionData: false,
380
+ variant: {
381
+ enabled: false,
382
+ name: 'disabled',
383
+ feature_enabled: true,
384
+ featureEnabled: true,
385
+ },
386
+ },
387
+ ],
388
+ });
389
+ });
390
+ });
391
+ test('should filter features by constraints', async () => {
392
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
393
+ await createFeatureToggle({
394
+ name: 'featureWithAppNameA',
395
+ enabled: true,
396
+ strategies: [
397
+ {
398
+ name: 'default',
399
+ constraints: [
400
+ { contextName: 'appName', operator: 'IN', values: ['a'] },
401
+ ],
402
+ parameters: {},
403
+ },
404
+ ],
405
+ });
406
+ await createFeatureToggle({
407
+ name: 'featureWithAppNameAorB',
408
+ enabled: true,
409
+ strategies: [
410
+ {
411
+ name: 'default',
412
+ constraints: [
413
+ {
414
+ contextName: 'appName',
415
+ operator: 'IN',
416
+ values: ['a', 'b'],
417
+ },
418
+ ],
419
+ parameters: {},
420
+ },
421
+ ],
422
+ });
423
+ await frontendApiService.refreshData();
424
+ await app.request
425
+ .get('/api/frontend?appName=a')
426
+ .set('Authorization', frontendToken.secret)
427
+ .expect('Content-Type', /json/)
428
+ .expect(200)
429
+ .expect((res) => expect(res.body.toggles).toHaveLength(2));
430
+ await app.request
431
+ .get('/api/frontend?appName=b')
432
+ .set('Authorization', frontendToken.secret)
433
+ .expect('Content-Type', /json/)
434
+ .expect(200)
435
+ .expect((res) => expect(res.body.toggles).toHaveLength(1));
436
+ await app.request
437
+ .get('/api/frontend?appName=c')
438
+ .set('Authorization', frontendToken.secret)
439
+ .expect('Content-Type', /json/)
440
+ .expect(200)
441
+ .expect((res) => expect(res.body.toggles).toHaveLength(0));
442
+ });
443
+ test('should be able to set environment as a context variable', async () => {
444
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
445
+ const featureName = 'featureWithEnvironmentConstraint';
446
+ await createFeatureToggle({
447
+ name: featureName,
448
+ enabled: true,
449
+ strategies: [
450
+ {
451
+ name: 'default',
452
+ constraints: [
453
+ {
454
+ contextName: 'environment',
455
+ operator: 'IN',
456
+ values: ['staging'],
457
+ },
458
+ ],
459
+ parameters: {},
460
+ },
461
+ ],
462
+ });
463
+ await frontendApiService.refreshData();
464
+ await app.request
465
+ .get('/api/frontend?environment=staging')
466
+ .set('Authorization', frontendToken.secret)
467
+ .expect('Content-Type', /json/)
468
+ .expect(200)
469
+ .expect((res) => {
470
+ expect(res.body.toggles).toHaveLength(1);
471
+ expect(res.body.toggles[0].name).toBe(featureName);
472
+ });
473
+ await app.request
474
+ .get('/api/frontend')
475
+ .set('Authorization', frontendToken.secret)
476
+ .expect('Content-Type', /json/)
477
+ .expect(200)
478
+ .expect((res) => {
479
+ expect(res.body.toggles).toHaveLength(0);
480
+ });
481
+ });
482
+ test('should filter features by project', async () => {
483
+ const projectA = 'projectA';
484
+ const projectB = 'projectB';
485
+ await createProject(projectA, randomId());
486
+ await createProject(projectB, randomId());
487
+ const frontendTokenDefault = await createApiToken(ApiTokenType.FRONTEND, {
488
+ projects: ['default'],
489
+ });
490
+ const frontendTokenProjectA = await createApiToken(ApiTokenType.FRONTEND, {
491
+ projects: [projectA],
492
+ });
493
+ const frontendTokenProjectAB = await createApiToken(ApiTokenType.FRONTEND, {
494
+ projects: [projectA, projectB],
495
+ });
496
+ await createFeatureToggle({
497
+ name: 'featureInProjectDefault',
498
+ enabled: true,
499
+ strategies: [{ name: 'default', parameters: {} }],
500
+ });
501
+ await createFeatureToggle({
502
+ name: 'featureInProjectA',
503
+ project: projectA,
504
+ enabled: true,
505
+ strategies: [{ name: 'default', parameters: {} }],
506
+ });
507
+ await createFeatureToggle({
508
+ name: 'featureInProjectB',
509
+ project: projectB,
510
+ enabled: true,
511
+ strategies: [{ name: 'default', parameters: {} }],
512
+ });
513
+ await frontendApiService.refreshData();
514
+ await app.request
515
+ .get('/api/frontend')
516
+ .set('Authorization', frontendTokenDefault.secret)
517
+ .expect('Content-Type', /json/)
518
+ .expect(200)
519
+ .expect((res) => {
520
+ expect(res.body).toEqual({
521
+ toggles: [
522
+ {
523
+ name: 'featureInProjectDefault',
524
+ enabled: true,
525
+ impressionData: false,
526
+ variant: {
527
+ enabled: false,
528
+ name: 'disabled',
529
+ feature_enabled: true,
530
+ featureEnabled: true,
531
+ },
532
+ },
533
+ ],
534
+ });
535
+ });
536
+ await app.request
537
+ .get('/api/frontend')
538
+ .set('Authorization', frontendTokenProjectA.secret)
539
+ .expect('Content-Type', /json/)
540
+ .expect(200)
541
+ .expect((res) => {
542
+ expect(res.body).toEqual({
543
+ toggles: [
544
+ {
545
+ name: 'featureInProjectA',
546
+ enabled: true,
547
+ impressionData: false,
548
+ variant: {
549
+ enabled: false,
550
+ name: 'disabled',
551
+ feature_enabled: true,
552
+ featureEnabled: true,
553
+ },
554
+ },
555
+ ],
556
+ });
557
+ });
558
+ await app.request
559
+ .get('/api/frontend')
560
+ .set('Authorization', frontendTokenProjectAB.secret)
561
+ .expect('Content-Type', /json/)
562
+ .expect(200)
563
+ .expect((res) => {
564
+ expect(res.body).toEqual({
565
+ toggles: [
566
+ {
567
+ name: 'featureInProjectA',
568
+ enabled: true,
569
+ impressionData: false,
570
+ variant: {
571
+ enabled: false,
572
+ name: 'disabled',
573
+ feature_enabled: true,
574
+ featureEnabled: true,
575
+ },
576
+ },
577
+ {
578
+ name: 'featureInProjectB',
579
+ enabled: true,
580
+ impressionData: false,
581
+ variant: {
582
+ enabled: false,
583
+ name: 'disabled',
584
+ feature_enabled: true,
585
+ featureEnabled: true,
586
+ },
587
+ },
588
+ ],
589
+ });
590
+ });
591
+ });
592
+ test('should filter features by environment', async () => {
593
+ const environmentA = 'environmentA';
594
+ const environmentB = 'environmentB';
595
+ await db.stores.environmentStore.create({
596
+ name: environmentA,
597
+ type: 'production',
598
+ });
599
+ await db.stores.environmentStore.create({
600
+ name: environmentB,
601
+ type: 'production',
602
+ });
603
+ await app.services.environmentService.addEnvironmentToProject(environmentA, 'default', SYSTEM_USER_AUDIT);
604
+ await app.services.environmentService.addEnvironmentToProject(environmentB, 'default', SYSTEM_USER_AUDIT);
605
+ const frontendTokenEnvironmentDefault = await createApiToken(ApiTokenType.FRONTEND);
606
+ const frontendTokenEnvironmentA = await createApiToken(ApiTokenType.FRONTEND, {
607
+ environment: environmentA,
608
+ });
609
+ const frontendTokenEnvironmentB = await createApiToken(ApiTokenType.FRONTEND, {
610
+ environment: environmentB,
611
+ });
612
+ await createFeatureToggle({
613
+ name: 'featureInEnvironmentDefault',
614
+ enabled: true,
615
+ strategies: [{ name: 'default', parameters: {} }],
616
+ });
617
+ await createFeatureToggle({
618
+ name: 'featureInEnvironmentA',
619
+ environment: environmentA,
620
+ enabled: true,
621
+ strategies: [{ name: 'default', parameters: {} }],
622
+ });
623
+ await createFeatureToggle({
624
+ name: 'featureInEnvironmentB',
625
+ environment: environmentB,
626
+ enabled: true,
627
+ strategies: [{ name: 'default', parameters: {} }],
628
+ });
629
+ await frontendApiService.refreshData();
630
+ await app.request
631
+ .get('/api/frontend')
632
+ .set('Authorization', frontendTokenEnvironmentDefault.secret)
633
+ .expect('Content-Type', /json/)
634
+ .expect(200)
635
+ .expect((res) => {
636
+ expect(res.body).toEqual({
637
+ toggles: [
638
+ {
639
+ name: 'featureInEnvironmentDefault',
640
+ enabled: true,
641
+ impressionData: false,
642
+ variant: {
643
+ enabled: false,
644
+ name: 'disabled',
645
+ feature_enabled: true,
646
+ featureEnabled: true,
647
+ },
648
+ },
649
+ ],
650
+ });
651
+ });
652
+ await app.request
653
+ .get('/api/frontend')
654
+ .set('Authorization', frontendTokenEnvironmentA.secret)
655
+ .expect('Content-Type', /json/)
656
+ .expect(200)
657
+ .expect((res) => {
658
+ expect(res.body).toEqual({
659
+ toggles: [
660
+ {
661
+ name: 'featureInEnvironmentA',
662
+ enabled: true,
663
+ impressionData: false,
664
+ variant: {
665
+ enabled: false,
666
+ name: 'disabled',
667
+ feature_enabled: true,
668
+ featureEnabled: true,
669
+ },
670
+ },
671
+ ],
672
+ });
673
+ });
674
+ await app.request
675
+ .get('/api/frontend')
676
+ .set('Authorization', frontendTokenEnvironmentB.secret)
677
+ .expect('Content-Type', /json/)
678
+ .expect(200)
679
+ .expect((res) => {
680
+ expect(res.body).toEqual({
681
+ toggles: [
682
+ {
683
+ name: 'featureInEnvironmentB',
684
+ enabled: true,
685
+ impressionData: false,
686
+ variant: {
687
+ enabled: false,
688
+ name: 'disabled',
689
+ feature_enabled: true,
690
+ featureEnabled: true,
691
+ },
692
+ },
693
+ ],
694
+ });
695
+ });
696
+ });
697
+ test('should return maxAge header on options call', async () => {
698
+ await app.request
699
+ .options('/api/frontend')
700
+ .set('Origin', 'https://example.com')
701
+ .expect(204)
702
+ .expect((res) => {
703
+ expect(res.headers['access-control-max-age']).toBe('86400');
704
+ });
705
+ });
706
+ test('should evaluate strategies when returning toggles', async () => {
707
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
708
+ await createFeatureToggle({
709
+ name: 'enabledFeature',
710
+ enabled: true,
711
+ strategies: [
712
+ {
713
+ name: 'flexibleRollout',
714
+ constraints: [],
715
+ parameters: {
716
+ rollout: '100',
717
+ stickiness: 'default',
718
+ groupId: 'some-new',
719
+ },
720
+ },
721
+ ],
722
+ });
723
+ await createFeatureToggle({
724
+ name: 'disabledFeature',
725
+ enabled: true,
726
+ strategies: [
727
+ {
728
+ name: 'flexibleRollout',
729
+ constraints: [],
730
+ parameters: {
731
+ rollout: '0',
732
+ stickiness: 'default',
733
+ groupId: 'some-new',
734
+ },
735
+ },
736
+ ],
737
+ });
738
+ await frontendApiService.refreshData();
739
+ await app.request
740
+ .get('/api/frontend')
741
+ .set('Authorization', frontendToken.secret)
742
+ .expect('Content-Type', /json/)
743
+ .expect(200)
744
+ .expect((res) => {
745
+ expect(res.body).toEqual({
746
+ toggles: [
747
+ {
748
+ name: 'enabledFeature',
749
+ enabled: true,
750
+ impressionData: false,
751
+ variant: {
752
+ enabled: false,
753
+ name: 'disabled',
754
+ feature_enabled: true,
755
+ featureEnabled: true,
756
+ },
757
+ },
758
+ ],
759
+ });
760
+ });
761
+ });
762
+ test('should NOT evaluate disabled strategies when returning toggles', async () => {
763
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
764
+ await createFeatureToggle({
765
+ name: 'enabledFeature',
766
+ enabled: true,
767
+ strategies: [
768
+ {
769
+ name: 'flexibleRollout',
770
+ constraints: [],
771
+ parameters: {
772
+ rollout: '100',
773
+ stickiness: 'default',
774
+ groupId: 'some-new',
775
+ },
776
+ },
777
+ ],
778
+ });
779
+ await createFeatureToggle({
780
+ name: 'disabledFeature',
781
+ enabled: false,
782
+ strategies: [
783
+ {
784
+ name: 'flexibleRollout',
785
+ constraints: [],
786
+ disabled: true,
787
+ parameters: {
788
+ rollout: '100',
789
+ stickiness: 'default',
790
+ groupId: 'some-new',
791
+ },
792
+ },
793
+ ],
794
+ });
795
+ await createFeatureToggle({
796
+ name: 'disabledFeature2',
797
+ enabled: true,
798
+ strategies: [
799
+ {
800
+ name: 'flexibleRollout',
801
+ constraints: [],
802
+ disabled: true,
803
+ parameters: {
804
+ rollout: '100',
805
+ stickiness: 'default',
806
+ groupId: 'some-new',
807
+ },
808
+ },
809
+ {
810
+ name: 'flexibleRollout',
811
+ constraints: [],
812
+ disabled: false,
813
+ parameters: {
814
+ rollout: '0',
815
+ stickiness: 'default',
816
+ groupId: 'some-new',
817
+ },
818
+ },
819
+ ],
820
+ });
821
+ await createFeatureToggle({
822
+ name: 'disabledFeature3',
823
+ enabled: false,
824
+ strategies: [
825
+ {
826
+ name: 'flexibleRollout',
827
+ constraints: [],
828
+ disabled: true,
829
+ parameters: {
830
+ rollout: '100',
831
+ stickiness: 'default',
832
+ groupId: 'some-new',
833
+ },
834
+ },
835
+ ],
836
+ });
837
+ await frontendApiService.refreshData();
838
+ await app.request
839
+ .get('/api/frontend')
840
+ .set('Authorization', frontendToken.secret)
841
+ .expect('Content-Type', /json/)
842
+ .expect(200)
843
+ .expect((res) => {
844
+ expect(res.body).toEqual({
845
+ toggles: [
846
+ {
847
+ name: 'enabledFeature',
848
+ enabled: true,
849
+ impressionData: false,
850
+ variant: {
851
+ enabled: false,
852
+ name: 'disabled',
853
+ feature_enabled: true,
854
+ featureEnabled: true,
855
+ },
856
+ },
857
+ ],
858
+ });
859
+ });
860
+ });
861
+ test('should return 204 if metrics are disabled', async () => {
862
+ const localApp = await setupAppWithAuth(db.stores, {
863
+ frontendApiOrigins: ['https://example.com'],
864
+ experimental: {
865
+ flags: {
866
+ disableMetrics: true,
867
+ },
868
+ },
869
+ }, db.rawDatabase);
870
+ const frontendToken = await localApp.services.apiTokenService.createApiTokenWithProjects({
871
+ type: ApiTokenType.FRONTEND,
872
+ projects: ['*'],
873
+ environment: DEFAULT_ENV,
874
+ tokenName: `disabledMetric-token-${randomId()}`,
875
+ });
876
+ const appName = randomId();
877
+ const instanceId = randomId();
878
+ const featureName = 'metricsDisabled';
879
+ const now = new Date();
880
+ await localApp.request
881
+ .post('/api/frontend/client/metrics')
882
+ .set('Authorization', frontendToken.secret)
883
+ .send({
884
+ appName,
885
+ instanceId,
886
+ bucket: {
887
+ start: now,
888
+ stop: now,
889
+ toggles: { [featureName]: { yes: 2, no: 20 } },
890
+ },
891
+ })
892
+ .expect(204);
893
+ });
894
+ test('should resolve variable rollout percentage consistently', async () => {
895
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
896
+ await createFeatureToggle({
897
+ name: 'randomFeature',
898
+ enabled: true,
899
+ strategies: [
900
+ {
901
+ name: 'flexibleRollout',
902
+ constraints: [],
903
+ parameters: {
904
+ rollout: '50',
905
+ stickiness: 'default',
906
+ groupId: 'some-new',
907
+ },
908
+ variants: [
909
+ {
910
+ name: 'a',
911
+ stickiness: 'default',
912
+ weightType: 'variable',
913
+ weight: 1000,
914
+ },
915
+ ],
916
+ },
917
+ ],
918
+ });
919
+ await frontendApiService.refreshData();
920
+ for (let i = 0; i < 10; ++i) {
921
+ const { body } = await app.request
922
+ .get('/api/frontend')
923
+ .set('Authorization', frontendToken.secret)
924
+ .expect('Content-Type', /json/)
925
+ .expect(200);
926
+ if (body.toggles.length > 0) {
927
+ // disabled variant should not be possible for enabled toggles
928
+ expect(body.toggles[0].variant.name).toBe('a');
929
+ }
930
+ }
931
+ });
932
+ test('should return enabled feature flags using POST', async () => {
933
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
934
+ await createFeatureToggle({
935
+ name: 'enabledFeature1',
936
+ enabled: true,
937
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
938
+ });
939
+ await createFeatureToggle({
940
+ name: 'enabledFeature2',
941
+ enabled: true,
942
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
943
+ });
944
+ await createFeatureToggle({
945
+ name: 'disabledFeature',
946
+ enabled: false,
947
+ strategies: [{ name: 'default', constraints: [], parameters: {} }],
948
+ });
949
+ await frontendApiService.refreshData();
950
+ await app.request
951
+ .post('/api/frontend')
952
+ .set('Authorization', frontendToken.secret)
953
+ .set('Content-Type', 'application/json')
954
+ .send()
955
+ .expect('Content-Type', /json/)
956
+ .expect(200)
957
+ .expect((res) => {
958
+ expect(res.body).toEqual({
959
+ toggles: [
960
+ {
961
+ name: 'enabledFeature1',
962
+ enabled: true,
963
+ impressionData: false,
964
+ variant: {
965
+ enabled: false,
966
+ name: 'disabled',
967
+ feature_enabled: true,
968
+ featureEnabled: true,
969
+ },
970
+ },
971
+ {
972
+ name: 'enabledFeature2',
973
+ enabled: true,
974
+ impressionData: false,
975
+ variant: {
976
+ enabled: false,
977
+ name: 'disabled',
978
+ feature_enabled: true,
979
+ featureEnabled: true,
980
+ },
981
+ },
982
+ ],
983
+ });
984
+ });
985
+ });
986
+ test('should accept impact metrics in frontend API metrics endpoint', async () => {
987
+ const localApp = await setupAppWithAuth(db.stores, {
988
+ frontendApiOrigins: ['https://example.com'],
989
+ experimental: {
990
+ flags: {
991
+ impactMetrics: true,
992
+ },
993
+ },
994
+ }, db.rawDatabase);
995
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
996
+ const now = new Date();
997
+ await localApp.request
998
+ .post('/api/frontend/client/metrics')
999
+ .set('Authorization', frontendToken.secret)
1000
+ .send({
1001
+ appName: 'impact-metrics-frontend',
1002
+ instanceId: 'instance-1',
1003
+ bucket: {
1004
+ start: now,
1005
+ stop: now,
1006
+ toggles: {},
1007
+ },
1008
+ impactMetrics: [
1009
+ {
1010
+ name: 'frontend_counter',
1011
+ help: 'A counter from frontend SDK',
1012
+ type: 'counter',
1013
+ samples: [
1014
+ {
1015
+ labels: { source: 'frontend' },
1016
+ value: 5,
1017
+ },
1018
+ ],
1019
+ },
1020
+ ],
1021
+ })
1022
+ .expect(200);
1023
+ const response = await localApp.request
1024
+ .get('/internal-backstage/impact/metrics')
1025
+ .expect(200);
1026
+ expect(response.text).toMatch(/frontend_counter{origin="sdk",metric_type="counter",source="frontend"} 5/);
1027
+ await localApp.destroy();
1028
+ });
1029
+ test('should return enabled feature flags based on context using POST', async () => {
1030
+ const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
1031
+ const featureName = 'featureWithEnvironmentConstraint';
1032
+ await createFeatureToggle({
1033
+ name: featureName,
1034
+ enabled: true,
1035
+ strategies: [
1036
+ {
1037
+ name: 'default',
1038
+ constraints: [
1039
+ {
1040
+ contextName: 'userId',
1041
+ operator: 'IN',
1042
+ values: ['1337'],
1043
+ },
1044
+ ],
1045
+ parameters: {},
1046
+ },
1047
+ ],
1048
+ });
1049
+ await frontendApiService.refreshData();
1050
+ await app.request
1051
+ .post('/api/frontend')
1052
+ .set('Authorization', frontendToken.secret)
1053
+ .set('Content-Type', 'application/json')
1054
+ .send({ context: { userId: '1337' } })
1055
+ .expect('Content-Type', /json/)
1056
+ .expect(200)
1057
+ .expect((res) => {
1058
+ expect(res.body.toggles).toHaveLength(1);
1059
+ expect(res.body.toggles[0].name).toBe(featureName);
1060
+ });
1061
+ await app.request
1062
+ .post('/api/frontend')
1063
+ .set('Authorization', frontendToken.secret)
1064
+ .send({ context: { appName: 'test', userId: '42' } })
1065
+ .expect('Content-Type', /json/)
1066
+ .expect(200)
1067
+ .expect((res) => {
1068
+ expect(res.body.toggles).toHaveLength(0);
1069
+ });
1070
+ });
1071
+ //# sourceMappingURL=frontend-api.middleware.e2e.test.js.map