UNPKG

9.83 kBJavaScriptView Raw
1const chai = require('chai');
2const spies = require('chai-spies');
3const { expect, assert } = chai;
4chai.use(spies);
5const mocks = require('../mocks/index');
6const abby = require('../lib/client');
7const expressionEvaluator = require('expression-evaluator');
8
9describe('client', () => {
10 let abbyClient,
11 synchroniser = mocks.synchroniser;
12 beforeEach(() => {
13 abbyClient = abby({
14 tags: 'test, production',
15 apiEndpoint: 'https://abby.io/api/v1/'
16 }, synchroniser);
17 });
18
19
20 describe('when there is already a session cookie', () => {
21 let request,
22 response,
23 props;
24 function initSpec(experiments = [], forced = [], rebalance = []) {
25 // create the request object having no cookies
26 request = {
27 headers: {
28 cookie: 'ABBY_SESSION=123-123;ABBY_EXPERIMENTS=' + experiments.join(',')
29 },
30 url: '?abby_experiment=' + forced.join(',')
31 };
32 // create the response object for later reference
33 response = {
34 headers: {},
35 setHeader() {
36 },
37 getHeader() { }
38 };
39 props = {
40 device: 'MOBILE'
41 };
42 // return experiments from api
43 abbyClient(request, response, props, { rebalance });
44 }
45 it('should not set features to request', () => {
46 initSpec();
47 expect(request.experiments).to.be.empty;
48 });
49 it('should preserve current features', () => {
50 initSpec(['feature_abc|control|1']);
51 expect(request.experiments.feature_abc).to.not.undefined;
52 });
53 it('should add a forced experiment', () => {
54 initSpec(['feature_abc|control|1'], ['feature_def|control|1']);
55 expect(request.experiments.feature_def).to.not.undefined;
56 });
57 it('should rebalance a experiment', () => {
58 initSpec(['feature_rebalance|variant|1'], [], ['feature_rebalance']);
59 expect(request.experiments.feature_rebalance.variantCode).to.equal('control');
60 });
61 it('should rebalance a experiment when toggle set to true', () => {
62 initSpec(['feature_rebalance_toggle|variant|1']);
63 expect(request.experiments.feature_rebalance_toggle.variantCode).to.equal('control');
64 });
65 });
66
67 describe('when there is no Abby cookie', () => {
68 let request,
69 response;
70 beforeEach(() => {
71 // create the request object having no cookies
72 request = {
73 headers: {
74 cookie: null
75 },
76 url: 'anything'
77 };
78 // create the response object for later reference
79 response = {
80 headers: {},
81 setHeader() {
82 // do something
83 response.headers.cookie = 'uhhh cookies!';
84 },
85 getHeader() { }
86 };
87 // return experiments from api
88 abbyClient(request, response);
89 });
90 it('should set all features to request', () => {
91 expect(request.experiments.feature_abc).to.not.undefined;
92 expect(request.experiments.feature_def).to.not.undefined;
93 });
94 it('should store the evaluation result in Cookies', () => {
95 // check res.cookies
96 expect(response.headers.cookie).to.equal('uhhh cookies!');
97 });
98 it('should store variant and experiment by experimentCode', () => {
99 expect(request.experiments.feature_abc.experiment).to.not.undefined;
100 expect(request.experiments.feature_abc.variant).to.not.undefined;
101 });
102 });
103
104 describe('when there is a forced query experiment in request', () => {
105 let request,
106 response;
107 function initSpec({ isAbbyQuery = true, experiment = 'feature_ghi', variant = 'variant_1_ghi' } = {}) {
108 // create the request object having no cookies
109 request = {
110 headers: {
111 cookie: null
112 },
113 url: isAbbyQuery ? `?abby_experiment=${experiment}|${variant}` : `anything`
114 };
115 // create the response object for later reference
116 response = {
117 headers: {},
118 setHeader() {
119 // do something
120 response.headers.cookie = 'uhhh cookies!';
121 },
122 getHeader() { }
123 };
124 // return experiments from api
125 abbyClient(request, response);
126 }
127 it('should set feature_ghi to variant_1_ghi', () => {
128 initSpec();
129 expect(request.experiments.feature_ghi.variant.code).to.equal('variant_1_ghi');
130 });
131 });
132
133 describe('distribution of original/variant', () => {
134 let deviation;
135
136 function initSpec() {
137 let originalCount = 0,
138 variantCount = 0,
139 distributionRate = 10000,
140 originalPercent,
141 variantPercent,
142 request,
143 response;
144 // create the request object having no cookies
145 request = {
146 headers: {
147 cookie: null
148 },
149 url: 'anything'
150 };
151 // create the response object for later reference
152 response = {
153 headers: {},
154 setHeader() {
155 // do something
156 response.headers.cookie = 'uhhh cookies!';
157 },
158 getHeader() { }
159 };
160 for (let i = 0; i < distributionRate; i++) {
161 abbyClient(request, response);
162 request.experiments.feature_ghi.variant.code === 'variant_1_ghi' ? variantCount++ : originalCount++;
163 }
164 originalPercent = (originalCount * 100) / distributionRate;
165 variantPercent = (variantCount * 100) / distributionRate;
166 deviation = Math.abs(originalPercent - variantPercent);
167 }
168
169 it('should have approximately same range', () => {
170 initSpec();
171 assert.isAtMost(deviation, 5);
172 }).timeout(10000);
173 });
174
175 describe('getTargetedExperiments', () => {
176 let targeting = { 'type': 'and' };
177 let userExperiments;
178 beforeEach(() => {
179 userExperiments = [{
180 experiment: {
181 tags: ['tag'],
182 targeting
183 },
184 variant: { original: false }
185 }];
186 });
187 afterEach(() => chai.spy.restore(expressionEvaluator));
188 describe('filtering', () => {
189 beforeEach(() => chai.spy.on(expressionEvaluator, 'evaluate', () => true));
190
191 it('should return empty array when tags do not match', () => {
192 let result = abbyClient.getTargetedExperiments(userExperiments, {}, { tags: ['whatever'] });
193 expect(result.length).to.equal(0);
194 });
195 it('should return 1 item when tag matches', () => {
196 let result = abbyClient.getTargetedExperiments(userExperiments, {}, { tags: ['tag'] });
197 expect(result.length).to.equal(1);
198 });
199 });
200
201 describe('targeting', () => {
202 let props = { 'a': '1' };
203
204 it('should return empty array when target does not match', () => {
205 let spy = chai.spy.on(expressionEvaluator, 'evaluate', () => false);
206 let result = abbyClient.getTargetedExperiments(userExperiments, props);
207 expect(spy).to.have.been.called.with(targeting, props);
208 expect(result.length).to.equal(0);
209 });
210 it('should return 1 item when target does match', () => {
211 let spy = chai.spy.on(expressionEvaluator, 'evaluate', () => true);
212 let result = abbyClient.getTargetedExperiments(userExperiments, props);
213 expect(spy).to.have.been.called.with(targeting, props);
214 expect(result.length).to.equal(1);
215 });
216 });
217
218 it('should return empty when variant is original', () => {
219 userExperiments[0].variant.original = true;
220 let result = abbyClient.getTargetedExperiments(userExperiments, {});
221 expect(result.length).to.equal(0);
222 });
223 });
224
225 describe('when there is a paused experiment', () => {
226 let request,
227 response,
228 addedToCookie;
229
230 function initSpec() {
231 addedToCookie = false;
232 // create the request object having no cookies
233 request = {
234 headers: {
235 cookie: ''
236 },
237 url: '/'
238 };
239 // create the response object for later reference
240 response = {
241 headers: {},
242 setHeader(key, value) {
243 if (Array.isArray(value) && value.join('').indexOf('feature_paused') > -1) {
244 addedToCookie = true;
245 }
246 },
247 getHeader() { }
248 };
249 // return experiments from api
250 abbyClient(request, response);
251 }
252 it('should not add the experiment to the request', () => {
253 initSpec();
254 expect(request.experiments.feature_paused).to.be.undefined;
255 });
256 it('should add the experiment to the cookie', () => {
257 initSpec();
258 expect(addedToCookie).to.be.true;
259 });
260 });
261});