@terreno/feature-flags 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bunfig.toml +1 -1
- package/package.json +3 -2
- package/src/tests/bunSetup.ts +6 -33
- package/src/tests/featureFlagsApp.liveUpdates.test.ts +1 -1
- package/src/tests/featureFlagsApp.test.ts +1 -1
- package/src/tests/openFeatureProvider.test.ts +1 -1
- package/tsconfig.json +1 -1
- package/dist/tests/bunSetup.d.ts +0 -1
- package/dist/tests/bunSetup.js +0 -35
- package/dist/tests/evaluate.test.d.ts +0 -1
- package/dist/tests/evaluate.test.js +0 -594
- package/dist/tests/featureFlagModel.test.d.ts +0 -1
- package/dist/tests/featureFlagModel.test.js +0 -428
- package/dist/tests/featureFlagsApp.liveUpdates.test.d.ts +0 -1
- package/dist/tests/featureFlagsApp.liveUpdates.test.js +0 -203
- package/dist/tests/featureFlagsApp.test.d.ts +0 -1
- package/dist/tests/featureFlagsApp.test.js +0 -540
- package/dist/tests/openFeatureProvider.test.d.ts +0 -1
- package/dist/tests/openFeatureProvider.test.js +0 -395
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
24
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
-
function step(op) {
|
|
27
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
-
switch (op[0]) {
|
|
32
|
-
case 0: case 1: t = op; break;
|
|
33
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
-
default:
|
|
37
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
-
if (t[2]) _.ops.pop();
|
|
42
|
-
_.trys.pop(); continue;
|
|
43
|
-
}
|
|
44
|
-
op = body.call(thisArg, _);
|
|
45
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
-
var bun_test_1 = require("bun:test");
|
|
51
|
-
var evaluate_1 = require("../evaluate");
|
|
52
|
-
var createFlag = function (overrides) {
|
|
53
|
-
if (overrides === void 0) { overrides = {}; }
|
|
54
|
-
return __assign({ archived: false, description: "Test flag", enabled: true, key: "test-flag", name: "Test Flag", rolloutPercentage: 100, rules: [], type: "boolean", variants: [] }, overrides);
|
|
55
|
-
};
|
|
56
|
-
var noSegments = {};
|
|
57
|
-
(0, bun_test_1.describe)("deterministicHash", function () {
|
|
58
|
-
(0, bun_test_1.it)("returns a number between 0 and 99", function () {
|
|
59
|
-
for (var i = 0; i < 100; i++) {
|
|
60
|
-
var hash = (0, evaluate_1.deterministicHash)("user-".concat(i, "flag-key"));
|
|
61
|
-
(0, bun_test_1.expect)(hash).toBeGreaterThanOrEqual(0);
|
|
62
|
-
(0, bun_test_1.expect)(hash).toBeLessThan(100);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
(0, bun_test_1.it)("returns the same value for the same input", function () {
|
|
66
|
-
var input = "user123test-flag";
|
|
67
|
-
var hash1 = (0, evaluate_1.deterministicHash)(input);
|
|
68
|
-
var hash2 = (0, evaluate_1.deterministicHash)(input);
|
|
69
|
-
(0, bun_test_1.expect)(hash1).toBe(hash2);
|
|
70
|
-
});
|
|
71
|
-
(0, bun_test_1.it)("returns different values for different inputs", function () {
|
|
72
|
-
var hash1 = (0, evaluate_1.deterministicHash)("user1test-flag");
|
|
73
|
-
var hash2 = (0, evaluate_1.deterministicHash)("user2test-flag");
|
|
74
|
-
// Not guaranteed but extremely likely for different inputs
|
|
75
|
-
// Just verify they're both valid
|
|
76
|
-
(0, bun_test_1.expect)(hash1).toBeGreaterThanOrEqual(0);
|
|
77
|
-
(0, bun_test_1.expect)(hash2).toBeGreaterThanOrEqual(0);
|
|
78
|
-
});
|
|
79
|
-
(0, bun_test_1.it)("does not collide for ambiguous userId/key pairs", function () {
|
|
80
|
-
// "12" + "3" must not equal "1" + "23" — delimiter prevents this
|
|
81
|
-
var hash1 = (0, evaluate_1.deterministicHash)("12:3");
|
|
82
|
-
var hash2 = (0, evaluate_1.deterministicHash)("1:23");
|
|
83
|
-
(0, bun_test_1.expect)(hash1).not.toBe(hash2);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
(0, bun_test_1.describe)("evaluateFlag", function () {
|
|
87
|
-
(0, bun_test_1.describe)("disabled flags", function () {
|
|
88
|
-
(0, bun_test_1.it)("returns false for disabled boolean flags", function () {
|
|
89
|
-
var flag = createFlag({ enabled: false, type: "boolean" });
|
|
90
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
(0, bun_test_1.it)("returns null for disabled variant flags", function () {
|
|
93
|
-
var flag = createFlag({
|
|
94
|
-
enabled: false,
|
|
95
|
-
type: "variant",
|
|
96
|
-
variants: [
|
|
97
|
-
{ key: "control", weight: 50 },
|
|
98
|
-
{ key: "variant-a", weight: 50 },
|
|
99
|
-
],
|
|
100
|
-
});
|
|
101
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
(0, bun_test_1.describe)("boolean flags with rollout percentage", function () {
|
|
105
|
-
(0, bun_test_1.it)("returns true for all users at 100% rollout", function () {
|
|
106
|
-
var flag = createFlag({ rolloutPercentage: 100 });
|
|
107
|
-
// Test multiple users — all should get true
|
|
108
|
-
for (var i = 0; i < 20; i++) {
|
|
109
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user-".concat(i), {}, noSegments)).toBe(true);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
(0, bun_test_1.it)("returns false for all users at 0% rollout", function () {
|
|
113
|
-
var flag = createFlag({ rolloutPercentage: 0 });
|
|
114
|
-
for (var i = 0; i < 20; i++) {
|
|
115
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user-".concat(i), {}, noSegments)).toBe(false);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
(0, bun_test_1.it)("gives consistent results for the same user", function () {
|
|
119
|
-
var flag = createFlag({ rolloutPercentage: 50 });
|
|
120
|
-
var result1 = (0, evaluate_1.evaluateFlag)(flag, "user-abc", {}, noSegments);
|
|
121
|
-
var result2 = (0, evaluate_1.evaluateFlag)(flag, "user-abc", {}, noSegments);
|
|
122
|
-
(0, bun_test_1.expect)(result1).toBe(result2);
|
|
123
|
-
});
|
|
124
|
-
(0, bun_test_1.it)("distributes roughly proportionally at 50%", function () {
|
|
125
|
-
var flag = createFlag({ rolloutPercentage: 50 });
|
|
126
|
-
var trueCount = 0;
|
|
127
|
-
var total = 1000;
|
|
128
|
-
for (var i = 0; i < total; i++) {
|
|
129
|
-
if ((0, evaluate_1.evaluateFlag)(flag, "user-".concat(i), {}, noSegments) === true) {
|
|
130
|
-
trueCount++;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Allow 15% tolerance for randomness
|
|
134
|
-
(0, bun_test_1.expect)(trueCount).toBeGreaterThan(total * 0.35);
|
|
135
|
-
(0, bun_test_1.expect)(trueCount).toBeLessThan(total * 0.65);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
(0, bun_test_1.describe)("variant flags", function () {
|
|
139
|
-
(0, bun_test_1.it)("assigns users to variants based on weights", function () {
|
|
140
|
-
var flag = createFlag({
|
|
141
|
-
type: "variant",
|
|
142
|
-
variants: [
|
|
143
|
-
{ key: "control", weight: 50 },
|
|
144
|
-
{ key: "variant-a", weight: 50 },
|
|
145
|
-
],
|
|
146
|
-
});
|
|
147
|
-
var counts = { control: 0, "variant-a": 0 };
|
|
148
|
-
var total = 1000;
|
|
149
|
-
for (var i = 0; i < total; i++) {
|
|
150
|
-
var result = (0, evaluate_1.evaluateFlag)(flag, "user-".concat(i), {}, noSegments);
|
|
151
|
-
counts[result]++;
|
|
152
|
-
}
|
|
153
|
-
// Both variants should get roughly 50%
|
|
154
|
-
(0, bun_test_1.expect)(counts.control).toBeGreaterThan(total * 0.35);
|
|
155
|
-
(0, bun_test_1.expect)(counts["variant-a"]).toBeGreaterThan(total * 0.35);
|
|
156
|
-
});
|
|
157
|
-
(0, bun_test_1.it)("returns consistent variant for the same user", function () {
|
|
158
|
-
var flag = createFlag({
|
|
159
|
-
type: "variant",
|
|
160
|
-
variants: [
|
|
161
|
-
{ key: "control", weight: 50 },
|
|
162
|
-
{ key: "variant-a", weight: 50 },
|
|
163
|
-
],
|
|
164
|
-
});
|
|
165
|
-
var result1 = (0, evaluate_1.evaluateFlag)(flag, "user-123", {}, noSegments);
|
|
166
|
-
var result2 = (0, evaluate_1.evaluateFlag)(flag, "user-123", {}, noSegments);
|
|
167
|
-
(0, bun_test_1.expect)(result1).toBe(result2);
|
|
168
|
-
});
|
|
169
|
-
(0, bun_test_1.it)("handles three-way split", function () {
|
|
170
|
-
var flag = createFlag({
|
|
171
|
-
type: "variant",
|
|
172
|
-
variants: [
|
|
173
|
-
{ key: "control", weight: 50 },
|
|
174
|
-
{ key: "variant-a", weight: 30 },
|
|
175
|
-
{ key: "variant-b", weight: 20 },
|
|
176
|
-
],
|
|
177
|
-
});
|
|
178
|
-
var counts = { control: 0, "variant-a": 0, "variant-b": 0 };
|
|
179
|
-
var total = 1000;
|
|
180
|
-
for (var i = 0; i < total; i++) {
|
|
181
|
-
var result = (0, evaluate_1.evaluateFlag)(flag, "user-".concat(i), {}, noSegments);
|
|
182
|
-
counts[result]++;
|
|
183
|
-
}
|
|
184
|
-
// All variants should get some users
|
|
185
|
-
(0, bun_test_1.expect)(counts.control).toBeGreaterThan(0);
|
|
186
|
-
(0, bun_test_1.expect)(counts["variant-a"]).toBeGreaterThan(0);
|
|
187
|
-
(0, bun_test_1.expect)(counts["variant-b"]).toBeGreaterThan(0);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
(0, bun_test_1.describe)("field rules", function () {
|
|
191
|
-
(0, bun_test_1.it)("matches eq operator", function () {
|
|
192
|
-
var flag = createFlag({
|
|
193
|
-
rules: [{ enabled: true, field: "admin", operator: "eq", value: true }],
|
|
194
|
-
});
|
|
195
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(true);
|
|
196
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { admin: false }, noSegments)).toBe(true); // Falls through to rollout at 100%
|
|
197
|
-
});
|
|
198
|
-
(0, bun_test_1.it)("matches neq operator", function () {
|
|
199
|
-
var flag = createFlag({
|
|
200
|
-
rolloutPercentage: 0,
|
|
201
|
-
rules: [{ enabled: true, field: "role", operator: "neq", value: "guest" }],
|
|
202
|
-
});
|
|
203
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { role: "admin" }, noSegments)).toBe(true);
|
|
204
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { role: "guest" }, noSegments)).toBe(false); // No match, 0% rollout
|
|
205
|
-
});
|
|
206
|
-
(0, bun_test_1.it)("matches in operator", function () {
|
|
207
|
-
var flag = createFlag({
|
|
208
|
-
rolloutPercentage: 0,
|
|
209
|
-
rules: [{ enabled: true, field: "plan", operator: "in", value: ["pro", "enterprise"] }],
|
|
210
|
-
});
|
|
211
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { plan: "pro" }, noSegments)).toBe(true);
|
|
212
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { plan: "free" }, noSegments)).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
(0, bun_test_1.it)("matches nin operator", function () {
|
|
215
|
-
var flag = createFlag({
|
|
216
|
-
rolloutPercentage: 0,
|
|
217
|
-
rules: [{ enabled: true, field: "plan", operator: "nin", value: ["free", "trial"] }],
|
|
218
|
-
});
|
|
219
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { plan: "pro" }, noSegments)).toBe(true);
|
|
220
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { plan: "free" }, noSegments)).toBe(false);
|
|
221
|
-
});
|
|
222
|
-
(0, bun_test_1.it)("matches gt operator", function () {
|
|
223
|
-
var flag = createFlag({
|
|
224
|
-
rolloutPercentage: 0,
|
|
225
|
-
rules: [{ enabled: true, field: "totalActions", operator: "gt", value: 100 }],
|
|
226
|
-
});
|
|
227
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { totalActions: 200 }, noSegments)).toBe(true);
|
|
228
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { totalActions: 50 }, noSegments)).toBe(false);
|
|
229
|
-
});
|
|
230
|
-
(0, bun_test_1.it)("matches lt operator", function () {
|
|
231
|
-
var flag = createFlag({
|
|
232
|
-
rolloutPercentage: 0,
|
|
233
|
-
rules: [{ enabled: true, field: "age", operator: "lt", value: 18 }],
|
|
234
|
-
});
|
|
235
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { age: 15 }, noSegments)).toBe(true);
|
|
236
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { age: 25 }, noSegments)).toBe(false);
|
|
237
|
-
});
|
|
238
|
-
(0, bun_test_1.it)("matches contains operator", function () {
|
|
239
|
-
var flag = createFlag({
|
|
240
|
-
rolloutPercentage: 0,
|
|
241
|
-
rules: [{ enabled: true, field: "email", operator: "contains", value: "@company.com" }],
|
|
242
|
-
});
|
|
243
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { email: "john@company.com" }, noSegments)).toBe(true);
|
|
244
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { email: "john@gmail.com" }, noSegments)).toBe(false);
|
|
245
|
-
});
|
|
246
|
-
(0, bun_test_1.it)("does not match eq when rule.value is undefined", function () {
|
|
247
|
-
var flag = createFlag({
|
|
248
|
-
rolloutPercentage: 0,
|
|
249
|
-
rules: [{ enabled: true, field: "missingField", operator: "eq" }],
|
|
250
|
-
});
|
|
251
|
-
// Both user field and rule.value are undefined — should NOT match
|
|
252
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBe(false);
|
|
253
|
-
});
|
|
254
|
-
(0, bun_test_1.it)("does not match neq when rule.value is undefined", function () {
|
|
255
|
-
var flag = createFlag({
|
|
256
|
-
rolloutPercentage: 0,
|
|
257
|
-
rules: [{ enabled: true, field: "missingField", operator: "neq" }],
|
|
258
|
-
});
|
|
259
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBe(false);
|
|
260
|
-
});
|
|
261
|
-
(0, bun_test_1.it)("supports dot notation for nested fields", function () {
|
|
262
|
-
var flag = createFlag({
|
|
263
|
-
rolloutPercentage: 0,
|
|
264
|
-
rules: [{ enabled: true, field: "address.zip", operator: "eq", value: "94105" }],
|
|
265
|
-
});
|
|
266
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { address: { zip: "94105" } }, noSegments)).toBe(true);
|
|
267
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { address: { zip: "10001" } }, noSegments)).toBe(false);
|
|
268
|
-
});
|
|
269
|
-
(0, bun_test_1.it)("returns rule.enabled value for boolean flags", function () {
|
|
270
|
-
var flag = createFlag({
|
|
271
|
-
rules: [{ enabled: false, field: "admin", operator: "eq", value: true }],
|
|
272
|
-
});
|
|
273
|
-
// Rule matches and returns enabled: false
|
|
274
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(false);
|
|
275
|
-
});
|
|
276
|
-
(0, bun_test_1.it)("first matching rule wins", function () {
|
|
277
|
-
var flag = createFlag({
|
|
278
|
-
rolloutPercentage: 0,
|
|
279
|
-
rules: [
|
|
280
|
-
{ enabled: true, field: "admin", operator: "eq", value: true },
|
|
281
|
-
{ enabled: false, field: "role", operator: "eq", value: "manager" },
|
|
282
|
-
],
|
|
283
|
-
});
|
|
284
|
-
// Admin manager — first rule matches, gets true
|
|
285
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true, role: "manager" }, noSegments)).toBe(true);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
(0, bun_test_1.describe)("variant flag rules", function () {
|
|
289
|
-
(0, bun_test_1.it)("returns forced variant from matching rule", function () {
|
|
290
|
-
var flag = createFlag({
|
|
291
|
-
rules: [{ field: "admin", operator: "eq", value: true, variant: "variant-a" }],
|
|
292
|
-
type: "variant",
|
|
293
|
-
variants: [
|
|
294
|
-
{ key: "control", weight: 50 },
|
|
295
|
-
{ key: "variant-a", weight: 50 },
|
|
296
|
-
],
|
|
297
|
-
});
|
|
298
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe("variant-a");
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
(0, bun_test_1.describe)("segment rules", function () {
|
|
302
|
-
(0, bun_test_1.it)("matches segment function", function () {
|
|
303
|
-
var segments = {
|
|
304
|
-
"pro-users": function (user) { return user.plan === "pro"; },
|
|
305
|
-
};
|
|
306
|
-
var flag = createFlag({
|
|
307
|
-
rolloutPercentage: 0,
|
|
308
|
-
rules: [{ enabled: true, segment: "pro-users" }],
|
|
309
|
-
});
|
|
310
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { plan: "pro" }, segments)).toBe(true);
|
|
311
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { plan: "free" }, segments)).toBe(false);
|
|
312
|
-
});
|
|
313
|
-
(0, bun_test_1.it)("returns false for unknown segment", function () {
|
|
314
|
-
var flag = createFlag({
|
|
315
|
-
rolloutPercentage: 0,
|
|
316
|
-
rules: [{ enabled: true, segment: "unknown-segment" }],
|
|
317
|
-
});
|
|
318
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBe(false);
|
|
319
|
-
});
|
|
320
|
-
(0, bun_test_1.it)("returns false when segment function throws", function () {
|
|
321
|
-
var segments = {
|
|
322
|
-
"throwing-segment": function () {
|
|
323
|
-
throw new Error("boom");
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
var flag = createFlag({
|
|
327
|
-
rolloutPercentage: 0,
|
|
328
|
-
rules: [{ enabled: true, segment: "throwing-segment" }],
|
|
329
|
-
});
|
|
330
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, segments)).toBe(false);
|
|
331
|
-
});
|
|
332
|
-
(0, bun_test_1.it)("uses segment rule for variant flags", function () {
|
|
333
|
-
var segments = {
|
|
334
|
-
"pro-users": function (user) { return user.plan === "pro"; },
|
|
335
|
-
};
|
|
336
|
-
var flag = createFlag({
|
|
337
|
-
rules: [{ segment: "pro-users", variant: "variant-a" }],
|
|
338
|
-
type: "variant",
|
|
339
|
-
variants: [
|
|
340
|
-
{ key: "control", weight: 50 },
|
|
341
|
-
{ key: "variant-a", weight: 50 },
|
|
342
|
-
],
|
|
343
|
-
});
|
|
344
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { plan: "pro" }, segments)).toBe("variant-a");
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
(0, bun_test_1.describe)("malformed field rules", function () {
|
|
348
|
-
(0, bun_test_1.it)("does not match a rule with no field and no segment", function () {
|
|
349
|
-
var flag = createFlag({
|
|
350
|
-
rolloutPercentage: 0,
|
|
351
|
-
rules: [{ enabled: true }],
|
|
352
|
-
});
|
|
353
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { anything: true }, noSegments)).toBe(false);
|
|
354
|
-
});
|
|
355
|
-
(0, bun_test_1.it)("does not match a rule with a field but no operator", function () {
|
|
356
|
-
var flag = createFlag({
|
|
357
|
-
rolloutPercentage: 0,
|
|
358
|
-
rules: [{ enabled: true, field: "admin" }],
|
|
359
|
-
});
|
|
360
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(false);
|
|
361
|
-
});
|
|
362
|
-
(0, bun_test_1.it)("does not match a rule with an unknown operator", function () {
|
|
363
|
-
var flag = createFlag({
|
|
364
|
-
rolloutPercentage: 0,
|
|
365
|
-
rules: [
|
|
366
|
-
{
|
|
367
|
-
enabled: true,
|
|
368
|
-
field: "admin",
|
|
369
|
-
operator: "bogus",
|
|
370
|
-
value: true,
|
|
371
|
-
},
|
|
372
|
-
],
|
|
373
|
-
});
|
|
374
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(false);
|
|
375
|
-
});
|
|
376
|
-
(0, bun_test_1.it)("does not match gt/lt when values are not numbers", function () {
|
|
377
|
-
var gtFlag = createFlag({
|
|
378
|
-
rolloutPercentage: 0,
|
|
379
|
-
rules: [{ enabled: true, field: "age", operator: "gt", value: 18 }],
|
|
380
|
-
});
|
|
381
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(gtFlag, "user1", { age: "twenty" }, noSegments)).toBe(false);
|
|
382
|
-
var ltFlag = createFlag({
|
|
383
|
-
rolloutPercentage: 0,
|
|
384
|
-
rules: [{ enabled: true, field: "age", operator: "lt", value: 18 }],
|
|
385
|
-
});
|
|
386
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(ltFlag, "user1", { age: undefined }, noSegments)).toBe(false);
|
|
387
|
-
});
|
|
388
|
-
(0, bun_test_1.it)("does not match contains when values are not strings", function () {
|
|
389
|
-
var flag = createFlag({
|
|
390
|
-
rolloutPercentage: 0,
|
|
391
|
-
rules: [{ enabled: true, field: "email", operator: "contains", value: "@company.com" }],
|
|
392
|
-
});
|
|
393
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { email: 42 }, noSegments)).toBe(false);
|
|
394
|
-
});
|
|
395
|
-
(0, bun_test_1.it)("does not match in/nin when rule.value is not an array", function () {
|
|
396
|
-
var inFlag = createFlag({
|
|
397
|
-
rolloutPercentage: 0,
|
|
398
|
-
rules: [{ enabled: true, field: "plan", operator: "in", value: "pro" }],
|
|
399
|
-
});
|
|
400
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(inFlag, "user1", { plan: "pro" }, noSegments)).toBe(false);
|
|
401
|
-
var ninFlag = createFlag({
|
|
402
|
-
rolloutPercentage: 0,
|
|
403
|
-
rules: [{ enabled: true, field: "plan", operator: "nin", value: "pro" }],
|
|
404
|
-
});
|
|
405
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(ninFlag, "user1", { plan: "pro" }, noSegments)).toBe(false);
|
|
406
|
-
});
|
|
407
|
-
(0, bun_test_1.it)("treats rule.enabled as false by default when undefined on a matching boolean rule", function () {
|
|
408
|
-
var flag = createFlag({
|
|
409
|
-
rolloutPercentage: 100,
|
|
410
|
-
rules: [{ field: "admin", operator: "eq", value: true }],
|
|
411
|
-
});
|
|
412
|
-
// rule matches, but rule.enabled is undefined → evaluates to false
|
|
413
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(false);
|
|
414
|
-
});
|
|
415
|
-
(0, bun_test_1.it)("treats rule.variant as null when undefined on a matching variant rule", function () {
|
|
416
|
-
var flag = createFlag({
|
|
417
|
-
rules: [{ field: "admin", operator: "eq", value: true }],
|
|
418
|
-
type: "variant",
|
|
419
|
-
variants: [
|
|
420
|
-
{ key: "control", weight: 50 },
|
|
421
|
-
{ key: "variant-a", weight: 50 },
|
|
422
|
-
],
|
|
423
|
-
});
|
|
424
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBeNull();
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
(0, bun_test_1.describe)("variant fallback behavior", function () {
|
|
428
|
-
(0, bun_test_1.it)("falls back to the last variant when weights do not sum to 100", function () {
|
|
429
|
-
var flag = createFlag({
|
|
430
|
-
type: "variant",
|
|
431
|
-
// Weights only sum to 10 — the hash will always exceed cumulative weight
|
|
432
|
-
variants: [
|
|
433
|
-
{ key: "a", weight: 5 },
|
|
434
|
-
{ key: "b", weight: 5 },
|
|
435
|
-
],
|
|
436
|
-
});
|
|
437
|
-
// Pick a user whose hash is guaranteed >= 10
|
|
438
|
-
var userId = "this-user-hashes-high";
|
|
439
|
-
var hash = (0, evaluate_1.deterministicHash)("".concat(userId, ":").concat(flag.key));
|
|
440
|
-
// Make sure our assumption holds before asserting the fallback
|
|
441
|
-
(0, bun_test_1.expect)(hash).toBeGreaterThanOrEqual(10);
|
|
442
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, userId, {}, noSegments)).toBe("b");
|
|
443
|
-
});
|
|
444
|
-
(0, bun_test_1.it)("returns null when an enabled variant flag has no variants defined", function () {
|
|
445
|
-
var flag = createFlag({
|
|
446
|
-
type: "variant",
|
|
447
|
-
variants: [],
|
|
448
|
-
});
|
|
449
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", {}, noSegments)).toBeNull();
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
(0, bun_test_1.describe)("debug logging toggle", function () {
|
|
453
|
-
(0, bun_test_1.it)("runs without logging when FEATURE_FLAGS_DEBUG is 'false'", function () {
|
|
454
|
-
var original = process.env.FEATURE_FLAGS_DEBUG;
|
|
455
|
-
process.env.FEATURE_FLAGS_DEBUG = "false";
|
|
456
|
-
try {
|
|
457
|
-
var flag = createFlag({
|
|
458
|
-
rolloutPercentage: 100,
|
|
459
|
-
rules: [{ enabled: true, field: "admin", operator: "eq", value: true }],
|
|
460
|
-
});
|
|
461
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user1", { admin: true }, noSegments)).toBe(true);
|
|
462
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(flag, "user2", { admin: false }, noSegments)).toBe(true);
|
|
463
|
-
var disabledFlag = createFlag({ enabled: false });
|
|
464
|
-
(0, bun_test_1.expect)((0, evaluate_1.evaluateFlag)(disabledFlag, "user3", {}, noSegments)).toBe(false);
|
|
465
|
-
}
|
|
466
|
-
finally {
|
|
467
|
-
if (original === undefined) {
|
|
468
|
-
delete process.env.FEATURE_FLAGS_DEBUG;
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
process.env.FEATURE_FLAGS_DEBUG = original;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
(0, bun_test_1.describe)("evaluateAllFlags", function () {
|
|
478
|
-
var makeMockModel = function (flags) {
|
|
479
|
-
return {
|
|
480
|
-
find: function (query) { return __awaiter(void 0, void 0, void 0, function () {
|
|
481
|
-
return __generator(this, function (_a) {
|
|
482
|
-
return [2 /*return*/, flags.filter(function (flag) {
|
|
483
|
-
if (query.archived &&
|
|
484
|
-
typeof query.archived === "object" &&
|
|
485
|
-
"$ne" in query.archived) {
|
|
486
|
-
if (flag.archived) {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (query.enabled !== undefined && flag.enabled !== query.enabled) {
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
return true;
|
|
494
|
-
})];
|
|
495
|
-
});
|
|
496
|
-
}); },
|
|
497
|
-
};
|
|
498
|
-
};
|
|
499
|
-
(0, bun_test_1.it)("returns an empty object when there are no flags", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
500
|
-
var model, result;
|
|
501
|
-
return __generator(this, function (_a) {
|
|
502
|
-
switch (_a.label) {
|
|
503
|
-
case 0:
|
|
504
|
-
model = makeMockModel([]);
|
|
505
|
-
return [4 /*yield*/, (0, evaluate_1.evaluateAllFlags)(model, "user1", {}, noSegments)];
|
|
506
|
-
case 1:
|
|
507
|
-
result = _a.sent();
|
|
508
|
-
(0, bun_test_1.expect)(result).toEqual({});
|
|
509
|
-
return [2 /*return*/];
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
}); });
|
|
513
|
-
(0, bun_test_1.it)("evaluates each enabled flag and keys results by flag key", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
514
|
-
var flags, model, result;
|
|
515
|
-
return __generator(this, function (_a) {
|
|
516
|
-
switch (_a.label) {
|
|
517
|
-
case 0:
|
|
518
|
-
flags = [
|
|
519
|
-
createFlag({ key: "flag-a", rolloutPercentage: 100 }),
|
|
520
|
-
createFlag({ key: "flag-b", rolloutPercentage: 0 }),
|
|
521
|
-
createFlag({
|
|
522
|
-
key: "flag-c",
|
|
523
|
-
rolloutPercentage: 0,
|
|
524
|
-
rules: [{ enabled: true, field: "admin", operator: "eq", value: true }],
|
|
525
|
-
}),
|
|
526
|
-
];
|
|
527
|
-
model = makeMockModel(flags);
|
|
528
|
-
return [4 /*yield*/, (0, evaluate_1.evaluateAllFlags)(model, "user1", { admin: true }, noSegments)];
|
|
529
|
-
case 1:
|
|
530
|
-
result = _a.sent();
|
|
531
|
-
(0, bun_test_1.expect)(result["flag-a"]).toBe(true);
|
|
532
|
-
(0, bun_test_1.expect)(result["flag-b"]).toBe(false);
|
|
533
|
-
(0, bun_test_1.expect)(result["flag-c"]).toBe(true);
|
|
534
|
-
return [2 /*return*/];
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
}); });
|
|
538
|
-
(0, bun_test_1.it)("supports variant flags in the aggregate evaluation", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
539
|
-
var flags, segments, model, result;
|
|
540
|
-
return __generator(this, function (_a) {
|
|
541
|
-
switch (_a.label) {
|
|
542
|
-
case 0:
|
|
543
|
-
flags = [
|
|
544
|
-
createFlag({
|
|
545
|
-
key: "variant-flag",
|
|
546
|
-
rules: [{ segment: "pros", variant: "variant-a" }],
|
|
547
|
-
type: "variant",
|
|
548
|
-
variants: [
|
|
549
|
-
{ key: "control", weight: 50 },
|
|
550
|
-
{ key: "variant-a", weight: 50 },
|
|
551
|
-
],
|
|
552
|
-
}),
|
|
553
|
-
];
|
|
554
|
-
segments = {
|
|
555
|
-
pros: function (user) { return user.pro === true; },
|
|
556
|
-
};
|
|
557
|
-
model = makeMockModel(flags);
|
|
558
|
-
return [4 /*yield*/, (0, evaluate_1.evaluateAllFlags)(model, "user1", { pro: true }, segments)];
|
|
559
|
-
case 1:
|
|
560
|
-
result = _a.sent();
|
|
561
|
-
(0, bun_test_1.expect)(result["variant-flag"]).toBe("variant-a");
|
|
562
|
-
return [2 /*return*/];
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}); });
|
|
566
|
-
(0, bun_test_1.it)("runs with debug logging disabled", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
567
|
-
var original, model, result;
|
|
568
|
-
return __generator(this, function (_a) {
|
|
569
|
-
switch (_a.label) {
|
|
570
|
-
case 0:
|
|
571
|
-
original = process.env.FEATURE_FLAGS_DEBUG;
|
|
572
|
-
process.env.FEATURE_FLAGS_DEBUG = "false";
|
|
573
|
-
_a.label = 1;
|
|
574
|
-
case 1:
|
|
575
|
-
_a.trys.push([1, , 3, 4]);
|
|
576
|
-
model = makeMockModel([createFlag({ key: "flag-a" })]);
|
|
577
|
-
return [4 /*yield*/, (0, evaluate_1.evaluateAllFlags)(model, "user1", {}, noSegments)];
|
|
578
|
-
case 2:
|
|
579
|
-
result = _a.sent();
|
|
580
|
-
(0, bun_test_1.expect)(result["flag-a"]).toBe(true);
|
|
581
|
-
return [3 /*break*/, 4];
|
|
582
|
-
case 3:
|
|
583
|
-
if (original === undefined) {
|
|
584
|
-
delete process.env.FEATURE_FLAGS_DEBUG;
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
process.env.FEATURE_FLAGS_DEBUG = original;
|
|
588
|
-
}
|
|
589
|
-
return [7 /*endfinally*/];
|
|
590
|
-
case 4: return [2 /*return*/];
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}); });
|
|
594
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|