Skip to content

Commit

Permalink
Working on buildJWT tests now, made great progress on the protocol cl…
Browse files Browse the repository at this point in the history
…ients
  • Loading branch information
ProfMoo committed Dec 4, 2024
1 parent c7f698e commit 68b569c
Show file tree
Hide file tree
Showing 11 changed files with 1,633 additions and 114 deletions.
740 changes: 638 additions & 102 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@
"dist/"
],
"dependencies": {
"@ethereumjs/tx": "^5.1.0",
"@solana/web3.js": "^1.91.3",
"bs58": "^5.0.0",
"node-jose": "^2.2.0"
},
"devDependencies": {
"@ethereumjs/tx": "^5.4.0",
"@types/chai": "^5.0.1",
"@types/mocha": "^10.0.10",
"@types/node": "^20.11.0",
"@types/node-jose": "^1.1.10",
"@types/sinon": "^17.0.3",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"chai": "^5.1.2",
Expand All @@ -43,10 +44,12 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^51.0.1",
"ethereumjs-util": "^7.1.5",
"mocha": "^11.0.1",
"prettier": "^3.0.3",
"prettier-eslint": "^16.0.0",
"rimraf": "^3.0.2",
"sinon": "^19.0.2",
"ts-node": "^10.9.2",
"typescript": "^5.2.2"
},
Expand Down
118 changes: 118 additions & 0 deletions src/auth/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { expect } from 'chai';
import sinon from 'sinon';
import * as fs from 'fs';
import { JWK, JWS } from 'node-jose';
import { buildJWT } from './index';

describe('buildJWT', () => {
let readFileSyncStub: sinon.SinonStub;
let asKeyStub: sinon.SinonStub;
let signStub: sinon.SinonStub;

beforeEach(() => {
readFileSyncStub = sinon.stub(fs, 'readFileSync');
asKeyStub = sinon.stub(JWK, 'asKey');
signStub = sinon.stub(JWS, 'createSign');
});

afterEach(() => {
sinon.restore();
});

it('should build a JWT with provided API key and private key', async () => {
const url = 'https://api.example.com/resource';
const method = 'POST';
const apiKeyName = 'test-api-key';
const apiPrivateKey = 'test-private-key';
const pemPrivateKey =
'-----BEGIN EC PRIVATE KEY-----\ntest-private-key\n-----END EC PRIVATE KEY-----';
const privateKey = { kty: 'EC' };

asKeyStub.resolves(privateKey);
signStub.returns({
update: sinon.stub().returnsThis(),
final: sinon.stub().resolves('signed-jwt'),
});

const jwt = await buildJWT(url, method, apiKeyName, apiPrivateKey);

expect(asKeyStub.calledOnceWithExactly(pemPrivateKey, 'pem')).to.be.true;
expect(signStub.calledOnce).to.be.true;
expect(jwt).to.equal('signed-jwt');
});

it('should build a JWT with API key from file', async () => {
const url = 'https://api.example.com/resource';
const method = 'POST';
const apiKey = {
name: 'test-api-key',
privateKey: 'test-private-key',
};
const pemPrivateKey =
'-----BEGIN EC PRIVATE KEY-----\ntest-private-key\n-----END EC PRIVATE KEY-----';
const privateKey = { kty: 'EC' };

readFileSyncStub.returns(JSON.stringify(apiKey));
asKeyStub.resolves(privateKey);
signStub.returns({
update: sinon.stub().returnsThis(),
final: sinon.stub().resolves('signed-jwt'),
});

const jwt = await buildJWT(url, method);

expect(
readFileSyncStub.calledOnceWithExactly('.coinbase_cloud_api_key.json', {
encoding: 'utf8',
}),
).to.be.true;
expect(asKeyStub.calledOnceWithExactly(pemPrivateKey, 'pem')).to.be.true;
expect(signStub.calledOnce).to.be.true;
expect(jwt).to.equal('signed-jwt');
});

it('should throw an error if private key is not EC', async () => {
const url = 'https://api.example.com/resource';
const method = 'POST';
const apiKeyName = 'test-api-key';
const apiPrivateKey = 'test-private-key';
const pemPrivateKey =
'-----BEGIN EC PRIVATE KEY-----\ntest-private-key\n-----END EC PRIVATE KEY-----';
const privateKey = { kty: 'RSA' };

asKeyStub.resolves(privateKey);

try {
await buildJWT(url, method, apiKeyName, apiPrivateKey);
expect.fail('Expected buildJWT to throw an error');
} catch (error) {
expect((error as Error).message).to.equal('Not an EC private key');
}

expect(asKeyStub.calledOnceWithExactly(pemPrivateKey, 'pem')).to.be.true;
expect(signStub.notCalled).to.be.true;
});

it('should throw an error if private key cannot be parsed', async () => {
const url = 'https://api.example.com/resource';
const method = 'POST';
const apiKeyName = 'test-api-key';
const apiPrivateKey = 'test-private-key';
const pemPrivateKey =
'-----BEGIN EC PRIVATE KEY-----\ntest-private-key\n-----END EC PRIVATE KEY-----';

asKeyStub.rejects(new Error('Invalid key'));

try {
await buildJWT(url, method, apiKeyName, apiPrivateKey);
expect.fail('Expected buildJWT to throw an error');
} catch (error) {
expect((error as Error).message).to.include(
'jwt: Could not decode or parse private key. Invalid key',
);
}

expect(asKeyStub.calledOnceWithExactly(pemPrivateKey, 'pem')).to.be.true;
expect(signStub.notCalled).to.be.true;
});
});
118 changes: 118 additions & 0 deletions src/client/protocols/cosmos-staking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { expect } from 'chai';
import sinon from 'sinon';
import { Cosmos } from './cosmos-staking';
import { StakingClient } from '../staking-client';
import {
ListRewardsRequest,
ListRewardsResponse,
} from '../../gen/coinbase/staking/rewards/v1/reward.pb';
import {
ListStakesRequest,
ListStakesResponse,
} from '../../gen/coinbase/staking/rewards/v1/stake.pb';

