From a8ddc7217826b4b1e5ecf27d0b00aa55cc6f09fa Mon Sep 17 00:00:00 2001 From: Melana Hammel <42477256+melanahammel@users.noreply.github.com> Date: Fri, 15 Jan 2021 14:48:55 -0800 Subject: [PATCH] feat: add referee utils tests (#106) --- packages/client/package.json | 2 +- .../__tests__/LoadCanaryConfigService.test.ts | 4 +- .../client/src/stores/CanaryExecutorStore.ts | 6 +- packages/client/src/util/MetricUtils.ts | 4 +- .../src/util/__tests__/LoggerFactory.test.ts | 21 +++ .../src/util/__tests__/MetricUtils.test.ts | 47 ++++++ .../src/util/__tests__/OptionalUtils.test.ts | 102 ++++++++++++ .../util/__tests__/ScoreClassUtils.test.ts | 145 ++++++++++++++++++ packages/server/package.json | 2 +- 9 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/util/__tests__/LoggerFactory.test.ts create mode 100644 packages/client/src/util/__tests__/MetricUtils.test.ts create mode 100644 packages/client/src/util/__tests__/OptionalUtils.test.ts create mode 100644 packages/client/src/util/__tests__/ScoreClassUtils.test.ts diff --git a/packages/client/package.json b/packages/client/package.json index 3fed04a7..008c60dd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "1.1.0", + "version": "1.2.0", "description": "The client lib for the Referee UX", "author": "Justin Field ", "homepage": "https://example.com/dashboard/", diff --git a/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts b/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts index d3def1bf..06f4473d 100644 --- a/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts +++ b/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts @@ -98,7 +98,7 @@ describe('LoadCanaryConfigService', () => { }); it('should fetch template content', async () => { - const fileName = "hello-world-config"; + const fileName = 'hello-world-config'; const expectedData = { response: true }; httpMock.onGet(`${process.env.PUBLIC_URL}/templates/${fileName}`).reply(200, expectedData); const actualData = await loadCanaryConfigService.fetchTemplateContent(fileName); @@ -106,7 +106,7 @@ describe('LoadCanaryConfigService', () => { }); it('should not fetch template content because template is not found', async () => { - const fileName = "fileName"; + const fileName = 'fileName'; const expectedData = { response: true }; httpMock.onGet(`${process.env.PUBLIC_URL}/templates/${fileName}`).reply(404, expectedData); await expect(loadCanaryConfigService.fetchTemplateContent(fileName)).rejects.toThrowError( diff --git a/packages/client/src/stores/CanaryExecutorStore.ts b/packages/client/src/stores/CanaryExecutorStore.ts index 73d68195..f42ef279 100644 --- a/packages/client/src/stores/CanaryExecutorStore.ts +++ b/packages/client/src/stores/CanaryExecutorStore.ts @@ -1,9 +1,5 @@ import { observable, action, computed, toJS } from 'mobx'; -import { - CanaryExecutionRequest, - CanaryScope, - KayentaCredential -} from '../domain/Kayenta'; +import { CanaryExecutionRequest, CanaryScope, KayentaCredential } from '../domain/Kayenta'; import CanaryExecutionFactory from '../util/CanaryExecutionFactory'; import { validateAdditionalParameters, diff --git a/packages/client/src/util/MetricUtils.ts b/packages/client/src/util/MetricUtils.ts index 197d699a..0ebae4f4 100644 --- a/packages/client/src/util/MetricUtils.ts +++ b/packages/client/src/util/MetricUtils.ts @@ -2,10 +2,10 @@ import axios from 'axios'; import log from './LoggerFactory'; import { ofNullable } from './OptionalUtils'; -export const trackEvent = (event: EVENT, dimensions?: KvMap): void => { +export const trackEvent = async (event: EVENT, dimensions?: KvMap): Promise => { dimensions = ofNullable(dimensions).orElse({}); try { - axios.post(`/metrics`, { + await axios.post(`/metrics`, { name: event, dimensions: dimensions }); diff --git a/packages/client/src/util/__tests__/LoggerFactory.test.ts b/packages/client/src/util/__tests__/LoggerFactory.test.ts new file mode 100644 index 00000000..61f71731 --- /dev/null +++ b/packages/client/src/util/__tests__/LoggerFactory.test.ts @@ -0,0 +1,21 @@ +import { LoggerFactory } from '../LoggerFactory'; + +describe('LoggerFactory', () => { + it('should get root logger', () => { + const expectedLoggerName = 'root'; + const logger = LoggerFactory.getRootLogger(); + expect(logger['name']).toBe(expectedLoggerName); + }); + + it('should get logger with empty name', () => { + const expectedLoggerName = 'root'; + const logger = LoggerFactory.getLogger(''); + expect(logger['name']).toBe(expectedLoggerName); + }); + + it('should get logger with name', () => { + const expectedLoggerName = 'test'; + const logger = LoggerFactory.getLogger(expectedLoggerName); + expect(logger['name']).toBe(expectedLoggerName); + }); +}); diff --git a/packages/client/src/util/__tests__/MetricUtils.test.ts b/packages/client/src/util/__tests__/MetricUtils.test.ts new file mode 100644 index 00000000..8990d245 --- /dev/null +++ b/packages/client/src/util/__tests__/MetricUtils.test.ts @@ -0,0 +1,47 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; +import { EVENT, trackEvent } from '../MetricUtils'; + +jest.mock('../../util/LoggerFactory', () => { + return { + error: () => {} + }; +}); + +describe('MetricUtils', () => { + let httpMock; + + beforeEach(() => { + httpMock = new MockAdapter(axios); + }); + + afterEach(() => { + httpMock.restore(); + }); + + it('should track load config event with empty dimensions', async () => { + httpMock.onPost(`/metrics`).reply(200); + await expect(trackEvent(EVENT.LOAD_CONFIG)).resolves; + }); + + it('should track new config event with empty dimensions', async () => { + httpMock.onPost(`/metrics`).reply(200); + await expect(trackEvent(EVENT.NEW_CONFIG)).resolves; + }); + + it('should track new config event with dimensions', async () => { + const dimensions = { key: 'value' }; + httpMock.onPost(`/metrics`).reply(200); + await expect(trackEvent(EVENT.NEW_CONFIG, dimensions)).resolves; + }); + + it('should track page view event with empty dimensions', async () => { + httpMock.onPost(`/metrics`).reply(200); + await expect(trackEvent(EVENT.PAGE_VIEW)).resolves; + }); + + it('should fail on track event', async () => { + httpMock.onGet(`/metrics`).reply(404); + await expect(trackEvent(EVENT.LOAD_CONFIG)).rejects; + }); +}); diff --git a/packages/client/src/util/__tests__/OptionalUtils.test.ts b/packages/client/src/util/__tests__/OptionalUtils.test.ts new file mode 100644 index 00000000..bbc90c49 --- /dev/null +++ b/packages/client/src/util/__tests__/OptionalUtils.test.ts @@ -0,0 +1,102 @@ +import { safeGet, safeGetAsync, trimToEmpty } from '../OptionalUtils'; + +describe('OptionalUtils safeGet', () => { + it('should safely get value', () => { + const inputObject = 'foo'; + const expectedValue = 'foo'; + const actualValue = safeGet(() => inputObject); + expect(actualValue.get()).toEqual(expectedValue); + }); + + it('should safely get value of null and return empty', () => { + const inputObject = null; + const actualValue = safeGet(() => inputObject); + expect(actualValue.isPresent()).toEqual(false); + }); + + it('should safely get value of undefined and return empty', () => { + const inputObject = undefined; + const actualValue = safeGet(() => inputObject); + expect(actualValue.isPresent()).toEqual(false); + }); + + it('should safely get value of nested object', () => { + const inputObject = { + foo: { + bar: 'baz' + } + }; + const expectedValue = 'baz'; + const actualValue = safeGet(() => inputObject.foo.bar); + expect(actualValue.get()).toEqual(expectedValue); + }); + + it('should safely get value of null in nested object and return empty', () => { + const inputObject = { + foo: { + bar: null + } + }; + const actualValue = safeGet(() => inputObject.foo.bar); + expect(actualValue.isPresent()).toEqual(false); + }); + + it('should safely get value of undefined in nested object and return empty', () => { + const inputObject = { + foo: { + bar: undefined + } + }; + const actualValue = safeGet(() => inputObject.foo.bar); + expect(actualValue.isPresent()).toEqual(false); + }); +}); + +describe('OptionalUtils safeGetAsync', () => { + it('should safely get value', async () => { + const inputObject = Promise.resolve('foo'); + const expectedValue = 'foo'; + const actualValue = await safeGetAsync(() => inputObject); + expect(actualValue.get()).toEqual(expectedValue); + }); + + it('should safely get value of null and return empty', async () => { + const inputObject = Promise.resolve(null); + const actualValue = await safeGetAsync(() => inputObject); + expect(actualValue.isPresent()).toEqual(false); + }); + + it('should safely get value of undefined and return empty', async () => { + const inputObject = Promise.resolve(undefined); + const actualValue = await safeGetAsync(() => inputObject); + expect(actualValue.isPresent()).toEqual(false); + }); +}); + +describe('OptionalUtils trimToEmpty', () => { + it('should trim string and return string', () => { + const inputString = ' test '; + const expectedString = 'test'; + const actualString = trimToEmpty(inputString); + expect(actualString.get()).toEqual(expectedString); + }); + + it('should trim already trimmed string and return string', () => { + const inputString = 'test'; + const expectedString = 'test'; + const actualString = trimToEmpty(inputString); + expect(actualString.get()).toEqual(expectedString); + }); + + it('should trim empty string and return empty', () => { + const inputString = ''; + const actualString = trimToEmpty(inputString); + expect(actualString.isPresent()).toEqual(false); + }); + + it('should trim undefined string and return empty', () => { + const inputString = undefined; + const actualString = trimToEmpty(inputString); + expect(actualString.isPresent()).toEqual(false); + }); +}); diff --git a/packages/client/src/util/__tests__/ScoreClassUtils.test.ts b/packages/client/src/util/__tests__/ScoreClassUtils.test.ts new file mode 100644 index 00000000..375f2bbd --- /dev/null +++ b/packages/client/src/util/__tests__/ScoreClassUtils.test.ts @@ -0,0 +1,145 @@ +import { getClassFromScore, getGroupClassFromScore } from '../ScoreClassUtils'; + +describe('ScoreClassUtils getClassFromScore', () => { + it('should get class from score and not be the last execution and return pass', () => { + const score = 100; + const canaryScores = [100, 100, 100]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 1; + const expectedClass = 'pass'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and not be the last execution and return marginal', () => { + const score = 80; + const canaryScores = [100, 80, 100]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 1; + const expectedClass = 'marginal'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and not be the last execution and return fail', () => { + const score = 60; + const canaryScores = [60, 60, 60]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 1; + const expectedClass = 'fail'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and be the last execution and return pass', () => { + const score = 95; + const canaryScores = [75, 75, 95]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 2; + const expectedClass = 'pass'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and be the last execution and return fail', () => { + const score = 60; + const canaryScores = [60, 60, 60]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 2; + const expectedClass = 'fail'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and be the only execution and return pass', () => { + const score = 100; + const canaryScores = [100]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 0; + const expectedClass = 'pass'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score and be the only execution and return fail', () => { + const score = 0; + const canaryScores = [0]; + const scoreThresholds = { marginal: 70, pass: 90 }; + const canaryRunIndex = 2; + const expectedClass = 'fail'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score with empty arrays and pass', () => { + const score = 100; + const canaryScores = []; + const scoreThresholds = { marginal: 75, pass: 95 }; + const canaryRunIndex = 2; + const expectedClass = 'pass'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score with empty arrays and be marginal', () => { + const score = 80; + const canaryScores = []; + const scoreThresholds = { marginal: 75, pass: 95 }; + const canaryRunIndex = 0; + const expectedClass = 'marginal'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get class from score with empty arrays and fail', () => { + const score = 0; + const canaryScores = []; + const scoreThresholds = { marginal: 0, pass: 0 }; + const canaryRunIndex = 0; + const expectedClass = 'pass'; + const actualClass = getClassFromScore(score, canaryScores, scoreThresholds, canaryRunIndex); + expect(actualClass).toEqual(expectedClass); + }); +}); + +describe('ScoreClassUtils getGroupClassFromScore', () => { + it('should get group class from score and pass', () => { + const score = 100; + const scoreThresholds = { marginal: 70, pass: 90 }; + const expectedClass = 'pass'; + const actualClass = getGroupClassFromScore(score, scoreThresholds); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get group class from score and fail', () => { + const score = 0; + const scoreThresholds = { marginal: 70, pass: 90 }; + const expectedClass = 'fail'; + const actualClass = getGroupClassFromScore(score, scoreThresholds); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get group class from score and fail for being marginal', () => { + const score = 80; + const scoreThresholds = { marginal: 70, pass: 90 }; + const expectedClass = 'fail'; + const actualClass = getGroupClassFromScore(score, scoreThresholds); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get group class from score for lower bound edge case and pass', () => { + const score = 0; + const scoreThresholds = { marginal: 0, pass: 0 }; + const expectedClass = 'pass'; + const actualClass = getGroupClassFromScore(score, scoreThresholds); + expect(actualClass).toEqual(expectedClass); + }); + + it('should get group class from score for upper bound edge case and pass', () => { + const score = 100; + const scoreThresholds = { marginal: 100, pass: 100 }; + const expectedClass = 'pass'; + const actualClass = getGroupClassFromScore(score, scoreThresholds); + expect(actualClass).toEqual(expectedClass); + }); +}); diff --git a/packages/server/package.json b/packages/server/package.json index f259a6c9..e557a10c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "1.1.0", + "version": "1.2.0", "description": "The backend for the Referee UI", "author": "Justin Field ", "homepage": "",