1
0
mirror of https://github.com/bigchaindb/js-bigchaindb-driver.git synced 2025-02-14 21:10:32 +01:00

Merge pull request #39 from bigchaindb/http-api-1.0-rc-changes

Http api 1.0 rc changes
This commit is contained in:
Tim Daubenschütz 2017-06-21 17:50:49 +02:00 committed by GitHub
commit b8d73b23e7
7 changed files with 607 additions and 96 deletions

View File

@ -7,18 +7,17 @@ export default class Connection {
this.headers = headers this.headers = headers
} }
getApiUrls(endpoints) { getApiUrls(endpoint) {
// TODO: Use camel case return this.path + {
return { 'blocks': 'blocks',
'blocks': `${this.path}blocks`, 'blocksDetail': 'blocks/%(blockId)s',
'blocks_detail': `${this.path}blocks/%(blockId)s`, 'outputs': 'outputs',
'outputs': `${this.path}outputs`, 'statuses': 'statuses',
'statuses': `${this.path}statuses`, 'transactions': 'transactions',
'transactions': `${this.path}transactions`, 'transactionsDetail': 'transactions/%(transactionId)s',
'transactions_detail': `${this.path}transactions/%(txId)s`, 'assets': 'assets',
'search_assets': `${this.path}assets`, 'votes': 'votes'
'votes': `${this.path}votes` }[endpoint]
}[endpoints]
} }
_req(path, options = {}) { _req(path, options = {}) {
@ -32,7 +31,7 @@ export default class Connection {
* @param blockId * @param blockId
*/ */
getBlock(blockId) { getBlock(blockId) {
return this._req(this.getApiUrls('blocks_detail'), { return this._req(this.getApiUrls('blocksDetail'), {
urlTemplateSpec: { urlTemplateSpec: {
blockId blockId
} }
@ -41,37 +40,37 @@ export default class Connection {
/** /**
* @public * @public
* @param tx_id * @param transactionId
*/ */
getStatus(tx_id) { // eslint-disable-line camelcase getStatus(transactionId) {
return this._req(this.getApiUrls('statuses'), { return this._req(this.getApiUrls('statuses'), {
query: { query: {
tx_id transaction_id: transactionId
} }
}) })
} }
/** /**
* @public * @public
* @param txId * @param transactionId
*/ */
getTransaction(txId) { getTransaction(transactionId) {
return this._req(this.getApiUrls('transactions_detail'), { return this._req(this.getApiUrls('transactionsDetail'), {
urlTemplateSpec: { urlTemplateSpec: {
txId transactionId
} }
}) })
} }
/** /**
* @public * @public
* @param tx_id * @param transactionId
* @param status * @param status
*/ */
listBlocks({ tx_id, status }) { listBlocks(transactionId, status) {
return this._req(this.getApiUrls('blocks'), { return this._req(this.getApiUrls('blocks'), {
query: { query: {
tx_id, transaction_id: transactionId,
status status
} }
}) })
@ -79,28 +78,33 @@ export default class Connection {
/** /**
* @public * @public
* @param public_key * @param publicKey
* @param unspent * @param spent
* @param onlyJsonResponse * @param onlyJsonResponse
*/ */
listOutputs({ public_key, unspent }, onlyJsonResponse = true) { listOutputs(publicKey, spent, onlyJsonResponse = true) {
return this._req(this.getApiUrls('outputs'), { const query = {
query: { public_key: publicKey
public_key,
unspent
} }
// NOTE: If `spent` is not defined, it must not be included in the
// query parameters.
if (spent !== undefined) {
query.spent = spent.toString()
}
return this._req(this.getApiUrls('outputs'), {
query
}, onlyJsonResponse) }, onlyJsonResponse)
} }
/** /**
* @public * @public
* @param asset_id * @param assetId
* @param operation * @param operation
*/ */
listTransactions({ asset_id, operation }) { listTransactions(assetId, operation) {
return this._req(this.getApiUrls('transactions'), { return this._req(this.getApiUrls('transactions'), {
query: { query: {
asset_id, asset_id: assetId,
operation operation
} }
}) })
@ -108,12 +112,12 @@ export default class Connection {
/** /**
* @public * @public
* @param block_id * @param blockId
*/ */
listVotes(block_id) { // eslint-disable-line camelcase listVotes(blockId) {
return this._req(this.getApiUrls('votes'), { return this._req(this.getApiUrls('votes'), {
query: { query: {
block_id block_id: blockId
} }
}) })
} }
@ -128,12 +132,10 @@ export default class Connection {
const timer = setInterval(() => { const timer = setInterval(() => {
this.getStatus(txId) this.getStatus(txId)
.then((res) => { .then((res) => {
console.log('Fetched transaction status:', res) // eslint-disable-line no-console
if (res.status === 'valid') { if (res.status === 'valid') {
clearInterval(timer) clearInterval(timer)
this.getTransaction(txId) this.getTransaction(txId)
.then((res_) => { .then((res_) => {
console.log('Fetched transaction:', res_) // eslint-disable-line no-console
resolve(res_) resolve(res_)
}) })
} }
@ -162,12 +164,12 @@ export default class Connection {
/** /**
* @public * @public
* *
* @param transaction * @param search
*/ */
searchAssets(query) { searchAssets(search) {
return this.req(this.getApiUrls('search_assets'), { return this._req(this.getApiUrls('assets'), {
query: { query: {
text_search: query search
} }
}) })
} }

View File

@ -9,7 +9,8 @@ import clone from 'clone'
* @return {string} a canonically serialized Transaction * @return {string} a canonically serialized Transaction
*/ */
export default function serializeTransactionIntoCanonicalString(transaction) { export default function serializeTransactionIntoCanonicalString(transaction) {
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where // BigchainDB signs fulfillments by serializing transactions into a
// "canonical" format where
const tx = clone(transaction) const tx = clone(transaction)
// TODO: set fulfillments to null // TODO: set fulfillments to null
// Sort the keys // Sort the keys

View File

@ -0,0 +1,208 @@
import test from 'ava'
import sinon from 'sinon'
import * as request from '../../src/request' // eslint-disable-line
import { Connection } from '../../src'
const API_PATH = 'http://localhost:9984/api/v1/'
const conn = new Connection(API_PATH)
test('generate API URLS', t => {
const endpoints = {
'blocks': 'blocks',
'blocksDetail': 'blocks/%(blockId)s',
'outputs': 'outputs',
'statuses': 'statuses',
'transactions': 'transactions',
'transactionsDetail': 'transactions/%(transactionId)s',
'assets': 'assets',
}
Object.keys(endpoints).forEach(endpointName => {
const url = conn.getApiUrls(endpointName)
const expected = API_PATH + endpoints[endpointName]
t.is(url, expected)
})
})
test('Request with custom headers', t => {
const testConn = new Connection(API_PATH, { hello: 'world' })
const expectedOptions = {
headers: {
hello: 'world',
custom: 'headers'
}
}
// request is read only, cannot be mocked?
sinon.spy(request, 'default')
testConn._req(API_PATH, { headers: { custom: 'headers' } })
t.truthy(request.default.calledWith(API_PATH, expectedOptions))
request.default.restore()
})
test('Get block for a block id', t => {
const expectedPath = 'path'
const blockId = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.getBlock(blockId)
t.truthy(conn._req.calledWith(
expectedPath,
{ urlTemplateSpec: { blockId } }
))
})
test('Get status for a transaction id', t => {
const expectedPath = 'path'
const transactionId = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.getStatus(transactionId)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { transaction_id: transactionId } }
))
})
test('Get transaction for a transaction id', t => {
const expectedPath = 'path'
const transactionId = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.getTransaction(transactionId)
t.truthy(conn._req.calledWith(
expectedPath,
{ urlTemplateSpec: { transactionId } }
))
})
test('Get list of blocks for a transaction id', t => {
const expectedPath = 'path'
const transactionId = 'abc'
const status = 'status'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listBlocks(transactionId, status)
t.truthy(conn._req.calledWith(
expectedPath,
{
query: {
transaction_id: transactionId,
status
}
}
))
})
test('Get list of transactions for an asset id', t => {
const expectedPath = 'path'
const assetId = 'abc'
const operation = 'operation'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listTransactions(assetId, operation)
t.truthy(conn._req.calledWith(
expectedPath,
{
query: {
asset_id: assetId,
operation
}
}
))
})
test('Get outputs for a public key and no spent flag', t => {
const expectedPath = 'path'
const publicKey = 'publicKey'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listOutputs(publicKey)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { public_key: publicKey } }
))
})
test('Get outputs for a public key and spent=false', t => {
const expectedPath = 'path'
const publicKey = 'publicKey'
const spent = false
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listOutputs(publicKey, spent)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { public_key: publicKey, spent: 'false' } }
))
})
test('Get outputs for a public key and spent=true', t => {
const expectedPath = 'path'
const publicKey = 'publicKey'
const spent = true
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listOutputs(publicKey, spent)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { public_key: publicKey, spent: 'true' } }
))
})
test('Get votes for a block id', t => {
const expectedPath = 'path'
const blockId = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listVotes(blockId)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { block_id: blockId } }
))
})
test('Get asset for text', t => {
const expectedPath = 'path'
const search = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.searchAssets(search)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { search } }
))
})

32
test/constants.js Normal file
View File

@ -0,0 +1,32 @@
import test from 'ava'
import { Transaction, Ed25519Keypair } from '../src'
// TODO: Find out if ava has something like conftest, if so put this there.
// NOTE: It's safer to cast `Math.random()` to a string, to avoid differences
// in "float interpretation" between languages (e.g. JavaScript and Python)
export function asset() { return { message: `${Math.random()}` } }
export const metaData = { message: 'metaDataMessage' }
export const alice = new Ed25519Keypair()
export const aliceCondition = Transaction.makeEd25519Condition(alice.publicKey)
export const aliceOutput = Transaction.makeOutput(aliceCondition)
export const createTx = Transaction.makeCreateTransaction(
asset,
metaData,
[aliceOutput],
alice.publicKey
)
export const transferTx = Transaction.makeTransferTransaction(
createTx,
metaData,
[aliceOutput],
0
)
export const bob = new Ed25519Keypair()
export const bobCondition = Transaction.makeEd25519Condition(bob.publicKey)
export const bobOutput = Transaction.makeOutput(bobCondition)
// TODO: https://github.com/avajs/ava/issues/1190
test('', () => 'dirty hack. TODO: Exclude this file from being run by ava')

View File

@ -0,0 +1,310 @@
import test from 'ava'
import { Ed25519Keypair, Transaction, Connection } from '../../src'
import {
alice,
aliceCondition,
aliceOutput,
bob,
bobOutput,
asset,
metaData
} from '../constants'
const API_PATH = 'http://localhost:9984/api/v1/'
test('Keypair is created', t => {
const keyPair = new Ed25519Keypair()
t.truthy(keyPair.publicKey)
t.truthy(keyPair.privateKey)
})
// TODO: The following tests are a bit messy currently, please do:
//
// - tidy up dependency on `pollStatusAndFetchTransaction`
test('Valid CREATE transaction', t => {
const conn = new Connection(API_PATH)
const tx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput],
alice.publicKey
)
const txSigned = Transaction.signTransaction(tx, alice.privateKey)
return conn.postTransaction(txSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx))
})
test('Valid TRANSFER transaction with single Ed25519 input', t => {
const conn = new Connection(API_PATH)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput],
alice.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
alice.privateKey
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => {
const transferTx = Transaction.makeTransferTransaction(
createTxSigned,
metaData,
[aliceOutput],
0
)
const transferTxSigned = Transaction.signTransaction(
transferTx,
alice.privateKey
)
return conn.postTransaction(transferTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx))
})
})
test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => {
const conn = new Connection(API_PATH)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput, bobOutput],
alice.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
alice.privateKey
)
return conn.postTransaction(createTxSigned)
.then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId))
.then(() => {
const transferTx = Transaction.makeTransferTransaction(
createTxSigned,
metaData,
[Transaction.makeOutput(aliceCondition, '2')],
0,
1
)
const transferTxSigned = Transaction.signTransaction(
transferTx,
alice.privateKey,
bob.privateKey
)
return conn.postTransaction(transferTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx))
})
})
test('Search for spent and unspent outputs of a given public key', t => {
const conn = new Connection(API_PATH)
const carol = new Ed25519Keypair()
const carolCondition = Transaction.makeEd25519Condition(carol.publicKey)
const carolOutput = Transaction.makeOutput(carolCondition)
const trent = new Ed25519Keypair()
const trentCondition = Transaction.makeEd25519Condition(trent.publicKey)
const trentOutput = Transaction.makeOutput(trentCondition)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[carolOutput, carolOutput],
carol.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
carol.privateKey,
carol.privateKey
)
// We spent output 1 (of 0, 1)
const transferTx = Transaction.makeTransferTransaction(
createTxSigned,
metaData,
[trentOutput],
1
)
const transferTxSigned = Transaction.signTransaction(
transferTx,
carol.privateKey,
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.listOutputs(carol.publicKey))
// now listOutputs should return us outputs 0 and 1 (unfiltered)
.then(outputs => t.truthy(outputs.length === 2))
})
test('Search for unspent outputs for a given public key', t => {
const conn = new Connection(API_PATH)
const carol = new Ed25519Keypair()
const carolCondition = Transaction.makeEd25519Condition(carol.publicKey)
const carolOutput = Transaction.makeOutput(carolCondition)
const trent = new Ed25519Keypair()
const trentCondition = Transaction.makeEd25519Condition(trent.publicKey)
const trentOutput = Transaction.makeOutput(trentCondition)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[carolOutput, carolOutput, carolOutput],
carol.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
carol.privateKey,
carol.privateKey
)
// We spent output 1 (of 0, 1, 2)
const transferTx = Transaction.makeTransferTransaction(
createTxSigned,
metaData,
[trentOutput],
1
)
const transferTxSigned = Transaction.signTransaction(
transferTx,
carol.privateKey,
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
// now listOutputs should return us outputs 0 and 2 (1 is spent)
.then(() => conn.listOutputs(carol.publicKey, 'false'))
.then(outputs => t.truthy(outputs.length === 2))
})
test('Search for spent outputs for a given public key', t => {
const conn = new Connection(API_PATH)
const carol = new Ed25519Keypair()
const carolCondition = Transaction.makeEd25519Condition(carol.publicKey)
const carolOutput = Transaction.makeOutput(carolCondition)
const trent = new Ed25519Keypair()
const trentCondition = Transaction.makeEd25519Condition(trent.publicKey)
const trentOutput = Transaction.makeOutput(trentCondition)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[carolOutput, carolOutput, carolOutput],
carol.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
carol.privateKey,
carol.privateKey
)
// We spent output 1 (of 0, 1, 2)
const transferTx = Transaction.makeTransferTransaction(
createTxSigned,
metaData,
[trentOutput],
1
)
const transferTxSigned = Transaction.signTransaction(
transferTx,
carol.privateKey,
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
// now listOutputs should only return us output 1 (0 and 2 are unspent)
.then(() => conn.listOutputs(carol.publicKey, true))
.then(outputs => t.truthy(outputs.length === 1))
})
test('Search for an asset', t => {
const conn = new Connection(API_PATH)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput],
alice.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
alice.privateKey
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.searchAssets(createTxSigned.asset.data.message))
.then(assets => t.truthy(
assets.pop(),
createTxSigned.asset.data.message
))
})
test('Search blocks containing a transaction', t => {
const conn = new Connection(API_PATH)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput],
alice.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
alice.privateKey
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(({ id }) => conn.listBlocks(id, 'VALID'))
.then(blocks => conn.getBlock(blocks.pop()))
.then(({ block: { transactions } }) => transactions.filter(
({ id }) => id === createTxSigned.id
))
.then(transactions => t.truthy(transactions.length === 1))
})
test('Search transaction containing an asset', t => {
const conn = new Connection(API_PATH)
const createTx = Transaction.makeCreateTransaction(
asset(),
metaData,
[aliceOutput],
alice.publicKey
)
const createTxSigned = Transaction.signTransaction(
createTx,
alice.privateKey
)
return conn.postTransaction(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id, 'CREATE'))
.then(({ id }) => conn.listTransactions(id))
.then(transactions => t.truthy(transactions.length === 1))
})

