UNPKG

abby-client

Version:

[![Coverage Status](https://coveralls.io/repos/github/GreetzNL/abby-client/badge.svg)](https://coveralls.io/github/GreetzNL/abby-client) [![Build Status](https://travis-ci.com/GreetzNL/abby-client.svg?token=n8i4tz6gpgW4zgU5pvDP&branch=master)](https://tra

365 lines (341 loc) 14 kB
const chai = require('chai'); const spies = require('chai-spies'); const { expect, assert } = chai; chai.use(spies); const mocks = require('../../mocks'); const abby = require('../../lib/client'); const expressionEvaluator = require('expression-evaluator'); describe('client', () => { let abbyClient, synchroniser = mocks.synchroniser; beforeEach(() => { abbyClient = abby({ tags: 'test, production', apiEndpoint: 'https://abby.io/api/v1/' }, synchroniser); }); describe('when there is already a session cookie', () => { let request, response, props; function initSpec(toggles = [], forced = [], rebalance = []) { // create the request object having no cookies request = { headers: { cookie: 'ABBY_SESSION=123-123;ABBY_TOGGLES=' + toggles.join(',') }, url: '?abby_toggle=' + forced.join(',') }; // create the response object for later reference response = { headers: {}, setHeader() { }, getHeader() { } }; props = { device: 'MOBILE' }; // return toggles from api abbyClient(request, response, props, { rebalance }); } it('should not set features to request', () => { initSpec(); expect(request.toggles).to.be.empty; }); it('should preserve current features', () => { initSpec(['feature_abc|control|1']); expect(request.toggles.feature_abc).to.not.undefined; }); it('should add a forced toggle', () => { initSpec(['feature_abc|control|1'], ['feature_def|control|1']); expect(request.toggles.feature_def).to.not.undefined; }); it('should rebalance a toggle', () => { initSpec(['feature_rebalance|variant|1'], [], ['feature_rebalance']); expect(request.toggles.feature_rebalance.variantCode).to.equal('control'); }); it('should rebalance a toggle when toggle set to true', () => { initSpec(['feature_rebalance_toggle|variant|1']); expect(request.toggles.feature_rebalance_toggle.variantCode).to.equal('control'); }); it('should preserve current development toggle', () => { initSpec(['feature_development_toggle|false|1']); expect(request.toggles.feature_development_toggle).to.not.undefined; }); it('should enforce forced development toggle', () => { initSpec(['feature_abc|variant|1'], ['feature_development_toggle|true|1']); expect(request.toggles.feature_development_toggle).to.not.undefined; }); }); describe('when there is no Abby cookie', () => { let request, response; beforeEach(() => { // create the request object having no cookies request = { headers: { cookie: null }, url: 'anything' }; // create the response object for later reference response = { headers: {}, setHeader() { // do something response.headers.cookie = 'uhhh cookies!'; }, getHeader() { } }; // return toggles from api abbyClient(request, response); }); it('should set all features to request', () => { expect(request.toggles.feature_abc).to.not.undefined; expect(request.toggles.feature_def).to.not.undefined; expect(request.toggles.feature_development_toggle).to.not.undefined; }); it('should store the evaluation result in Cookies', () => { // check res.cookies expect(response.headers.cookie).to.equal('uhhh cookies!'); }); it('should store variant and toggle per toggleCode', () => { expect(request.toggles.feature_abc.toggle).to.not.undefined; expect(request.toggles.feature_abc.variant).to.not.undefined; }); it('should not set variant property when building a development toggle', () => { expect(request.toggles.feature_development_toggle.variant).to.be.undefined; }); }); describe('when there is a forced query toggle in request', () => { let request, response; function initSpec({ isAbbyQuery = true, toggle = 'feature_ghi', variant = 'variant_1_ghi' } = {}) { // create the request object having no cookies request = { headers: { cookie: null }, url: isAbbyQuery ? `?abby_toggle=${toggle}|${variant}` : `anything` }; // create the response object for later reference response = { headers: {}, setHeader() { // do something response.headers.cookie = 'uhhh cookies!'; }, getHeader() { } }; // return toggles from api abbyClient(request, response); } it('should set feature_ghi to variant_1_ghi', () => { initSpec(); expect(request.toggles.feature_ghi.variant.code).to.equal('variant_1_ghi'); }); }); describe('distribution of original/variant', () => { let deviation; function initSpec() { let originalCount = 0, variantCount = 0, distributionRate = 10000, originalPercent, variantPercent, request, response; // create the request object having no cookies request = { headers: { cookie: null }, url: 'anything' }; // create the response object for later reference response = { headers: {}, setHeader() { // do something response.headers.cookie = 'uhhh cookies!'; }, getHeader() { } }; for (let i = 0; i < distributionRate; i++) { abbyClient(request, response); request.toggles.feature_ghi && request.toggles.feature_ghi.variant.code === 'variant_1_ghi' ? variantCount++ : originalCount++; } originalPercent = (originalCount * 100) / distributionRate; variantPercent = (variantCount * 100) / distributionRate; deviation = originalPercent - variantPercent; } it('should have approximately same range', () => { initSpec(); assert.isAtMost(deviation, 5); }).timeout(10000); }); describe('getTargetedExperiments', () => { let targeting = { 'type': 'and' }; let userExperiments; beforeEach(() => { userExperiments = [{ toggle: { tags: ['tag'], targeting }, variant: { original: false }, type: 'EXPERIMENT', }]; }); afterEach(() => chai.spy.restore(expressionEvaluator)); describe('filtering', () => { beforeEach(() => chai.spy.on(expressionEvaluator, 'evaluate', () => true)); it('should return empty array when tags do not match', () => { let result = abbyClient.getTargetedExperiments(userExperiments, {}, { tags: ['whatever'] }); expect(result.length).to.equal(0); }); it('should return 1 item when tag matches', () => { let result = abbyClient.getTargetedExperiments(userExperiments, {}, { tags: ['tag'] }); expect(result.length).to.equal(1); }); }); describe('targeting', () => { let props = { 'a': '1' }; it('should return empty array when target does not match', () => { let spy = chai.spy.on(expressionEvaluator, 'evaluate', () => false); let result = abbyClient.getTargetedExperiments(userExperiments, props); expect(spy).to.have.been.called.with(targeting, props); expect(result.length).to.equal(0); }); it('should return 1 item when target does match', () => { let spy = chai.spy.on(expressionEvaluator, 'evaluate', () => true); let result = abbyClient.getTargetedExperiments(userExperiments, props); expect(spy).to.have.been.called.with(targeting, props); expect(result.length).to.equal(1); }); }); it('should return empty when variant is original', () => { userExperiments[0].variant.original = true; let result = abbyClient.getTargetedExperiments(userExperiments, {}); expect(result.length).to.equal(0); }); }); describe('when there is a paused toggle', () => { let request, response, addedToCookie; function initSpec() { addedToCookie = false; // create the request object having no cookies request = { headers: { cookie: '' }, url: '/' }; // create the response object for later reference response = { headers: {}, setHeader(key, value) { if (Array.isArray(value) && value.join('').indexOf('feature_paused') > -1) { addedToCookie = true; } }, getHeader() { } }; // return toggles from api abbyClient(request, response); } it('should not add the toggle to the request', () => { initSpec(); expect(request.toggles.feature_paused).to.be.undefined; }); it('should add the toggle to the cookie', () => { initSpec(); expect(addedToCookie).to.be.true; }); }); describe('areTogglesEnforcedToControl', () => { let request; let toggles; function initSpec({ isEnforced = 'false' } = {}) { request = { headers: { cookie: '' }, url: `?abby_enforce_control=${isEnforced}` }; toggles = abbyClient.areTogglesEnforcedToControl(request); return toggles; } it('should return empty string with default parameters', () => { initSpec(); expect(toggles).to.equal(false); }); it('should return false when control enforcement is NOT true', () => { initSpec({ isEnforced: 'something' }); expect(toggles).to.equal(false); }); it('should return true when control enforcement is true', () => { initSpec({ isEnforced: 'true' }); expect(toggles).to.equal(true); }); }); describe('setTogglesToControl', () => { let toggles = { 'toggle1': { variantCode: 'variant', toggleCode: 'toggle1', hash: '123456', type: 'EXPERIMENT' }, 'toggle2': { variantCode: 'control', toggleCode: 'toggle2', hash: '789012', type: 'EXPERIMENT' }, }; function initSpec() { abbyClient.setTogglesToControl(toggles); } it('should return toggles with all variant codes equal to "control" or "false"', () => { initSpec(); expect(toggles['toggle1'].variantCode).to.equal('control'); expect(toggles['toggle2'].variantCode).to.equal('control'); }); }); describe('request handler when there is an enforcement of all toggles in request query', () => { let request, response; const toggles = [ 'feature_abc|control|134738', 'feature_def|variant|2323', ]; function initSpec({ isEnforced = 'false'} = {}) { request = { headers: { cookie: 'ABBY_SESSION=123-123;ABBY_TOGGLES=' + toggles.join(',') }, url: `?abby_enforce_control=${isEnforced}` }; response = { headers: {}, setHeader() { // do something response.headers.cookie = 'uhhh cookies!'; }, getHeader() { } }; // return toggles from api abbyClient(request, response); } it('should NOT change toggles variations when control enforcement is "false"', () => { initSpec(); expect(request.toggles['feature_abc'].variant.code).to.equal('control'); expect(request.toggles['feature_def'].variant.code).to.equal('variant'); }); it('should NOT change toggles\' variations when control enforcement is NOT "true"', () => { initSpec({ isEnforced: 'something' }); expect(request.toggles['feature_abc'].variantCode).to.equal('control'); expect(request.toggles['feature_def'].variantCode).to.equal('variant'); }); it('should set all toggles to "control" when control enforcement is "true"', () => { initSpec({ isEnforced: 'true' }); expect(request.toggles['feature_abc'].variantCode).to.equal('control'); expect(request.toggles['feature_def'].variantCode).to.equal('control'); }); }); });