unleash-server 4.2.0 → 4.3.0-beta.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 (263) hide show
  1. package/README.md +1 -0
  2. package/dist/lib/addons/addon.d.ts +2 -1
  3. package/dist/lib/addons/addon.js.map +1 -1
  4. package/dist/lib/addons/datadog.d.ts +2 -1
  5. package/dist/lib/addons/datadog.js.map +1 -1
  6. package/dist/lib/addons/datadog.test.js +3 -0
  7. package/dist/lib/addons/datadog.test.js.map +1 -1
  8. package/dist/lib/addons/feature-event-formatter-md.d.ts +1 -1
  9. package/dist/lib/addons/feature-event-formatter-md.js +13 -14
  10. package/dist/lib/addons/feature-event-formatter-md.js.map +1 -1
  11. package/dist/lib/addons/slack.d.ts +2 -1
  12. package/dist/lib/addons/slack.js.map +1 -1
  13. package/dist/lib/addons/slack.test.js +6 -0
  14. package/dist/lib/addons/slack.test.js.map +1 -1
  15. package/dist/lib/addons/teams.d.ts +2 -1
  16. package/dist/lib/addons/teams.js.map +1 -1
  17. package/dist/lib/addons/teams.test.js +3 -0
  18. package/dist/lib/addons/teams.test.js.map +1 -1
  19. package/dist/lib/addons/webhook.d.ts +8 -2
  20. package/dist/lib/addons/webhook.js +0 -1
  21. package/dist/lib/addons/webhook.js.map +1 -1
  22. package/dist/lib/addons/webhook.test.js +2 -0
  23. package/dist/lib/addons/webhook.test.js.map +1 -1
  24. package/dist/lib/create-config.js +11 -20
  25. package/dist/lib/create-config.js.map +1 -1
  26. package/dist/lib/create-config.test.js +0 -37
  27. package/dist/lib/create-config.test.js.map +1 -1
  28. package/dist/lib/db/client-instance-store.js +2 -2
  29. package/dist/lib/db/client-instance-store.js.map +1 -1
  30. package/dist/lib/db/client-metrics-db.js +2 -2
  31. package/dist/lib/db/client-metrics-db.js.map +1 -1
  32. package/dist/lib/db/client-metrics-store-v2.js +5 -1
  33. package/dist/lib/db/client-metrics-store-v2.js.map +1 -1
  34. package/dist/lib/db/client-metrics-store.js +2 -2
  35. package/dist/lib/db/client-metrics-store.js.map +1 -1
  36. package/dist/lib/db/event-store.d.ts +10 -6
  37. package/dist/lib/db/event-store.js +21 -1
  38. package/dist/lib/db/event-store.js.map +1 -1
  39. package/dist/lib/db/feature-strategy-store.js +1 -0
  40. package/dist/lib/db/feature-strategy-store.js.map +1 -1
  41. package/dist/lib/db/feature-toggle-store.d.ts +4 -1
  42. package/dist/lib/db/feature-toggle-store.js +27 -2
  43. package/dist/lib/db/feature-toggle-store.js.map +1 -1
  44. package/dist/lib/db/index.js +2 -0
  45. package/dist/lib/db/index.js.map +1 -1
  46. package/dist/lib/db/session-store.js +2 -1
  47. package/dist/lib/db/session-store.js.map +1 -1
  48. package/dist/lib/db/user-splash-store.d.ts +19 -0
  49. package/dist/lib/db/user-splash-store.js +70 -0
  50. package/dist/lib/db/user-splash-store.js.map +1 -0
  51. package/dist/lib/error/minimum-one-environment-error.d.ts +5 -0
  52. package/dist/lib/error/minimum-one-environment-error.js +24 -0
  53. package/dist/lib/error/minimum-one-environment-error.js.map +1 -0
  54. package/dist/lib/error/operation-denied-error.d.ts +4 -0
  55. package/dist/lib/error/operation-denied-error.js +24 -0
  56. package/dist/lib/error/operation-denied-error.js.map +1 -0
  57. package/dist/lib/metrics.js +20 -12
  58. package/dist/lib/metrics.js.map +1 -1
  59. package/dist/lib/metrics.test.js +1 -0
  60. package/dist/lib/metrics.test.js.map +1 -1
  61. package/dist/lib/middleware/rbac-middleware.js +2 -17
  62. package/dist/lib/middleware/rbac-middleware.js.map +1 -1
  63. package/dist/lib/middleware/rbac-middleware.test.js +0 -21
  64. package/dist/lib/middleware/rbac-middleware.test.js.map +1 -1
  65. package/dist/lib/middleware/secure-headers.js +2 -1
  66. package/dist/lib/middleware/secure-headers.js.map +1 -1
  67. package/dist/lib/middleware/session-db.js +2 -3
  68. package/dist/lib/middleware/session-db.js.map +1 -1
  69. package/dist/lib/routes/admin-api/event.d.ts +8 -4
  70. package/dist/lib/routes/admin-api/event.js +8 -10
  71. package/dist/lib/routes/admin-api/event.js.map +1 -1
  72. package/dist/lib/routes/admin-api/feature.js +7 -3
  73. package/dist/lib/routes/admin-api/feature.js.map +1 -1
  74. package/dist/lib/routes/admin-api/index.js +2 -0
  75. package/dist/lib/routes/admin-api/index.js.map +1 -1
  76. package/dist/lib/routes/admin-api/project/features.js +11 -8
  77. package/dist/lib/routes/admin-api/project/features.js.map +1 -1
  78. package/dist/lib/routes/admin-api/project/index.js +2 -0
  79. package/dist/lib/routes/admin-api/project/index.js.map +1 -1
  80. package/dist/lib/routes/admin-api/project/variants.d.ts +21 -0
  81. package/dist/lib/routes/admin-api/project/variants.js +41 -0
  82. package/dist/lib/routes/admin-api/project/variants.js.map +1 -0
  83. package/dist/lib/routes/admin-api/state.js +2 -3
  84. package/dist/lib/routes/admin-api/state.js.map +1 -1
  85. package/dist/lib/routes/admin-api/user-splash-controller.d.ts +10 -0
  86. package/dist/lib/routes/admin-api/user-splash-controller.js +28 -0
  87. package/dist/lib/routes/admin-api/user-splash-controller.js.map +1 -0
  88. package/dist/lib/routes/admin-api/user.d.ts +2 -1
  89. package/dist/lib/routes/admin-api/user.js +7 -2
  90. package/dist/lib/routes/admin-api/user.js.map +1 -1
  91. package/dist/lib/routes/client-api/feature.test.js +3 -2
  92. package/dist/lib/routes/client-api/feature.test.js.map +1 -1
  93. package/dist/lib/routes/client-api/metrics.d.ts +0 -1
  94. package/dist/lib/routes/client-api/metrics.js +2 -10
  95. package/dist/lib/routes/client-api/metrics.js.map +1 -1
  96. package/dist/lib/routes/util.js +18 -14
  97. package/dist/lib/routes/util.js.map +1 -1
  98. package/dist/lib/schema/feature-schema.d.ts +1 -0
  99. package/dist/lib/schema/feature-schema.js +6 -7
  100. package/dist/lib/schema/feature-schema.js.map +1 -1
  101. package/dist/lib/services/addon-service-test-simple-addon.d.ts +1 -1
  102. package/dist/lib/services/addon-service-test-simple-addon.js.map +1 -1
  103. package/dist/lib/services/addon-service.js +2 -2
  104. package/dist/lib/services/addon-service.js.map +1 -1
  105. package/dist/lib/services/api-token-service.js +2 -2
  106. package/dist/lib/services/api-token-service.js.map +1 -1
  107. package/dist/lib/services/client-metrics/client-metrics-service-v2.js +5 -6
  108. package/dist/lib/services/client-metrics/client-metrics-service-v2.js.map +1 -1
  109. package/dist/lib/services/client-metrics/client-metrics.test.js +74 -41
  110. package/dist/lib/services/client-metrics/client-metrics.test.js.map +1 -1
  111. package/dist/lib/services/client-metrics/index.d.ts +6 -5
  112. package/dist/lib/services/client-metrics/index.js +13 -10
  113. package/dist/lib/services/client-metrics/index.js.map +1 -1
  114. package/dist/lib/services/client-metrics/list.d.ts +19 -16
  115. package/dist/lib/services/client-metrics/list.js +6 -4
  116. package/dist/lib/services/client-metrics/list.js.map +1 -1
  117. package/dist/lib/services/client-metrics/list.test.js +9 -5
  118. package/dist/lib/services/client-metrics/list.test.js.map +1 -1
  119. package/dist/lib/services/client-metrics/ttl-list.d.ts +20 -13
  120. package/dist/lib/services/client-metrics/ttl-list.js +26 -15
  121. package/dist/lib/services/client-metrics/ttl-list.js.map +1 -1
  122. package/dist/lib/services/client-metrics/ttl-list.test.js +33 -9
  123. package/dist/lib/services/client-metrics/ttl-list.test.js.map +1 -1
  124. package/dist/lib/services/environment-service.d.ts +2 -1
  125. package/dist/lib/services/environment-service.js +10 -3
  126. package/dist/lib/services/environment-service.js.map +1 -1
  127. package/dist/lib/services/event-service.d.ts +1 -1
  128. package/dist/lib/services/event-service.js +1 -1
  129. package/dist/lib/services/event-service.js.map +1 -1
  130. package/dist/lib/services/feature-tag-service.js +7 -11
  131. package/dist/lib/services/feature-tag-service.js.map +1 -1
  132. package/dist/lib/services/{feature-toggle-service-v2.d.ts → feature-toggle-service.d.ts} +39 -20
  133. package/dist/lib/services/{feature-toggle-service-v2.js → feature-toggle-service.js} +233 -146
  134. package/dist/lib/services/feature-toggle-service.js.map +1 -0
  135. package/dist/lib/services/index.js +6 -2
  136. package/dist/lib/services/index.js.map +1 -1
  137. package/dist/lib/services/project-health-service.d.ts +2 -2
  138. package/dist/lib/services/project-health-service.js +4 -3
  139. package/dist/lib/services/project-health-service.js.map +1 -1
  140. package/dist/lib/services/project-service.d.ts +2 -3
  141. package/dist/lib/services/project-service.js +12 -18
  142. package/dist/lib/services/project-service.js.map +1 -1
  143. package/dist/lib/services/reset-token-service.js +2 -2
  144. package/dist/lib/services/reset-token-service.js.map +1 -1
  145. package/dist/lib/services/state-util.js +1 -1
  146. package/dist/lib/services/state-util.js.map +1 -1
  147. package/dist/lib/services/user-service.d.ts +3 -2
  148. package/dist/lib/services/user-service.js +37 -21
  149. package/dist/lib/services/user-service.js.map +1 -1
  150. package/dist/lib/services/user-splash-service.d.ts +12 -0
  151. package/dist/lib/services/user-splash-service.js +33 -0
  152. package/dist/lib/services/user-splash-service.js.map +1 -0
  153. package/dist/lib/services/version-service.js +2 -2
  154. package/dist/lib/services/version-service.js.map +1 -1
  155. package/dist/lib/types/events.d.ts +165 -2
  156. package/dist/lib/types/events.js +124 -4
  157. package/dist/lib/types/events.js.map +1 -1
  158. package/dist/lib/types/model.d.ts +4 -12
  159. package/dist/lib/types/model.js +6 -1
  160. package/dist/lib/types/model.js.map +1 -1
  161. package/dist/lib/types/services.d.ts +5 -2
  162. package/dist/lib/types/stores/event-store.d.ts +4 -3
  163. package/dist/lib/types/stores/feature-toggle-store.d.ts +3 -1
  164. package/dist/lib/types/stores/user-splash-store.d.ts +15 -0
  165. package/dist/lib/types/stores/user-splash-store.js +3 -0
  166. package/dist/lib/types/stores/user-splash-store.js.map +1 -0
  167. package/dist/lib/types/stores.d.ts +2 -0
  168. package/dist/lib/util/constants.d.ts +0 -2
  169. package/dist/lib/util/constants.js +1 -3
  170. package/dist/lib/util/constants.js.map +1 -1
  171. package/dist/migrations/20211105104316-add-feature-name-column-to-events.d.ts +2 -0
  172. package/dist/migrations/20211105104316-add-feature-name-column-to-events.js +16 -0
  173. package/dist/migrations/20211105104316-add-feature-name-column-to-events.js.map +1 -0
  174. package/dist/migrations/20211105105509-add-predata-column-to-events.d.ts +2 -0
  175. package/dist/migrations/20211105105509-add-predata-column-to-events.js +8 -0
  176. package/dist/migrations/20211105105509-add-predata-column-to-events.js.map +1 -0
  177. package/dist/migrations/20211108130333-create-user-splash-table.d.ts +2 -0
  178. package/dist/migrations/20211108130333-create-user-splash-table.js +18 -0
  179. package/dist/migrations/20211108130333-create-user-splash-table.js.map +1 -0
  180. package/dist/migrations/20211109103930-add-splash-entry-for-users.d.ts +2 -0
  181. package/dist/migrations/20211109103930-add-splash-entry-for-users.js +7 -0
  182. package/dist/migrations/20211109103930-add-splash-entry-for-users.js.map +1 -0
  183. package/dist/migrations/20211126112551-disable-default-environment.d.ts +2 -0
  184. package/dist/migrations/20211126112551-disable-default-environment.js +24 -0
  185. package/dist/migrations/20211126112551-disable-default-environment.js.map +1 -0
  186. package/dist/migrator.js +9 -2
  187. package/dist/migrator.js.map +1 -1
  188. package/dist/test/e2e/api/admin/client-metrics.e2e.test.js +18 -14
  189. package/dist/test/e2e/api/admin/client-metrics.e2e.test.js.map +1 -1
  190. package/dist/test/e2e/api/admin/feature-archive.e2e.test.js +1 -2
  191. package/dist/test/e2e/api/admin/feature-archive.e2e.test.js.map +1 -1
  192. package/dist/test/e2e/api/admin/feature.e2e.test.js +43 -20
  193. package/dist/test/e2e/api/admin/feature.e2e.test.js.map +1 -1
  194. package/dist/test/e2e/api/admin/metrics.e2e.test.js +5 -3
  195. package/dist/test/e2e/api/admin/metrics.e2e.test.js.map +1 -1
  196. package/dist/test/e2e/api/admin/project/environments.e2e.test.js +10 -0
  197. package/dist/test/e2e/api/admin/project/environments.e2e.test.js.map +1 -1
  198. package/dist/test/e2e/api/admin/project/features.e2e.test.js +145 -8
  199. package/dist/test/e2e/api/admin/project/features.e2e.test.js.map +1 -1
  200. package/dist/test/e2e/api/admin/project/project.health.e2e.test.js +60 -0
  201. package/dist/test/e2e/api/admin/project/project.health.e2e.test.js.map +1 -1
  202. package/dist/{lib/event-differ.test.d.ts → test/e2e/api/admin/project/variants.e2e.test.d.ts} +0 -0
  203. package/dist/test/e2e/api/admin/project/variants.e2e.test.js +727 -0
  204. package/dist/test/e2e/api/admin/project/variants.e2e.test.js.map +1 -0
  205. package/dist/test/e2e/api/admin/splash.e2e.test.d.ts +1 -0
  206. package/dist/test/e2e/api/admin/splash.e2e.test.js +51 -0
  207. package/dist/test/e2e/api/admin/splash.e2e.test.js.map +1 -0
  208. package/dist/test/e2e/api/admin/state.e2e.test.js +17 -13
  209. package/dist/test/e2e/api/admin/state.e2e.test.js.map +1 -1
  210. package/dist/test/e2e/api/admin/user-admin.e2e.test.js +2 -16
  211. package/dist/test/e2e/api/admin/user-admin.e2e.test.js.map +1 -1
  212. package/dist/test/e2e/api/client/feature.env.disabled.e2e.test.js +8 -4
  213. package/dist/test/e2e/api/client/feature.env.disabled.e2e.test.js.map +1 -1
  214. package/dist/test/e2e/api/client/feature.token.access.e2e.test.js +4 -4
  215. package/dist/test/e2e/api/client/feature.token.access.e2e.test.js.map +1 -1
  216. package/dist/test/e2e/api/client/register.e2e.test.js +1 -1
  217. package/dist/test/e2e/api/client/register.e2e.test.js.map +1 -1
  218. package/dist/test/e2e/services/addon-service.e2e.test.js +1 -1
  219. package/dist/test/e2e/services/addon-service.e2e.test.js.map +1 -1
  220. package/dist/test/e2e/services/api-token-service.e2e.test.js +5 -3
  221. package/dist/test/e2e/services/api-token-service.e2e.test.js.map +1 -1
  222. package/dist/test/e2e/services/client-metrics-service.e2e.test.js +5 -2
  223. package/dist/test/e2e/services/client-metrics-service.e2e.test.js.map +1 -1
  224. package/dist/test/e2e/services/environment-service.test.js +1 -1
  225. package/dist/test/e2e/services/environment-service.test.js.map +1 -1
  226. package/dist/test/e2e/services/feature-toggle-service-v2.e2e.test.js +14 -11
  227. package/dist/test/e2e/services/feature-toggle-service-v2.e2e.test.js.map +1 -1
  228. package/dist/test/e2e/services/project-health-service.e2e.test.js +2 -2
  229. package/dist/test/e2e/services/project-health-service.e2e.test.js.map +1 -1
  230. package/dist/test/e2e/services/project-service.e2e.test.js +2 -32
  231. package/dist/test/e2e/services/project-service.e2e.test.js.map +1 -1
  232. package/dist/test/e2e/services/session-service.e2e.test.js +5 -4
  233. package/dist/test/e2e/services/session-service.e2e.test.js.map +1 -1
  234. package/dist/test/e2e/services/user-service.e2e.test.js +3 -2
  235. package/dist/test/e2e/services/user-service.e2e.test.js.map +1 -1
  236. package/dist/test/e2e/stores/client-metrics-store-v2.e2e.test.js +4 -6
  237. package/dist/test/e2e/stores/client-metrics-store-v2.e2e.test.js.map +1 -1
  238. package/dist/test/e2e/stores/event-store.e2e.test.js +1 -1
  239. package/dist/test/e2e/stores/event-store.e2e.test.js.map +1 -1
  240. package/dist/test/e2e/stores/feature-toggle-store.e2e.test.js +1 -1
  241. package/dist/test/e2e/stores/feature-toggle-store.e2e.test.js.map +1 -1
  242. package/dist/test/e2e/stores/user-splash-store.e2e.test.d.ts +1 -0
  243. package/dist/test/e2e/stores/user-splash-store.e2e.test.js +74 -0
  244. package/dist/test/e2e/stores/user-splash-store.e2e.test.js.map +1 -0
  245. package/dist/test/fixtures/fake-event-store.d.ts +2 -1
  246. package/dist/test/fixtures/fake-event-store.js +3 -0
  247. package/dist/test/fixtures/fake-event-store.js.map +1 -1
  248. package/dist/test/fixtures/fake-feature-toggle-store.d.ts +3 -1
  249. package/dist/test/fixtures/fake-feature-toggle-store.js +9 -0
  250. package/dist/test/fixtures/fake-feature-toggle-store.js.map +1 -1
  251. package/dist/test/fixtures/fake-user-splash-store.d.ts +12 -0
  252. package/dist/test/fixtures/fake-user-splash-store.js +40 -0
  253. package/dist/test/fixtures/fake-user-splash-store.js.map +1 -0
  254. package/dist/test/fixtures/store.js +2 -0
  255. package/dist/test/fixtures/store.js.map +1 -1
  256. package/docs/api/oas/openapi.yaml +35 -55
  257. package/package.json +17 -18
  258. package/dist/lib/event-differ.d.ts +0 -1
  259. package/dist/lib/event-differ.js +0 -117
  260. package/dist/lib/event-differ.js.map +0 -1
  261. package/dist/lib/event-differ.test.js +0 -142
  262. package/dist/lib/event-differ.test.js.map +0 -1
  263. package/dist/lib/services/feature-toggle-service-v2.js.map +0 -1
