diff --git a/api/controllers/addresses.js b/api/controllers/addresses.js index 6ae1045a..7e58264f 100644 --- a/api/controllers/addresses.js +++ b/api/controllers/addresses.js @@ -32,11 +32,35 @@ Addresses.addressParam = function(req, res, next, address) { next(); }; +/* + * Parse address list + */ +Addresses.addressesParam = function(req, res, next, addresses) { + var addrList = addresses.split(','); + var allAddressesValid = _.every(addrList, function(addr) { + return Address.isValid(addr); + }); + + if (!allAddressesValid) { + res.status(422); + res.send('/v1/addresses/ parameter must be a bitcoin address list'); + return; + } + req.addresses = addrList.map(function (a) { + return new Address(a); + }); + next(); +}; + /* * controllers */ + +/** + * Gets an address information + */ Addresses.get = function(req, res) { $.checkState(req.address instanceof Address); node.getAddressInfo(req.address) @@ -45,4 +69,17 @@ Addresses.get = function(req, res) { }); }; +/** + * Gets an address utxos + */ +Addresses.utxos = function(req, res) { + $.checkState(_.all(req.addresses, function(addr) { + return addr instanceof Address; + })); + node.getUTXOs(req.addresses) + .then(function(utxos) { + res.send(utxos); + }); +}; + module.exports = Addresses; diff --git a/api/controllers/transactions.js b/api/controllers/transactions.js index 2cfdf35c..388f297a 100644 --- a/api/controllers/transactions.js +++ b/api/controllers/transactions.js @@ -35,6 +35,15 @@ Transactions.txHashParam = function(req, res, next, txHash) { }); }; +/* + * sets an input or output index + */ +Transactions.indexParam = function(req, res, next, index) { + index = parseInt(index); + req.index = index; + next(); +}; + /* * controllers @@ -74,6 +83,54 @@ Transactions.send = function(req, res) { }); }; + +/* + * Returns a list of transactions given certain request options + */ +Transactions.list = function(req, res) { + var opts = {}; + opts.address = req.address; + node.listTransactions(opts) + .then(function(transactions) { + res.send(transactions); + }); +}; + + +var buildIOHelper = function(name) { + $.checkArgument(name === 'inputs' || name === 'outputs'); + return function(req, res) { + $.checkState(req.tx instanceof Transaction); + if (_.isNumber(req.index)) { + if (req.index >= req.tx[name].length) { + res.status(404).send('Transaction ' + name.substring(0, name.length - 1) + ' ' + req.index + + ' for ' + req.tx.id + ' not found, it only has ' + req.tx[name].length + ' ' + name + '.'); + return; + } + res.send(req.tx[name][req.index].toJSON()); + return; + } + res.send(req.tx[name].map(function(x) { + return x.toJSON(); + })); + }; + +}; + +/** + * Returns a transaction's outputs + */ +Transactions.getInputs = buildIOHelper('inputs'); + +/** + * Returns a transaction's outputs + */ +Transactions.getOutputs = buildIOHelper('outputs'); + +/** + * errors + */ + Transaction._sendError = function(res) { res.status(422); res.send('/v1/transactions/send parameter must be a raw transaction hex'); @@ -85,4 +142,9 @@ Transactions.getTxError = function(req, res) { res.send('/v1/transactions/ parameter must be a 64 digit hex'); }; +Transactions.indexError = function(req, res) { + res.status(422); + res.send('index parameter must be a positive integer'); +}; + module.exports = Transactions; diff --git a/api/routes/v1.js b/api/routes/v1.js index 37a74ad3..5faf1cb2 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -6,7 +6,6 @@ var Blocks = require('../controllers/blocks'); var Transactions = require('../controllers/transactions'); var Addresses = require('../controllers/addresses'); - function initRouter(node) { var router = express.Router(); @@ -14,17 +13,13 @@ function initRouter(node) { controller.setNode(node); }); - function mockResponse(req, res) { - res.send({ - 'message': 'This is a mocked response' - }); - } - // parameter middleware router.param('blockHash', Blocks.blockHashParam); router.param('height', Blocks.heightParam); router.param('txHash', Transactions.txHashParam); router.param('address', Addresses.addressParam); + router.param('addresses', Addresses.addressesParam); + router.param('index', Transactions.indexParam); // Node routes router.get('/node', NodeStatus.getStatus); @@ -40,22 +35,22 @@ function initRouter(node) { router.post('/transactions/send', Transactions.send); // Input routes - router.get('/transactions/:txHash([A-Fa-f0-9]{64})/inputs', mockResponse); - router.get('/transactions/:txHash([A-Fa-f0-9]{64})/inputs/:index([0-9]+)', mockResponse); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/inputs', Transactions.getInputs); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/inputs/:index([0-9]+)', Transactions.getInputs); // Output routes - router.get('/transactions/:txHash([A-Fa-f0-9]{64})/outputs', mockResponse); - router.get('/transactions/:txHash([A-Fa-f0-9]{64})/outputs/:index([0-9]+)', mockResponse); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/outputs', Transactions.getOutputs); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/outputs/:index([0-9]+)', Transactions.getOutputs); // Address routes router.get('/addresses/:address', Addresses.get); - router.get('/addresses/:address/transactions', mockResponse); - router.get('/addresses/:address/utxos', mockResponse); - // TODO: check if this is really restful - router.get('/addresses/:addresses/utxos', mockResponse); + router.get('/addresses/:address/transactions', Transactions.list); + router.get('/addresses/:addresses/utxos', Addresses.utxos); // error routes router.get('/blocks/*', Blocks.getBlockError); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/inputs/*', Transactions.indexError); + router.get('/transactions/:txHash([A-Fa-f0-9]{64})/outputs/*', Transactions.indexError); router.get('/transactions/*', Transactions.getTxError); return router; diff --git a/api/test/data/addresses.js b/api/test/data/addresses.js index eebe1541..bed32ae6 100644 --- a/api/test/data/addresses.js +++ b/api/test/data/addresses.js @@ -2,65 +2,124 @@ var mockAddresses = { '1CT9huFgxMFveRvzZ7zPPJNoaMm2Fo64VH': { - address: '1CT9huFgxMFveRvzZ7zPPJNoaMm2Fo64VH', - transactions: [ - 'b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09' - ], - unconfirmed: { - balance: 5000000000, - received: 5000000000, - sent: 0, + summary: { + address: '1CT9huFgxMFveRvzZ7zPPJNoaMm2Fo64VH', + transactions: [ + 'b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09' + ], + unconfirmed: { + balance: 5000000000, + received: 5000000000, + sent: 0, + }, + confirmed: { + balance: 5000000000, + received: 5000000000, + sent: 0, + } }, - confirmed: { - balance: 5000000000, - received: 5000000000, - sent: 0, - } + utxos: [{ + satoshis: 5000000000, + script: '4104b715afd59b31be928e073e375a6196d654a78d9aa709789665dd4aecf1b85ebc850ffb90a1c04f18565afe0be4a042ff6629c398f674a5c632b017d793dc8e04ac', + txid: 'b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09', + index: 0 + }] }, '1HZH6zHri1qc68s34MmE5MwG9xstbkFavo': { - address: '1HZH6zHri1qc68s34MmE5MwG9xstbkFavo', - transactions: [ - '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', - '7b309cef1b87471baee38a533c850ce25350f10e88a64e04da1ee08a69dbbba1', - '0c88e745b5c1dffccc39a96f3e25e9486bcafde82b92441f463859df15685959', - ], - unconfirmed: { - balance: 200000043000, - received: 200000043000, - sent: 0, + summary: { + address: '1HZH6zHri1qc68s34MmE5MwG9xstbkFavo', + transactions: [ + '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', + '7b309cef1b87471baee38a533c850ce25350f10e88a64e04da1ee08a69dbbba1', + '0c88e745b5c1dffccc39a96f3e25e9486bcafde82b92441f463859df15685959', + ], + unconfirmed: { + balance: 200000043000, + received: 200000043000, + sent: 0, + }, + confirmed: { + balance: 200000043000, + received: 200000043000, + sent: 0, + } }, - confirmed: { - balance: 200000043000, - received: 200000043000, - sent: 0, - } + utxos: [{ + satoshis: 200000000000, + script: '76a914b59cc3ffe416e460a75baaae3d78cafc787e329d88ac', + txid: '0c88e745b5c1dffccc39a96f3e25e9486bcafde82b92441f463859df15685959', + index: 1 + }, { + satoshis: 1000, + script: '76a914b59cc3ffe416e460a75baaae3d78cafc787e329d88ac', + txid: '7b309cef1b87471baee38a533c850ce25350f10e88a64e04da1ee08a69dbbba1', + index: 46 + }, { + satoshis: 42000, + script: '76a914b59cc3ffe416e460a75baaae3d78cafc787e329d88ac', + txid: '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', + index: 41 + }] }, '1CEXio2gSCozXeSuKQJCDMEpgHfaiT48A3': { - address: '1CEXio2gSCozXeSuKQJCDMEpgHfaiT48A3', - transactions: [ - '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', - 'b6025e6835966b31f40a9f0bb4a1717df0976ec23934934d2b2580a884c09b68', - '6ae158f49c25435c472f1533bce7d090f9edeb75b20fc30297ee78c962f4295a', - '35dd6607d21b3b0739fc0696d0633eaaa26f5ab10e2cbb0fa12353c2ccff6f83', - 'f14c1e10e8b0657068df4d53d8d93d1eb6b1f699041f7d505d5c482479c59634', - '9aa72c5b116a12f80b2d38b1f7bb43356d3a4f02637e7ac5abfeebb14862a3f8', - '9a0a957583f5ea390b2b5573ace7d67a876aeb66c59ada5c0d79a6b7affb34f6', - '585d59d3223eef73ccdc3c19b4e85cb0cc66ea818f173cf6d54723785c7210a1', - '2952d4f79d2388c3cb931e92699ded43fe3b92f2a58f03ee0c68a0a5b0d73e46', - 'f4e18bfbd9edc5ac0cfdd5b0869d77ef5cd38908afe106c02d189ac835569c87', - '4fb1495d114e6853acbe95c38f0acad1b8f790f8979148015e8fbfc3d0c394e9', - ], - unconfirmed: { - balance: 93350245, - received: 1230747491, - sent: 1137397246, + summary: { + address: '1CEXio2gSCozXeSuKQJCDMEpgHfaiT48A3', + transactions: [ + '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', + 'b6025e6835966b31f40a9f0bb4a1717df0976ec23934934d2b2580a884c09b68', + '6ae158f49c25435c472f1533bce7d090f9edeb75b20fc30297ee78c962f4295a', + '35dd6607d21b3b0739fc0696d0633eaaa26f5ab10e2cbb0fa12353c2ccff6f83', + 'f14c1e10e8b0657068df4d53d8d93d1eb6b1f699041f7d505d5c482479c59634', + '9aa72c5b116a12f80b2d38b1f7bb43356d3a4f02637e7ac5abfeebb14862a3f8', + '9a0a957583f5ea390b2b5573ace7d67a876aeb66c59ada5c0d79a6b7affb34f6', + '585d59d3223eef73ccdc3c19b4e85cb0cc66ea818f173cf6d54723785c7210a1', + '2952d4f79d2388c3cb931e92699ded43fe3b92f2a58f03ee0c68a0a5b0d73e46', + 'f4e18bfbd9edc5ac0cfdd5b0869d77ef5cd38908afe106c02d189ac835569c87', + '4fb1495d114e6853acbe95c38f0acad1b8f790f8979148015e8fbfc3d0c394e9', + ], + unconfirmed: { + balance: 93350245, + received: 1230747491, + sent: 1137397246, + }, + confirmed: { + balance: 93350245, + received: 1230747491, + sent: 1137397246, + } }, - confirmed: { - balance: 93350245, - received: 1230747491, - sent: 1137397246, - } + utxos: [{ + 'satoshis': 5636607, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': '9aa72c5b116a12f80b2d38b1f7bb43356d3a4f02637e7ac5abfeebb14862a3f8', + 'index': 1 + }, { + 'satoshis': 47379701, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': 'f14c1e10e8b0657068df4d53d8d93d1eb6b1f699041f7d505d5c482479c59634', + 'index': 1 + }, { + 'satoshis': 17254743, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': '35dd6607d21b3b0739fc0696d0633eaaa26f5ab10e2cbb0fa12353c2ccff6f83', + 'index': 1 + }, { + 'satoshis': 8460000, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': 'b6025e6835966b31f40a9f0bb4a1717df0976ec23934934d2b2580a884c09b68', + 'index': 0 + }, { + 'satoshis': 8460000, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': '6ae158f49c25435c472f1533bce7d090f9edeb75b20fc30297ee78c962f4295a', + 'index': 0 + }, { + 'satoshis': 6159194, + 'script': '76a9147b386f749b54b874f8ce5d2a344bd524f2d7c62188ac', + 'txid': '07ebb557e5782d4b9b7180c5b0b299ab1249d28f3454ccc19d4e7bd819e5ec35', + 'index': 100 + }] }, }; diff --git a/api/test/data/transactions.js b/api/test/data/transactions.js index 4317a081..aa95b4e3 100644 --- a/api/test/data/transactions.js +++ b/api/test/data/transactions.js @@ -1,14 +1,11 @@ 'use strict'; var bitcore = require('bitcore'); -var Block = bitcore.Block; +var _ = bitcore.deps._; var mockTransactions = {}; -var blockHexs = require('./blocks.json'); -blockHexs.map(function(hex) { - var block = new Block(new Buffer(hex, 'hex')); - return block; -}).forEach(function(block) { +var blocks = require('./blocks'); +_.each(blocks, function(block) { block.transactions.forEach(function(tx) { mockTransactions[tx.id] = tx; }); diff --git a/api/test/v1/addresses.js b/api/test/v1/addresses.js index 49b605fd..ae2793f1 100644 --- a/api/test/v1/addresses.js +++ b/api/test/v1/addresses.js @@ -11,29 +11,70 @@ var bitcore = require('bitcore'); var _ = bitcore.deps._; var BitcoreHTTP = require('../../lib/http'); -var BitcoreNode = require('../../../'); + var mockAddresses = require('../data/addresses'); +var mockTransactions = require('../data/transactions'); describe('BitcoreHTTP v1 addresses routes', function() { // mocks + var transactionList = _.values(mockTransactions); var nodeMock, app, agent; + var txs_for_addr = function(addr) { + var amount = mockAddresses[addr].summary.transactions.length; + return transactionList.slice(0, amount); + }; + var utxos_for_addrs = function(addrs) { + return _.reduce(addrs, function(utxos, addr) { + return utxos.concat(mockAddresses[addr].utxos); + }, []); + }; + + var powerset = function(set) { + if (set.length === 0) { + return [ + [] + ]; + } + var sets = []; + var head = set.shift(); + var tail = set; + powerset(tail).forEach(function(s) { + var copy = s.slice(); + copy.push(head); + + sets.push(copy); + sets.push(s); + }); + return sets; + }; + beforeEach(function() { nodeMock = new EventEmitter(); nodeMock.getAddressInfo = function(address) { return Promise.resolve(mockAddresses[address.toString()]); }; + nodeMock.listTransactions = function(opts) { + return Promise.resolve(txs_for_addr(opts.address)); + }; + nodeMock.getUTXOs = function(addresses) { + return Promise.resolve(utxos_for_addrs(addresses)); + }; app = new BitcoreHTTP(nodeMock).app; agent = request(app); }); - describe('/addresses/:addresss', function() { + var failsWithInvalidAddress = function(agent, url, cb) { + agent.get(url) + .expect(422) + .expect('/v1/addresses/ parameter must be a valid bitcoin address', cb); + }; + + describe('/addresses/:address', function() { it('fails with invalid address', function(cb) { - agent.get('/v1/addresses/1BpbpfLdY7oBS9gK7aDXgvMgr1DpvNH3B2') - .expect(422) - .expect('/v1/addresses/ parameter must be a valid bitcoin address', cb); + failsWithInvalidAddress(agent, '/v1/addresses/1BpbpfLdY7oBS9gK7aDXgvMgr1DpvNH3B2', cb); }); - Object.keys(mockAddresses).forEach(function(addr) { + _.keys(mockAddresses).forEach(function(addr) { var info = mockAddresses[addr]; it('works with valid address ' + addr, function(cb) { agent.get('/v1/addresses/' + addr) @@ -42,4 +83,45 @@ describe('BitcoreHTTP v1 addresses routes', function() { }); }); }); + describe('/addresses/:address/transactions', function() { + it('fails with invalid address', function(cb) { + failsWithInvalidAddress(agent, '/v1/addresses/1BpbpfLdY7oBS9gK7aDXgvMgr1DpvNH3B2/transactions', cb); + }); + _.keys(mockAddresses).forEach(function(addr) { + it('works with valid address ' + addr, function(cb) { + agent.get('/v1/addresses/' + addr + '/transactions') + .expect(200) + .expect(JSON.stringify(txs_for_addr(addr)), cb); + }); + }); + }); + describe('/addresses/:address/utxos', function() { + it('fails with invalid address', function(cb) { + agent.get('/v1/addresses/1BpbpfLdY7oBS9gK7aDXgvMgr1DpvNH3B2/utxos') + .expect(422) + .expect('/v1/addresses/ parameter must be a bitcoin address list', cb); + + }); + _.keys(mockAddresses).forEach(function(addr) { + it('works with valid address ' + addr, function(cb) { + agent.get('/v1/addresses/' + addr + '/utxos') + .expect(200) + .expect(JSON.stringify(utxos_for_addrs([addr])), cb); + }); + }); + }); + describe('/addresses/:addresses/utxos', function() { + powerset(_.keys(mockAddresses)).forEach(function(addresses) { + if (addresses.length === 0) { + return; + } + var list = addresses.join(','); + it('works with valid addresses ' + list, function(cb) { + var path = '/v1/addresses/' + list + '/utxos'; + agent.get(path) + .expect(200) + .expect(JSON.stringify(utxos_for_addrs(addresses)), cb); + }); + }); + }); }); diff --git a/api/test/v1/blocks.js b/api/test/v1/blocks.js index 2fc86286..b25acfa7 100644 --- a/api/test/v1/blocks.js +++ b/api/test/v1/blocks.js @@ -14,37 +14,27 @@ var BitcoreHTTP = require('../../lib/http'); var BitcoreNode = require('../../../'); var mockBlocks = require('../data/blocks'); -Object.values = function(obj) { - var vals = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - vals.push(obj[key]); - } - } - return vals; -}; - describe('BitcoreHTTP v1 blocks routes', function() { // mocks - var b1 = mockBlocks[Object.keys(mockBlocks)[0]]; - var firstBlock = mockBlocks[Object.keys(mockBlocks).splice(0, 1)[0]]; - var secondBlock = mockBlocks[Object.keys(mockBlocks).splice(1, 1)[0]]; - var lastBlock = mockBlocks[Object.keys(mockBlocks).splice(-1)[0]]; + var b1 = mockBlocks[_.keys(mockBlocks)[0]]; + var firstBlock = mockBlocks[_.keys(mockBlocks).splice(0, 1)[0]]; + var secondBlock = mockBlocks[_.keys(mockBlocks).splice(1, 1)[0]]; + var lastBlock = mockBlocks[_.keys(mockBlocks).splice(-1)[0]]; var blockForHash = function(hash) { return mockBlocks[hash]; }; - var last3 = Object.keys(mockBlocks).splice(-3).map(blockForHash); - var some2 = Object.keys(mockBlocks).splice(2,2).map(blockForHash); + var last3 = _.keys(mockBlocks).splice(-3).map(blockForHash); + var some2 = _.keys(mockBlocks).splice(2,2).map(blockForHash); var nodeMock, app, agent; - var blockList = Object.values(mockBlocks); + var blockList = _.values(mockBlocks); beforeEach(function() { nodeMock = new EventEmitter(); nodeMock.getBlock = function(blockHash) { var block; if (typeof blockHash === 'number') { var height = blockHash; - block = mockBlocks[Object.keys(mockBlocks)[height - 100000]]; + block = mockBlocks[_.keys(mockBlocks)[height - 100000]]; } else { block = mockBlocks[blockHash]; } @@ -117,7 +107,7 @@ describe('BitcoreHTTP v1 blocks routes', function() { .expect(404) .expect('Block with id 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000 not found', cb); }); - Object.keys(mockBlocks).forEach(function(hash) { + _.keys(mockBlocks).forEach(function(hash) { var block = mockBlocks[hash]; it('works with valid blockHash ...' + hash.substring(hash.length - 8), function(cb) { agent.get('/v1/blocks/' + hash) diff --git a/api/test/v1/transactions.js b/api/test/v1/transactions.js index 9a8d1272..287573f3 100644 --- a/api/test/v1/transactions.js +++ b/api/test/v1/transactions.js @@ -19,7 +19,7 @@ describe('BitcoreHTTP v1 transactions routes', function() { // mocks var mockValidTx = new Transaction(); - var t1 = mockTransactions[Object.keys(mockTransactions)[0]]; + var t1 = mockTransactions[_.keys(mockTransactions)[0]]; var nodeMock, app, agent; beforeEach(function() { nodeMock = new EventEmitter(); @@ -40,18 +40,25 @@ describe('BitcoreHTTP v1 transactions routes', function() { agent = request(app); }); + var failsWithInvalidHash = function(agent, url, cb) { + agent.get(url) + .expect(422) + .expect('/v1/transactions/ parameter must be a 64 digit hex', cb); + }; + var reportsNotFound = function(agent, url, cb) { + agent.get(url) + .expect(404) + .expect('Transaction with id 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000 not found', cb); + }; + describe('/transactions/:txHash', function() { it('fails with invalid txHash', function(cb) { - agent.get('/v1/transactions/abad1dea') - .expect(422) - .expect('/v1/transactions/ parameter must be a 64 digit hex', cb); + failsWithInvalidHash(agent, '/v1/transactions/abad1dea', cb); }); it('returns 404 with non existent transaction', function(cb) { - agent.get('/v1/transactions/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000') - .expect(404) - .expect('Transaction with id 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000 not found', cb); + reportsNotFound(agent, '/v1/transactions/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000', cb); }); - Object.keys(mockTransactions).forEach(function(hash) { + _.keys(mockTransactions).forEach(function(hash) { it('works with valid txHash ...' + hash.substring(hash.length - 8), function(cb) { agent.get('/v1/transactions/' + hash) .expect(200) @@ -99,5 +106,48 @@ describe('BitcoreHTTP v1 transactions routes', function() { .expect('Unable to broadcast transaction 8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87', cb); }); }); - + var testIO = function(name) { + describe('/transactions/:txHash/' + name + '/', function() { + it('fails with invalid txHash', function(cb) { + failsWithInvalidHash(agent, '/v1/transactions/abad1dea/' + name, cb); + }); + it('returns 404 with non existent transaction', function(cb) { + reportsNotFound(agent, + '/v1/transactions/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b600000000/' + name, cb); + }); + _.keys(mockTransactions).forEach(function(hash) { + var tx = mockTransactions[hash]; + var summary = hash.substring(hash.length - 8); + it('works with valid txHash ...' + summary + 'getting all ' + name, function(cb) { + agent.get('/v1/transactions/' + hash + '/' + name + '/') + .expect(200) + .expect(tx[name].map(function(x) { + return x.toJSON(); + }), cb); + }); + var canGetSpecificInput = function(i) { + var x = tx[name][i]; + return function(cb) { + agent.get('/v1/transactions/' + hash + '/' + name + '/' + i) + .expect(200) + .expect(x.toJSON(), cb); + }; + }; + for (var i = 0; i < tx[name].length; i++) { + it('works with valid txHash ...' + summary + ' ' + name + ' ' + i, canGetSpecificInput(i)); + } + it('fails with invalid ' + name + ' index ' + i + ' for txHash ...' + summary, function(cb) { + agent.get('/v1/transactions/' + hash + '/' + name + '/' + i) + .expect(404, cb); + }); + }); + it('fails with invalid ' + name + ' format', function(cb) { + agent.get('/v1/transactions/' + t1.id + '/' + name + '/-1') + .expect(422) + .expect('index parameter must be a positive integer', cb); + }); + }); + }; + testIO('inputs'); + testIO('outputs'); });