describe('Cosmos', () => {
let stakingClientStub: sinon.SinonStubbedInstance<StakingClient>;
let cosmos: Cosmos;

beforeEach(() => {
stakingClientStub = sinon.createStubInstance(StakingClient);
cosmos = new Cosmos(stakingClientStub);
});

afterEach(() => {
sinon.restore();
});

describe('listRewards', () => {
it('should list rewards with the correct parameters', async () => {
const filter = 'some-filter';
const pageSize = 50;
const pageToken = 'some-token';
const req: ListRewardsRequest = {
parent: 'protocols/cosmos',
filter: filter,
pageSize: pageSize,
pageToken: pageToken,
};
const res: ListRewardsResponse = {
/* response data */
};

stakingClientStub.listRewards.resolves(res);

const response = await cosmos.listRewards(filter, pageSize, pageToken);

expect(stakingClientStub.listRewards.calledOnceWithExactly('cosmos', req))
.to.be.true;
expect(response).to.deep.equal(res);
});

it('should use default parameters when not provided', async () => {
const filter = 'some-filter';
const req: ListRewardsRequest = {
parent: 'protocols/cosmos',
filter: filter,
pageSize: 100,
pageToken: undefined,
};
const res: ListRewardsResponse = {
/* response data */
};

stakingClientStub.listRewards.resolves(res);

const response = await cosmos.listRewards(filter);

expect(stakingClientStub.listRewards.calledOnceWithExactly('cosmos', req))
.to.be.true;
expect(response).to.deep.equal(res);
});
});

describe('listStakes', () => {
it('should list stakes with the correct parameters', async () => {
const filter = 'some-filter';
const pageSize = 50;
const pageToken = 'some-token';
const req: ListStakesRequest = {
parent: 'protocols/cosmos',
filter: filter,
pageSize: pageSize,
pageToken: pageToken,
};
const res: ListStakesResponse = {
/* response data */
};

stakingClientStub.listStakes.resolves(res);

const response = await cosmos.listStakes(filter, pageSize, pageToken);

expect(stakingClientStub.listStakes.calledOnceWithExactly('cosmos', req))
.to.be.true;
expect(response).to.deep.equal(res);
});

it('should use default parameters when not provided', async () => {
const filter = 'some-filter';
const req: ListStakesRequest = {
parent: 'protocols/cosmos',
filter: filter,
pageSize: 100,
pageToken: undefined,
};
const res: ListStakesResponse = {
/* response data */
};

stakingClientStub.listStakes.resolves(res);

const response = await cosmos.listStakes(filter);

expect(stakingClientStub.listStakes.calledOnceWithExactly('cosmos', req))
.to.be.true;
expect(response).to.deep.equal(res);
});
});
});
Loading

0 comments on commit 68b569c

Please sign in to comment.