@@ -0,0 +1,727 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ const test_helper_1 = require("../../../helpers/test-helper");
26
+ const database_init_1 = __importDefault(require("../../../helpers/database-init"));
27
+ const no_logger_1 = __importDefault(require("../../../../fixtures/no-logger"));
28
+ const jsonpatch = __importStar(require("fast-json-patch"));
29
+ const model_1 = require("../../../../../lib/types/model");
30
+ let app;
31
+ let db;
32
+ beforeAll(async () => {
33
+ db = await (0, database_init_1.default)('project_feature_variants_api_serial', no_logger_1.default);
34
+ app = await (0, test_helper_1.setupApp)(db.stores);
35
+ });
36
+ afterAll(async () => {
37
+ await app.destroy();
38
+ await db.destroy();
39
+ });
40
+ test('Can get variants for a feature', async () => {
41
+ const featureName = 'feature-variants';
42
+ const variantName = 'fancy-variant';
43
+ await db.stores.featureToggleStore.create('default', {
44
+ name: featureName,
45
+ variants: [
46
+ {
47
+ name: variantName,
48
+ stickiness: 'default',
49
+ weight: 1000,
50
+ weightType: model_1.WeightType.VARIABLE,
51
+ },
52
+ ],
53
+ });
54
+ await app.request
55
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
56
+ .expect(200)
57
+ .expect((res) => {
58
+ expect(res.body.version).toBe('1');
59
+ expect(res.body.variants).toHaveLength(1);
60
+ expect(res.body.variants[0].name).toBe(variantName);
61
+ });
62
+ });
63
+ test('Trying to do operations on a non-existing feature yields 404', async () => {
64
+ await app.request
65
+ .get('/api/admin/projects/default/features/non-existing-feature/variants')
66
+ .expect(404);
67
+ const variants = [
68
+ {
69
+ name: 'variant-put-overwrites',
70
+ stickiness: 'default',
71
+ weight: 1000,
72
+ weightType: model_1.WeightType.VARIABLE,
73
+ },
74
+ ];
75
+ await app.request
76
+ .put('/api/admin/projects/default/features/${featureName}/variants')
77
+ .send(variants)
78
+ .expect(404);
79
+ const newVariants = [];
80
+ const observer = jsonpatch.observe(newVariants);
81
+ newVariants.push({
82
+ name: 'variant1',
83
+ stickiness: 'default',
84
+ weight: 700,
85
+ weightType: model_1.WeightType.VARIABLE,
86
+ });
87
+ let patch = jsonpatch.generate(observer);
88
+ await app.request
89
+ .patch('/api/admin/projects/default/features/${featureName}/variants')
90
+ .send(patch)
91
+ .expect(404);
92
+ });
93
+ test('Can patch variants for a feature and get a response of new variant', async () => {
94
+ const featureName = 'feature-variants-patch';
95
+ const variantName = 'fancy-variant-patch';
96
+ const expectedVariantName = 'not-so-cool-variant-name';
97
+ const variants = [
98
+ {
99
+ name: variantName,
100
+ stickiness: 'default',
101
+ weight: 1000,
102
+ weightType: model_1.WeightType.VARIABLE,
103
+ },
104
+ ];
105
+ await db.stores.featureToggleStore.create('default', {
106
+ name: featureName,
107
+ variants,
108
+ });
109
+ const observer = jsonpatch.observe(variants);
110
+ variants[0].name = expectedVariantName;
111
+ const patch = jsonpatch.generate(observer);
112
+ await app.request
113
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
114
+ .send(patch)
115
+ .expect(200)
116
+ .expect((res) => {
117
+ expect(res.body.version).toBe('1');
118
+ expect(res.body.variants).toHaveLength(1);
119
+ expect(res.body.variants[0].name).toBe(expectedVariantName);
120
+ });
121
+ });
122
+ test('Can add variant for a feature', async () => {
123
+ const featureName = 'feature-variants-patch-add';
124
+ const variantName = 'fancy-variant-patch';
125
+ const expectedVariantName = 'not-so-cool-variant-name';
126
+ const variants = [
127
+ {
128
+ name: variantName,
129
+ stickiness: 'default',
130
+ weight: 1000,
131
+ weightType: model_1.WeightType.VARIABLE,
132
+ },
133
+ ];
134
+ await db.stores.featureToggleStore.create('default', {
135
+ name: featureName,
136
+ variants,
137
+ });
138
+ const observer = jsonpatch.observe(variants);
139
+ variants.push({
140
+ name: expectedVariantName,
141
+ stickiness: 'default',
142
+ weight: 1000,
143
+ weightType: model_1.WeightType.VARIABLE,
144
+ });
145
+ const patch = jsonpatch.generate(observer);
146
+ await app.request
147
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
148
+ .send(patch)
149
+ .expect(200);
150
+ await app.request
151
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
152
+ .expect((res) => {
153
+ expect(res.body.version).toBe('1');
154
+ expect(res.body.variants).toHaveLength(2);
155
+ expect(res.body.variants.find((x) => x.name === expectedVariantName)).toBeTruthy();
156
+ expect(res.body.variants.find((x) => x.name === variantName)).toBeTruthy();
157
+ });
158
+ });
159
+ test('Can remove variant for a feature', async () => {
160
+ const featureName = 'feature-variants-patch-remove';
161
+ const variantName = 'fancy-variant-patch';
162
+ const variants = [
163
+ {
164
+ name: variantName,
165
+ stickiness: 'default',
166
+ weight: 1000,
167
+ weightType: model_1.WeightType.VARIABLE,
168
+ },
169
+ ];
170
+ await db.stores.featureToggleStore.create('default', {
171
+ name: featureName,
172
+ variants,
173
+ });
174
+ const observer = jsonpatch.observe(variants);
175
+ variants.pop();
176
+ const patch = jsonpatch.generate(observer);
177
+ await app.request
178
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
179
+ .send(patch)
180
+ .expect(200);
181
+ await app.request
182
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
183
+ .expect((res) => {
184
+ expect(res.body.version).toBe('1');
185
+ expect(res.body.variants).toHaveLength(0);
186
+ });
187
+ });
188
+ test('PUT overwrites current variant on feature', async () => {
189
+ const featureName = 'variant-put-overwrites';
190
+ const variantName = 'overwriting-for-fun';
191
+ const variants = [
192
+ {
193
+ name: variantName,
194
+ stickiness: 'default',
195
+ weight: 1000,
196
+ weightType: model_1.WeightType.VARIABLE,
197
+ },
198
+ ];
199
+ await db.stores.featureToggleStore.create('default', {
200
+ name: featureName,
201
+ variants,
202
+ });
203
+ const newVariants = [
204
+ {
205
+ name: 'variant1',
206
+ stickiness: 'default',
207
+ weight: 250,
208
+ weightType: model_1.WeightType.FIX,
209
+ },
210
+ {
211
+ name: 'variant2',
212
+ stickiness: 'default',
213
+ weight: 375,
214
+ weightType: model_1.WeightType.VARIABLE,
215
+ },
216
+ {
217
+ name: 'variant3',
218
+ stickiness: 'default',
219
+ weight: 450,
220
+ weightType: model_1.WeightType.VARIABLE,
221
+ },
222
+ ];
223
+ await app.request
224
+ .put(`/api/admin/projects/default/features/${featureName}/variants`)
225
+ .send(newVariants)
226
+ .expect(200)
227
+ .expect((res) => {
228
+ expect(res.body.variants).toHaveLength(3);
229
+ });
230
+ await app.request
231
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
232
+ .expect(200)
233
+ .expect((res) => {
234
+ expect(res.body.variants).toHaveLength(3);
235
+ expect(res.body.variants.reduce((a, v) => a + v.weight, 0)).toEqual(1000);
236
+ });
237
+ });
238
+ test('PUTing an invalid variant throws 400 exception', async () => {
239
+ const featureName = 'variants-validation-feature';
240
+ await db.stores.featureToggleStore.create('default', {
241
+ name: featureName,
242
+ });
243
+ const invalidJson = [
244
+ {
245
+ name: 'variant',
246
+ weight: 500,
247
+ weightType: 'party',
248
+ },
249
+ ];
250
+ await app.request
251
+ .put(`/api/admin/projects/default/features/${featureName}/variants`)
252
+ .send(invalidJson)
253
+ .expect(400)
254
+ .expect((res) => {
255
+ expect(res.body.details).toHaveLength(1);
256
+ expect(res.body.details[0].message).toMatch(/.*weightType\" must be one of/);
257
+ });
258
+ });
259
+ test('Invalid variant in PATCH also throws 400 exception', async () => {
260
+ const featureName = 'patch-validation-feature';
261
+ await db.stores.featureToggleStore.create('default', {
262
+ name: featureName,
263
+ });
264
+ const invalidPatch = `[{
265
+ "op": "add",
266
+ "path": "/1",
267
+ "value": {
268
+ "name": "not-so-cool-variant-name",
269
+ "stickiness": "default",
270
+ "weight": 2000,
271
+ "weightType": "variable"
272
+ }
273
+ }]`;
274
+ await app.request
275
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
276
+ .set('Content-Type', 'application/json')
277
+ .send(invalidPatch)
278
+ .expect(400)
279
+ .expect((res) => {
280
+ expect(res.body.details).toHaveLength(1);
281
+ expect(res.body.details[0].message).toMatch(/.*weight\" must be less than or equal to 1000/);
282
+ });
283
+ });
284
+ test('PATCHING with all variable weightTypes forces weights to sum to no less than 1000 minus the number of variable variants', async () => {
285
+ const featureName = 'variants-validation-with-all-variable-weights';
286
+ await db.stores.featureToggleStore.create('default', {
287
+ name: featureName,
288
+ });
289
+ const newVariants = [];
290
+ const observer = jsonpatch.observe(newVariants);
291
+ newVariants.push({
292
+ name: 'variant1',
293
+ stickiness: 'default',
294
+ weight: 700,
295
+ weightType: model_1.WeightType.VARIABLE,
296
+ });
297
+ let patch = jsonpatch.generate(observer);
298
+ await app.request
299
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
300
+ .send(patch)
301
+ .expect(200)
302
+ .expect((res) => {
303
+ expect(res.body.variants).toHaveLength(1);
304
+ expect(res.body.variants[0].weight).toEqual(1000);
305
+ });
306
+ newVariants.push({
307
+ name: 'variant2',
308
+ stickiness: 'default',
309
+ weight: 700,
310
+ weightType: model_1.WeightType.VARIABLE,
311
+ });
312
+ patch = jsonpatch.generate(observer);
313
+ await app.request
314
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
315
+ .send(patch)
316
+ .expect(200)
317
+ .expect((res) => {
318
+ expect(res.body.variants).toHaveLength(2);
319
+ expect(res.body.variants.every((x) => x.weight === 500)).toBeTruthy();
320
+ });
321
+ newVariants.push({
322
+ name: 'variant3',
323
+ stickiness: 'default',
324
+ weight: 700,
325
+ weightType: model_1.WeightType.VARIABLE,
326
+ });
327
+ patch = jsonpatch.generate(observer);
328
+ await app.request
329
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
330
+ .send(patch)
331
+ .expect(200)
332
+ .expect((res) => {
333
+ res.body.variants.sort((v, other) => other.weight - v.weight);
334
+ expect(res.body.variants).toHaveLength(3);
335
+ expect(res.body.variants[0].weight).toBe(334);
336
+ expect(res.body.variants[1].weight).toBe(333);
337
+ expect(res.body.variants[2].weight).toBe(333);
338
+ });
339
+ newVariants.push({
340
+ name: 'variant4',
341
+ stickiness: 'default',
342
+ weight: 700,
343
+ weightType: model_1.WeightType.VARIABLE,
344
+ });
345
+ patch = jsonpatch.generate(observer);
346
+ await app.request
347
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
348
+ .send(patch)
349
+ .expect(200)
350
+ .expect((res) => {
351
+ expect(res.body.variants).toHaveLength(4);
352
+ expect(res.body.variants.every((x) => x.weight === 250)).toBeTruthy();
353
+ });
354
+ });
355
+ test('PATCHING with no variable variants fails with 400', async () => {
356
+ const featureName = 'variants-validation-with-no-variable-weights';
357
+ await db.stores.featureToggleStore.create('default', {
358
+ name: featureName,
359
+ });
360
+ const newVariants = [];
361
+ const observer = jsonpatch.observe(newVariants);
362
+ newVariants.push({
363
+ name: 'variant1',
364
+ stickiness: 'default',
365
+ weight: 900,
366
+ weightType: model_1.WeightType.FIX,
367
+ });
368
+ const patch = jsonpatch.generate(observer);
369
+ await app.request
370
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
371
+ .send(patch)
372
+ .expect(400)
373
+ .expect((res) => {
374
+ expect(res.body.details).toHaveLength(1);
375
+ expect(res.body.details[0].message).toEqual('There must be at least one "variable" variant');
376
+ });
377
+ });
378
+ test('Patching with a fixed variant and variable variants splits remaining weight among variable variants', async () => {
379
+ const featureName = 'variants-fixed-and-variable';
380
+ await db.stores.featureToggleStore.create('default', {
381
+ name: featureName,
382
+ });
383
+ const newVariants = [];
384
+ const observer = jsonpatch.observe(newVariants);
385
+ newVariants.push({
386
+ name: 'variant1',
387
+ stickiness: 'default',
388
+ weight: 900,
389
+ weightType: model_1.WeightType.FIX,
390
+ });
391
+ newVariants.push({
392
+ name: 'variant2',
393
+ stickiness: 'default',
394
+ weight: 20,
395
+ weightType: model_1.WeightType.VARIABLE,
396
+ });
397
+ newVariants.push({
398
+ name: 'variant3',
399
+ stickiness: 'default',
400
+ weight: 123,
401
+ weightType: model_1.WeightType.VARIABLE,
402
+ });
403
+ newVariants.push({
404
+ name: 'variant4',
405
+ stickiness: 'default',
406
+ weight: 123,
407
+ weightType: model_1.WeightType.VARIABLE,
408
+ });
409
+ newVariants.push({
410
+ name: 'variant5',
411
+ stickiness: 'default',
412
+ weight: 123,
413
+ weightType: model_1.WeightType.VARIABLE,
414
+ });
415
+ newVariants.push({
416
+ name: 'variant6',
417
+ stickiness: 'default',
418
+ weight: 123,
419
+ weightType: model_1.WeightType.VARIABLE,
420
+ });
421
+ newVariants.push({
422
+ name: 'variant7',
423
+ stickiness: 'default',
424
+ weight: 123,
425
+ weightType: model_1.WeightType.VARIABLE,
426
+ });
427
+ const patch = jsonpatch.generate(observer);
428
+ await app.request
429
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
430
+ .send(patch)
431
+ .expect(200);
432
+ await app.request
433
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
434
+ .expect(200)
435
+ .expect((res) => {
436
+ let body = res.body;
437
+ expect(body.variants).toHaveLength(7);
438
+ expect(body.variants.reduce((total, v) => total + v.weight, 0)).toEqual(1000);
439
+ body.variants.sort((a, b) => b.weight - a.weight);
440
+ expect(body.variants.find((v) => v.name === 'variant1').weight).toEqual(900);
441
+ expect(body.variants.find((v) => v.name === 'variant2').weight).toEqual(17);
442
+ expect(body.variants.find((v) => v.name === 'variant3').weight).toEqual(17);
443
+ expect(body.variants.find((v) => v.name === 'variant4').weight).toEqual(17);
444
+ expect(body.variants.find((v) => v.name === 'variant5').weight).toEqual(17);
445
+ expect(body.variants.find((v) => v.name === 'variant6').weight).toEqual(16);
446
+ expect(body.variants.find((v) => v.name === 'variant7').weight).toEqual(16);
447
+ });
448
+ });
449
+ test('Multiple fixed variants gets added together to decide how much weight variable variants should get', async () => {
450
+ const featureName = 'variants-multiple-fixed-and-variable';
451
+ await db.stores.featureToggleStore.create('default', {
452
+ name: featureName,
453
+ });
454
+ const newVariants = [];
455
+ const observer = jsonpatch.observe(newVariants);
456
+ newVariants.push({
457
+ name: 'variant1',
458
+ stickiness: 'default',
459
+ weight: 600,
460
+ weightType: model_1.WeightType.FIX,
461
+ });
462
+ newVariants.push({
463
+ name: 'variant2',
464
+ stickiness: 'default',
465
+ weight: 350,
466
+ weightType: model_1.WeightType.FIX,
467
+ });
468
+ newVariants.push({
469
+ name: 'variant3',
470
+ stickiness: 'default',
471
+ weight: 350,
472
+ weightType: model_1.WeightType.VARIABLE,
473
+ });
474
+ const patch = jsonpatch.generate(observer);
475
+ await app.request
476
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
477
+ .send(patch)
478
+ .expect(200);
479
+ await app.request
480
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
481
+ .expect(200)
482
+ .expect((res) => {
483
+ let body = res.body;
484
+ expect(body.variants).toHaveLength(3);
485
+ expect(body.variants.find((v) => v.name === 'variant3').weight).toEqual(50);
486
+ });
487
+ });
488
+ test('If sum of fixed variant weight exceed 1000 fails with 400', async () => {
489
+ const featureName = 'variants-fixed-weight-over-1000';
490
+ await db.stores.featureToggleStore.create('default', {
491
+ name: featureName,
492
+ });
493
+ const newVariants = [];
494
+ const observer = jsonpatch.observe(newVariants);
495
+ newVariants.push({
496
+ name: 'variant1',
497
+ stickiness: 'default',
498
+ weight: 900,
499
+ weightType: model_1.WeightType.FIX,
500
+ });
501
+ newVariants.push({
502
+ name: 'variant2',
503
+ stickiness: 'default',
504
+ weight: 900,
505
+ weightType: model_1.WeightType.FIX,
506
+ });
507
+ newVariants.push({
508
+ name: 'variant3',
509
+ stickiness: 'default',
510
+ weight: 350,
511
+ weightType: model_1.WeightType.VARIABLE,
512
+ });
513
+ const patch = jsonpatch.generate(observer);
514
+ await app.request
515
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
516
+ .send(patch)
517
+ .expect(400)
518
+ .expect((res) => {
519
+ expect(res.body.details).toHaveLength(1);
520
+ expect(res.body.details[0].message).toEqual('The traffic distribution total must equal 100%');
521
+ });
522
+ });
523
+ test('If sum of fixed variant weight equals 1000 variable variants gets weight 0', async () => {
524
+ const featureName = 'variants-fixed-weight-equals-1000-no-variable-weight';
525
+ await db.stores.featureToggleStore.create('default', {
526
+ name: featureName,
527
+ });
528
+ const newVariants = [];
529
+ const observer = jsonpatch.observe(newVariants);
530
+ newVariants.push({
531
+ name: 'variant1',
532
+ stickiness: 'default',
533
+ weight: 900,
534
+ weightType: model_1.WeightType.FIX,
535
+ });
536
+ newVariants.push({
537
+ name: 'variant2',
538
+ stickiness: 'default',
539
+ weight: 100,
540
+ weightType: model_1.WeightType.FIX,
541
+ });
542
+ newVariants.push({
543
+ name: 'variant3',
544
+ stickiness: 'default',
545
+ weight: 350,
546
+ weightType: model_1.WeightType.VARIABLE,
547
+ });
548
+ newVariants.push({
549
+ name: 'variant4',
550
+ stickiness: 'default',
551
+ weight: 350,
552
+ weightType: model_1.WeightType.VARIABLE,
553
+ });
554
+ const patch = jsonpatch.generate(observer);
555
+ await app.request
556
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
557
+ .send(patch)
558
+ .expect(200);
559
+ await app.request
560
+ .get(`/api/admin/projects/default/features/${featureName}/variants`)
561
+ .expect(200)
562
+ .expect((res) => {
563
+ let body = res.body;
564
+ expect(body.variants).toHaveLength(4);
565
+ expect(body.variants.find((v) => v.name === 'variant3').weight).toEqual(0);
566
+ expect(body.variants.find((v) => v.name === 'variant4').weight).toEqual(0);
567
+ });
568
+ });
569
+ test('PATCH endpoint validates uniqueness of variant names', async () => {
570
+ const featureName = 'variants-uniqueness-names';
571
+ await db.stores.featureToggleStore.create('default', {
572
+ name: featureName,
573
+ variants: [
574
+ {
575
+ name: 'variant1',
576
+ weight: 1000,
577
+ weightType: model_1.WeightType.VARIABLE,
578
+ stickiness: 'default',
579
+ },
580
+ ],
581
+ });
582
+ const newVariants = [];
583
+ const observer = jsonpatch.observe(newVariants);
584
+ newVariants.push({
585
+ name: 'variant1',
586
+ weight: 550,
587
+ weightType: model_1.WeightType.VARIABLE,
588
+ stickiness: 'default',
589
+ });
590
+ newVariants.push({
591
+ name: 'variant2',
592
+ weight: 550,
593
+ weightType: model_1.WeightType.VARIABLE,
594
+ stickiness: 'default',
595
+ });
596
+ const patch = jsonpatch.generate(observer);
597
+ await app.request
598
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
599
+ .send(patch)
600
+ .expect(400)
601
+ .expect((res) => {
602
+ expect(res.body.details[0].message).toMatch(/contains a duplicate value/);
603
+ });
604
+ });
605
+ test('PUT endpoint validates uniqueness of variant names', async () => {
606
+ const featureName = 'variants-put-uniqueness-names';
607
+ await db.stores.featureToggleStore.create('default', {
608
+ name: featureName,
609
+ variants: [],
610
+ });
611
+ await app.request
612
+ .put(`/api/admin/projects/default/features/${featureName}/variants`)
613
+ .send([
614
+ {
615
+ name: 'variant1',
616
+ weightType: 'variable',
617
+ weight: 500,
618
+ stickiness: 'default',
619
+ },
620
+ {
621
+ name: 'variant1',
622
+ weightType: 'variable',
623
+ weight: 500,
624
+ stickiness: 'default',
625
+ },
626
+ ])
627
+ .expect(400)
628
+ .expect((res) => {
629
+ expect(res.body.details[0].message).toMatch(/contains a duplicate value/);
630
+ });
631
+ });
632
+ test('Variants should be sorted by their name when PUT', async () => {
633
+ const featureName = 'variants-sort-by-name';
634
+ await db.stores.featureToggleStore.create('default', {
635
+ name: featureName,
636
+ });
637
+ await app.request
638
+ .put(`/api/admin/projects/default/features/${featureName}/variants`)
639
+ .send([
640
+ {
641
+ name: 'zvariant',
642
+ weightType: 'variable',
643
+ weight: 500,
644
+ stickiness: 'default',
645
+ },
646
+ {
647
+ name: 'variant-a',
648
+ weightType: 'variable',
649
+ weight: 500,
650
+ stickiness: 'default',
651
+ },
652
+ {
653
+ name: 'g-variant',
654
+ weightType: 'variable',
655
+ weight: 500,
656
+ stickiness: 'default',
657
+ },
658
+ {
659
+ name: 'variant-g',
660
+ weightType: 'variable',
661
+ weight: 500,
662
+ stickiness: 'default',
663
+ },
664
+ ])
665
+ .expect(200)
666
+ .expect((res) => {
667
+ expect(res.body.variants[0].name).toBe('g-variant');
668
+ expect(res.body.variants[1].name).toBe('variant-a');
669
+ expect(res.body.variants[2].name).toBe('variant-g');
670
+ expect(res.body.variants[3].name).toBe('zvariant');
671
+ });
672
+ });
673
+ test('Variants should be sorted by name when PATCHed as well', async () => {
674
+ const featureName = 'variants-patch-sort-by-name';
675
+ await db.stores.featureToggleStore.create('default', {
676
+ name: featureName,
677
+ });
678
+ const variants = [];
679
+ const observer = jsonpatch.observe(variants);
680
+ variants.push({
681
+ name: 'g-variant',
682
+ weightType: 'variable',
683
+ weight: 500,
684
+ stickiness: 'default',
685
+ });
686
+ variants.push({
687
+ name: 'a-variant',
688
+ weightType: 'variable',
689
+ weight: 500,
690
+ stickiness: 'default',
691
+ });
692
+ const patch = jsonpatch.generate(observer);
693
+ await app.request
694
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
695
+ .send(patch)
696
+ .expect(200)
697
+ .expect((res) => {
698
+ expect(res.body.variants[0].name).toBe('a-variant');
699
+ expect(res.body.variants[1].name).toBe('g-variant');
700
+ });
701
+ variants.push({
702
+ name: '00-variant',
703
+ weightType: 'variable',
704
+ weight: 500,
705
+ stickiness: 'default',
706
+ });
707
+ variants.push({
708
+ name: 'z-variant',
709
+ weightType: 'variable',
710
+ weight: 500,
711
+ stickiness: 'default',
712
+ });
713
+ const secondPatch = jsonpatch.generate(observer);
714
+ expect(secondPatch).toHaveLength(2);
715
+ await app.request
716
+ .patch(`/api/admin/projects/default/features/${featureName}/variants`)
717
+ .send(secondPatch)
718
+ .expect(200)
719
+ .expect((res) => {
720
+ expect(res.body.variants).toHaveLength(4);
721
+ expect(res.body.variants[0].name).toBe('00-variant');
722
+ expect(res.body.variants[1].name).toBe('a-variant');
723
+ expect(res.body.variants[2].name).toBe('g-variant');
724
+ expect(res.body.variants[3].name).toBe('z-variant');
725
+ });
726
+ });
727
+ //# sourceMappingURL=variants.e2e.test.js.map