Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'test/system/V1MessageWriterTest.ts')
-rw-r--r--test/system/V1MessageWriterTest.ts532
1 files changed, 532 insertions, 0 deletions
diff --git a/test/system/V1MessageWriterTest.ts b/test/system/V1MessageWriterTest.ts
new file mode 100644
index 0000000..271d735
--- /dev/null
+++ b/test/system/V1MessageWriterTest.ts
@@ -0,0 +1,532 @@
+/**
+ * V1 Message Writer
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of liza.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Tests for Version 1 of the avro message writer
+ */
+
+import { V1MessageWriter as Sut } from '../../src/system/avro/V1MessageWriter';
+import { hasContext, context } from '../../src/error/ContextError';
+import { AvroEncoderCtr } from '../../src/system/avro/AvroFactory';
+import { Delta, DeltaResult, DeltaType } from '../../src/bucket/delta';
+import { DocumentMeta, DocumentId } from '../../src/document/Document';
+import { Duplex } from 'stream';
+import { AvroSchema } from 'avro-js';
+
+import { expect, use as chai_use } from 'chai';
+chai_use( require( 'chai-as-promised' ) );
+
+const sinon = require( 'sinon' );
+
+describe( 'system.V1MessageWriter', () =>
+{
+ it( 'Rejects improperly formatted data', () =>
+ {
+ const delta = createMockDelta();
+ const bucket = createMockBucketData();
+ const ratedata = createMockBucketData();
+ const error = new Error( 'Oh no' );
+ const schema = createMockAvroSchema();
+ const ts = <UnixTimestamp>123;
+ const meta = <DocumentMeta>{
+ id: <DocumentId>123,
+ entity_name: 'Some Agency',
+ entity_id: 234,
+ startDate: <UnixTimestamp>345,
+ lastUpdate: <UnixTimestamp>456,
+ };
+
+ const expected = {
+ invalid_paths: 'Foo',
+ invalid_data: 'Bar',
+ };
+
+ const error_context = context( error, expected );
+
+ schema.isValid = () => { throw error_context; };
+
+ const result = new Sut(
+ createMockEncoderCtor( schema ),
+ schema,
+ ).write( ts, meta, delta, bucket, ratedata );
+
+ return Promise.all( [
+ expect( result ).to.eventually.be.rejectedWith( error ),
+ result.catch( e =>
+ {
+ if ( !hasContext( e ) )
+ {
+ return expect.fail();
+ }
+
+ return expect( e.context ).to.deep.equal( expected );
+ } )
+ ] );
+ } );
+
+
+ describe( '#avroEncode parses', () =>
+ {
+ [
+ {
+ label: 'Null value',
+ valid: true,
+ delta_data: { foo: null },
+ },
+ {
+ label: 'Null array',
+ valid: true,
+ delta_data: { foo: { 'array': [ null ] } },
+ },
+ {
+ label: 'Boolean value',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'boolean': true },
+ ] } },
+ },
+ {
+ label: 'Simple string',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ ] } },
+ },
+ {
+ label: 'Simple int',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'double': 123 },
+ ] } },
+ },
+ {
+ label: 'Nested array',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'array': [
+ { 'string': 'bar' },
+ ] },
+ ] } },
+ },
+ {
+ label: 'Array with nulls',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ null,
+ ] } },
+ },
+ {
+ label: 'Nested Array with mixed values',
+ valid: true,
+ delta_data: { foo: { 'array': [
+ { 'array': [
+ { 'string': 'bar' },
+ { 'double': 123321 },
+ null,
+ ] }
+ ] } },
+ },
+ {
+ label: 'Non-array',
+ valid: false,
+ delta_data: { foo: 'bar' },
+ },
+ {
+ label: 'Map objects',
+ valid: true,
+ delta_data: { 'foo': { 'array': [
+ { 'map': {
+ 'bar': { 'map': {
+ 'baz': { 'double': 1572903485000 },
+ } }
+ } }
+ ] } },
+ }
+ ].forEach( ( { label, delta_data, valid } ) =>
+ {
+ it( label, () =>
+ {
+ const data = createMockData( delta_data );
+ const schema = createMockAvroSchema();
+
+ const sut = new Sut(
+ createMockEncoderCtor( schema ),
+ schema
+ );
+
+ sut.avroEncode( data )
+ .then( b =>
+ {
+ expect( typeof(b) ).to.equal( 'object' );
+ expect( valid ).to.be.true;
+ } )
+ .catch( _ =>
+ {
+ expect( valid ).to.be.false;
+ } );
+ } );
+ } );
+ } );
+
+
+ describe( '#setDataTypes annotates', () =>
+ {
+ [
+ {
+ label: 'Null',
+ delta_data: null,
+ expected: null,
+ },
+ {
+ label: 'Null Value',
+ delta_data: { foo: null },
+ expected: { foo: null },
+ },
+ {
+ label: 'Boolean Value',
+ delta_data: { foo: [ true ] },
+ expected: { foo: { 'array': [
+ { 'boolean': true },
+ ] } },
+ },
+ {
+ label: 'Simple string',
+ delta_data: { foo: [
+ 'bar',
+ 'baz',
+ ] },
+ expected: { foo: { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ ] } },
+ },
+ {
+ label: 'Simple int',
+ delta_data: { foo: [
+ 123
+ ] },
+ expected: { foo: { 'array': [
+ { 'double': 123 },
+ ] } },
+ },
+ {
+ label: 'Nested array',
+ delta_data: { foo: [
+ [
+ 'bar',
+ 'baz',
+ ]
+ ] },
+ expected: { foo: { 'array': [
+ { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ ] },
+ ] } },
+ },
+ {
+ label: 'Double nested array',
+ delta_data: { foo: [
+ [
+ [
+ 'bar',
+ 123,
+ null
+ ],
+ ],
+ ] },
+ expected: { foo: { 'array': [
+ { 'array': [
+ { 'array': [
+ { 'string': 'bar' },
+ { 'double': 123 },
+ null,
+ ] },
+ ] },
+ ] } },
+ },
+ {
+ label: 'Array with nulls',
+ delta_data: { foo: [
+ 'bar',
+ 'baz',
+ null
+ ] },
+ expected: { foo: { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ null
+ ] } },
+ },
+ {
+ label: 'Nested Array with mixed values',
+ delta_data: { foo: [
+ [
+ 'bar',
+ 123321,
+ null,
+ ]
+ ] },
+ expected: { foo: { 'array': [
+ { 'array': [
+ { 'string': 'bar' },
+ { 'double': 123321 },
+ null,
+ ] },
+ ] } },
+ },
+ {
+ label: 'Nested Array with mixed values',
+ delta_data: { foo: [
+ {
+ 'bar': {
+ 'wer': 'qaz',
+ 'qwe': 1572903485000,
+ 'asd': true,
+ 'zxc': null,
+ },
+ },
+ ] },
+ expected: { 'foo': { 'array': [
+ { 'map': {
+ 'bar': { 'map': {
+ 'wer': { 'string': 'qaz' },
+ 'qwe': { 'double': 1572903485000 },
+ 'asd': { 'boolean': true },
+ 'zxc': null,
+ } },
+ } },
+ ] } },
+ },
+ ].forEach( ( { label, delta_data, expected } ) =>
+ {
+ it( label, () =>
+ {
+ const encoded = 'FooBar';
+ const avroEncoderCtr = createMockEncoder( encoded );
+ const stub_schema = <AvroSchema>{};
+ const sut = new Sut(
+ avroEncoderCtr,
+ stub_schema,
+ );
+ const actual = sut.setDataTypes( delta_data );
+
+ expect( actual ).to.deep.equal( expected );
+ } );
+ } );
+ } );
+
+
+ it( 'Message is formatted correctly', () =>
+ {
+ const bucket = { foo: [ 'bar', 'baz' ] };
+ const ratedata = {};
+ const doc_id = <DocumentId>123;
+ const entity_name = 'Some Agency';
+ const entity_id = 123;
+ const startDate = <UnixTimestamp>345;
+ const lastUpdate = <UnixTimestamp>456;
+ const schema = createMockAvroSchema();
+ const ts = <UnixTimestamp>123;
+ const encoder = createMockEncoderCtor( schema );
+ const meta = <DocumentMeta>{
+ id: doc_id,
+ entity_name: entity_name,
+ entity_id: entity_id,
+ startDate: startDate,
+ lastUpdate: lastUpdate,
+ };
+
+ const delta = <Delta<any>>{
+ type: <DeltaType>'data',
+ timestamp: <UnixTimestamp>123123123,
+ data: <DeltaResult<any>>{},
+ };
+
+ const expected = {
+ event: {
+ id: 'STEP_SAVE',
+ ts: ts,
+ actor: 'SERVER',
+ step: null,
+ },
+ document: {
+ id: doc_id,
+ created: startDate,
+ modified: lastUpdate,
+ },
+ session: {
+ Session: {
+ entity_name: entity_name,
+ entity_id: entity_id,
+ },
+ },
+ data: {
+ Data: {
+ bucket: {
+ 'foo': { 'array': [
+ { 'string': 'bar' },
+ { 'string': 'baz' },
+ ] }
+ },
+ },
+ },
+ ratedata: {
+ Data: {
+ bucket: {},
+ },
+ },
+ delta: {
+ Data: {
+ bucket: delta.data,
+ },
+ },
+ program: {
+ Program: {
+ id: 'quote_server',
+ version: '',
+ },
+ },
+ };
+
+ let is_valid_called = false;
+
+ schema.isValid = ( data: Record<string, any>, _:any ) =>
+ {
+ expect( data ).to.deep.equal( expected );
+
+ is_valid_called = true;
+
+ return null;
+ }
+
+ return expect( new Sut( encoder, schema )
+ .write( ts, meta, delta, bucket, ratedata ) )
+ .to.eventually.deep.equal( Buffer.from( '' ) )
+ .then( _ =>
+ {
+ expect( is_valid_called ).to.be.true;
+ } )
+ } );
+} );
+
+
+function createMockEncoder( mock_encoded_data: string ): AvroEncoderCtr
+{
+ return ( _schema: AvroSchema ) =>
+ {
+ const mock = sinon.mock( Duplex );
+
+ mock.on = ( _: string, __: any ) => {};
+ mock.end = ( _: any ) => { return mock_encoded_data; };
+
+ return mock;
+ };
+}
+
+
+function createMockData( delta_data: any ): any
+{
+
+ return {
+ event: {
+ id: 'RATE',
+ ts: 1573856916,
+ actor: 'SERVER',
+ step: null,
+ },
+ document: {
+ id: 123123,
+ created: 1573856916,
+ modified: 1573856916,
+ top_visited_step: '2',
+ },
+ data: null,
+ ratedata: null,
+ delta: {
+ Data: {
+ bucket: delta_data,
+ },
+ },
+ program: {
+ Program: {
+ id: 'quote_server',
+ version: 'dadaddwafdwa',
+ },
+ },
+ };
+}
+
+
+function createMockDelta(): Delta<any>
+{
+ return <Delta<any>>{
+ type: <DeltaType>'data',
+ timestamp: <UnixTimestamp>123123123,
+ data: <DeltaResult<any>>{},
+ }
+}
+
+
+function createMockBucketData(): Record<string, any>
+{
+ return {
+ foo: [ 'bar', 'baz' ]
+ }
+}
+
+
+function createMockEncoderCtor( stub_schema: AvroSchema ):
+ ( schema: AvroSchema ) => Duplex
+{
+ const events = <Record<string, () => void>>{};
+
+ const mock_duplex = <Duplex>(<unknown>{
+ on( event_name: string, callback: () => void )
+ {
+ events[ event_name ] = callback;
+ },
+
+ end()
+ {
+ events.end();
+ },
+ } );
+
+ return ( schema: AvroSchema ): Duplex =>
+ {
+ expect( schema ).to.equal( stub_schema );
+ return mock_duplex;
+ };
+}
+
+
+function createMockAvroSchema(): AvroSchema
+{
+ return <AvroSchema>{
+ toBuffer() { return null },
+ isValid() { return null },
+ encode() {},
+ toString() { return '' },
+ fromBuffer() { return {} },
+ };
+}