From eb30622bff6763cbcc676d7e658d153d4ce493ab Mon Sep 17 00:00:00 2001 From: Martin Ayora <17176539+martinayora@users.noreply.github.com> Date: Tue, 30 Jun 2020 08:46:35 +0200 Subject: [PATCH] Reviewed hash locks endpoint --- catapult-sdk/src/plugins/lockHash.js | 1 + catapult-sdk/test/plugins/lockHash_spec.js | 2 +- rest/src/plugins/lockHash/LockHashDb.js | 22 +- rest/src/plugins/lockHash/lockHashRoutes.js | 8 +- rest/test/plugins/lockHash/LockHashDb_spec.js | 369 ++++++++++-------- .../plugins/lockHash/lockHashDbTestUtils.js | 56 --- .../plugins/lockHash/lockHashRoutes_spec.js | 235 ++++++++--- 7 files changed, 415 insertions(+), 278 deletions(-) delete mode 100644 rest/test/plugins/lockHash/lockHashDbTestUtils.js diff --git a/catapult-sdk/src/plugins/lockHash.js b/catapult-sdk/src/plugins/lockHash.js index c8819bc96..79de8b556 100644 --- a/catapult-sdk/src/plugins/lockHash.js +++ b/catapult-sdk/src/plugins/lockHash.js @@ -32,6 +32,7 @@ const constants = { sizes }; const lockHashPlugin = { registerSchema: builder => { builder.addSchema('hashLockInfo', { + id: ModelType.objectId, lock: { type: ModelType.object, schemaName: 'hashLockInfo.lock' } }); builder.addSchema('hashLockInfo.lock', { diff --git a/catapult-sdk/test/plugins/lockHash_spec.js b/catapult-sdk/test/plugins/lockHash_spec.js index 284e2be14..184bee751 100644 --- a/catapult-sdk/test/plugins/lockHash_spec.js +++ b/catapult-sdk/test/plugins/lockHash_spec.js @@ -47,7 +47,7 @@ describe('lock hash plugin', () => { ]); // - hash lock infos - assertSchema(modelSchema.hashLockInfo, 1, 'lock'); + assertSchema(modelSchema.hashLockInfo, 2, 'id', 'lock'); assertSchema(modelSchema['hashLockInfo.lock'], 5, 'ownerAddress', 'mosaicId', 'amount', 'endHeight', 'hash'); diff --git a/rest/src/plugins/lockHash/LockHashDb.js b/rest/src/plugins/lockHash/LockHashDb.js index 7f9b22213..ccbb087ab 100644 --- a/rest/src/plugins/lockHash/LockHashDb.js +++ b/rest/src/plugins/lockHash/LockHashDb.js @@ -30,18 +30,22 @@ class LockHashDb { // region lock retrieval /** - * Retrieves hash infos for given addresses. + * Retrieves hash lock infos for given accounts filtered and paginated. * @param {array<{Uint8Array}>} addresses Account addresses. - * @param {string} id Paging id. - * @param {int} pageSize Page size. - * @param {object} options Additional options. + * @param {object} options Options for ordering and pagination. Can have an `offset`, and must contain the `sortField`, `sortDirection`, + * `pageSize` and `pageNumber`. 'sortField' must be within allowed 'sortingOptions'. * @returns {Promise.} Hash lock infos for all accounts. */ - hashLocksByAddresses(addresses, id, pageSize, options) { + hashLocks(addresses, options) { + const sortingOptions = { id: '_id' }; const buffers = addresses.map(address => Buffer.from(address)); - const conditions = { 'lock.ownerAddress': { $in: buffers } }; - return this.catapultDb.queryPagedDocuments('hashLocks', conditions, id, pageSize, options) - .then(this.catapultDb.sanitizer.copyAndDeleteIds); + const conditions = [{ 'lock.ownerAddress': { $in: buffers } }]; + + if (options.offset) + conditions.push({ [sortingOptions[options.sortField]]: { [1 === options.sortDirection ? '$gt' : '$lt']: options.offset } }); + + const sortConditions = { $sort: { [sortingOptions[options.sortField]]: options.sortDirection } }; + return this.catapultDb.queryPagedDocuments_2(conditions, [], sortConditions, 'hashLocks', options); } /** @@ -51,7 +55,7 @@ class LockHashDb { */ hashLockByHash(hash) { return this.catapultDb.queryDocument('hashLocks', { 'lock.hash': Buffer.from(hash) }) - .then(this.catapultDb.sanitizer.copyAndDeleteId); + .then(this.catapultDb.sanitizer.renameId); } // endregion diff --git a/rest/src/plugins/lockHash/lockHashRoutes.js b/rest/src/plugins/lockHash/lockHashRoutes.js index 568b2ec01..d9d0550df 100644 --- a/rest/src/plugins/lockHash/lockHashRoutes.js +++ b/rest/src/plugins/lockHash/lockHashRoutes.js @@ -21,13 +21,13 @@ const routeUtils = require('../../routes/routeUtils'); module.exports = { - register: (server, db) => { + register: (server, db, services) => { server.get('/account/:address/lock/hash', (req, res, next) => { const accountAddress = routeUtils.parseArgument(req.params, 'address', 'address'); - const pagingOptions = routeUtils.parsePagingArguments(req.params); + const options = routeUtils.parsePaginationArguments(req.params, services.config.pageSize, { id: 'objectId' }); - return db.hashLocksByAddresses([accountAddress], pagingOptions.id, pagingOptions.pageSize) - .then(routeUtils.createSender('hashLockInfo').sendArray('address', res, next)); + return db.hashLocks([accountAddress], options) + .then(result => routeUtils.createSender('hashLockInfo').sendPage(res, next)(result)); }); server.get('/lock/hash/:hash', (req, res, next) => { diff --git a/rest/test/plugins/lockHash/LockHashDb_spec.js b/rest/test/plugins/lockHash/LockHashDb_spec.js index 2da108a9a..91b726021 100644 --- a/rest/test/plugins/lockHash/LockHashDb_spec.js +++ b/rest/test/plugins/lockHash/LockHashDb_spec.js @@ -18,185 +18,244 @@ * along with Catapult. If not, see . */ -const test = require('./lockHashDbTestUtils'); -const testUtils = require('../../testUtils'); +const CatapultDb = require('../../../src/db/CatapultDb'); +const HashLocksDb = require('../../../src/plugins/lockHash/LockHashDb'); +const test = require('../../db/utils/dbTestUtils'); const { expect } = require('chai'); +const sinon = require('sinon'); -describe('lock hash db', () => { - const createOwner = testUtils.random.account; - - describe('fetch locks hash by account', () => { - const addLockHashByAccountTests = traits => { - const assertLocks = (dbCallParams, lockGroups) => - // Assert: - test.db.runDbTest( - traits.collectionName, - lockGroups.seed, - db => db[traits.dbMethodName](...dbCallParams), - locks => { - // Assert: - const expectedLocks = lockGroups.expected; - const expectedIds = expectedLocks.map(lock => lock._id); - - const ids = locks.map(lock => lock._id); - expect(locks.length).to.equal(expectedLocks.length); - expect(ids).to.deep.equal(expectedIds); - expect(locks).to.deep.equal(expectedLocks); - } - ); +describe('hash locks db', () => { + const ownerAddressTest1 = test.random.address(); + const ownerAddressTest2 = test.random.address(); + const ownerAddressTest3 = test.random.address(); + const hashTest01 = test.random.hash(); + const hashTest02 = test.random.hash(); - const createRandomLocks = (startId, count) => { - const locks = []; - for (let id = startId; id < startId + count; ++id) - locks.push(traits.createLockHash(id, createOwner(), traits.createRandomHash())); - return locks; - }; + const { createObjectId } = test.db; - const ownerToDbApiIds = owner => [traits.toDbApiId(owner)]; + const runHashLocksDbTest = (dbEntities, issueDbCommand, assertDbCommandResult) => + test.db.runDbTest(dbEntities, 'hashLocks', db => new HashLocksDb(db), issueDbCommand, assertDbCommandResult); - it('returns empty array for account with no locks', () => { - // Arrange: create 3 locks - const allLocks = traits.createLockHashes(3, createOwner()); + const createHashLock = (objectId, ownerAddress, hash) => ({ + _id: createObjectId(objectId), + lock: { + ownerAddress: ownerAddress ? Buffer.from(ownerAddress) : undefined, + mosaicId: '', + amount: '', + endHeight: '', + status: '', + hash: hash || undefined + } + }); - // Assert: - return assertLocks([ownerToDbApiIds(createOwner())], { - seed: allLocks, - expected: [] - }); - }); + describe('hashLocks', () => { + const paginationOptions = { + pageSize: 10, + pageNumber: 1, + sortField: 'id', + sortDirection: -1 + }; + + const runTestAndVerifyIds = (dbHashLocks, dbQuery, expectedIds) => { + const expectedObjectIds = expectedIds.map(id => createObjectId(id)); - it('returns all locks for single account with locks', () => { - // Arrange: create 10 locks - const owner = createOwner(); - const seedLocks = traits.createLockHashes(10, owner); + return runHashLocksDbTest( + dbHashLocks, + dbQuery, + page => { + const returnedIds = page.data.map(t => t.id); + expect(page.data.length).to.equal(expectedObjectIds.length); + expect(returnedIds.sort()).to.deep.equal(expectedObjectIds.sort()); + } + ); + }; - // - create additional 5 locks with random owner - const additionalLocks = createRandomLocks(20, 5); + it('returns expected structure', () => { + // Arrange: + const dbHashLocks = [createHashLock(10, ownerAddressTest1)]; - // Assert: - return assertLocks( - [ownerToDbApiIds(owner)], - { seed: seedLocks.concat(additionalLocks), expected: seedLocks.reverse() } + // Act + Assert: + return runHashLocksDbTest( + dbHashLocks, + db => db.hashLocks([ownerAddressTest1], paginationOptions), + page => { + const expected_keys = ['id', 'lock']; + expect(Object.keys(page.data[0]).sort()).to.deep.equal(expected_keys.sort()); + } + ); + }); + + it('returns empty array for unknown address', () => { + // Arrange: + const dbHashLocks = [ + createHashLock(10, ownerAddressTest1), + createHashLock(20, ownerAddressTest1) + ]; + + // Act + Assert: + return runTestAndVerifyIds(dbHashLocks, db => db.hashLocks([ownerAddressTest2], paginationOptions), []); + }); + + it('returns filtered hash locks by owner address', () => { + // Arrange: + const dbHashLocks = [ + createHashLock(10, ownerAddressTest1), + createHashLock(20, ownerAddressTest2) + ]; + + // Act + Assert: + return runTestAndVerifyIds(dbHashLocks, db => db.hashLocks([ownerAddressTest2], paginationOptions), [20]); + }); + + it('returns filtered hash locks by owner address, multiple addresses', () => { + // Arrange: + const dbHashLocks = [ + createHashLock(10, ownerAddressTest1), + createHashLock(20, ownerAddressTest2), + createHashLock(30, ownerAddressTest3) + ]; + + // Act + Assert: + return runTestAndVerifyIds( + dbHashLocks, + db => db.hashLocks([ownerAddressTest1, ownerAddressTest2], paginationOptions), + [10, 20] + ); + }); + + describe('respects sort conditions', () => { + // Arrange: + const dbHashLocks = () => [ + createHashLock(10, ownerAddressTest1), + createHashLock(20, ownerAddressTest1), + createHashLock(30, ownerAddressTest1) + ]; + + it('direction ascending', () => { + const options = { + pageSize: 10, + pageNumber: 1, + sortField: 'id', + sortDirection: 1 + }; + + // Act + Assert: + return runHashLocksDbTest( + dbHashLocks(), + db => db.hashLocks([ownerAddressTest1], options), + page => { + expect(page.data[0].id).to.deep.equal(createObjectId(10)); + expect(page.data[1].id).to.deep.equal(createObjectId(20)); + expect(page.data[2].id).to.deep.equal(createObjectId(30)); + } ); }); - describe('paging', () => { - it('query respects supplied document id', () => { - // Arrange: create 10 locks - const owner = createOwner(); - const seedLocks = traits.createLockHashes(10, owner).reverse(); - const expectedLocks = seedLocks.slice(8); - - // Assert: - return assertLocks( - [ownerToDbApiIds(owner), seedLocks[7]._id.toString()], - { seed: seedLocks, expected: expectedLocks } - ); - }); - - const assertPageSize = (pageSize, expectedSize) => { - // Arrange: create 200 locks - const owner = createOwner(); - const seedLocks = traits.createLockHashes(200, owner); - const expectedLocks = seedLocks.slice(0, 200).reverse().slice(0, expectedSize); - - // Assert: - expect(expectedSize).to.equal(expectedLocks.length); - return assertLocks( - [ownerToDbApiIds(owner), undefined, pageSize], - { seed: seedLocks, expected: expectedLocks } - ); + it('direction descending', () => { + const options = { + pageSize: 10, + pageNumber: 1, + sortField: 'id', + sortDirection: -1 }; - // minimum and maximum values are set in CatapultDb ctor - it('query respects page size', () => assertPageSize(12, 12)); - it('query ensures minimum page size', () => assertPageSize(5, 10)); - it('query ensures maximum page size', () => assertPageSize(150, 100)); + // Act + Assert: + return runHashLocksDbTest( + dbHashLocks(), + db => db.hashLocks([ownerAddressTest1], options), + page => { + expect(page.data[0].id).to.deep.equal(createObjectId(30)); + expect(page.data[1].id).to.deep.equal(createObjectId(20)); + expect(page.data[2].id).to.deep.equal(createObjectId(10)); + } + ); }); - const runMultiOwnerTest = createAccountIds => { - // Arrange: create 5 locks and 5 inactive locks with known owner - const owner1 = createOwner(); - const seedLocks1 = traits.createLockHashes(5, owner1); - const activeLocks1 = seedLocks1.slice(0, 5); - - // - create 3 locks and 3 inactive locks with (other) known owner - const owner2 = createOwner(); - const seedLocks2 = traits.createLockHashes(3, owner2, 10); - const activeLocks2 = seedLocks2.slice(0, 3); - - // - create additional 5 locks with random owner - const additionalLocks = createRandomLocks(16, 5); - - // Assert: - return assertLocks([createAccountIds(owner1, owner2)], { - seed: seedLocks1.concat(additionalLocks, seedLocks2), - expected: activeLocks1.concat(activeLocks2).reverse() - }); + it('sort field', () => { + const queryPagedDocumentsSpy = sinon.spy(CatapultDb.prototype, 'queryPagedDocuments_2'); + const options = { + pageSize: 10, + pageNumber: 1, + sortField: 'id', + sortDirection: 1 + }; + + // Act + Assert: + return runHashLocksDbTest( + dbHashLocks(), + db => db.hashLocks([ownerAddressTest1], options), + () => { + expect(queryPagedDocumentsSpy.calledOnce).to.equal(true); + expect(Object.keys(queryPagedDocumentsSpy.firstCall.args[2].$sort)[0]).to.equal('_id'); + queryPagedDocumentsSpy.restore(); + } + ); + }); + }); + + describe('respects offset', () => { + // Arrange: + const dbHashLocks = () => [ + createHashLock(10, ownerAddressTest1), + createHashLock(20, ownerAddressTest1), + createHashLock(30, ownerAddressTest1) + ]; + + const options = { + pageSize: 10, + pageNumber: 1, + sortField: 'id', + sortDirection: 1, + offset: createObjectId(20) }; - it( - 'returns all hashLocks for multiple accounts with hashLocks', - () => runMultiOwnerTest((owner1, owner2) => [owner1, owner2].map(traits.toDbApiId)) - ); + it('gt', () => { + options.sortDirection = 1; - it( - 'returns all hashLocks for multiple accounts with hashLocks and ignores accounts with no hashLocks ', - () => runMultiOwnerTest((owner1, owner2) => - [owner1, createOwner(), createOwner(), owner2].map(traits.toDbApiId)) - ); - }; + // Act + Assert: + return runTestAndVerifyIds(dbHashLocks(), db => db.hashLocks([ownerAddressTest1], options), [30]); + }); - describe('hashLocks by owners', () => { - describe('by address', () => addLockHashByAccountTests({ - collectionName: 'hash', - dbMethodName: 'hashLocksByAddresses', - createRandomHash: testUtils.random.hash, - createLockHash: test.db.createHashLock, - createLockHashes: test.db.createHashLocks, - toDbApiId: owner => owner.address - })); + it('lt', () => { + options.sortDirection = -1; + + // Act + Assert: + return runTestAndVerifyIds(dbHashLocks(), db => db.hashLocks([ownerAddressTest1], options), [10]); + }); }); }); - describe('fetch individual', () => { - const addTests = traits => { - it('returns undefined', () => { - // Arrange: create lock hash info - const hash = traits.createRandomHash(); - const lockInfo = traits.createLockHash(0, createOwner(), hash); - - // Assert: - return test.db.runDbTest( - traits.type, - lockInfo, - db => db[traits.dbFunctionName](traits.createRandomHash()), - entity => { expect(entity).to.equal(undefined); } - ); - }); + describe('hashLockByHash', () => { + it('returns undefined for unknown hash', () => { + // Arrange: + const dbHashLocks = [createHashLock(10, ownerAddressTest1, hashTest01)]; - it('returns an entity', () => { - // Arrange: create lock hash info - const hash = traits.createRandomHash(); - const lockInfo = traits.createLockHash(0, createOwner(), hash); - - // Assert: - return test.db.runDbTest( - traits.type, - lockInfo, - db => db[traits.dbFunctionName](hash), - entity => { - expect(entity).to.deep.equal(lockInfo); - } - ); - }); - }; + // Assert: + return runHashLocksDbTest( + dbHashLocks, + db => db.hashLockByHash(hashTest02), + hashLock => { expect(hashLock).to.equal(undefined); } + ); + }); - describe('hash lock by hash', () => addTests({ - createRandomHash: testUtils.random.hash, - createLockHash: test.db.createHashLock, - type: 'hash', - dbFunctionName: 'hashLockByHash' - })); + it('returns matching hash lock', () => { + // Arrange: + const dbHashLocks = [ + createHashLock(10, ownerAddressTest1, hashTest01), + createHashLock(20, ownerAddressTest1, hashTest02), + createHashLock(30, ownerAddressTest1, hashTest01) + ]; + + // Assert: + return runHashLocksDbTest( + dbHashLocks, + db => db.hashLockByHash(hashTest02), + hashLock => { + expect(hashLock.id).to.deep.equal(createObjectId(20)); + expect(hashLock.lock.ownerAddress.buffer).to.deep.equal(ownerAddressTest1); + expect(hashLock.lock.hash.buffer).to.deep.equal(hashTest02); + } + ); + }); }); }); diff --git a/rest/test/plugins/lockHash/lockHashDbTestUtils.js b/rest/test/plugins/lockHash/lockHashDbTestUtils.js deleted file mode 100644 index bdceddd96..000000000 --- a/rest/test/plugins/lockHash/lockHashDbTestUtils.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016-present, - * Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp. All rights reserved. - * - * This file is part of Catapult. - * - * Catapult is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Catapult 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Catapult. If not, see . - */ - -const LockHashDb = require('../../../src/plugins/lockHash/LockHashDb'); -const dbTestUtils = require('../../db/utils/dbTestUtils'); -const test = require('../../testUtils'); -const MongoDb = require('mongodb'); - -const { Binary } = MongoDb; - -const createLockHash = (id, owner, hashPropertyName, value) => ({ - _id: dbTestUtils.db.createObjectId(id), - meta: {}, - lock: { - ownerAddress: new Binary(owner.address), - [hashPropertyName]: new Binary(value) - } -}); - -const createLockHashes = ((numRounds, owner, hashPropertyName, startdId = 0) => { - const lockInfos = []; - - for (let i = 0; i < numRounds; ++i) - lockInfos.push(createLockHash(startdId + i, owner, hashPropertyName, test.random.hash())); - - return lockInfos; -}); - -const lockHashDbTestUtils = { - db: { - createHashLock: (id, owner, value) => createLockHash(id, owner, 'hash', value), - createHashLocks: (numRounds, owner, startdId = 0) => createLockHashes(numRounds, owner, 'hash', startdId), - runDbTest: (lockName, dbEntities, issueDbCommand, assertDbCommandResult) => - dbTestUtils.db.runDbTest(dbEntities, `${lockName}Locks`, db => new LockHashDb(db), issueDbCommand, assertDbCommandResult) - } -}; -Object.assign(lockHashDbTestUtils, test); - -module.exports = lockHashDbTestUtils; diff --git a/rest/test/plugins/lockHash/lockHashRoutes_spec.js b/rest/test/plugins/lockHash/lockHashRoutes_spec.js index 930c26dd9..0d25b365b 100644 --- a/rest/test/plugins/lockHash/lockHashRoutes_spec.js +++ b/rest/test/plugins/lockHash/lockHashRoutes_spec.js @@ -19,70 +19,199 @@ */ const lockHashRoutes = require('../../../src/plugins/lockHash/lockHashRoutes'); +const routeUtils = require('../../../src/routes/routeUtils'); +const { MockServer } = require('../../routes/utils/routeTestUtils'); const { test } = require('../../routes/utils/routeTestUtils'); const catapult = require('catapult-sdk'); +const { expect } = require('chai'); +const sinon = require('sinon'); -const { addresses } = test.sets; const { address } = catapult.model; const { convert } = catapult.utils; describe('lock hash routes', () => { - const factory = { - createLockHashPagingRouteInfo: (routeName, routeCaptureMethod, dbMethod) => ({ - routes: lockHashRoutes, - routeName, - createDb: (keyGroups, documents) => ({ - [dbMethod]: (accountAddresses, pageId, pageSize) => { - keyGroups.push({ - accountAddresses, pageId, pageSize - }); - return Promise.resolve(documents); - } - }), - routeCaptureMethod - }) - }; - - describe('get hash lock infos', () => { - describe('get by address', () => { - const addGetTests = traits => { - const pagingTestsFactory = test.setup.createPagingTestsFactory( - factory.createLockHashPagingRouteInfo('/account/:address/lock/hash', 'get', 'hashLocksByAddresses'), - traits.valid.params, - traits.valid.expected, - 'hashLockInfo' - ); - - pagingTestsFactory.addDefault(); - pagingTestsFactory.addNonPagingParamFailureTest('address', traits.invalid.addresses); - }; - - describe('by address', () => addGetTests({ - valid: { - params: { address: addresses.valid[0] }, - expected: { accountAddresses: [address.stringToAddress(addresses.valid[0])] } + describe('hash locks', () => { + const testAddress = 'NAR3W7B4BCOZSZMFIZRYB3N5YGOUSWIYJCJ6HDA'; + const testAddressNoLocks = 'A34B57B4BCOZSZMFIZRYB3N5YGOUSWIYJCJ45AB'; + + const emptyPageSample = { + data: [], + pagination: { + pageNumber: 1, + pageSize: 10, + totalEntries: 0, + totalPages: 0 + } + }; + + const pageSample = { + data: [ + { + id: 'random1', + lock: { + ownerAddress: '', + mosaicId: '', + amount: '', + endHeight: '', + status: '', + hash: '' + } }, - invalid: { - addresses: addresses.invalid, - error: 'illegal base32 character 1' + { + id: 'random2', + lock: { + ownerAddress: '', + mosaicId: '', + amount: '', + endHeight: '', + status: '', + hash: '' + } } - })); - }); - }); + ], + pagination: { + pageNumber: 1, + pageSize: 10, + totalEntries: 2, + totalPages: 1 + } + }; - describe('get hash lock info by hash', () => { - const hash = 'C54AFD996DF1F52748EBC5B40F8D0DC242A6A661299149F5F96A0C21ECCB653F'; - test.route.document.addGetDocumentRouteTests(lockHashRoutes.register, { - route: '/lock/hash/:hash', - inputs: { - valid: { object: { hash }, parsed: [convert.hexToUint8(hash)], printable: hash }, - invalid: { - object: { hash: '12345' }, - error: 'hash has an invalid format' + const dbHashLocksFake = sinon.fake(addresses => + (Buffer.from(addresses[0]).equals(Buffer.from(address.stringToAddress(testAddress))) + ? Promise.resolve(pageSample) + : Promise.resolve(emptyPageSample))); + + const services = { + config: { + pageSize: { + min: 10, + max: 100, + default: 20 } - }, - dbApiName: 'hashLockByHash', - type: 'hashLockInfo' + } + }; + + const mockServer = new MockServer(); + const db = { hashLocks: dbHashLocksFake }; + lockHashRoutes.register(mockServer.server, db, services); + + beforeEach(() => { + mockServer.resetStats(); + dbHashLocksFake.resetHistory(); + }); + + describe('GET', () => { + const route = mockServer.getRoute('/account/:address/lock/hash').get(); + + describe('by address', () => { + it('parses and forwards paging options', () => { + // Arrange: + const pagingBag = 'fakePagingBagObject'; + const paginationParser = sinon.stub(routeUtils, 'parsePaginationArguments').returns(pagingBag); + const req = { params: { address: testAddress } }; + + // Act: + return mockServer.callRoute(route, req).then(() => { + // Assert: + expect(paginationParser.firstCall.args[0]).to.deep.equal(req.params); + expect(paginationParser.firstCall.args[2]).to.deep.equal({ id: 'objectId' }); + expect(dbHashLocksFake.calledOnce).to.equal(true); + expect(dbHashLocksFake.firstCall.args[1]).to.deep.equal(pagingBag); + paginationParser.restore(); + }); + }); + + it('allowed sort fields are taken into account', () => { + // Arrange: + const paginationParserSpy = sinon.spy(routeUtils, 'parsePaginationArguments'); + const expectedAllowedSortFields = { id: 'objectId' }; + + // Act: + return mockServer.callRoute(route, { params: { address: testAddress } }).then(() => { + // Assert: + expect(paginationParserSpy.calledOnce).to.equal(true); + expect(paginationParserSpy.firstCall.args[2]).to.deep.equal(expectedAllowedSortFields); + paginationParserSpy.restore(); + }); + }); + + it('forwards address', () => { + // Arrange: + const req = { params: { address: testAddress } }; + + // Act: + return mockServer.callRoute(route, req).then(() => { + // Assert: + expect(dbHashLocksFake.calledOnce).to.equal(true); + expect(dbHashLocksFake.firstCall.args[0]).to.deep.equal([address.stringToAddress(testAddress)]); + + expect(mockServer.next.calledOnce).to.equal(true); + }); + }); + + it('returns empty if no hash locks found', () => { + // Arrange: + const req = { params: { address: testAddressNoLocks } }; + + // Act: + return mockServer.callRoute(route, req).then(() => { + // Assert: + expect(dbHashLocksFake.calledOnce).to.equal(true); + expect(dbHashLocksFake.firstCall.args[0]).to.deep.equal([address.stringToAddress(testAddressNoLocks)]); + + expect(mockServer.send.firstCall.args[0]).to.deep.equal({ + payload: emptyPageSample, + type: 'hashLockInfo', + structure: 'page' + }); + expect(mockServer.next.calledOnce).to.equal(true); + }); + }); + + it('returns page with results', () => { + // Arrange: + const req = { params: { address: testAddress } }; + + // Act: + return mockServer.callRoute(route, req).then(() => { + // Assert: + expect(dbHashLocksFake.calledOnce).to.equal(true); + expect(dbHashLocksFake.firstCall.args[0]).to.deep.equal([address.stringToAddress(testAddress)]); + + expect(mockServer.send.firstCall.args[0]).to.deep.equal({ + payload: pageSample, + type: 'hashLockInfo', + structure: 'page' + }); + expect(mockServer.next.calledOnce).to.equal(true); + }); + }); + + it('throws error if address is invalid', () => { + // Arrange: + const req = { params: { address: 'AB12345' } }; + + // Act + Assert: + expect(() => mockServer.callRoute(route, req)).to.throw('address has an invalid format'); + }); + }); + + describe('by hash', () => { + const hash = 'C54AFD996DF1F52748EBC5B40F8D0DC242A6A661299149F5F96A0C21ECCB653F'; + test.route.document.addGetDocumentRouteTests(lockHashRoutes.register, { + route: '/lock/hash/:hash', + inputs: { + valid: { object: { hash }, parsed: [convert.hexToUint8(hash)], printable: hash }, + invalid: { + object: { hash: '12345' }, + error: 'hash has an invalid format' + } + }, + dbApiName: 'hashLockByHash', + type: 'hashLockInfo' + }); + }); }); }); });