Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorAustin Schaffer <austin.schaffer@ryansg.com>2019-11-26 19:31:39 -0500
committerAustin Schaffer <austin.schaffer@ryansg.com>2019-12-12 10:13:27 -0500
commit1ddb6f29eb7e9e1b55820f1c94ea9a4b73db7fb7 (patch)
treef2b9e06f513e26a7e3dedf07cfaf09ccadb5a515 /test
parent1b96cd91470bc01fb87ee0ee3e92f2e3c10f20f4 (diff)
downloadliza-1ddb6f29eb7e9e1b55820f1c94ea9a4b73db7fb7.tar.gz
liza-1ddb6f29eb7e9e1b55820f1c94ea9a4b73db7fb7.tar.bz2
liza-1ddb6f29eb7e9e1b55820f1c94ea9a4b73db7fb7.zip
[DEV-5312] Add logic to apply delta to bucket then publish modified bucket
Diffstat (limited to 'test')
-rw-r--r--test/bucket/delta.ts205
-rw-r--r--test/system/DeltaProcessorTest.ts256
-rw-r--r--test/system/DeltaPublisherTest.ts4
-rw-r--r--test/system/EventLoggerTest.ts103
-rw-r--r--test/system/MetricsCollectorTest.ts157
5 files changed, 627 insertions, 98 deletions
diff --git a/test/bucket/delta.ts b/test/bucket/delta.ts
index ba1d192..cc9a790 100644
--- a/test/bucket/delta.ts
+++ b/test/bucket/delta.ts
@@ -19,12 +19,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-import { createDelta as sut, Kv , DeltaResult} from "../../src/bucket/delta";
+import {
+ createDelta as sutCreate,
+ applyDelta as sutApply,
+ Kv,
+ DeltaResult,
+} from "../../src/bucket/delta";
import { expect, use as chai_use } from 'chai';
chai_use( require( 'chai-as-promised' ) );
-interface SutTestCase<T>
+
+interface SutCreateTestCase<T>
{
label: string;
src_data: T;
@@ -32,68 +38,149 @@ interface SutTestCase<T>
expected: DeltaResult<T>;
}
+
+interface SutApplyTestCase<T>
+{
+ label: string;
+ bucket: T;
+ delta: DeltaResult<T>;
+ expected: T;
+}
+
+
describe( 'Delta', () =>
{
- ( <SutTestCase<Kv<string>>[]>[
- {
- label: "No changes are made, key is dropped",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', 'baz' ] },
- expected: {},
- },
- {
- label: "Only the unchanged key is dropped",
- src_data: { foo: [ 'bar', 'baz' ], bar: [ 'qwe' ] },
- dest_data: { foo: [ 'bar', 'baz' ], bar: [ 'asd' ] },
- expected: { bar: [ 'asd' ] },
- },
- {
- label: "Changed values are updated by index with old value",
- src_data: { foo: [ "bar", "baz", "quux" ] },
- dest_data: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
- expected: { foo: [ undefined, "quuux", null ], moo: [ "cow" ] },
- },
- {
- label: "The keys are null when they don't exist in first set",
- src_data: {},
- dest_data: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
- expected: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
- },
- {
- label: "Removed keys in new set show up",
- src_data: { foo: [ "bar" ] },
- dest_data: {},
- expected: { foo: null },
- },
- {
- label: "Indexes after a null terminator aren't included",
- src_data: { foo: [ "one", "two", "three", "four" ] },
- dest_data: { foo: [ "one", "done" ] },
- expected: { foo: [ undefined, "done", null ] },
- },
- {
- label: "Consider nested arrays to be scalar values",
- src_data: { foo: [ [ "one" ], [ "two", "three" ] ] },
- dest_data: { foo: [ [ "one" ], [ "two" ] ] },
- expected: { foo: [ undefined, [ "two" ] ] },
- },
- {
- label: "Don't evaluate zeros as falsy",
- src_data: { foo: [ 0 ] },
- dest_data: { foo: [ 0 ] },
- expected: {},
- },
+ describe( '#createDelta', () =>
+ {
+ ( <SutCreateTestCase<Kv<string>>[]>[
+ {
+ label: "No changes are made, key is dropped",
+ src_data: { foo: [ 'bar', 'baz' ] },
+ dest_data: { foo: [ 'bar', 'baz' ] },
+ expected: {},
+ },
+ {
+ label: "Only the unchanged key is dropped",
+ src_data: { foo: [ 'bar', 'baz' ], bar: [ 'qwe' ] },
+ dest_data: { foo: [ 'bar', 'baz' ], bar: [ 'asd' ] },
+ expected: { bar: [ 'asd' ] },
+ },
+ {
+ label: "Changed values are updated by index with old value",
+ src_data: { foo: [ "bar", "baz", "quux" ] },
+ dest_data: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ expected: { foo: [ undefined, "quuux", null ], moo: [ "cow" ] },
+ },
+ {
+ label: "The keys are null when they don't exist in first set",
+ src_data: {},
+ dest_data: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ expected: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ },
+ {
+ label: "Removed keys in new set show up",
+ src_data: { foo: [ "bar" ] },
+ dest_data: {},
+ expected: { foo: null },
+ },
+ {
+ label: "Indexes after a null terminator aren't included",
+ src_data: { foo: [ "one", "two", "three", "four" ] },
+ dest_data: { foo: [ "one", "done" ] },
+ expected: { foo: [ undefined, "done", null ] },
+ },
+ {
+ label: "Consider nested arrays to be scalar values",
+ src_data: { foo: [ [ "one" ], [ "two", "three" ] ] },
+ dest_data: { foo: [ [ "one" ], [ "two" ] ] },
+ expected: { foo: [ undefined, [ "two" ] ] },
+ },
+ {
+ label: "Don't evaluate zeros as falsy",
+ src_data: { foo: [ 0 ] },
+ dest_data: { foo: [ 0 ] },
+ expected: {},
+ },
+ {
+ label: "Don't evaluate empty strings as falsy",
+ src_data: { foo: [ '' ] },
+ dest_data: { foo: [ '' ] },
+ expected: {},
+ },
+ ] ).forEach( ( { label, src_data, dest_data, expected } ) =>
{
- label: "Don't evaluate empty strings as falsy",
- src_data: { foo: [ '' ] },
- dest_data: { foo: [ '' ] },
- expected: {},
- },
- ] ).forEach( ( { label, src_data, dest_data, expected } ) =>
+ it( label, () =>
+ {
+ expect( sutCreate( src_data, dest_data ) )
+ .to.deep.equal( expected );
+ } );
+ } );
+ } );
+
+
+ describe( '#applyDelta', () =>
{
- it( label, () =>
+ ( <SutApplyTestCase<Kv<string>>[]>[
+ {
+ label: "Empty delta changes nothing",
+ bucket: { foo: [ 'bar', 'baz' ] },
+ delta: {},
+ expected: { foo: [ 'bar', 'baz' ] },
+ },
+ {
+ label: "Field not in delta is unchanged",
+ bucket: { foo: [ 'bar', 'baz' ], bar: [ 'qwe' ] },
+ delta: { bar: [ 'asd' ] },
+ expected: { foo: [ 'bar', 'baz' ], bar: [ 'asd' ] },
+ },
+ {
+ label: "Undefined doesn't affect its corresponding index",
+ bucket: { foo: [ "bar", "baz", "quux" ] },
+ delta: { foo: [ undefined, "quuux", null ], moo: [ "cow" ] },
+ expected: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ },
+ {
+ label: "Delta applys correctly on empty bucket",
+ bucket: {},
+ delta: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ expected: { foo: [ "bar", "quuux" ], moo: [ "cow" ] },
+ },
+ {
+ label: "Keys are removed properly",
+ bucket: { foo: [ "bar" ] },
+ delta: { foo: null },
+ expected: {},
+ },
+ {
+ label: "Indexes after a null terminator aren't included",
+ bucket: { foo: [ "one", "two", "three", "four" ] },
+ delta: { foo: [ undefined, "done", null ] },
+ expected: { foo: [ "one", "done" ] },
+ },
+ {
+ label: "Consider nested arrays to be scalar values",
+ bucket: { foo: [ [ "one" ], [ "two", "three" ] ] },
+ delta: { foo: [ undefined, [ "two" ] ] },
+ expected: { foo: [ [ "one" ], [ "two" ] ] },
+ },
+ {
+ label: "Don't evaluate zeros as falsy",
+ bucket: { foo: [ 0 ] },
+ delta: {},
+ expected: { foo: [ 0 ] },
+ },
+ {
+ label: "Don't evaluate empty strings as falsy",
+ bucket: { foo: [ '' ] },
+ delta: {},
+ expected: { foo: [ '' ] },
+ },
+ ] ).forEach( ( { label, bucket, delta, expected } ) =>
{
- expect( sut( src_data, dest_data ) ).to.deep.equal( expected );
+ it( label, () =>
+ {
+ expect( sutApply( bucket, delta ) ).to.deep.equal( expected );
+ } );
} );
} );
} );
diff --git a/test/system/DeltaProcessorTest.ts b/test/system/DeltaProcessorTest.ts
index e1db8d2..d0cd8f8 100644
--- a/test/system/DeltaProcessorTest.ts
+++ b/test/system/DeltaProcessorTest.ts
@@ -22,7 +22,7 @@
import { DeltaProcessor as Sut } from '../../src/system/DeltaProcessor';
import { AmqpPublisher } from '../../src/system/AmqpPublisher';
import { DeltaDao } from '../../src/system/db/DeltaDao';
-import { MongoDeltaType } from '../../src/system/db/MongoDeltaDao';
+import { DeltaType } from "../../src/bucket/delta";
import { EventEmitter } from 'events';
import { expect, use as chai_use } from 'chai';
@@ -40,21 +40,21 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
],
ratedata: [
{
- data: { foo: 'third_bar' },
+ data: { foo: [ 'third_bar' ] },
timestamp: 345,
},
{
- data: { foo: 'fourth_bar' },
+ data: { foo: [ 'fourth_bar' ] },
timestamp: 456,
},
]
@@ -62,22 +62,22 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
type: 'data',
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
{
- data: { foo: 'third_bar' },
+ data: { foo: [ 'third_bar' ] },
timestamp: 345,
type: 'ratedata',
},
{
- data: { foo: 'fourth_bar' },
+ data: { foo: [ 'fourth_bar' ] },
timestamp: 456,
type: 'ratedata',
},
@@ -89,11 +89,11 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
],
@@ -102,12 +102,12 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
type: 'data',
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
@@ -119,21 +119,21 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
{
- data: { foo: 'fourth_bar' },
+ data: { foo: [ 'fourth_bar' ] },
timestamp: 456,
},
],
ratedata: [
{
- data: { foo: 'third_bar' },
+ data: { foo: [ 'third_bar' ] },
timestamp: 345,
},
],
@@ -141,22 +141,22 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
type: 'data',
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
{
- data: { foo: 'third_bar' },
+ data: { foo: [ 'third_bar' ] },
timestamp: 345,
type: 'ratedata',
},
{
- data: { foo: 'fourth_bar' },
+ data: { foo: [ 'fourth_bar' ] },
timestamp: 456,
type: 'data',
},
@@ -181,7 +181,7 @@ describe( 'system.DeltaProcessor', () =>
{
( <{
label: string,
- type: MongoDeltaType,
+ type: DeltaType,
given: any,
expected: any
}[]>[
@@ -200,11 +200,11 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
],
@@ -212,12 +212,12 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
type: 'data',
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
@@ -230,11 +230,11 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
],
@@ -245,12 +245,12 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
type: 'data',
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
@@ -263,11 +263,11 @@ describe( 'system.DeltaProcessor', () =>
rdelta: {
data: [
{
- data: { foo: 'first_bar' },
+ data: { foo: [ 'first_bar' ] },
timestamp: 123,
},
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
},
],
@@ -278,7 +278,7 @@ describe( 'system.DeltaProcessor', () =>
},
expected: [
{
- data: { foo: 'second_bar' },
+ data: { foo: [ 'second_bar' ] },
timestamp: 234,
type: 'data',
},
@@ -297,6 +297,188 @@ describe( 'system.DeltaProcessor', () =>
expect( actual ).to.deep.equal( expected );
} ) );
} );
+
+
+ describe( '#process', () =>
+ {
+ ( <{
+ label: string,
+ given: any[],
+ expected: any
+ }[]>[
+ {
+ label: 'No deltas are processed',
+ docs: [
+ {
+ id: 123,
+ lastUpdate: 123123123,
+ bucket: {},
+ rdelta: {},
+ },
+ ],
+ expected: [],
+ },
+ {
+ label: 'Publishes deltas in order',
+ given: [
+ {
+ id: 123,
+ lastUpdate: 123123123,
+ bucket: { foo: [ 'start_bar' ] },
+ rdelta: {
+ data: [
+ {
+ data: { foo: [ 'first_bar' ] },
+ timestamp: 123123,
+ },
+ {
+ data: { foo: [ 'second_bar' ] },
+ timestamp: 234123,
+ },
+ ],
+ },
+ },
+ ],
+ expected: [
+ {
+ delta: { foo: [ 'first_bar' ] },
+ bucket: { foo: [ 'first_bar' ] },
+ doc_id: 123,
+ },
+ {
+ delta: { foo: [ 'second_bar' ] },
+ bucket: { foo: [ 'second_bar' ] },
+ doc_id: 123,
+ },
+ ],
+ },
+ {
+ label: 'Publishes deltas in order for multiple documents',
+ given: [
+ {
+ id: 123,
+ lastUpdate: 123123123,
+ bucket: { foo: 'start_bar' },
+ rdelta: {
+ data: [
+ {
+ data: { foo: [ 'second_bar' ] },
+ timestamp: 234,
+ },
+ ],
+ ratedata: [
+ {
+ data: { foo: [ 'first_bar' ] },
+ timestamp: 123,
+ },
+ ],
+ },
+ },
+ {
+ id: 234,
+ lastUpdate: 123123123,
+ bucket: { foo: 'start_bar' },
+ rdelta: {
+ data: [
+ {
+ data: { foo: [ 'first_bar' ] },
+ timestamp: 123,
+ },
+ {
+ data: { foo: [ 'second_bar' ] },
+ timestamp: 234,
+ },
+ {
+ data: { foo: [ 'third_bar' ] },
+ timestamp: 345,
+ },
+ ],
+ },
+ },
+ {
+ id: 345,
+ lastUpdate: 123123123,
+ bucket: { foo: 'start_bar' },
+ rdelta: {
+ ratedata: [
+ {
+ data: { foo: [ 'first_bar' ] },
+ timestamp: 123,
+ },
+ {
+ data: { foo: [ 'second_bar' ] },
+ timestamp: 234,
+ },
+ ],
+ },
+ },
+ ],
+ expected: [
+ {
+ delta: { foo: [ 'first_bar' ] },
+ bucket: { foo: [ 'first_bar' ] },
+ doc_id: 123,
+ },
+ {
+ delta: { foo: [ 'second_bar' ] },
+ bucket: { foo: [ 'second_bar' ] },
+ doc_id: 123,
+ },
+ {
+ delta: { foo: [ 'first_bar' ] },
+ bucket: { foo: [ 'first_bar' ] },
+ doc_id: 234,
+ },
+ {
+ delta: { foo: [ 'second_bar' ] },
+ bucket: { foo: [ 'second_bar' ] },
+ doc_id: 234,
+ },
+ {
+ delta: { foo: [ 'third_bar' ] },
+ bucket: { foo: [ 'third_bar' ] },
+ doc_id: 234,
+ },
+ {
+ delta: { foo: [ 'first_bar' ] },
+ bucket: { foo: [ 'first_bar' ] },
+ doc_id: 345,
+ },
+ {
+ delta: { foo: [ 'second_bar' ] },
+ bucket: { foo: [ 'second_bar' ] },
+ doc_id: 345,
+ },
+ ],
+ },
+ ] ).forEach( ( { given, expected, label } ) => it( label, () =>
+ {
+ let published: any = [];
+ const dao = createMockDeltaDao();
+ const publisher = createMockDeltaPublisher();
+ const emitter = new EventEmitter();
+
+ dao.getUnprocessedDocuments = (): Promise<Record<string, any>[]> =>
+ {
+ return Promise.resolve( given );
+ }
+
+ publisher.publish = ( delta, bucket, doc_id ): Promise<void> =>
+ {
+ published.push( {
+ delta: delta.data,
+ bucket: bucket,
+ doc_id: doc_id,
+ } );
+
+ return Promise.resolve();
+ }
+
+ return expect( new Sut( dao, publisher, emitter ).process() )
+ .to.eventually.deep.equal( undefined )
+ .then( _ => expect( published ).to.deep.equal( expected ) );
+ } ) );
+ } );
} );
@@ -304,9 +486,9 @@ function createMockDeltaDao(): DeltaDao
{
return <DeltaDao>{
getUnprocessedDocuments() { return Promise.resolve( [] ); },
- advanceDeltaIndex() { return Promise.resolve( null ); },
- markDocumentAsProcessed() { return Promise.resolve( null ); },
- setErrorFlag() { return Promise.resolve( null ); },
+ advanceDeltaIndex() { return Promise.resolve(); },
+ markDocumentAsProcessed() { return Promise.resolve(); },
+ setErrorFlag() { return Promise.resolve(); },
getErrorCount() { return Promise.resolve( 0 ); },
};
}
@@ -315,6 +497,6 @@ function createMockDeltaDao(): DeltaDao
function createMockDeltaPublisher(): AmqpPublisher
{
return <AmqpPublisher>{
- publish() { return Promise.resolve( null ); },
+ publish() { return Promise.resolve(); },
};
}
diff --git a/test/system/DeltaPublisherTest.ts b/test/system/DeltaPublisherTest.ts
index cf3872f..40663d0 100644
--- a/test/system/DeltaPublisherTest.ts
+++ b/test/system/DeltaPublisherTest.ts
@@ -168,7 +168,7 @@ describe( 'server.DeltaPublisher', () =>
} );
- describe( '#avroFormat formats', () =>
+ describe( '#setDataTypes annotates', () =>
{
[
{
@@ -304,7 +304,7 @@ describe( 'server.DeltaPublisher', () =>
const emitter = <EventEmitter>{}
const conf = createMockConf();
const sut = new Sut( conf, emitter, ts_ctr );
- const actual = sut.avroFormat( delta_data );
+ const actual = sut.setDataTypes( delta_data );
expect( actual ).to.deep.equal( expected );
} );
diff --git a/test/system/EventLoggerTest.ts b/test/system/EventLoggerTest.ts
new file mode 100644
index 0000000..b3d5f0f
--- /dev/null
+++ b/test/system/EventLoggerTest.ts
@@ -0,0 +1,103 @@
+/**
+ * Event logger test
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { EventLogger as Sut } from '../../src/system/EventLogger';
+import { EventEmitter } from "events";
+import { expect } from 'chai';
+
+const sinon = require( 'sinon' );
+
+declare interface MockConsole extends Console {
+ getLevel(): string,
+}
+
+describe( 'system.EventLogger captures and logs events', () =>
+{
+ [
+ {
+ event_id: 'document-processed',
+ console_level: 'log',
+ },
+ {
+ event_id: 'delta-publish',
+ console_level: 'log',
+ },
+ {
+ event_id: 'amqp-conn-error',
+ console_level: 'warn',
+ },
+ {
+ event_id: 'amqp-reconnect',
+ console_level: 'warn',
+ },
+ {
+ event_id: 'amqp-reconnect-fail',
+ console_level: 'error',
+ },
+ {
+ event_id: 'avro-err',
+ console_level: 'error',
+ },
+ {
+ event_id: 'dao-err',
+ console_level: 'error',
+ },
+ {
+ event_id: 'publish-err',
+ console_level: 'error',
+ },
+ ].forEach( ( { event_id, console_level } ) =>
+ {
+ it( event_id + ' triggers console output level: ' + console_level, () =>
+ {
+ const emitter = new EventEmitter();
+ const con = createMockConsole();
+ const env = 'test';
+
+ new Sut( con, env, emitter, ts_ctr );
+
+ emitter.emit( event_id );
+
+ expect( con.getLevel() ).to.equal( console_level );
+ } );
+ } );
+} );
+
+
+function ts_ctr(): UnixTimestamp
+{
+ return <UnixTimestamp>Math.floor( new Date().getTime() / 1000 );
+}
+
+
+function createMockConsole(): MockConsole
+{
+ const mock = sinon.mock( console );
+
+ mock.level = '';
+ mock.info = ( _str: string ) => { mock.level = 'info'; };
+ mock.log = ( _str: string ) => { mock.level = 'log'; };
+ mock.warn = ( _str: string ) => { mock.level = 'warn'; };
+ mock.error = ( _str: string ) => { mock.level = 'error'; };
+ mock.getLevel = () => mock.level;
+
+ return mock;
+} \ No newline at end of file
diff --git a/test/system/MetricsCollectorTest.ts b/test/system/MetricsCollectorTest.ts
new file mode 100644
index 0000000..07867a8
--- /dev/null
+++ b/test/system/MetricsCollectorTest.ts
@@ -0,0 +1,157 @@
+/**
+ * Metrics collector test
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { PrometheusFactory } from '../../src/system/PrometheusFactory';
+import { Histogram, Pushgateway, Counter, Gauge } from 'prom-client';
+import { EventEmitter } from 'events';
+import { expect } from 'chai';
+import {
+ MetricsCollector as Sut,
+ PrometheusConfig,
+ MetricTimer,
+} from '../../src/system/MetricsCollector';
+
+const sinon = require( 'sinon' );
+
+describe( 'system.MetricsCollector captures events and pushes metrics', () =>
+{
+ it( 'process-complete event is hooked', () =>
+ {
+ let histogram_called = false;
+ let counter_called = false;
+
+ const emitter = new EventEmitter();
+ const conf = createMockConfig();
+ const timer = createMockTimer();
+ const factory = createMockFactory( {
+ histogram_cb: () => { histogram_called = true },
+ counter_cb: () => { counter_called = true },
+ } );
+
+ new Sut( factory, conf, emitter, timer );
+
+ emitter.emit( 'delta-process-end' );
+
+ expect( histogram_called ).to.be.true;
+ expect( counter_called ).to.be.true;
+ } );
+
+
+ it( 'process-error event is hooked', () =>
+ {
+ let counter_called = false;
+
+ const emitter = new EventEmitter();
+ const conf = createMockConfig();
+ const timer = createMockTimer();
+ const factory = createMockFactory( {
+ counter_cb: () => { counter_called = true },
+ } );
+
+ new Sut( factory, conf, emitter, timer );
+
+ emitter.emit( 'delta-process-error' );
+
+ expect( counter_called ).to.be.true;
+ } );
+
+
+ it( 'process-complete is timed properly', () =>
+ {
+ let actual_ms = 0;
+ const uid = 'foo';
+ const start_time_ns = 1234;
+ const end_time_ns = 5678;
+ const expected_ms = ( end_time_ns - start_time_ns ) / 1000;
+ const emitter = new EventEmitter();
+ const conf = createMockConfig();
+ const timer = createMockTimer( start_time_ns, end_time_ns );
+ const factory = createMockFactory( {
+ histogram_cb: ( n: number ) => { actual_ms = n },
+ } );
+
+ new Sut( factory, conf, emitter, timer );
+
+ emitter.emit( 'delta-process-start', uid );
+ emitter.emit( 'delta-process-end', uid );
+
+ expect( actual_ms ).to.be.equal( expected_ms );
+ } );
+} );
+
+
+function createMockFactory(
+ {
+ gateway_cb = () => {},
+ counter_cb = () => {},
+ histogram_cb = ( _n: number = 0 ) => {},
+ gauge_cb = ( _n: number = 0 ) => {},
+ }:
+ {
+ gateway_cb ?: () => void;
+ counter_cb ?: () => void;
+ histogram_cb ?: ( _n: number ) => void;
+ gauge_cb ?: ( _n: number ) => void;
+ }
+): PrometheusFactory
+{
+ const gateway = sinon.mock( Pushgateway );
+ const counter = sinon.mock( Counter );
+ const histogram = sinon.mock( Histogram );
+ const gauge = sinon.mock( Gauge );
+
+ gateway.pushAdd = gateway_cb;
+ counter.inc = counter_cb;
+ histogram.observe = histogram_cb;
+ gauge.set = gauge_cb;
+
+ return <PrometheusFactory>{
+ createGateway() { return gateway },
+ createCounter() { return counter },
+ createHistogram(){ return histogram },
+ createGauge() { return gauge },
+ };
+}
+
+
+function createMockConfig(): PrometheusConfig
+{
+ return <PrometheusConfig>{
+ hostname: 'foo.com',
+ port: 123,
+ env: 'test',
+ push_interval_ms: 1000,
+ }
+}
+
+
+function createMockTimer( _start: number = 0, _end: number = 0 ): MetricTimer
+{
+ return ( _start_time?: [ number, number ] ) =>
+ {
+ if ( !_start_time )
+ {
+ return [ 0, _start ];
+ }
+
+ return [ 0, _end - _start_time[ 1 ] ];
+ };
+} \ No newline at end of file