Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
afharo committed Feb 26, 2020
1 parent 528cb7f commit ad91cfe
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector';

import { registerApplicationUsageCollector } from './';
import {
ROLL_INDICES_INTERVAL,
SAVED_OBJECTS_TOTAL_TYPE,
} from './telemetry_application_usage_collector';

describe('telemetry_application_usage', () => {
jest.useFakeTimers();

let collector: CollectorOptions;

const usageCollectionMock: jest.Mocked<UsageCollectionSetup> = {
makeUsageCollector: jest.fn().mockImplementation(config => (collector = config)),
registerCollector: jest.fn(),
} as any;

const getUsageCollector = jest.fn();

beforeAll(() => registerApplicationUsageCollector(usageCollectionMock, getUsageCollector));
afterAll(() => jest.clearAllTimers());

test('registered collector is set', () => {
expect(collector).not.toBeUndefined();
});

test('if no savedObjectClient initialised, return undefined', async () => {
expect(await collector.fetch({})).toBeUndefined();
jest.runTimersToTime(ROLL_INDICES_INTERVAL);
});

test('when savedObjectClient is initialised, return something', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
savedObjectClient.find.mockImplementation(
async () =>
({
saved_objects: [],
total: 0,
} as any)
);
getUsageCollector.mockImplementation(() => savedObjectClient);

expect(await collector.fetch({})).toStrictEqual({});
jest.runTimersToTime(ROLL_INDICES_INTERVAL);
});

test('paging in findAll works', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
let total = 201;
savedObjectClient.find.mockImplementation(async opts => {
if (opts.type === SAVED_OBJECTS_TOTAL_TYPE) {
return {
saved_objects: [
{
id: 'test-id',
attributes: {
appId: 'appId',
minutesOnScreen: 10,
numberOfClicks: 10,
},
},
],
total: 1,
} as any;
}
if (opts.page > 2) {
return { saved_objects: [], total };
}
const doc = {
id: 'test-id',
attributes: {
appId: 'appId',
timestamp: new Date().toISOString(),
minutesOnScreen: 1,
numberOfClicks: 1,
},
};
const savedObjects = new Array(opts.perPage).fill(doc);
total = savedObjects.length * 2 + 1;
return { saved_objects: savedObjects, total };
});
getUsageCollector.mockImplementation(() => savedObjectClient);

expect(await collector.fetch({})).toStrictEqual({
appId: {
clicks_total: total - 1 + 10,
clicks_30_days: total - 1,
clicks_90_days: total - 1,
minutes_on_screen_total: total - 1 + 10,
minutes_on_screen_30_days: total - 1,
minutes_on_screen_90_days: total - 1,
},
});
jest.runTimersToTime(ROLL_INDICES_INTERVAL);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ import {
/**
* Roll indices every 24h
*/
const ROLL_INDICES_INTERVAL = 24 * 60 * 60 * 1000;
export const ROLL_INDICES_INTERVAL = 24 * 60 * 60 * 1000;

const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional';
/**
* Start rolling indices after 5 minutes up
*/
export const ROLL_INDICES_START = 5 * 60 * 1000;

export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
export const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional';

interface ApplicationUsageTotal extends SavedObjectAttributes {
appId: string;
Expand Down Expand Up @@ -157,7 +162,7 @@ export function registerApplicationUsageCollector(
usageCollection.registerCollector(collector);

setInterval(() => rollTotals(getSavedObjectsClient()), ROLL_INDICES_INTERVAL);
setTimeout(() => rollTotals(getSavedObjectsClient()), 5 * 60 * 1000); // On startup, wait 5 minutes to rollup totals.
setTimeout(() => rollTotals(getSavedObjectsClient()), ROLL_INDICES_START);
}

async function rollTotals(savedObjectsClient?: ISavedObjectsRepository) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Reporter } from '@kbn/analytics';
import { Subject } from 'rxjs';

import { reportApplicationUsage } from './application_usage';

describe('application_usage', () => {
test('report an appId change', () => {
const reporterMock: jest.Mocked<Reporter> = {
reportApplicationUsage: jest.fn(),
} as any;

const currentAppId$ = new Subject<string | undefined>();
reportApplicationUsage(currentAppId$, reporterMock);

currentAppId$.next('appId');

expect(reporterMock.reportApplicationUsage).toHaveBeenCalledWith('appId');
expect(reporterMock.reportApplicationUsage).toHaveBeenCalledTimes(1);
});

test('skip duplicates', () => {
const reporterMock: jest.Mocked<Reporter> = {
reportApplicationUsage: jest.fn(),
} as any;

const currentAppId$ = new Subject<string | undefined>();
reportApplicationUsage(currentAppId$, reporterMock);

currentAppId$.next('appId');
currentAppId$.next('appId');

expect(reporterMock.reportApplicationUsage).toHaveBeenCalledWith('appId');
expect(reporterMock.reportApplicationUsage).toHaveBeenCalledTimes(1);
});

test('skip if not a valid value', () => {
const reporterMock: jest.Mocked<Reporter> = {
reportApplicationUsage: jest.fn(),
} as any;

const currentAppId$ = new Subject<string | undefined>();
reportApplicationUsage(currentAppId$, reporterMock);

currentAppId$.next('');
currentAppId$.next('kibana');
currentAppId$.next(undefined);

expect(reporterMock.reportApplicationUsage).toHaveBeenCalledTimes(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { Observable, Subject, merge } from 'rxjs';
import { Observable } from 'rxjs';
import { filter, distinctUntilChanged } from 'rxjs/operators';
import { Reporter } from '@kbn/analytics';

Expand All @@ -30,8 +30,7 @@ export function reportApplicationUsage(
currentAppId$: Observable<string | undefined>,
reporter: Reporter
) {
const customAppIdEmitter$ = new Subject<string>();
merge(currentAppId$, customAppIdEmitter$)
currentAppId$
.pipe(
filter(appId => typeof appId === 'string' && !DO_NOT_REPORT.includes(appId)),
distinctUntilChanged()
Expand Down
102 changes: 102 additions & 0 deletions src/plugins/usage_collection/server/report/store_report.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
import { storeReport } from './store_report';
import { ReportSchemaType } from './schema';
import { METRIC_TYPE } from '../../public';

describe('store_report', () => {
test('stores report for all types of data', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 1,
userAgent: {
'key-user-agent': {
key: 'test-key',
type: METRIC_TYPE.USER_AGENT,
appName: 'test-app-name',
userAgent: 'test-user-agent',
},
},
uiStatsMetrics: {
any: {
key: 'test-key',
type: METRIC_TYPE.CLICK,
appName: 'test-app-name',
eventName: 'test-event-name',
stats: {
min: 1,
max: 2,
avg: 1.5,
sum: 3,
},
},
},
application_usage: {
appId: {
numberOfClicks: 3,
minutesOnScreen: 10,
},
},
};
await storeReport(savedObjectClient, report);

expect(savedObjectClient.create).toHaveBeenCalledWith(
'ui-metric',
{ count: 1 },
{
id: 'key-user-agent:test-user-agent',
overwrite: true,
}
);
expect(savedObjectClient.incrementCounter).toHaveBeenCalledWith(
'ui-metric',
'test-app-name:test-event-name',
'count'
);
expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith([
{
type: 'application_usage_transactional',
attributes: {
numberOfClicks: 3,
minutesOnScreen: 10,
appId: 'appId',
timestamp: expect.any(Date),
},
},
]);
});

test('it should not fail if nothing to store', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 1,
userAgent: void 0,
uiStatsMetrics: void 0,
application_usage: void 0,
};
await storeReport(savedObjectClient, report);

expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
expect(savedObjectClient.incrementCounter).not.toHaveBeenCalled();
expect(savedObjectClient.create).not.toHaveBeenCalled();
expect(savedObjectClient.create).not.toHaveBeenCalled();
});
});

0 comments on commit ad91cfe

Please sign in to comment.