Merge pull request #1356 from balena-io/singleton-eventtracker

Make the event-tracker module a singleton
This commit is contained in:
bulldozer-balena[bot] 2020-06-08 12:36:10 +00:00 committed by GitHub
commit f8e48573c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 326 additions and 305 deletions

View File

@ -10,7 +10,7 @@ import * as url from 'url';
import * as deviceRegister from './lib/register-device';
import * as config from './config';
import { EventTracker } from './event-tracker';
import * as eventTracker from './event-tracker';
import { loadBackupFromMigration } from './lib/migration';
import constants = require('./lib/constants');
@ -41,7 +41,6 @@ const INTERNAL_STATE_KEYS = [
];
export interface APIBinderConstructOpts {
eventTracker: EventTracker;
logger: Logger;
}
@ -68,7 +67,6 @@ export class APIBinder {
public router: express.Router;
private deviceState: DeviceState;
private eventTracker: EventTracker;
private logger: Logger;
public balenaApi: PinejsClientRequest | null = null;
@ -84,8 +82,7 @@ export class APIBinder {
private stateReportErrors = 0;
private readyForUpdates = false;
public constructor({ eventTracker, logger }: APIBinderConstructOpts) {
this.eventTracker = eventTracker;
public constructor({ logger }: APIBinderConstructOpts) {
this.logger = logger;
this.router = this.createAPIBinderRouter(this);
@ -549,7 +546,7 @@ export class APIBinder {
await this.report();
this.reportCurrentState();
} catch (e) {
this.eventTracker.track('Device state report failure', { error: e });
eventTracker.track('Device state report failure', { error: e });
// We use the poll interval as the upper limit of
// the exponential backoff
const maxDelay = await config.get('appUpdatePollInterval');
@ -828,7 +825,7 @@ export class APIBinder {
apiKey: null,
};
await config.set(configToUpdate);
this.eventTracker.track('Device bootstrap success');
eventTracker.track('Device bootstrap success');
}
// Now check if we need to pin the device
@ -847,11 +844,11 @@ export class APIBinder {
}
private async provisionOrRetry(retryDelay: number): Promise<void> {
this.eventTracker.track('Device bootstrap');
eventTracker.track('Device bootstrap');
try {
await this.provision();
} catch (e) {
this.eventTracker.track(`Device bootstrap failed, retrying`, {
eventTracker.track(`Device bootstrap failed, retrying`, {
error: e,
delay: retryDelay,
});
@ -889,7 +886,7 @@ export class APIBinder {
router.use(bodyParser.json({ limit: '10mb' }));
router.post('/v1/update', (req, res, next) => {
apiBinder.eventTracker.track('Update notification');
eventTracker.track('Update notification');
if (apiBinder.readyForUpdates) {
config
.get('instantUpdates')

View File

@ -4,7 +4,6 @@ import { Router } from 'express';
import Knex = require('knex');
import { ServiceAction } from './device-api/common';
import { EventTracker } from './event-tracker';
import { Logger } from './logger';
import { DeviceStatus, InstancedAppState } from './types/state';
@ -49,7 +48,6 @@ class ApplicationManager extends EventEmitter {
public _lockingIfNecessary: any;
public logger: Logger;
public deviceState: DeviceState;
public eventTracker: EventTracker;
public apiBinder: APIBinder;
public services: ServiceManager;
@ -67,7 +65,6 @@ class ApplicationManager extends EventEmitter {
public constructor({
logger: Logger,
eventTracker: EventTracker,
deviceState: DeviceState,
apiBinder: APIBinder,
});

View File

@ -79,7 +79,7 @@ const createApplicationManagerRouter = function (applications) {
};
export class ApplicationManager extends EventEmitter {
constructor({ logger, eventTracker, deviceState, apiBinder }) {
constructor({ logger, deviceState, apiBinder }) {
super();
this.serviceAction = serviceAction;
@ -170,7 +170,6 @@ export class ApplicationManager extends EventEmitter {
this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this);
this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
this.logger = logger;
this.eventTracker = eventTracker;
this.deviceState = deviceState;
this.apiBinder = apiBinder;
this.images = new Images({

View File

@ -1,12 +1,12 @@
import * as Promise from 'bluebird';
import * as _ from 'lodash';
import * as eventTracker from '../event-tracker';
import * as constants from '../lib/constants';
import { checkInt, checkTruthy } from '../lib/validation';
import { doRestart, doPurge, serviceAction } from './common';
export const createV1Api = function (router, applications) {
const { eventTracker } = applications;
router.post('/v1/restart', function (req, res, next) {
const appId = checkInt(req.body.appId);
const force = checkTruthy(req.body.force) ?? false;

View File

@ -10,7 +10,6 @@ import prettyMs = require('pretty-ms');
import * as config from './config';
import * as db from './db';
import EventTracker from './event-tracker';
import Logger from './logger';
import {
@ -177,7 +176,6 @@ function createDeviceStateRouter(deviceState: DeviceState) {
}
interface DeviceStateConstructOpts {
eventTracker: EventTracker;
logger: Logger;
apiBinder: APIBinder;
}
@ -218,7 +216,6 @@ type DeviceStateStep<T extends PossibleStepTargets> =
| ConfigStep;
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
public eventTracker: EventTracker;
public logger: Logger;
public applications: ApplicationManager;
@ -242,16 +239,14 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
public connected: boolean;
public router: express.Router;
constructor({ eventTracker, logger, apiBinder }: DeviceStateConstructOpts) {
constructor({ logger, apiBinder }: DeviceStateConstructOpts) {
super();
this.eventTracker = eventTracker;
this.logger = logger;
this.deviceConfig = new DeviceConfig({
logger: this.logger,
});
this.applications = new ApplicationManager({
logger: this.logger,
eventTracker: this.eventTracker,
deviceState: this,
apiBinder,
});

View File

@ -4,6 +4,7 @@ import { fs } from 'mz';
import { Image } from '../compose/images';
import DeviceState from '../device-state';
import * as config from '../config';
import * as eventTracker from '../event-tracker';
import constants = require('../lib/constants');
import { AppsJsonParseError, EISDIR, ENOENT } from '../lib/errors';
@ -111,7 +112,7 @@ export async function loadTargetFromFile(
if (ENOENT(e) || EISDIR(e)) {
log.debug('No apps.json file present, skipping preload');
} else {
deviceState.eventTracker.track('Loading preloaded apps failed', {
eventTracker.track('Loading preloaded apps failed', {
error: e,
});
}

View File

@ -1,22 +1,14 @@
import * as Bluebird from 'bluebird';
import mask = require('json-mask');
import * as _ from 'lodash';
import * as memoizee from 'memoizee';
import * as mixpanel from 'mixpanel';
import { ConfigType } from './config';
import * as config from './config';
import log from './lib/supervisor-console';
import supervisorVersion = require('./lib/supervisor-version');
export type EventTrackProperties = Dictionary<any>;
interface InitArgs {
uuid: ConfigType<'uuid'>;
unmanaged: ConfigType<'unmanaged'>;
mixpanelHost: ConfigType<'mixpanelHost'>;
mixpanelToken: ConfigType<'mixpanelToken'>;
}
// The minimum amount of time to wait between sending
// events of the same type
const eventDebounceTime = 60000;
@ -32,38 +24,47 @@ const mixpanelMask = [
'stateDiff/local(os_version,superisor_version,ip_address,apps/*/services)',
].join(',');
export class EventTracker {
private defaultProperties: EventTrackProperties | null;
private client: mixpanel.Mixpanel | null;
let defaultProperties: EventTrackProperties;
// We must export this for the tests, but we make no references
// to it within the rest of the supervisor codebase
export let client: mixpanel.Mixpanel | null = null;
public constructor() {
this.client = null;
this.defaultProperties = null;
}
export const initialized = (async () => {
await config.initialized;
public init({
const {
unmanaged,
mixpanelHost,
mixpanelToken,
uuid,
}: InitArgs): Bluebird<void> {
return Bluebird.try(() => {
this.defaultProperties = {
} = await config.getMany([
'unmanaged',
'mixpanelHost',
'mixpanelToken',
'uuid',
]);
defaultProperties = {
distinct_id: uuid,
uuid,
supervisorVersion,
};
if (unmanaged || mixpanelHost == null || mixpanelToken == null) {
return;
}
this.client = mixpanel.init(mixpanelToken, {
client = mixpanel.init(mixpanelToken, {
host: mixpanelHost.host,
path: mixpanelHost.path,
});
});
}
})();
export async function track(
event: string,
properties: EventTrackProperties | Error = {},
) {
await initialized;
public track(event: string, properties: EventTrackProperties | Error = {}) {
if (properties instanceof Error) {
properties = { error: properties };
}
@ -79,21 +80,21 @@ export class EventTracker {
// Don't send potentially sensitive information, by using a whitelist
properties = mask(properties, mixpanelMask);
this.logEvent('Event:', event, JSON.stringify(properties));
if (this.client == null) {
log.event('Event:', event, JSON.stringify(properties));
if (client == null) {
return;
}
properties = this.assignDefaultProperties(properties);
this.throttleddLogger(event)(properties);
properties = assignDefaultProperties(properties);
throttleddLogger(event)(properties);
}
private throttleddLogger = memoizee(
const throttleddLogger = memoizee(
(event: string) => {
// Call this function at maximum once every minute
return _.throttle(
(properties: EventTrackProperties | Error) => {
this.client?.track(event, properties);
client?.track(event, properties);
},
eventDebounceTime,
{ leading: true },
@ -102,15 +103,8 @@ export class EventTracker {
{ primitive: true },
);
private logEvent(...args: string[]) {
log.event(...args);
}
private assignDefaultProperties(
function assignDefaultProperties(
properties: EventTrackProperties,
): EventTrackProperties {
return _.merge({}, properties, this.defaultProperties);
return _.merge({}, properties, defaultProperties);
}
}
export default EventTracker;

View File

@ -3,7 +3,7 @@ import * as _ from 'lodash';
import * as config from './config';
import * as db from './db';
import { EventTracker } from './event-tracker';
import * as eventTracker from './event-tracker';
import { LogType } from './lib/log-types';
import { writeLock } from './lib/update-lock';
import {
@ -30,22 +30,16 @@ interface LoggerSetupOptions {
type LogEventObject = Dictionary<any> | null;
interface LoggerConstructOptions {
eventTracker: EventTracker;
}
export class Logger {
private backend: LogBackend | null = null;
private balenaBackend: BalenaLogBackend | null = null;
private localBackend: LocalLogBackend | null = null;
private eventTracker: EventTracker;
private containerLogs: { [containerId: string]: ContainerLogs } = {};
private logMonitor: LogMonitor;
public constructor({ eventTracker }: LoggerConstructOptions) {
public constructor() {
this.backend = null;
this.eventTracker = eventTracker;
this.logMonitor = new LogMonitor();
}
@ -144,7 +138,7 @@ export class Logger {
}
this.log(msgObj);
if (track) {
this.eventTracker.track(
eventTracker.track(
eventName != null ? eventName : message,
eventObj != null ? eventObj : {},
);

View File

@ -5,7 +5,7 @@ import * as _ from 'lodash';
import * as morgan from 'morgan';
import * as config from './config';
import { EventTracker } from './event-tracker';
import * as eventTracker from './event-tracker';
import blink = require('./lib/blink');
import * as iptables from './lib/iptables';
import { checkTruthy } from './lib/validation';
@ -76,7 +76,6 @@ const expressLogger = morgan(
);
interface SupervisorAPIConstructOpts {
eventTracker: EventTracker;
routers: express.Router[];
healthchecks: Array<() => Promise<boolean>>;
}
@ -86,7 +85,6 @@ interface SupervisorAPIStopOpts {
}
export class SupervisorAPI {
private eventTracker: EventTracker;
private routers: express.Router[];
private healthchecks: Array<() => Promise<boolean>>;
@ -101,12 +99,7 @@ export class SupervisorAPI {
}
: this.applyListeningRules.bind(this);
public constructor({
eventTracker,
routers,
healthchecks,
}: SupervisorAPIConstructOpts) {
this.eventTracker = eventTracker;
public constructor({ routers, healthchecks }: SupervisorAPIConstructOpts) {
this.routers = routers;
this.healthchecks = healthchecks;
@ -132,7 +125,7 @@ export class SupervisorAPI {
this.api.use(authenticate());
this.api.post('/v1/blink', (_req, res) => {
this.eventTracker.track('Device blink');
eventTracker.track('Device blink');
blink.pattern.start();
setTimeout(blink.pattern.stop, 15000);
return res.sendStatus(200);

View File

@ -2,7 +2,7 @@ import APIBinder from './api-binder';
import * as db from './db';
import * as config from './config';
import DeviceState from './device-state';
import EventTracker from './event-tracker';
import * as eventTracker from './event-tracker';
import { intialiseContractRequirements } from './lib/contracts';
import { normaliseLegacyDatabase } from './lib/migration';
import * as osRelease from './lib/os-release';
@ -29,21 +29,17 @@ const startupConfigFields: config.ConfigKey[] = [
];
export class Supervisor {
private eventTracker: EventTracker;
private logger: Logger;
private deviceState: DeviceState;
private apiBinder: APIBinder;
private api: SupervisorAPI;
public constructor() {
this.eventTracker = new EventTracker();
this.logger = new Logger({ eventTracker: this.eventTracker });
this.logger = new Logger();
this.apiBinder = new APIBinder({
eventTracker: this.eventTracker,
logger: this.logger,
});
this.deviceState = new DeviceState({
eventTracker: this.eventTracker,
logger: this.logger,
apiBinder: this.apiBinder,
});
@ -55,7 +51,6 @@ export class Supervisor {
this.deviceState.applications.proxyvisor.bindToAPI(this.apiBinder);
this.api = new SupervisorAPI({
eventTracker: this.eventTracker,
routers: [this.apiBinder.router, this.deviceState.router],
healthchecks: [
this.apiBinder.healthcheck.bind(this.apiBinder),
@ -69,16 +64,10 @@ export class Supervisor {
await db.initialized;
await config.initialized;
await eventTracker.initialized;
const conf = await config.getMany(startupConfigFields);
// We can't print to the dashboard until the logger
// has started up, so we leave a trail of breadcrumbs
// in the logs in case runtime fails to get to the
// first dashboard logs
log.debug('Starting event tracker');
await this.eventTracker.init(conf);
log.debug('Starting logging infrastructure');
this.logger.init({
enableLogs: conf.loggingEnabled,

View File

@ -216,9 +216,6 @@ describe('deviceState', () => {
let deviceState: DeviceState;
before(async () => {
await prepare();
const eventTracker = {
track: console.log,
};
stub(Service as any, 'extendEnvVars').callsFake((env) => {
env['ADDITIONAL_ENV_VAR'] = 'foo';
@ -231,7 +228,6 @@ describe('deviceState', () => {
});
deviceState = new DeviceState({
eventTracker: eventTracker as any,
logger: logger as any,
apiBinder: null as any,
});

View File

@ -1,67 +1,96 @@
import { Mixpanel } from 'mixpanel';
import * as mixpanel from 'mixpanel';
import { SinonStub, stub } from 'sinon';
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
import { expect } from './lib/chai-config';
import EventTracker from '../src/event-tracker';
import log from '../src/lib/supervisor-console';
import supervisorVersion = require('../src/lib/supervisor-version');
import * as config from '../src/config';
describe('EventTracker', () => {
let eventTrackerOffline: EventTracker;
let eventTracker: EventTracker;
let initStub: SinonStub;
let logEventStub: SinonStub;
before(() => {
initStub = stub(mixpanel, 'init').callsFake(
(token) =>
(({
token,
track: stub().returns(undefined),
} as unknown) as Mixpanel),
);
logEventStub = stub(log, 'event');
eventTrackerOffline = new EventTracker();
eventTracker = new EventTracker();
return stub(EventTracker.prototype as any, 'logEvent');
delete require.cache[require.resolve('../src/event-tracker')];
});
afterEach(() => {
logEventStub.reset();
});
after(() => {
(EventTracker.prototype as any).logEvent.restore();
return initStub.restore();
logEventStub.restore();
});
it('initializes in unmanaged mode', () => {
const promise = eventTrackerOffline.init({
describe('Unmanaged', () => {
let configStub: SinonStub;
let eventTracker: typeof import('../src/event-tracker');
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
unmanaged: true,
uuid: 'foobar',
mixpanelHost: { host: '', path: '' },
mixpanelToken: '',
}) as any,
);
eventTracker = await import('../src/event-tracker');
});
expect(promise).to.be.fulfilled.then(() => {
// @ts-ignore
expect(eventTrackerOffline.client).to.be.null;
after(() => {
configStub.restore();
delete require.cache[require.resolve('../src/event-tracker')];
});
it('initializes in unmanaged mode', () => {
expect(eventTracker.initialized).to.be.fulfilled.then(() => {
expect(eventTracker.client).to.be.null;
});
});
it('logs events in unmanaged mode, with the correct properties', () => {
eventTrackerOffline.track('Test event', { appId: 'someValue' });
// @ts-ignore
expect(eventTrackerOffline.logEvent).to.be.calledWith(
it('logs events in unmanaged mode, with the correct properties', async () => {
await eventTracker.track('Test event', { appId: 'someValue' });
expect(logEventStub).to.be.calledWith(
'Event:',
'Test event',
JSON.stringify({ appId: 'someValue' }),
);
});
});
it('initializes a mixpanel client when not in unmanaged mode', () => {
const promise = eventTracker.init({
describe('Init', () => {
let eventTracker: typeof import('../src/event-tracker');
let configStub: SinonStub;
let mixpanelSpy: SinonSpy;
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
mixpanelToken: 'someToken',
uuid: 'barbaz',
mixpanelHost: { host: '', path: '' },
unmanaged: false,
}) as any,
);
mixpanelSpy = spy(mixpanel, 'init');
eventTracker = await import('../src/event-tracker');
});
expect(promise).to.be.fulfilled.then(() => {
after(() => {
configStub.restore();
mixpanelSpy.restore();
delete require.cache[require.resolve('../src/event-tracker')];
});
it('initializes a mixpanel client when not in unmanaged mode', () => {
expect(eventTracker.initialized).to.be.fulfilled.then(() => {
expect(mixpanel.init).to.have.been.calledWith('someToken');
// @ts-ignore
expect(eventTracker.client.token).to.equal('someToken');
@ -69,11 +98,43 @@ describe('EventTracker', () => {
expect(eventTracker.client.track).to.be.a('function');
});
});
});
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', () => {
eventTracker.track('Test event 2', { appId: 'someOtherValue' });
// @ts-ignore
expect(eventTracker.logEvent).to.be.calledWith(
describe('Managed', () => {
let eventTracker: typeof import('../src/event-tracker');
let configStub: SinonStub;
let mixpanelStub: SinonStub;
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
mixpanelToken: 'someToken',
uuid: 'barbaz',
mixpanelHost: { host: '', path: '' },
unmanaged: false,
}) as any,
);
mixpanelStub = stub(mixpanel, 'init').returns({
token: 'someToken',
track: stub(),
} as any);
eventTracker = await import('../src/event-tracker');
await eventTracker.initialized;
});
after(() => {
configStub.restore();
mixpanelStub.restore();
delete require.cache[require.resolve('../src/event-tracker')];
});
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', async () => {
await eventTracker.track('Test event 2', { appId: 'someOtherValue' });
expect(logEventStub).to.be.calledWith(
'Event:',
'Test event 2',
JSON.stringify({ appId: 'someOtherValue' }),
@ -87,9 +148,9 @@ describe('EventTracker', () => {
});
});
it('can be passed an Error and it is added to the event properties', () => {
it('can be passed an Error and it is added to the event properties', async () => {
const theError = new Error('something went wrong');
eventTracker.track('Error event', theError);
await eventTracker.track('Error event', theError);
// @ts-ignore
expect(eventTracker.client.track).to.be.calledWith('Error event', {
error: {
@ -102,7 +163,7 @@ describe('EventTracker', () => {
});
});
it('hides service environment variables, to avoid logging keys or secrets', () => {
it('hides service environment variables, to avoid logging keys or secrets', async () => {
const props = {
service: {
appId: '1',
@ -113,7 +174,7 @@ describe('EventTracker', () => {
},
},
};
eventTracker.track('Some app event', props);
await eventTracker.track('Some app event', props);
// @ts-ignore
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
service: { appId: '1' },
@ -124,50 +185,65 @@ describe('EventTracker', () => {
});
it('should handle being passed no properties object', () => {
expect(eventTracker.track('no-options')).to.not.throw;
expect(eventTracker.track('no-options')).to.be.fulfilled;
});
});
return describe('Rate limiting', () => {
it('should rate limit events of the same type', () => {
// @ts-ignore
eventTracker.client.track.reset();
describe('Rate limiting', () => {
let eventTracker: typeof import('../src/event-tracker');
let mixpanelStub: SinonStub;
eventTracker.track('test', {});
eventTracker.track('test', {});
eventTracker.track('test', {});
eventTracker.track('test', {});
eventTracker.track('test', {});
// @ts-ignore
expect(eventTracker.client.track).to.have.callCount(1);
before(async () => {
mixpanelStub = stub(mixpanel, 'init').returns({
track: stub(),
} as any);
eventTracker = await import('../src/event-tracker');
await eventTracker.initialized;
});
it('should rate limit events of the same type with different arguments', () => {
// @ts-ignore
eventTracker.client.track.reset();
after(() => {
mixpanelStub.restore();
eventTracker.track('test2', { a: 1 });
eventTracker.track('test2', { b: 2 });
eventTracker.track('test2', { c: 3 });
eventTracker.track('test2', { d: 4 });
eventTracker.track('test2', { e: 5 });
// @ts-ignore
expect(eventTracker.client.track).to.have.callCount(1);
delete require.cache[require.resolve('../src/event-tracker')];
});
it('should not rate limit events of different types', () => {
// @ts-ignore
eventTracker.client.track.reset();
it('should rate limit events of the same type', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
eventTracker.track('test3', { a: 1 });
eventTracker.track('test4', { b: 2 });
eventTracker.track('test5', { c: 3 });
eventTracker.track('test6', { d: 4 });
eventTracker.track('test7', { e: 5 });
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
// @ts-ignore
expect(eventTracker.client.track).to.have.callCount(5);
expect(eventTracker.client?.track).to.have.callCount(1);
});
it('should rate limit events of the same type with different arguments', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
await eventTracker.track('test2', { a: 1 });
await eventTracker.track('test2', { b: 2 });
await eventTracker.track('test2', { c: 3 });
await eventTracker.track('test2', { d: 4 });
await eventTracker.track('test2', { e: 5 });
expect(eventTracker.client?.track).to.have.callCount(1);
});
it('should not rate limit events of different types', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
await eventTracker.track('test3', { a: 1 });
await eventTracker.track('test4', { b: 2 });
await eventTracker.track('test5', { c: 3 });
await eventTracker.track('test6', { d: 4 });
await eventTracker.track('test7', { e: 5 });
expect(eventTracker.client?.track).to.have.callCount(5);
});
});
});

View File

@ -7,6 +7,7 @@ import ApiBinder from '../src/api-binder';
import prepare = require('./lib/prepare');
import * as config from '../src/config';
import DeviceState from '../src/device-state';
import * as eventTracker from '../src/event-tracker';
import Log from '../src/lib/supervisor-console';
import chai = require('./lib/chai-config');
import balenaAPI = require('./lib/mocked-balena-api');
@ -28,10 +29,6 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
(config.configJsonBackend as any).cache = await (config.configJsonBackend as any).read();
await config.generateRequiredFields();
obj.eventTracker = {
track: stub().callsFake((ev, props) => console.log(ev, props)),
} as any;
obj.logger = {
clearOutOfDateDBLogs: () => {
/* noop */
@ -40,11 +37,9 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
obj.apiBinder = new ApiBinder({
logger: obj.logger,
eventTracker: obj.eventTracker,
});
obj.deviceState = new DeviceState({
eventTracker: obj.eventTracker,
logger: obj.logger,
apiBinder: obj.apiBinder,
});
@ -65,6 +60,14 @@ const mockProvisioningOpts = {
describe('ApiBinder', () => {
let server: Server;
beforeEach(() => {
stub(eventTracker, 'track');
});
afterEach(() => {
// @ts-expect-error Restoring a non-stub type function
eventTracker.track.restore();
});
before(() => {
spy(balenaAPI.balenaBackend!, 'registerHandler');
server = balenaAPI.listen(3000);
@ -102,9 +105,7 @@ describe('ApiBinder', () => {
// @ts-ignore
balenaAPI.balenaBackend!.registerHandler.resetHistory();
expect(components.eventTracker.track).to.be.calledWith(
'Device bootstrap success',
);
expect(eventTracker.track).to.be.calledWith('Device bootstrap success');
});
});

View File

@ -7,6 +7,9 @@ import * as sinon from 'sinon';
import { Logger } from '../src/logger';
import { ContainerLogs } from '../src/logging/container';
import * as eventTracker from '../src/event-tracker';
import { stub } from 'sinon';
describe('Logger', function () {
beforeEach(function () {
this._req = new stream.PassThrough();
@ -20,12 +23,9 @@ describe('Logger', function () {
this.requestStub = sinon.stub(https, 'request').returns(this._req);
this.fakeEventTracker = {
track: sinon.spy(),
};
this.eventTrackerStub = stub(eventTracker, 'track');
// @ts-ignore missing db property
this.logger = new Logger({ eventTracker: this.fakeEventTracker });
this.logger = new Logger();
return this.logger.init({
apiEndpoint: 'https://example.com',
uuid: 'deadbeef',
@ -38,6 +38,7 @@ describe('Logger', function () {
afterEach(function () {
this.requestStub.restore();
this.eventTrackerStub.restore();
});
it('waits the grace period before sending any logs', function () {
@ -108,7 +109,7 @@ describe('Logger', function () {
);
return Promise.delay(5500).then(() => {
expect(this.fakeEventTracker.track).to.be.calledWith('Some event name', {
expect(this.eventTrackerStub).to.be.calledWith('Some event name', {
someProp: 'someVal',
});
const lines = this._req.body.split('\n');

View File

@ -7,7 +7,6 @@ import Network from '../src/compose/network';
import Service from '../src/compose/service';
import Volume from '../src/compose/volume';
import DeviceState from '../src/device-state';
import EventTracker from '../src/event-tracker';
import * as dockerUtils from '../src/lib/docker-utils';
import chai = require('./lib/chai-config');
@ -126,14 +125,12 @@ const dependentDBFormat = {
describe('ApplicationManager', function () {
before(async function () {
await prepare();
const eventTracker = new EventTracker();
this.logger = {
clearOutOfDateDBLogs: () => {
/* noop */
},
} as any;
this.deviceState = new DeviceState({
eventTracker,
logger: this.logger,
apiBinder: null as any,
});

View File

@ -13,7 +13,6 @@ import { createV1Api } from '../../src/device-api/v1';
import { createV2Api } from '../../src/device-api/v2';
import APIBinder from '../../src/api-binder';
import DeviceState from '../../src/device-state';
import EventTracker from '../../src/event-tracker';
import SupervisorAPI from '../../src/supervisor-api';
const DB_PATH = './test/data/supervisor-api.sqlite';
@ -68,19 +67,17 @@ const STUBBED_VALUES = {
async function create(): Promise<SupervisorAPI> {
// Get SupervisorAPI construct options
const { eventTracker, deviceState, apiBinder } = await createAPIOpts();
const { deviceState, apiBinder } = await createAPIOpts();
// Stub functions
setupStubs();
// Create ApplicationManager
const appManager = new ApplicationManager({
eventTracker,
logger: null,
deviceState,
apiBinder: null,
});
// Create SupervisorAPI
const api = new SupervisorAPI({
eventTracker,
routers: [buildRoutes(appManager)],
healthchecks: [deviceState.healthcheck, apiBinder.healthcheck],
});
@ -103,20 +100,15 @@ async function createAPIOpts(): Promise<SupervisorAPIOpts> {
await db.initialized;
// Initialize and set values for mocked Config
await initConfig();
// Create EventTracker
const tracker = new EventTracker();
// Create deviceState
const deviceState = new DeviceState({
eventTracker: tracker,
logger: null as any,
apiBinder: null as any,
});
const apiBinder = new APIBinder({
eventTracker: tracker,
logger: null as any,
});
return {
eventTracker: tracker,
deviceState,
apiBinder,
};
@ -165,7 +157,6 @@ function restoreStubs() {
}
interface SupervisorAPIOpts {
eventTracker: EventTracker;
deviceState: DeviceState;
apiBinder: APIBinder;
}