TokenDao: Add test and further refine types
This tests the existing state of TokenDao before additional modifications are made. This commit also further refines the types introduced in a previous commit. This is also the first test written in Typescript. * package.json.in (devDependencies): Add node, chai, and mocha types. * src/server/token/TokenDao.ts (updateToken): `data` accepts null (as it should). Do not conditionall add data to object (it doesn't matter for later retrieval). Note nondeterminism with date. More concise syntax for object fields. * src/server/token/TokenQueryResult.ts: Make all fields readonly. (TokenStatus): Date is no longer optional (see above mention). * src/types/mongodb.d.ts: Remove generics (erroneously added). (Collection)[update]: Remove 3-argument declaration (see comment). * test/server/token/TokenDaoTest.ts: New test case.master
parent
742955a671
commit
18e86ebfe7
|
@ -30,9 +30,12 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"typescript": ">=3.6",
|
||||
"@types/node": "~4.9",
|
||||
"chai": ">=1.9.1 < 4",
|
||||
"@types/chai": ">=1.9.1 < 4",
|
||||
"chai-as-promised": ">=6.0.0",
|
||||
"mocha": "5.2.0",
|
||||
"@types/mocha": "5.2.0",
|
||||
"sinon": ">=1.17.4",
|
||||
"es6-promise": "~3"
|
||||
},
|
||||
|
|
|
@ -88,30 +88,30 @@ export = class TokenDao
|
|||
ns: string,
|
||||
token: string,
|
||||
type: TokenType,
|
||||
data: string,
|
||||
data: string | null,
|
||||
callback: ( err: Error|null ) => void,
|
||||
): this
|
||||
{
|
||||
const token_data: any = {};
|
||||
const token_log: any = {};
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
const current_ts = Math.floor( ( new Date() ).getTime() / 1000 );
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
|
||||
// XXX: nondeterminism
|
||||
const current_ts = Math.floor( ( new Date() ).getTime() / 1000 );
|
||||
|
||||
const token_entry: TokenStatus = {
|
||||
type: type,
|
||||
timestamp: current_ts,
|
||||
data: data,
|
||||
};
|
||||
|
||||
if ( data )
|
||||
{
|
||||
token_entry.data = data;
|
||||
}
|
||||
const token_data = {
|
||||
[ root + 'last' ]: token,
|
||||
[ root + 'lastStatus' ]: token_entry,
|
||||
[ root + token + '.status' ]: token_entry,
|
||||
};
|
||||
|
||||
token_data[ root + 'last' ] = token;
|
||||
token_data[ root + 'lastStatus' ] = token_entry;
|
||||
token_data[ root + token + '.status' ] = token_entry;
|
||||
|
||||
token_log[ root + token + '.statusLog' ] = token_entry;
|
||||
const token_log = {
|
||||
[ root + token + '.statusLog' ]: token_entry,
|
||||
};
|
||||
|
||||
this._collection.update(
|
||||
{ id: +quote_id },
|
||||
|
|
|
@ -40,7 +40,7 @@ export type TokenType = 'ACTIVE' | 'DONE' | 'ACCEPTED' | 'DEAD';
|
|||
*/
|
||||
export interface TokenQueryResult
|
||||
{
|
||||
[propName: string]: TokenNamespaceResults | null,
|
||||
readonly [propName: string]: TokenNamespaceResults | null,
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,7 +49,7 @@ export interface TokenQueryResult
|
|||
*/
|
||||
export interface TokenNamespaceResults
|
||||
{
|
||||
[propName: string]: TokenNamespaceData | null,
|
||||
readonly [propName: string]: TokenNamespaceData | null,
|
||||
}
|
||||
|
||||
|
||||
|
@ -70,14 +70,14 @@ export interface TokenNamespaceData
|
|||
/**
|
||||
* Identifier of last token touched in this namespace
|
||||
*/
|
||||
last: TokenId,
|
||||
readonly last: TokenId,
|
||||
|
||||
/**
|
||||
* Most recent token status
|
||||
*
|
||||
* This is a duplicate of the last entry in `TokenEntry#statusLog`.
|
||||
*/
|
||||
lastStatus: TokenStatus,
|
||||
readonly lastStatus: TokenStatus,
|
||||
|
||||
/**
|
||||
* Tokens indexed by identifier
|
||||
|
@ -86,7 +86,7 @@ export interface TokenNamespaceData
|
|||
* accommodate the above fields. Anything using this should cast to
|
||||
* `TokenEntry`.
|
||||
*/
|
||||
[propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
||||
readonly [propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,7 +100,7 @@ export interface TokenEntry
|
|||
*
|
||||
* This is a duplicate of the last element of `statusLog`.
|
||||
*/
|
||||
status: TokenStatus,
|
||||
readonly status: TokenStatus,
|
||||
|
||||
/**
|
||||
* Log of all past status changes and any associated data
|
||||
|
@ -108,7 +108,7 @@ export interface TokenEntry
|
|||
* This is pushed to on each status change. The last element is
|
||||
* duplicated in `status`.
|
||||
*/
|
||||
statusLog: TokenStatus[],
|
||||
readonly statusLog: TokenStatus[],
|
||||
}
|
||||
|
||||
|
||||
|
@ -123,12 +123,12 @@ export interface TokenStatus
|
|||
/**
|
||||
* State of the token
|
||||
*/
|
||||
type: TokenType,
|
||||
readonly type: TokenType,
|
||||
|
||||
/**
|
||||
* Unix timestamp representing when the status change occurred
|
||||
*/
|
||||
timestamp: number,
|
||||
readonly timestamp: number,
|
||||
|
||||
/**
|
||||
* Arbitrary data associated with the status change
|
||||
|
@ -137,5 +137,5 @@ export interface TokenStatus
|
|||
* fulfillment of a request, in which case this may contain the response
|
||||
* data.
|
||||
*/
|
||||
data?: string,
|
||||
readonly data: string | null,
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ declare module "mongodb";
|
|||
/**
|
||||
* Node-style callback for queries
|
||||
*/
|
||||
type MongoCallback<T> = ( err: Error|null, data: any ) => T;
|
||||
type MongoCallback = ( err: Error|null, data: any ) => void;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -65,25 +65,13 @@ interface MongoFindOneOptions
|
|||
*/
|
||||
declare interface MongoCollection
|
||||
{
|
||||
/**
|
||||
* Update a document
|
||||
*
|
||||
* @param selector document query
|
||||
* @param data update data
|
||||
* @param callback continuation on completion
|
||||
*
|
||||
* @return callback return value
|
||||
*/
|
||||
update<T>(
|
||||
selector: object,
|
||||
data: object,
|
||||
callback: MongoCallback<T>
|
||||
): T;
|
||||
|
||||
|
||||
/**
|
||||
* Update a document with additional query options
|
||||
*
|
||||
* To simplify the interface, we're always going to require `options`,
|
||||
* even if they are empty. Otherwise typing is a verbose PITA when
|
||||
* writing tests.
|
||||
*
|
||||
* @param selector document query
|
||||
* @param data update data
|
||||
* @param options query options
|
||||
|
@ -91,12 +79,12 @@ declare interface MongoCollection
|
|||
*
|
||||
* @return callback return value
|
||||
*/
|
||||
update<T>(
|
||||
update(
|
||||
selector: object,
|
||||
data: object,
|
||||
options: MongoQueryUpdateOptions,
|
||||
callback: MongoCallback<T>
|
||||
): T;
|
||||
callback: MongoCallback
|
||||
): void;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -112,6 +100,6 @@ declare interface MongoCollection
|
|||
findOne(
|
||||
selector: object,
|
||||
fields: MongoFindOneOptions,
|
||||
callback: MongoCallback<void>
|
||||
callback: MongoCallback
|
||||
): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
/**
|
||||
* Token state management 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 { expect } from 'chai';
|
||||
|
||||
import {
|
||||
TokenQueryResult,
|
||||
TokenStatus,
|
||||
} from "../../../src/server/token/TokenQueryResult";
|
||||
|
||||
import Sut = require( "../../../src/server/token/TokenDao" );
|
||||
|
||||
|
||||
describe( 'server.token.TokenDao', () =>
|
||||
{
|
||||
describe( '#updateToken', () =>
|
||||
{
|
||||
it( 'updates token with given data', done =>
|
||||
{
|
||||
const field = 'foo_field';
|
||||
const qid = 12345;
|
||||
const ns = 'namespace';
|
||||
const tok_id = 'tok123';
|
||||
const tok_type = 'DONE';
|
||||
const data = "some data";
|
||||
|
||||
const root = field + '.' + ns;
|
||||
|
||||
const coll: MongoCollection = {
|
||||
update( selector: any, given_data: any, options, callback )
|
||||
{
|
||||
expect( given_data.$set[ `${root}.lastStatus` ].timestamp )
|
||||
.to.be.greaterThan( 0 );
|
||||
|
||||
// TODO: ts is nondeterministic; pass in
|
||||
const expected_entry: TokenStatus = {
|
||||
type: tok_type,
|
||||
timestamp: given_data.$set[ `${root}.lastStatus` ].timestamp,
|
||||
data: data,
|
||||
};
|
||||
|
||||
expect( selector.id ).to.equal( qid );
|
||||
|
||||
expect( given_data ).to.deep.equal( {
|
||||
$set: {
|
||||
[`${root}.last`]: tok_id,
|
||||
[`${root}.lastStatus`]: expected_entry,
|
||||
[`${root}.${tok_id}.status`]: expected_entry,
|
||||
},
|
||||
$push: {
|
||||
[`${root}.${tok_id}.statusLog`]: expected_entry,
|
||||
},
|
||||
} );
|
||||
|
||||
expect( ( <MongoQueryUpdateOptions>options ).upsert )
|
||||
.to.be.true;
|
||||
|
||||
callback( null, {} );
|
||||
},
|
||||
|
||||
findOne() {},
|
||||
};
|
||||
|
||||
new Sut( coll, field )
|
||||
.updateToken( qid, ns, tok_id, tok_type, data, done );
|
||||
} );
|
||||
|
||||
|
||||
it( 'proxies error to callback', done =>
|
||||
{
|
||||
const expected_error = Error( "expected error" );
|
||||
|
||||
const coll: MongoCollection = {
|
||||
update( _selector, _data, _options, callback )
|
||||
{
|
||||
callback( expected_error, {} );
|
||||
},
|
||||
|
||||
findOne() {},
|
||||
};
|
||||
|
||||
new Sut( coll, 'foo' )
|
||||
.updateToken( 0, 'ns', 'id', 'DONE', null, err =>
|
||||
{
|
||||
expect( err ).to.equal( expected_error );
|
||||
done();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
describe( '#getToken', () =>
|
||||
{
|
||||
const field = 'get_field';
|
||||
const qid = 12345;
|
||||
const ns = 'get_ns';
|
||||
|
||||
const expected_status: TokenStatus = {
|
||||
type: 'ACTIVE',
|
||||
timestamp: 0,
|
||||
data: "",
|
||||
};
|
||||
|
||||
// TODO: export and use TokenData
|
||||
( <[string, string, TokenQueryResult, any][]>[
|
||||
[
|
||||
'retrieves token by id',
|
||||
'tok123',
|
||||
{
|
||||
[field]: {
|
||||
[ns]: {
|
||||
last: 'tok123',
|
||||
lastStatus: expected_status,
|
||||
|
||||
tok123: {
|
||||
status: expected_status,
|
||||
statusLog: [ expected_status ],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tok123',
|
||||
status: expected_status,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
'returns null for namespace if token is not found',
|
||||
'tok123',
|
||||
{
|
||||
[field]: {
|
||||
[ns]: {
|
||||
last: 'something',
|
||||
lastStatus: expected_status,
|
||||
|
||||
// just to make sure we don't grab another tok
|
||||
othertok: {
|
||||
status: expected_status,
|
||||
statusLog: [ expected_status ],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
],
|
||||
|
||||
[
|
||||
'returns null for field if namespace is not found',
|
||||
'tok123',
|
||||
{
|
||||
[field]: {},
|
||||
},
|
||||
null,
|
||||
],
|
||||
|
||||
[
|
||||
'returns lastest modified token given no token id',
|
||||
'',
|
||||
{
|
||||
[field]: {
|
||||
[ns]: {
|
||||
last: 'toklast',
|
||||
lastStatus: expected_status,
|
||||
|
||||
toklast: {
|
||||
status: expected_status,
|
||||
statusLog: [ expected_status ],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'toklast',
|
||||
status: expected_status,
|
||||
},
|
||||
],
|
||||
] ).forEach( ( [ label, tok_id, result, expected ] ) =>
|
||||
it( label, done =>
|
||||
{
|
||||
const coll: MongoCollection = {
|
||||
findOne( _selector, _fields, callback )
|
||||
{
|
||||
callback( null, result );
|
||||
},
|
||||
|
||||
update() {},
|
||||
};
|
||||
|
||||
new Sut( coll, field )
|
||||
.getToken( qid, ns, tok_id, ( err, data ) =>
|
||||
{
|
||||
expect( err ).to.equal( null );
|
||||
expect( data ).to.deep.equal( expected );
|
||||
|
||||
done();
|
||||
} );
|
||||
} )
|
||||
);
|
||||
|
||||
|
||||
it( 'proxies error to callback', done =>
|
||||
{
|
||||
const expected_error = Error( "expected error" );
|
||||
|
||||
const coll: MongoCollection = {
|
||||
findOne( _selector, _fields, callback )
|
||||
{
|
||||
callback( expected_error, {} );
|
||||
},
|
||||
|
||||
update() {},
|
||||
};
|
||||
|
||||
new Sut( coll, 'foo' )
|
||||
.getToken( 0, 'ns', 'id', ( err, data ) =>
|
||||
{
|
||||
expect( err ).to.equal( expected_error );
|
||||
expect( data ).to.equal( null );
|
||||
|
||||
done();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
Loading…
Reference in New Issue