UNPKG

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