Add tests (Need pairingcode fix)

This commit is contained in:
junderw 2019-05-19 14:20:15 +09:00
parent b9429cb1ee
commit c506084a41
No known key found for this signature in database
GPG Key ID: B256185D3A971908
13 changed files with 4382 additions and 111 deletions

3
.gitignore vendored
View File

@ -58,4 +58,5 @@ yarn-error.log*
.vscode
.idea
dist/
dist/
coverage/

31
jest.json Normal file
View File

@ -0,0 +1,31 @@
{
"moduleFileExtensions": [
"ts",
"js",
"json"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
"testEnvironment" : "node",
"testRegex": "/tests/.*\\.(test|spec)\\.(ts)$",
"testURL": "http://localhost/",
"coverageThreshold": {
"global": {
"statements": 90,
"branches": 90,
"functions": 90,
"lines": 90
}
},
"collectCoverageFrom": [
"src/**/*.ts",
"!**/node_modules/**",
"!**/vendor/**"
],
"coverageReporters": [
"lcov",
"text"
],
"verbose": true
}

4112
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,12 +9,14 @@
],
"scripts": {
"build": "rimraf dist && tsc -p ./tsconfig.json",
"coverage": "npm run unit -- --coverage",
"format": "npm run prettier -- --write",
"format:ci": "npm run prettier -- --check",
"lint": "tslint -p tsconfig.json -c tslint.json",
"prepare": "npm run build",
"prettier": "prettier 'src/**/*.ts' --single-quote --trailing-comma all",
"test": "npm run format:ci && npm run lint && echo \"###!!!Warning!!!### No tests exist! We ONLY checked formatting and linting!\""
"prettier": "prettier 'src/**/*.ts' 'tests/**/*.spec.ts' --single-quote --trailing-comma all",
"test": "npm run format:ci && npm run lint && npm run coverage",
"unit": "jest --config=jest.json --runInBand"
},
"author": "Tim Akinbo <tim@tanjalo.com>; Christoph Ott <christoph.ott@lean-coders.at>",
"license": "MIT",
@ -32,13 +34,16 @@
"devDependencies": {
"@types/bs58": "^4.0.0",
"@types/elliptic": "^6.4.6",
"@types/jest": "^24.0.13",
"@types/node": "^11.13.5",
"@types/request": "^2.48.1",
"@types/request-promise": "^4.1.42",
"@types/underscore": "^1.8.14",
"eslint": "^5.16.0",
"jest": "^24.8.0",
"prettier": "^1.17.1",
"rimraf": "^2.6.3",
"ts-jest": "^24.0.2",
"ts-node": "^8.1.0",
"tslint": "^5.16.0",
"typescript": "^3.4.4"

View File

@ -2,6 +2,7 @@ import * as elliptic from 'elliptic';
import * as qs from 'querystring';
import * as rp from 'request-promise';
import * as _ from 'underscore';
import { GetInvoicesArgs, PairClientResponse } from '../models/client';
import { Cryptography as crypto } from './cryptography';
import { Invoice } from '../models/invoice';
import { Rate } from '../models/rate';
@ -16,6 +17,7 @@ export class BTCPayClient {
private kp: elliptic.ec.KeyPair,
private tokens: any = {},
) {
this.host = this.host.replace(/\/+$/, '');
this.clientId = crypto.get_sin_from_key(this.kp);
this.userAgent = 'node-btcpay';
this.options = {
@ -29,11 +31,11 @@ export class BTCPayClient {
};
}
public async pair_client(code: string): Promise<any> {
const re = new RegExp('^\\w{7,7}$');
public async pair_client(code: string): Promise<PairClientResponse> {
const re = new RegExp('^\\w{7}$');
if (!re.test(code)) {
throw 'pairing code is not valid';
throw new Error('pairing code is not valid');
}
const payload = {
@ -54,32 +56,36 @@ export class BTCPayClient {
storeID: string,
): Promise<Rate[]> {
return this.signed_get_request('/rates', {
currencyPairs,
currencyPairs: currencyPairs.join(','),
storeID,
});
}
public async create_invoice(payload: any, token?: any): Promise<Invoice> {
const re = new RegExp('^[A-Z]{3,3}$');
public async create_invoice(
payload: { currency: string; price: string | number },
token?: any,
): Promise<Invoice> {
const re = new RegExp('^[A-Z]{3}$');
if (!re.test(payload.currency)) {
throw 'Currency is invalid';
throw new Error('Currency is invalid');
}
if (isNaN(parseFloat(payload.price))) {
throw 'Price must be a float';
if (isNaN(parseFloat(payload.price as string))) {
throw new Error('Price must be a float');
}
return this.signed_post_request('/invoices', payload, token) as Promise<
Invoice
>;
return this.signed_post_request('/invoices', payload, token);
}
public async get_invoice(invoiceId: string, token?: any): Promise<Invoice[]> {
public async get_invoice(invoiceId: string, token?: any): Promise<Invoice> {
return this.signed_get_request('/invoices/' + invoiceId, token);
}
public async get_invoices(params: any, token?: any): Promise<Invoice[]> {
public async get_invoices(
params?: GetInvoicesArgs,
token?: any,
): Promise<Invoice[]> {
return this.signed_get_request('/invoices', params, token);
}
@ -113,7 +119,7 @@ export class BTCPayClient {
private async signed_post_request(
path: string,
payload: any = {},
payload: any,
token: any = _.values(this.tokens)[0],
): Promise<any> {
payload.token = token;
@ -129,13 +135,11 @@ export class BTCPayClient {
return rp.post(_options).then((resp: any) => resp.data);
}
private async unsigned_request(path: string, payload?: any): Promise<any> {
const hasPayload = payload !== undefined;
private async unsigned_request(path: string, payload: any): Promise<any> {
const _mixin: any = {
method: hasPayload ? 'POST' : 'GET',
method: 'POST',
uri: this.host + path,
...(hasPayload ? { body: payload } : undefined),
body: payload,
};
const _options = { ...JSON.parse(JSON.stringify(this.options)), ..._mixin };

13
src/models/client.ts Normal file
View File

@ -0,0 +1,13 @@
export interface PairClientResponse {
merchant: string;
}
export interface GetInvoicesArgs {
status?: string;
orderId?: string;
itemCode?: string;
dateStart?: string;
dateEnd?: string;
limit?: number;
offset?: number;
}

View File

@ -1,68 +1,73 @@
export interface Invoice {
id: string;
token: string;
url: string;
posData: string | null;
status: string;
btcPrice: string;
btcDue: string;
cryptoInfo: Array<{
cryptoCode: string;
paymentType: string;
rate: number;
exRates: any[];
paid: string;
price: string;
due: string;
paymentUrls: any[];
address: string;
url: string;
totalDue: string;
networkFee: string;
txCount: number;
cryptoPaid: string;
payments: any[];
}>;
price: number;
currency: string;
orderId: string;
orderID?: string;
itemDesc: string;
itemCode: string;
notificationEmail: string;
notificationURL: string;
redirectURL: string;
paymentUrls?: any;
paymentCodes: any;
posData: string;
transactionSpeed: string;
fullNotifications: boolean;
extendedNotifications: boolean;
physical: boolean;
buyer: {
name: string;
address1: string;
address2: string;
locality: string;
region: string;
postalCode: string;
country: string;
email: string;
phone: string;
notify: boolean;
};
url: string;
status: string;
btcPaid?: number;
amountPaid: number;
btcPrice?: number;
paymentSubtotals: any;
btcDue?: number;
paymentTotals: any;
minerFees: any;
exRates: any;
buyerTotalBtcAmount: string | null;
itemDesc: string | null;
itemCode: string | null;
orderId: string | null;
guid: string;
id: string;
invoiceTime: number;
expirationTime: number;
currentTime: string; // 'date' === string?
exceptionStatus: string | boolean;
rate?: number;
exRates?: any;
exchangeRates: any;
transactions: Array<{
amount: number;
confirmations: number;
time: string; // 'date' === string?
receivedTime: string; // 'date' === string?
}>;
flags?: {
refundable: string;
currentTime: number;
lowFeeDetected: boolean;
btcPaid: string;
rate: number;
exceptionStatus: boolean;
paymentUrls: {
BIP21: string | null;
BIP72: string | null;
BIP72b: string | null;
BIP73: string | null;
BOLT11: string | null;
};
creditedOverpaymentAmounts: any;
refundInfo: Array<{
supportRequest: string;
currency: string;
amounts: any;
}>;
transactionCurrency: string;
refundAddressRequestPending: boolean;
buyerPaidBtcMinerFee: string | null;
bitcoinAddress: string;
token: string;
flags: {
refundable: boolean;
};
paymentSubtotals: any;
paymentTotals: any;
amountPaid: number;
minerFees: any;
exchangeRates: any;
supportedTransactionCurrencies: any;
buyerProvidedInfo: {
selectedTransactionCurrency: any;
addresses: any;
paymentCodes: any;
buyer: {
name: string | null;
address1: string | null;
address2: string | null;
locality: string | null;
region: string | null;
postalCode: string | null;
country: string | null;
phone: string | null;
email: string | null;
};
}

View File

@ -1,5 +1,7 @@
export interface Rate {
code: string;
name: string;
cryptoCode: string;
currencyPair: string;
code: string;
rate: number;
}

86
tests/core/client.spec.ts Normal file
View File

@ -0,0 +1,86 @@
import * as elliptic from 'elliptic';
import { BTCPayClient } from '../../src/core/client';
import { Cryptography as myCrypto } from '../../src/core/cryptography';
const USER_NAME = 'test@example.com';
const PASSWORD = 'satoshinakamoto';
const URL = 'https://testnet.demo.btcpayserver.org/';
const MY_PRIVATE_KEY = Buffer.from(
'31eb31ecf1a640c9d1e0a1105501f36235f8c7d51d67dcf74ccc968d74cb6b25',
'hex',
);
const STORE_ID = 'HPPHFtqtsKsF3KU18fBNwVGP64hicGoRynvQrC3R2Rkw';
const TOKENS = {
merchant: 'DwSMQ4SF7GAJRaMiLn4zjAR35bFJwgSpuKt9pxYoQNjJ',
};
const INVOICE_ID = 'TRnwXeAkuLQihe22mJs7J4';
// We need a way to programmatically get a new pairing code...
const SERVER_PAIRING_CODE = 'apYAxP9';
let MY_KEYPAIR: elliptic.ec.KeyPair;
let client: BTCPayClient;
describe('btcpay.core.cryptography', () => {
beforeAll(() => {
MY_KEYPAIR = myCrypto.load_keypair(MY_PRIVATE_KEY);
client = new BTCPayClient(URL, MY_KEYPAIR, TOKENS);
});
it('should pair with server', async () => {
const myClient = new BTCPayClient(URL, MY_KEYPAIR);
const result = await myClient.pair_client(SERVER_PAIRING_CODE).then(
v => v,
async err => {
if (
err.message.match(
/^404 - {"error":"The specified pairingCode is not found"}$/,
)
)
return { merchant: 'test' };
throw err;
},
);
expect(result.merchant).toBeDefined();
await expect(myClient.pair_client('hduheufhfuf')).rejects.toThrow(
/^pairing code is not valid$/,
);
});
it('should get rates', async () => {
const results = await client.get_rates(['LTC_USD', 'BTC_USD'], STORE_ID);
expect(results[0].rate).toBeDefined();
});
it('should create an invoice', async () => {
const results = await client.create_invoice({
currency: 'USD',
price: 1.12,
});
expect(results.bitcoinAddress).toBeDefined();
await expect(
client.create_invoice({
currency: 'KDFAHKJFKJ',
price: 1.12,
}),
).rejects.toThrow(/^Currency is invalid$/);
await expect(
client.create_invoice({
currency: 'USD',
price: 'xkhdfhu',
}),
).rejects.toThrow(/^Price must be a float$/);
});
it('should get invoice', async () => {
const results = await client.get_invoice(INVOICE_ID);
expect(results.id).toBe(INVOICE_ID);
});
it('should get multiple invoices', async () => {
const results = await client.get_invoices();
expect(results[0].bitcoinAddress).toBeDefined();
});
});

View File

@ -0,0 +1,33 @@
import { Cryptography as myCrypto } from '../../src/core/cryptography';
const MY_PRIVATE_KEY = Buffer.from(
'31eb31ecf1a640cd91e0a1105501f36235f8c7d51d67dcf74ccc968d74cb6b25',
'hex',
);
describe('btcpay.core.cryptography', () => {
it('should generate a keypair', () => {
const kp = myCrypto.generate_keypair();
const priv = kp.getPrivate();
expect(priv).toBeDefined();
});
it('should load a keypair from Buffer', () => {
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
const priv = kp.getPrivate();
expect(priv).toBeDefined();
});
it('should get sin from key', () => {
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
const sin = myCrypto.get_sin_from_key(kp);
expect(sin).toBe('TfDnXWvj6bBhkduYiZnohg5qhtDu5VWohhw');
});
it('should sign a message', () => {
const kp = myCrypto.load_keypair(MY_PRIVATE_KEY);
const message = Buffer.from('Satoshi', 'utf8');
const sig = myCrypto.sign(message, kp);
expect(sig.toString('hex')).toBe(
'304402205b0a505c180bddbd4a8836de0f2ac10b52b327d0e932352d28d170fb81517a' +
'770220307ac2ec2134d81fd04df6a1662b0962ad1322209c2e45ff8af63d3f12e0d089',
);
});
});

7
tests/index.spec.ts Normal file
View File

@ -0,0 +1,7 @@
import * as btcpay from '../src/index';
describe('btcpay.index', () => {
it('should import', () => {
expect(btcpay).toBeDefined();
});
});

View File

@ -1,9 +0,0 @@
const btcpay = require('../dist');
const keypair = btcpay.crypto.load_keypair(new Buffer.from('', 'hex'));
const client = new btcpay.BTCPayClient('', keypair, { merchant: '' });
client
.get_rates('BTC_USD', '')
.then(rates => console.log(rates))
.catch(err => console.log(err));

View File

@ -1,19 +0,0 @@
import * as btcpay from '../dist';
import { METHODS } from 'http';
const keypair = btcpay.crypto.load_keypair(Buffer.from('', 'hex'));
const client = new btcpay.BTCPayClient('', keypair, {
merchant: '',
});
async function test() {
try {
const rates = await client.get_rates('BTC_EUR', '');
console.log(JSON.stringify(rates));
} catch (err) {
console.log(err);
}
}
test();