View File

@ -1,30 +0,0 @@
import test from 'ava'
import { Ed25519Keypair, Transaction, Connection } from '../src'
const API_PATH = 'http://localhost:9984/api/v1/'
test('Keypair is created', t => {
const keyPair = new Ed25519Keypair()
t.truthy(keyPair.publicKey)
t.truthy(keyPair.privateKey)
})
test('Valid CREATE transaction is evaluated by BigchainDB', t => {
const alice = new Ed25519Keypair()
const asset = { name: 'Shmui', type: 'cat' }
const metadata = { dayOfTheWeek: 'Caturday' }
const tx = Transaction.makeCreateTransaction(
asset,
metadata,
[Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))],
alice.publicKey
)
const txSigned = Transaction.signTransaction(tx, alice.privateKey)
const conn = new Connection(API_PATH)
return conn.postTransaction(txSigned)
.then(resTx => t.truthy(resTx))
})

View File

@ -1,29 +1,17 @@
import test from 'ava' import test from 'ava'
import sinon from 'sinon' import sinon from 'sinon'
import { Transaction, Ed25519Keypair } from '../../src' import { Transaction } from '../../src'
import * as makeTransaction from '../../src/transaction/makeTransaction' // eslint-disable-line import * as makeTransaction from '../../src/transaction/makeTransaction' // eslint-disable-line
import makeInputTemplate from '../../src/transaction/makeInputTemplate' import makeInputTemplate from '../../src/transaction/makeInputTemplate'
import {
// TODO: Find out if ava has something like conftest, if so put this there. alice,
const alice = new Ed25519Keypair() aliceOutput,
const aliceCondition = Transaction.makeEd25519Condition(alice.publicKey) metaData,
const aliceOutput = Transaction.makeOutput(aliceCondition)
const assetMessage = { assetMessage: 'assetMessage' }
const metaDataMessage = { metaDataMessage: 'metaDataMessage' }
const createTx = Transaction.makeCreateTransaction(
assetMessage,
metaDataMessage,
[aliceOutput],
alice.publicKey
)
const transferTx = Transaction.makeTransferTransaction(
createTx, createTx,
metaDataMessage, transferTx
[aliceOutput], } from '../constants'
0
)
test('Create valid output with default amount', t => { test('Create valid output with default amount', t => {
@ -84,14 +72,14 @@ test('Create TRANSFER transaction based on CREATE transaction', t => {
Transaction.makeTransferTransaction( Transaction.makeTransferTransaction(
createTx, createTx,
metaDataMessage, metaData,
[aliceOutput], [aliceOutput],
0 0
) )
const expected = [ const expected = [
'TRANSFER', 'TRANSFER',
{ id: createTx.id }, { id: createTx.id },
metaDataMessage, metaData,
[aliceOutput], [aliceOutput],
[makeInputTemplate( [makeInputTemplate(
[alice.publicKey], [alice.publicKey],
@ -112,14 +100,14 @@ test('Create TRANSFER transaction based on TRANSFER transaction', t => {
Transaction.makeTransferTransaction( Transaction.makeTransferTransaction(
transferTx, transferTx,
metaDataMessage, metaData,
[aliceOutput], [aliceOutput],
0 0
) )
const expected = [ const expected = [
'TRANSFER', 'TRANSFER',
{ id: transferTx.asset.id }, { id: transferTx.asset.id },
metaDataMessage, metaData,
[aliceOutput], [aliceOutput],
[makeInputTemplate( [makeInputTemplate(
[alice.publicKey], [alice.publicKey],