unleash-server 4.2.2 → 4.3.0-beta.3

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 (186) 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 +5 -15
  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-metrics-store-v2.js +5 -1
  29. package/dist/lib/db/client-metrics-store-v2.js.map +1 -1
  30. package/dist/lib/db/event-store.d.ts +10 -6
  31. package/dist/lib/db/event-store.js +21 -1
  32. package/dist/lib/db/event-store.js.map +1 -1
  33. package/dist/lib/db/feature-strategy-store.js +1 -0
  34. package/dist/lib/db/feature-strategy-store.js.map +1 -1
  35. package/dist/lib/db/feature-toggle-store.d.ts +4 -1
  36. package/dist/lib/db/feature-toggle-store.js +27 -2
  37. package/dist/lib/db/feature-toggle-store.js.map +1 -1
  38. package/dist/lib/db/index.js +2 -0
  39. package/dist/lib/db/index.js.map +1 -1
  40. package/dist/lib/db/user-splash-store.d.ts +19 -0
  41. package/dist/lib/db/user-splash-store.js +70 -0
  42. package/dist/lib/db/user-splash-store.js.map +1 -0
  43. package/dist/lib/error/operation-denied-error.d.ts +4 -0
  44. package/dist/lib/error/operation-denied-error.js +24 -0
  45. package/dist/lib/error/operation-denied-error.js.map +1 -0
  46. package/dist/lib/metrics.js +17 -8
  47. package/dist/lib/metrics.js.map +1 -1
  48. package/dist/lib/metrics.test.js +1 -0
  49. package/dist/lib/metrics.test.js.map +1 -1
  50. package/dist/lib/routes/admin-api/event.d.ts +8 -4
  51. package/dist/lib/routes/admin-api/event.js +8 -10
  52. package/dist/lib/routes/admin-api/event.js.map +1 -1
  53. package/dist/lib/routes/admin-api/feature.js +1 -1
  54. package/dist/lib/routes/admin-api/feature.js.map +1 -1
  55. package/dist/lib/routes/admin-api/index.js +2 -0
  56. package/dist/lib/routes/admin-api/index.js.map +1 -1
  57. package/dist/lib/routes/admin-api/project/index.js +2 -0
  58. package/dist/lib/routes/admin-api/project/index.js.map +1 -1
  59. package/dist/lib/routes/admin-api/project/variants.d.ts +21 -0
  60. package/dist/lib/routes/admin-api/project/variants.js +41 -0
  61. package/dist/lib/routes/admin-api/project/variants.js.map +1 -0
  62. package/dist/lib/routes/admin-api/state.js +0 -1
  63. package/dist/lib/routes/admin-api/state.js.map +1 -1
  64. package/dist/lib/routes/admin-api/user-splash-controller.d.ts +10 -0
  65. package/dist/lib/routes/admin-api/user-splash-controller.js +28 -0
  66. package/dist/lib/routes/admin-api/user-splash-controller.js.map +1 -0
  67. package/dist/lib/routes/admin-api/user.d.ts +2 -1
  68. package/dist/lib/routes/admin-api/user.js +7 -2
  69. package/dist/lib/routes/admin-api/user.js.map +1 -1
  70. package/dist/lib/routes/client-api/metrics.d.ts +0 -1
  71. package/dist/lib/routes/client-api/metrics.js +2 -10
  72. package/dist/lib/routes/client-api/metrics.js.map +1 -1
  73. package/dist/lib/routes/util.js +18 -16
  74. package/dist/lib/routes/util.js.map +1 -1
  75. package/dist/lib/schema/feature-schema.d.ts +1 -0
  76. package/dist/lib/schema/feature-schema.js +6 -7
  77. package/dist/lib/schema/feature-schema.js.map +1 -1
  78. package/dist/lib/services/addon-service-test-simple-addon.d.ts +1 -1
  79. package/dist/lib/services/addon-service-test-simple-addon.js.map +1 -1
  80. package/dist/lib/services/client-metrics/client-metrics-service-v2.js +3 -3
  81. package/dist/lib/services/client-metrics/client-metrics-service-v2.js.map +1 -1
  82. package/dist/lib/services/client-metrics/index.d.ts +6 -5
  83. package/dist/lib/services/client-metrics/index.js +6 -3
  84. package/dist/lib/services/client-metrics/index.js.map +1 -1
  85. package/dist/lib/services/client-metrics/list.d.ts +19 -16
  86. package/dist/lib/services/client-metrics/list.js +6 -4
  87. package/dist/lib/services/client-metrics/list.js.map +1 -1
  88. package/dist/lib/services/client-metrics/list.test.js +9 -5
  89. package/dist/lib/services/client-metrics/list.test.js.map +1 -1
  90. package/dist/lib/services/client-metrics/ttl-list.d.ts +20 -14
  91. package/dist/lib/services/client-metrics/ttl-list.js +17 -12
  92. package/dist/lib/services/client-metrics/ttl-list.js.map +1 -1
  93. package/dist/lib/services/client-metrics/ttl-list.test.js +14 -10
  94. package/dist/lib/services/client-metrics/ttl-list.test.js.map +1 -1
  95. package/dist/lib/services/event-service.d.ts +1 -1
  96. package/dist/lib/services/event-service.js +1 -1
  97. package/dist/lib/services/event-service.js.map +1 -1
  98. package/dist/lib/services/feature-tag-service.js +7 -11
  99. package/dist/lib/services/feature-tag-service.js.map +1 -1
  100. package/dist/lib/services/feature-toggle-service.d.ts +25 -15
  101. package/dist/lib/services/feature-toggle-service.js +198 -136
  102. package/dist/lib/services/feature-toggle-service.js.map +1 -1
  103. package/dist/lib/services/index.js +3 -0
  104. package/dist/lib/services/index.js.map +1 -1
  105. package/dist/lib/services/project-health-service.d.ts +2 -2
  106. package/dist/lib/services/project-health-service.js +1 -0
  107. package/dist/lib/services/project-health-service.js.map +1 -1
  108. package/dist/lib/services/project-service.d.ts +2 -3
  109. package/dist/lib/services/project-service.js +12 -18
  110. package/dist/lib/services/project-service.js.map +1 -1
  111. package/dist/lib/services/user-service.d.ts +3 -2
  112. package/dist/lib/services/user-service.js +37 -21
  113. package/dist/lib/services/user-service.js.map +1 -1
  114. package/dist/lib/services/user-splash-service.d.ts +12 -0
  115. package/dist/lib/services/user-splash-service.js +33 -0
  116. package/dist/lib/services/user-splash-service.js.map +1 -0
  117. package/dist/lib/types/events.d.ts +165 -2
  118. package/dist/lib/types/events.js +124 -4
  119. package/dist/lib/types/events.js.map +1 -1
  120. package/dist/lib/types/model.d.ts +4 -12
  121. package/dist/lib/types/model.js +6 -1
  122. package/dist/lib/types/model.js.map +1 -1
  123. package/dist/lib/types/services.d.ts +2 -0
  124. package/dist/lib/types/stores/event-store.d.ts +4 -3
  125. package/dist/lib/types/stores/feature-toggle-store.d.ts +3 -1
  126. package/dist/lib/types/stores/user-splash-store.d.ts +15 -0
  127. package/dist/lib/types/stores/user-splash-store.js +3 -0
  128. package/dist/lib/types/stores/user-splash-store.js.map +1 -0
  129. package/dist/lib/types/stores.d.ts +2 -0
  130. package/dist/migrations/20211105104316-add-feature-name-column-to-events.d.ts +2 -0
  131. package/dist/migrations/20211105104316-add-feature-name-column-to-events.js +16 -0
  132. package/dist/migrations/20211105104316-add-feature-name-column-to-events.js.map +1 -0
  133. package/dist/migrations/20211105105509-add-predata-column-to-events.d.ts +2 -0
  134. package/dist/migrations/20211105105509-add-predata-column-to-events.js +8 -0
  135. package/dist/migrations/20211105105509-add-predata-column-to-events.js.map +1 -0
  136. package/dist/migrations/20211108130333-create-user-splash-table.d.ts +2 -0
  137. package/dist/migrations/20211108130333-create-user-splash-table.js +18 -0
  138. package/dist/migrations/20211108130333-create-user-splash-table.js.map +1 -0
  139. package/dist/migrations/20211109103930-add-splash-entry-for-users.d.ts +2 -0
  140. package/dist/migrations/20211109103930-add-splash-entry-for-users.js +7 -0
  141. package/dist/migrations/20211109103930-add-splash-entry-for-users.js.map +1 -0
  142. package/dist/migrations/20211126112551-disable-default-environment.d.ts +2 -0
  143. package/dist/migrations/20211126112551-disable-default-environment.js +24 -0
  144. package/dist/migrations/20211126112551-disable-default-environment.js.map +1 -0
  145. package/dist/test/e2e/api/admin/client-metrics.e2e.test.js +18 -14
  146. package/dist/test/e2e/api/admin/client-metrics.e2e.test.js.map +1 -1
  147. package/dist/test/e2e/api/admin/feature.e2e.test.js +24 -18
  148. package/dist/test/e2e/api/admin/feature.e2e.test.js.map +1 -1
  149. package/dist/test/e2e/api/admin/project/features.e2e.test.js +31 -8
  150. package/dist/test/e2e/api/admin/project/features.e2e.test.js.map +1 -1
  151. package/dist/test/e2e/api/admin/project/project.health.e2e.test.js +60 -0
  152. package/dist/test/e2e/api/admin/project/project.health.e2e.test.js.map +1 -1
  153. package/dist/{lib/event-differ.test.d.ts → test/e2e/api/admin/project/variants.e2e.test.d.ts} +0 -0
  154. package/dist/test/e2e/api/admin/project/variants.e2e.test.js +727 -0
  155. package/dist/test/e2e/api/admin/project/variants.e2e.test.js.map +1 -0
  156. package/dist/test/e2e/api/admin/splash.e2e.test.d.ts +1 -0
  157. package/dist/test/e2e/api/admin/splash.e2e.test.js +51 -0
  158. package/dist/test/e2e/api/admin/splash.e2e.test.js.map +1 -0
  159. package/dist/test/e2e/api/admin/user-admin.e2e.test.js +2 -16
  160. package/dist/test/e2e/api/admin/user-admin.e2e.test.js.map +1 -1
  161. package/dist/test/e2e/services/feature-toggle-service-v2.e2e.test.js.map +1 -1
  162. package/dist/test/e2e/services/project-health-service.e2e.test.js.map +1 -1
  163. package/dist/test/e2e/services/project-service.e2e.test.js +0 -30
  164. package/dist/test/e2e/services/project-service.e2e.test.js.map +1 -1
  165. package/dist/test/e2e/stores/event-store.e2e.test.js.map +1 -1
  166. package/dist/test/e2e/stores/user-splash-store.e2e.test.d.ts +1 -0
  167. package/dist/test/e2e/stores/user-splash-store.e2e.test.js +74 -0
  168. package/dist/test/e2e/stores/user-splash-store.e2e.test.js.map +1 -0
  169. package/dist/test/fixtures/fake-event-store.d.ts +2 -1
  170. package/dist/test/fixtures/fake-event-store.js +3 -0
  171. package/dist/test/fixtures/fake-event-store.js.map +1 -1
  172. package/dist/test/fixtures/fake-feature-toggle-store.d.ts +3 -1
  173. package/dist/test/fixtures/fake-feature-toggle-store.js +9 -0
  174. package/dist/test/fixtures/fake-feature-toggle-store.js.map +1 -1
  175. package/dist/test/fixtures/fake-user-splash-store.d.ts +12 -0
  176. package/dist/test/fixtures/fake-user-splash-store.js +40 -0
  177. package/dist/test/fixtures/fake-user-splash-store.js.map +1 -0
  178. package/dist/test/fixtures/store.js +2 -0
  179. package/dist/test/fixtures/store.js.map +1 -1
  180. package/docs/api/oas/openapi.yaml +35 -55
  181. package/package.json +16 -17
  182. package/dist/lib/event-differ.d.ts +0 -1
  183. package/dist/lib/event-differ.js +0 -117
  184. package/dist/lib/event-differ.js.map +0 -1
  185. package/dist/lib/event-differ.test.js +0 -142
  186. package/dist/lib/event-differ.test.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