From 03d8d58234087e81b4f57922b8c186d1e7bb8128 Mon Sep 17 00:00:00 2001 From: cgeek <cem.moreau@gmail.com> Date: Thu, 8 Nov 2018 20:58:21 +0100 Subject: [PATCH] [enh] First implementation of GVA --- index.ts | 119 +++++++-------- package.json | 10 ++ src/gva.ts | 151 +++++++++++++++++++ src/network.ts | 14 ++ src/schema.graphqls | 198 +++++++++++++++++++++++++ tests/gva.ts | 138 +++++++++++++++++ tests/test-gva-client.ts | 310 +++++++++++++++++++++++++++++++++++++++ tests/test-user.ts | 213 +++++++++++++++++++++++++++ tsconfig.json | 3 +- yarn.lock | 293 ++++++++++++++++++++++++++++++++++-- 10 files changed, 1371 insertions(+), 78 deletions(-) create mode 100644 src/gva.ts create mode 100644 src/network.ts create mode 100644 src/schema.graphqls create mode 100644 tests/gva.ts create mode 100644 tests/test-gva-client.ts create mode 100644 tests/test-user.ts diff --git a/index.ts b/index.ts index e8edd9a..71750b7 100644 --- a/index.ts +++ b/index.ts @@ -1,22 +1,58 @@ -import * as express from 'express'; import {Duniter} from 'duniter'; +import {gvaHttpListen} from "./src/network"; +import * as Http from "http"; import {UpnpProvider} from "duniter/app/modules/upnp-provider"; -import graphqlHTTP = require("express-graphql"); -import {makeExecutableSchema} from "graphql-tools"; + +const DEFAULT_GVA_PORT = 15000 Duniter.run([{ name: 'gva', required: { duniter: { + + cliOptions: [ + { value: '--gva-port <port>', desc: 'Force usage of a specific port for GVA.', parser: (val: string) => parseInt(val) }, + { value: '--gva-host <porthost>', desc: 'Force usage of a specific host for GVA.' }, + { value: '--gva-upnp', desc: 'Use UPnP for port opening.' }, + { value: '--gva-noupnp', desc: 'Do not use UPnP for port opening.' }, + ], + + config: { + onLoading: (conf: any, program: any) => { + if (program.gvaPort || program.gvaHost || program.noupnp || program.upnp) { + if (!conf.gva) { + conf.gva = {} + } + if (program.gvaPort) { + conf.gva.port = program.gvaPort + } + if (program.gvaHost) { + conf.gva.host = program.gvaHost + } + if (program.noupnp) { + conf.gva.upnp = false + } + if (program.upnp) { + conf.gva.upnp = true + } + } + }, + beforeSave: (conf, program, logger, confDAL) => { + } + }, + cli: [{ name: 'gva', desc: 'Starts the UI', onDatabaseExecute: async (server, conf, program, params, start, stop) => { - // Never ending await new Promise(async (res, rej) => { try { - await start(); + const { available, host, port } = { available: true, host: 'localhost', port: 15000 } + // const { available, host, port } = await api.startRegular() + if (available) { + gvaHttpListen(server, port, host) + } } catch (e) { rej(e) } @@ -24,74 +60,29 @@ Duniter.run([{ } }], service: { - neutral: (server, conf, logger) => { + neutral: (server, conf: any) => { + let app: Http.Server|undefined let api = new UpnpProvider( - 15000, - 16000, + (conf.gva && conf.gva.port) || DEFAULT_GVA_PORT, + (conf.gva && conf.gva.port) || DEFAULT_GVA_PORT + 1000, ':gva:' + conf.pair.pub.substr(0, 6)) - - let app = express() - return { startService: async () => { - const schema = makeExecutableSchema({ - typeDefs: ` - - type Certification { - from: String! - to: String! - } - - type Identity { - uid: String! - pub: String! - certs: [Certification!]! - } - - type Query { - hello: String - identity(uid: String!): Identity - } - `, - resolvers: { - - Query: { - hello: () => 'Hello world!', - - identity: async (_, { uid }: { uid: string }) => { - return server.dal.iindexDAL.getFullFromUID(uid) - }, - - }, - - Identity: { - certs: async (identity: { pub:string }) => { - return (await server.dal.cindexDAL.getValidLinksTo(identity.pub)).map(c => ({ - from: c.issuer, - to: c.receiver, - })) - } - } + if (!conf.gva || conf.gva.upnp) { + // Using UPnP + const { available, host, port } = await api.startRegular() + if (available) { + app = gvaHttpListen(server, port, host) } - }) - // app.use('/graphql', graphqlHTTP({ - // schema, - // graphiql: true, - // })) - // app.listen(15000, () => console.log(`Now browse to ${'192.168.1.24'}:${15000}/graphql`)) - const { available, host, port } = { available: true, host: 'localhost', port: 15000 } - // const { available, host, port } = await api.startRegular() - if (available) { - app.use('/graphql', graphqlHTTP({ - schema, - graphiql: true, - })) - app.listen(port, () => console.log(`Now browse to ${host}:${port}/graphql`)) + } else if (!conf.gva) { + // Static usage of port + host, no UPnP + app = gvaHttpListen(server, DEFAULT_GVA_PORT) } }, stopService: async () => { - return undefined; + await new Promise(res => app && app.close(res)) + await (api && api.stopRegular()) } } as any } diff --git a/package.json b/package.json index 3751d04..12d8a3f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,17 @@ "@types/express": "^4.16.0", "@types/express-graphql": "^0.6.2", "@types/graphql": "^14.0.3", + "@types/mocha": "^5.2.5", + "@types/node": "^10.12.3", + "@types/request-promise": "^4.1.42", + "@types/should": "^13.0.0", + "assert": "^1.4.1", "duniter": "file:///home/cgeek/dev/duniter", + "mocha": "^5.2.0", + "request": "^2.88.0", + "request-promise": "^4.2.2", + "should": "^13.2.3", + "ts-node": "^7.0.1", "typescript": "^3.1.6" } } diff --git a/src/gva.ts b/src/gva.ts new file mode 100644 index 0000000..b3aecc6 --- /dev/null +++ b/src/gva.ts @@ -0,0 +1,151 @@ +import * as path from "path"; +import * as fs from "fs"; +import {Server} from "duniter/server"; +import {makeExecutableSchema} from "graphql-tools"; +import {FullIindexEntry, FullMindexEntry} from "duniter/app/lib/indexer"; +import {hashf} from "duniter/app/lib/common"; + +export function plugModule(server: Server) { + const schemaFile = path.join(__dirname, 'schema.graphqls'); + const typeDefs = fs.readFileSync(schemaFile, 'utf8'); + return makeExecutableSchema({ + typeDefs, + resolvers: { + + Query: { + hello: () => 'Welcome to Duniter GVA API.', + + currency: () => server.conf.currency, + + block: async (_, { number }: { number?: number }) => { + if (number !== undefined) { + return server.dal.getBlock(number) + } + const b = await server.dal.getCurrentBlockOrNull() + return b + }, + + member: async (_, { uid, pub }: { uid: string, pub: string }) => { + if (uid) { + return server.dal.iindexDAL.getFullFromUID(uid) + } + return server.dal.iindexDAL.getFromPubkey(pub) + }, + + pendingIdentities: async (_, { search }: { search: string }) => { + if (!search) { + return server.dal.idtyDAL.getPendingIdentities() + } + return server.dal.idtyDAL.searchThoseMatching(search) + }, + + pendingIdentityByHash: async (_, { hash }: { hash: string }) => { + return server.dal.idtyDAL.getByHash(hash) + }, + + pendingTransactions: async () => { + return server.dal.txsDAL.getAllPending(1) + }, + + transactionByHash: async (_, { hash }: { hash: string }) => { + return server.dal.txsDAL.getTX(hash) + }, + + transactionsOfIssuer: async (_, { issuer }: { issuer: string }) => { + return (await server.dal.txsDAL.getLinkedWithIssuer(issuer)) + .concat(await server.dal.txsDAL.getPendingWithIssuer(issuer)) + }, + + transactionsOfReceiver: async (_, { receiver }: { receiver: string }) => { + return (await server.dal.txsDAL.getLinkedWithRecipient(receiver)) + .concat(await server.dal.txsDAL.getPendingWithRecipient(receiver)) + }, + + sourcesOfPubkey: async (_, { pub }: { pub: string }) => { + const txSources = await server.dal.sindexDAL.getAvailableForPubkey(pub) + const udSources = await server.dal.dividendDAL.getUDSources(pub) + const sources: { + type: string + noffset: number + identifier: string + amount: number + base: number + conditions: string + consumed: boolean + }[] = [] + txSources.forEach(s => sources.push({ + type: 'T', + identifier: s.identifier, + noffset: s.pos, + amount: s.amount, + base: s.base, + conditions: s.conditions, + consumed: false + })) + udSources.forEach(s => sources.push({ + type: 'D', + identifier: pub, + noffset: s.pos, + amount: s.amount, + base: s.base, + conditions: `SIG(${pub})`, + consumed: false + })) + return sources + }, + + }, + + Identity: { + certsIssued: async (identity: FullIindexEntry) => { + return server.dal.cindexDAL.getValidLinksFrom(identity.pub) + }, + certsReceived: async (identity: FullIindexEntry) => { + return server.dal.cindexDAL.getValidLinksTo(identity.pub) + }, + pendingIssued: async (identity: FullIindexEntry) => { + return server.dal.certDAL.getFromPubkeyCerts(identity.pub) + }, + pendingReceived: async (identity: FullIindexEntry) => { + return server.dal.certDAL.getNotLinkedToTarget(identity.hash) + }, + membership: async (identity: { pub:string }) => { + const ms = (await server.dal.mindexDAL.getReducedMS(identity.pub)) as FullMindexEntry + return { + revokes_on: ms.revokes_on, + expires_on: ms.expires_on, + chainable_on: ms.chainable_on, + } + }, + }, + + PendingIdentity: { + certs: async (identity: { hash:string }) => { + return server.dal.certDAL.getNotLinkedToTarget(identity.hash) + }, + memberships: async (identity: { hash:string }) => { + return server.dal.msDAL.getPendingINOfTarget(identity.hash) + }, + }, + + Mutation: { + submitIdentity(_, { raw }: { raw: string }) { + return server.writeRawIdentity(raw) + }, + async submitCertification(_, { raw }: { raw: string }) { + const res = await server.writeRawCertification(raw) + const targetHash = hashf(res.idty_uid + res.idty_buid + res.idty_issuer) + return server.dal.idtyDAL.getByHash(targetHash) + }, + async submitMembership(_, { raw }: { raw: string }) { + const res = await server.writeRawMembership(raw) + const targetHash = hashf(res.userid + res.blockstamp + res.pub) + return server.dal.idtyDAL.getByHash(targetHash) + }, + async submitTransaction(_, { raw }: { raw: string }) { + return server.writeRawTransaction(raw) + }, + } + } + }) +} \ No newline at end of file diff --git a/src/network.ts b/src/network.ts new file mode 100644 index 0000000..fc67b80 --- /dev/null +++ b/src/network.ts @@ -0,0 +1,14 @@ +import {plugModule} from "./gva"; +import * as express from "express"; +import graphqlHTTP = require("express-graphql"); +import {Server} from "duniter/server"; +import * as http from "http"; + +export function gvaHttpListen(server: Server, port: number, host = 'localhost'): http.Server { + const app = express() + app.use('/graphql', graphqlHTTP({ + schema: plugModule(server), + graphiql: true, + })) + return app.listen(port, host, () => console.log(`Now browse to ${host}:${port}/graphql`)) +} diff --git a/src/schema.graphqls b/src/schema.graphqls new file mode 100644 index 0000000..c365130 --- /dev/null +++ b/src/schema.graphqls @@ -0,0 +1,198 @@ +type Membership { + pub: String! + created_on: String! + written_on: String! + expires_on: Int! + expired_on: Int + revokes_on: Int! + revoked_on: String! + leaving: Boolean! + revocation: String! + chainable_on: Int! + writtenOn: Int! +} + +type Certification { + issuer: String! + receiver: String! + created_on: Int! + sig: String! + chainable_on: Int! + expires_on: Int! + expired_on: Int! +} + +type Source { + type: String! + noffset: Int! + identifier: String! + amount: Int! + base: Int! + conditions: String! + consumed: Boolean! +} + +type Identity { + uid: String! + pub: String! + hash: String! + sig: String! + created_on: String! + written_on: String! + member: Boolean! + wasMember: Boolean! + certsIssued: [Certification!]! + certsReceived: [Certification!]! + pendingIssued: [PendingCertification!]! + pendingReceived: [PendingCertification!]! + membership: Membership! +} + +type PendingIdentity { + revoked: Boolean! + buid: String! + member: Boolean! + kick: Boolean! + leaving: Boolean + wasMember: Boolean! + pubkey: String! + uid: String! + sig: String! + revocation_sig: String + hash: String! + written: Boolean! + revoked_on: Int + expires_on: Int! + certs: [PendingCertification!]! + memberships: [PendingMembership!]! +} + +type PendingCertification { + linked: Boolean! + written: Boolean! + written_block: Int + written_hash: String + sig: String! + block_number: Int! + block_hash: String! + target: String! + to: String! + from: String! + block: Int! + expired: Boolean + expires_on: Int! +} + +type PendingMembership { + membership: String! + issuer: String! + number: Int! + blockNumber: Int! + blockHash: String! + userid: String! + certts: String! + block: String! + fpr: String! + idtyHash: String! + written: Boolean! + written_number: Int + expires_on: Int! + signature: String! + expired: Boolean + block_number: Int +} + +type Transaction { + hash: String! + block_number: Int + locktime: Int! + version: Int! + currency: String! + comment: String! + blockstamp: String! + blockstampTime: Int + time: Int + inputs: [String!]! + unlocks: [String!]! + outputs: [String!]! + issuers: [String!]! + signatures: [String!]! + written: Boolean + removed: Boolean + received: Int + output_base: Int! + output_amount: Int! + written_on: String + writtenOn: Int +} + +type BlockTransaction { + version: Int! + currency: String! + locktime: Int! + hash: String! + blockstamp: String! + blockstampTime: Int! + issuers: [String!]! + inputs: [String!]! + outputs: [String!]! + unlocks: [String!]! + signatures: [String!]! + comment: String! +} + +type Block { + version: Int! + number: Int! + currency: String! + hash: String! + inner_hash: String! + signature: String! + previousHash: String + issuer: String! + previousIssuer: String + time: Int! + powMin: Int! + unitbase: Int! + membersCount: Int! + issuersCount: Int! + issuersFrame: Int! + issuersFrameVar: Int! + identities: [String!]! + joiners: [String!]! + actives: [String!]! + leavers: [String!]! + revoked: [String!]! + excluded: [String!]! + certifications: [String!]! + transactions: [BlockTransaction!]! + medianTime: Int! + nonce: String! + parameters: String! + monetaryMass: Int! + dividend: Int + UDTime: Int + writtenOn: Int! + written_on: String! +} + +type Mutation { + submitIdentity(raw: String!): PendingIdentity! + submitCertification(raw: String!): PendingIdentity! + submitMembership(raw: String!): PendingIdentity! + submitTransaction(raw: String!): Transaction! +} + +type Query { + hello: String + currency: String! + block(number: Int): Block + member(uid: String, pub: String): Identity + pendingIdentities(search: String): [PendingIdentity!]! + pendingIdentityByHash(hash: String!): PendingIdentity + pendingTransactions: [Transaction!]! + transactionByHash(hash: String!): Transaction + transactionsOfIssuer(issuer: String!): [Transaction!]! + transactionsOfReceiver(receiver: String!): [Transaction!]! + sourcesOfPubkey(pub: String!): [Source!]! +} diff --git a/tests/gva.ts b/tests/gva.ts new file mode 100644 index 0000000..6bbda23 --- /dev/null +++ b/tests/gva.ts @@ -0,0 +1,138 @@ +import * as assert from "assert" +import {NewTestingServer, TestingServer} from "duniter/test/integration/tools/toolbox"; +import {gvaHttpListen} from "../src/network"; +import {Underscore} from "duniter/app/lib/common-libs/underscore"; +import {TestUser} from "duniter/test/integration/tools/TestUser"; +import {TestGvaClient} from "./test-gva-client"; +import * as http from "http"; +import {GvaTestUser} from "./test-user"; +import {DBBlock} from "duniter/app/lib/db/DBBlock"; + +export const prepareDuniterServer = async (options:any) => { + + const catKeyring = { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}; + const tacKeyring = { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}; + + const s1 = NewTestingServer(Underscore.extend({ pair: catKeyring }, options || {})); + + const cat = new TestUser('cat', catKeyring, { server: s1 }); + const tac = new TestUser('tac', tacKeyring, { server: s1 }); + + await s1._server.initWithDAL() + + return { s1, cat, tac }; +} + +describe('GVA', () => { + + const port = 15000 + const now = 1480000000 + + let s1: TestingServer + let cat: GvaTestUser + let tac: GvaTestUser + let gva: http.Server + let gvaClient: TestGvaClient + + before(async () => { + const { s1: _s1, cat: _cat, tac: _tac } = await prepareDuniterServer({ + dt: 1000, + ud0: 200, + udTime0: now - 1, // So we have a UD right on block#1 + medianTimeBlocks: 1 // Easy: medianTime(b) = time(b-1) + }) + s1 = _s1 + gva = gvaHttpListen(s1._server, port) + gvaClient = new TestGvaClient(`http://localhost:${port}/graphql`) + cat = new GvaTestUser( + _cat.uid, + _cat.pub, + _cat.sec, + gvaClient + ) + tac = new GvaTestUser( + _tac.uid, + _tac.pub, + _tac.sec, + gvaClient + ) + }) + + it('GVA should answer', async () => { + const res = await gvaClient.hello() + assert.deepEqual(res.data, { + "hello": "Welcome to Duniter GVA API." + }) + assert.equal(null, await gvaClient.current()) + }) + + it('cat & tac should be able to create its identity', async () => { + await cat.createIdentityAndSubmit() + assert.equal(1, (await gvaClient.pendingIdentities()).length) + await tac.createIdentityAndSubmit() + assert.equal(2, (await gvaClient.pendingIdentities()).length) + }) + + it('cat & tac should be able to certify each other', async () => { + await cat.createCertAndSubmit(tac) + assert.equal(1, (await gvaClient.pendingIdentities())[1].certs.length) + assert.equal(0, (await gvaClient.pendingIdentities())[0].certs.length) + await tac.createCertAndSubmit(cat) + assert.equal(1, (await gvaClient.pendingIdentities())[1].certs.length) + assert.equal(1, (await gvaClient.pendingIdentities())[0].certs.length) + }) + + it('cat & tac should be able to submit membership', async () => { + await cat.createJoinAndSubmit() + assert.equal(1, ((await gvaClient.pendingIdentities())[0] as any).memberships.length) + assert.equal(0, ((await gvaClient.pendingIdentities())[1] as any).memberships.length) + await tac.createJoinAndSubmit() + assert.equal(1, ((await gvaClient.pendingIdentities())[0] as any).memberships.length) + assert.equal(1, ((await gvaClient.pendingIdentities())[1] as any).memberships.length) + }) + + it('root block should be generated', async () => { + await s1.commit({ time: now }) + const current = await gvaClient.current() + assert.notEqual(null, current) + assert.equal(0, (current as DBBlock).number) + assert.equal(cat.pub, (current as DBBlock).issuer) + assert.equal(0, (current as DBBlock).issuersCount) + }) + + it('UD should be available at block#1', async () => { + await s1.commit({ time: now }) + const current = await gvaClient.current() + assert.notEqual(null, current) + assert.equal(1, (current as DBBlock).number) + assert.equal(200, (current as DBBlock).dividend) + }) + + it('cat & tac should both have 200 units', async () => { + assert.equal(200, (await cat.balance())) + assert.equal(200, (await tac.balance())) + }) + + it('cat should be able to send money to tac', async () => { + await cat.createTxAndSubmit(tac, 100) + await s1.commit({ time: now }) + const current = await gvaClient.current() + assert.notEqual(null, current) + assert.equal(2, (current as DBBlock).number) + assert.equal(null, (current as DBBlock).dividend) + assert.equal(1, (current as DBBlock).transactions.length) + }) + + it('cat should have 100 units', async () => { + assert.equal(100, (await cat.balance())) + }) + + it('tac should have 300 units', async () => { + assert.equal(300, (await tac.balance())) + }) + + after(() => Promise.all([ + s1.closeCluster(), + new Promise(res => gva.close(res)), + ])) +}) diff --git a/tests/test-gva-client.ts b/tests/test-gva-client.ts new file mode 100644 index 0000000..ee47fc0 --- /dev/null +++ b/tests/test-gva-client.ts @@ -0,0 +1,310 @@ +import * as requestPromise from "request-promise"; +import {DBBlock} from "duniter/app/lib/db/DBBlock"; +import {DBIdentity} from "duniter/app/lib/dal/sqliteDAL/IdentityDAL"; + +export class TestGvaClient { + + constructor(protected uri: string) {} + + graphQLPost(req: string) { + return requestPromise(this.uri, { + method: 'POST', + json: true, + body: { + query: req + } + }) + } + + async currency(): Promise<string> { + return (await this.graphQLPost(`{ currency }`) as any).data.currency + } + + async current(): Promise<DBBlock|null> { + const res = await (await this.graphQLPost(`{ block { + version + number + currency + hash + inner_hash + signature + previousHash + issuer + previousIssuer + time + powMin + unitbase + membersCount + issuersCount + issuersFrame + issuersFrameVar + identities + joiners + actives + leavers + revoked + excluded + certifications + transactions { + version + currency + locktime + hash + blockstamp + blockstampTime + issuers + inputs + outputs + unlocks + signatures + comment + } + medianTime + nonce + parameters + monetaryMass + dividend + UDTime + writtenOn + written_on + } }`) as any) + return res.data.block + } + + hello() { + return this.graphQLPost('{ hello }') + } + + submitIdentity(raw: string) { + return this.graphQLPost(`mutation { + submitIdentity(raw: "${raw.replace(/\n/g, '\\n')}") { + revoked + buid + member + kick + leaving + wasMember + pubkey + uid + sig + revocation_sig + hash + written + revoked_on + expires_on + } + }`) + } + + submitCertification(raw: string) { + return this.graphQLPost(`mutation { + submitCertification(raw: "${raw.replace(/\n/g, '\\n')}") { + revoked + buid + member + kick + leaving + wasMember + pubkey + uid + sig + revocation_sig + hash + written + revoked_on + expires_on + certs { + linked + written + written_block + written_hash + sig + block_number + block_hash + target + to + from + block + expired + expires_on + } + memberships { + membership + issuer + number + blockNumber + blockHash + userid + certts + block + fpr + idtyHash + written + written_number + expires_on + signature + expired + block_number + } + } + }`) + } + + submitMembership(raw: string) { + return this.graphQLPost(`mutation { + submitMembership(raw: "${raw.replace(/\n/g, '\\n')}") { + revoked + buid + member + kick + leaving + wasMember + pubkey + uid + sig + revocation_sig + hash + written + revoked_on + expires_on + certs { + linked + written + written_block + written_hash + sig + block_number + block_hash + target + to + from + block + expired + expires_on + } + memberships { + membership + issuer + number + blockNumber + blockHash + userid + certts + block + fpr + idtyHash + written + written_number + expires_on + signature + expired + block_number + } + } + }`) + } + + async submitTransaction(raw: string) { + return (await this.graphQLPost(`mutation { + submitTransaction(raw: "${raw.replace(/\n/g, '\\n')}") { + hash + block_number + locktime + version + currency + comment + blockstamp + blockstampTime + time + inputs + unlocks + outputs + issuers + signatures + written + removed + received + output_base + output_amount + written_on + writtenOn + } + }`)) + } + + async pendingIdentities(search = ''): Promise<DBIdentity[]> { + return (await this.graphQLPost(`{ + pendingIdentities(search: "${search}") { + revoked + buid + member + kick + leaving + wasMember + pubkey + uid + sig + revocation_sig + hash + written + revoked_on + expires_on + certs { + linked + written + written_block + written_hash + sig + block_number + block_hash + target + to + from + block + expired + expires_on + } + memberships { + membership + issuer + number + blockNumber + blockHash + userid + certts + block + fpr + idtyHash + written + written_number + expires_on + signature + expired + block_number + } + } + }`) as any).data.pendingIdentities + } + + async sourcesOfPubkey(pub: string): Promise<{ + type: string + noffset: number + identifier: string + amount: number + base: number + conditions: string + consumed: boolean + }[]> { + return (await this.graphQLPost(`{ + sourcesOfPubkey(pub: "${pub}") { + type + noffset + identifier + amount + base + conditions + consumed + } + }`) as any).data.sourcesOfPubkey + } +} \ No newline at end of file diff --git a/tests/test-user.ts b/tests/test-user.ts new file mode 100644 index 0000000..6534c48 --- /dev/null +++ b/tests/test-user.ts @@ -0,0 +1,213 @@ +import {Buid} from "duniter/app/lib/common-libs/buid"; +import {CommonConstants} from "duniter/app/lib/common-libs/constants"; +import {IdentityDTO} from "duniter/app/lib/dto/IdentityDTO"; +import {KeyGen} from "duniter/app/lib/common-libs/crypto/keyring"; +import {TestGvaClient} from "./test-gva-client"; +import {CertificationDTO} from "duniter/app/lib/dto/CertificationDTO"; +import {MembershipDTO} from "duniter/app/lib/dto/MembershipDTO"; + +export class GvaTestUser { + + protected createdIdentity: IdentityDTO + + constructor( + public readonly uid: string, + public readonly pub: string, + public readonly sec: string, + public readonly gva: TestGvaClient) {} + + public async createIdentity(useRoot?:boolean|null) { + const { current, currency } = await this.getCurrentAndCurrency() + const buid = !useRoot && current ? Buid.format.buid(current.number, current.hash) : CommonConstants.SPECIAL_BLOCK + this.createdIdentity = IdentityDTO.fromJSONObject({ + buid, + uid: this.uid, + issuer: this.pub, + currency + }) + const raw = this.createdIdentity.getRawUnSigned() + this.createdIdentity.sig = KeyGen(this.pub, this.sec).signSync(raw) + } + + private async getCurrentAndCurrency() { + return { + current: await this.gva.current(), + currency: await this.gva.currency(), + } + } + + async createIdentityAndSubmit() { + await this.createIdentity() + await this.gva.submitIdentity(this.createdIdentity.getRawSigned()) + } + + public async createCert(user: GvaTestUser) { + const { current, currency } = await this.getCurrentAndCurrency() + const idty = (await this.gva.pendingIdentities(user.pub))[0] + let buid = current ? Buid.format.buid(current.number, current.hash) : CommonConstants.SPECIAL_BLOCK + const cert = { + "version": CommonConstants.DOCUMENTS_VERSION, + "currency": currency, + "issuer": this.pub, + "idty_issuer": user.pub, + "idty_uid": idty.uid, + "idty_buid": idty.buid, + "idty_sig": idty.sig, + "buid": buid, + "sig": "" + } + const rawCert = CertificationDTO.fromJSONObject(cert).getRawUnSigned() + cert.sig = KeyGen(this.pub, this.sec).signSync(rawCert) + return CertificationDTO.fromJSONObject(cert) + } + + public async createCertAndSubmit(user: GvaTestUser) { + const cert = await this.createCert(user) + await this.gva.submitCertification(cert.getRawSigned()) + } + + public async makeMembership(type:string) { + const { current, currency } = await this.getCurrentAndCurrency() + const idty = (await this.gva.pendingIdentities(this.pub))[0] + const block = Buid.format.buid(current); + const join = { + "version": CommonConstants.DOCUMENTS_VERSION, + "currency": currency, + "issuer": this.pub, + "block": block, + "membership": type, + "userid": this.uid, + "certts": idty.buid, + "signature": "" + }; + const rawJoin = MembershipDTO.fromJSONObject(join).getRaw() + join.signature = KeyGen(this.pub, this.sec).signSync(rawJoin) + return MembershipDTO.fromJSONObject(join) + } + + async createJoinAndSubmit() { + const join = await this.makeMembership('IN') + await this.gva.submitMembership(join.getRawSigned()) + } + + async balance(): Promise<number> { + const sources = await this.gva.sourcesOfPubkey(this.pub) + return sources.reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) + } + + + + public async prepareITX(amount:number, recipient:GvaTestUser|string, comment?:string) { + let sources = [] + if (!amount || !recipient) { + throw 'Amount and recipient are required' + } + const { current, currency } = await this.getCurrentAndCurrency() + const version = current && Math.min(CommonConstants.LAST_VERSION_FOR_TX, current.version) + const json = await this.gva.sourcesOfPubkey(this.pub) + let i = 0 + let cumulated = 0 + let commonbase = 99999999 + while (i < json.length) { + const src = json[i] + sources.push({ + 'type': src.type, + 'amount': src.amount, + 'base': src.base, + 'noffset': src.noffset, + 'identifier': src.identifier + }) + commonbase = Math.min(commonbase, src.base); + cumulated += src.amount * Math.pow(10, src.base); + i++; + } + if (cumulated < amount) { + throw 'You do not have enough coins! (' + cumulated + ' ' + currency + ' left)'; + } + let sources2 = []; + let total = 0; + for (let j = 0; j < sources.length && total < amount; j++) { + let src = sources[j]; + total += src.amount * Math.pow(10, src.base); + sources2.push(src); + } + let inputSum = 0; + sources2.forEach((src) => inputSum += src.amount * Math.pow(10, src.base)); + let inputs = sources2.map((src) => { + return { + src: ([src.amount, src.base] as any[]).concat([src.type, src.identifier, src.noffset]).join(':'), + unlock: 'SIG(0)' + }; + }); + let outputs = [{ + qty: amount, + base: commonbase, + lock: 'SIG(' + (typeof recipient === 'string' ? recipient : recipient.pub) + ')' + }]; + if (inputSum - amount > 0) { + // Rest back to issuer + outputs.push({ + qty: inputSum - amount, + base: commonbase, + lock: "SIG(" + this.pub + ")" + }); + } + let raw = this.prepareTX(inputs, outputs, { + version: version, + blockstamp: current && [current.number, current.hash].join('-'), + comment: comment + }, currency) + return this.signed(raw) + } + + public prepareTX(inputs:TestInput[], outputs:TestOutput[], theOptions:any, currency: string) { + let opts = theOptions || {}; + let issuers = opts.issuers || [this.pub]; + let raw = ''; + raw += "Version: " + (opts.version || CommonConstants.TRANSACTION_VERSION) + '\n'; + raw += "Type: Transaction\n"; + raw += "Currency: " + (opts.currency || currency) + '\n'; + raw += "Blockstamp: " + opts.blockstamp + '\n'; + raw += "Locktime: " + (opts.locktime || 0) + '\n'; + raw += "Issuers:\n"; + issuers.forEach((issuer:string) => raw += issuer + '\n'); + raw += "Inputs:\n"; + inputs.forEach(function (input) { + raw += input.src + '\n'; + }); + raw += "Unlocks:\n"; + inputs.forEach(function (input, index) { + if (input.unlock) { + raw += index + ":" + input.unlock + '\n'; + } + }); + raw += "Outputs:\n"; + outputs.forEach(function (output) { + raw += [output.qty, output.base, output.lock].join(':') + '\n'; + }); + raw += "Comment: " + (opts.comment || "") + "\n"; + return raw; + } + + private signed(raw:string) { + let signatures = [KeyGen(this.pub, this.sec).signSync(raw)] + return raw + signatures.join('\n') + '\n' + } + + async createTxAndSubmit(recipient: GvaTestUser, amount: number, comment = '') { + const raw = await this.prepareITX(amount, recipient, comment) + await this.gva.submitTransaction(raw) + } +} + + +export interface TestInput { + src:string + unlock:string +} + +export interface TestOutput { + qty:number + base:number + lock:string +} diff --git a/tsconfig.json b/tsconfig.json index 7892102..1bb40ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "lib": ["es6", "dom", "esnext"] }, "include": [ - "*.ts" + "*.ts", + "tests/" ], "compileOnSave": true } diff --git a/yarn.lock b/yarn.lock index c8cc845..6125b5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,6 +6,10 @@ version "5.0.1" resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-5.0.1.tgz#3c7750d0186b954c7f2d2f6acc8c3c7ba0c3412e" +"@types/bluebird@*": + version "3.5.24" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.24.tgz#11f76812531c14f793b8ecbf1de96f672905de8a" + "@types/body-parser@*": version "1.17.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" @@ -13,6 +17,10 @@ "@types/connect" "*" "@types/node" "*" +"@types/caseless@*": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" + "@types/connect@*": version "3.4.32" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" @@ -46,6 +54,12 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/form-data@*": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" + dependencies: + "@types/node" "*" + "@types/fs-extra@5.0.1": version "5.0.1" resolved "http://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.1.tgz#cd856fbbdd6af2c11f26f8928fd8644c9e9616c9" @@ -109,14 +123,38 @@ version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" +"@types/mocha@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" + "@types/node@*": version "10.12.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.2.tgz#d77f9faa027cadad9c912cd47f4f8b07b0fb0864" +"@types/node@^10.12.3": + version "10.12.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.3.tgz#3918b73ceed484e58367be5acb79d1775239e393" + "@types/range-parser@*": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.2.tgz#fa8e1ad1d474688a757140c91de6dace6f4abc8d" +"@types/request-promise@^4.1.42": + version "4.1.42" + resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.42.tgz#a70a6777429531e60ed09faa077ead9b995204cd" + dependencies: + "@types/bluebird" "*" + "@types/request" "*" + +"@types/request@*": + version "2.48.1" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.1.tgz#e402d691aa6670fbbff1957b15f1270230ab42fa" + dependencies: + "@types/caseless" "*" + "@types/form-data" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + "@types/serve-static@*": version "1.13.2" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" @@ -131,6 +169,16 @@ "@types/glob" "*" "@types/node" "*" +"@types/should@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/should/-/should-13.0.0.tgz#96c00117f1896177848fdecfa336313c230c879e" + dependencies: + should "*" + +"@types/tough-cookie@*": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.4.tgz#821878b81bfab971b93a265a561d54ea61f9059f" + "@types/ws@^5.1.2": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" @@ -276,6 +324,10 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -290,6 +342,12 @@ assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" +assert@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + dependencies: + util "0.10.3" + async@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/async/-/async-2.2.0.tgz#c324eba010a237e4fbd55a12dee86367d5c0ef32" @@ -433,6 +491,10 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -458,6 +520,10 @@ buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" +buffer-from@^1.0.0, buffer-from@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -584,6 +650,10 @@ combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined dependencies: delayed-stream "~1.0.0" +commander@2.15.1: + version "2.15.1" + resolved "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + commander@2.9.0: version "2.9.0" resolved "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -706,6 +776,12 @@ debug@2.6.9, debug@^2.2.0: dependencies: ms "2.0.0" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + debug@~2.2.0: version "2.2.0" resolved "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -764,6 +840,10 @@ dicer@0.2.5: readable-stream "1.1.x" streamsearch "0.1.2" +diff@3.5.0, diff@^3.1.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + "duniter@file:../duniter": version "1.7.1" dependencies: @@ -821,6 +901,63 @@ dicer@0.2.5: wotb "^0.6.5" ws "1.1.5" +"duniter@file:///home/cgeek/dev/duniter": + version "1.7.1" + dependencies: + "@types/leveldown" "^4.0.0" + "@types/levelup" "^3.1.0" + "@types/memdown" "^3.0.0" + "@types/ws" "^5.1.2" + archiver "1.3.0" + async "2.2.0" + bindings "1.2.1" + body-parser "1.17.1" + bs58 "^4.0.1" + cli-table "^0.3.1" + colors "1.1.2" + commander "2.9.0" + cors "2.8.2" + daemonize2 "0.4.2" + ddos "0.1.16" + errorhandler "1.5.0" + event-stream "3.3.4" + express "4.15.2" + express-fileupload "0.0.5" + inquirer "3.0.6" + jison "0.4.17" + js-yaml "3.8.2" + leveldown "^4.0.1" + levelup "^3.1.1" + lokijs "^1.5.3" + memdown "^3.0.0" + merkle "0.5.1" + moment "2.19.3" + morgan "1.8.1" + multimeter "0.1.1" + naclb "1.3.10" + nat-upnp "^1.1.1" + node-pre-gyp "0.6.34" + node-uuid "1.4.8" + optimist "0.6.1" + q-io "^1.13.5" + querablep "^0.1.0" + request "2.81.0" + request-promise "4.2.0" + scryptb "6.0.5" + seedrandom "^2.4.3" + sha1 "1.1.1" + socks-proxy-agent "^3.0.1" + sqlite3 "3.1.13" + tail "^1.2.1" + tweetnacl "0.14.3" + typedoc "^0.11.1" + underscore "1.8.3" + unzip "0.1.11" + unzip2 "0.2.5" + winston "2.3.1" + wotb "^0.6.5" + ws "1.1.5" + duplexer@~0.1.1: version "0.1.1" resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -910,7 +1047,7 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1241,6 +1378,17 @@ github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.5: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -1282,6 +1430,10 @@ graphql@^14.0.2: dependencies: iterall "^1.2.2" +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + handlebars@^4.0.6: version "4.0.12" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" @@ -1324,6 +1476,10 @@ has-color@~0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -1343,6 +1499,10 @@ hawk@3.1.3, hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + highlight.js@^9.0.0: version "9.13.1" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" @@ -1421,6 +1581,10 @@ inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, i version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -1641,6 +1805,10 @@ ltgt@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" +make-error@^1.1.1: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + map-stream@~0.1.0: version "0.1.0" resolved "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" @@ -1724,7 +1892,7 @@ mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" -minimatch@^3.0.0, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -1742,12 +1910,28 @@ minimist@~0.0.1: version "0.0.10" resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -mkdirp@0.5, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5, mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + moment@2.19.3: version "2.19.3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f" @@ -2272,6 +2456,15 @@ request-promise@4.2.0: request-promise-core "1.1.1" stealthy-require "^1.0.0" +request-promise@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" + dependencies: + bluebird "^3.5.0" + request-promise-core "1.1.1" + stealthy-require "^1.1.0" + tough-cookie ">=2.3.3" + request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -2299,7 +2492,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@2.x, request@^2.79.0, request@^2.81.0: +request@2.x, request@^2.79.0, request@^2.81.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: @@ -2489,6 +2682,44 @@ shelljs@^0.8.1: interpret "^1.0.0" rechoir "^0.6.2" +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + +should-util@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063" + +should@*, should@^13.2.3: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -2535,7 +2766,14 @@ socks@^1.1.10: ip "^1.1.4" smart-buffer "^1.0.13" -source-map@^0.6.1, source-map@~0.6.1: +source-map-support@^0.5.6: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -2592,7 +2830,7 @@ statuses@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" -stealthy-require@^1.0.0: +stealthy-require@^1.0.0, stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -2659,6 +2897,12 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -2757,19 +3001,19 @@ toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" -tough-cookie@~2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - dependencies: - punycode "^1.4.1" - -tough-cookie@~2.4.3: +tough-cookie@>=2.3.3, tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" dependencies: psl "^1.1.24" punycode "^1.4.1" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + traverse@>=0.2.4: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -2778,6 +3022,19 @@ traverse@>=0.2.4: version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" +ts-node@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" + dependencies: + arrify "^1.0.0" + buffer-from "^1.1.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.6" + yn "^2.0.0" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -2904,6 +3161,12 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +util@0.10.3: + version "0.10.3" + resolved "http://registry.npmjs.org/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" @@ -2986,6 +3249,10 @@ xtend@^4.0.0, xtend@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + zen-observable-ts@^0.8.10: version "0.8.10" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829" -- GitLab