@@ -10,7 +10,13 @@ import { usersFixture } from '../fixture';
1010import { Product, ProductType } from '../../src/entity/Product';
1111import type { AuthContext } from '../../src/Context';
1212import { createClient } from '@connectrpc/connect';
13- import { Credits, EntityType, TransferStatus } from '@dailydotdev/schema';
13+ import {
14+ Credits,
15+ Currency,
16+ EntityType,
17+ GetBalanceRequest,
18+ TransferStatus,
19+ } from '@dailydotdev/schema';
1420import * as njordCommon from '../../src/common/njord';
1521import { User } from '../../src/entity/user/User';
1622import { ForbiddenError } from 'apollo-server-errors';
@@ -23,6 +29,8 @@ import * as redisFile from '../../src/redis';
2329import { ioRedisPool } from '../../src/redis';
2430import { parseBigInt } from '../../src/common';
2531import { TransferError } from '../../src/errors';
32+ import { verifyJwt } from '../../src/auth';
33+ import { serviceClientId } from '../../src/types';
2634
2735let con: DataSource;
2836
@@ -212,6 +220,47 @@ describe('transferCores', () => {
212220 }),
213221 ).rejects.toBeInstanceOf(TransferError);
214222 });
223+
224+ it('should sign request', async () => {
225+ const mockTransport = createMockNjordTransport();
226+ const mockedClient = createClient(Credits, mockTransport);
227+ const clientSpy = jest.spyOn(mockedClient, 'transfer');
228+ jest
229+ .spyOn(njordCommon, 'getNjordClient')
230+ .mockImplementation(() => mockedClient);
231+
232+ const transaction = await njordCommon.createTransaction({
233+ ctx: {
234+ userId: 't-tc-1',
235+ } as unknown as AuthContext,
236+ entityManager: con.manager,
237+ productId: 'dd65570f-86c0-40a0-b8a0-3fdbd0d3945d',
238+ receiverId: 't-tc-2',
239+ note: 'Test test!',
240+ });
241+
242+ await njordCommon.transferCores({
243+ ctx: {
244+ userId: 't-tc-1',
245+ } as unknown as AuthContext,
246+ transaction,
247+ entityManager: con.manager,
248+ });
249+
250+ expect(clientSpy).toHaveBeenCalledTimes(1);
251+ expect(clientSpy).toHaveBeenCalledWith(
252+ expect.objectContaining({
253+ idempotencyKey: transaction.id,
254+ transfers: expect.toBeArrayOfSize(1),
255+ }),
256+ expect.objectContaining({
257+ headers: expect.any(Headers),
258+ }),
259+ );
260+ expect(
261+ (clientSpy.mock.calls[0][1]!.headers as Headers).get('authorization'),
262+ ).toStartWith('Bearer ');
263+ });
215264});
216265
217266describe('getBalance', () => {
@@ -392,6 +441,36 @@ describe('getBalance', () => {
392441
393442 expect(result).toEqual({ amount: 0 });
394443 });
444+
445+ it('should sign request', async () => {
446+ const mockTransport = createMockNjordTransport();
447+ const mockedClient = createClient(Credits, mockTransport);
448+ const clientSpy = jest.spyOn(mockedClient, 'getBalance');
449+ jest
450+ .spyOn(njordCommon, 'getNjordClient')
451+ .mockImplementation(() => mockedClient);
452+
453+ const result = await njordCommon.getBalance({
454+ userId: 't-gb-1',
455+ });
456+
457+ expect(result).toEqual({ amount: 0 });
458+ expect(clientSpy).toHaveBeenCalledTimes(1);
459+ expect(clientSpy).toHaveBeenCalledWith(
460+ {
461+ account: {
462+ userId: 't-gb-1',
463+ currency: 0,
464+ },
465+ },
466+ expect.objectContaining({
467+ headers: expect.any(Headers),
468+ }),
469+ );
470+ expect(
471+ (clientSpy.mock.calls[0][1]!.headers as Headers).get('authorization'),
472+ ).toStartWith('Bearer ');
473+ });
395474});
396475
397476describe('updatedBalanceCache', () => {
@@ -478,3 +557,239 @@ describe('expireBalanceCache', () => {
478557 expect(getFreshBalanceSpy).toHaveBeenCalledTimes(2);
479558 });
480559});
560+
561+ describe('purchaseCores', () => {
562+ beforeEach(async () => {
563+ jest.clearAllMocks();
564+
565+ const mockTransport = createMockNjordTransport();
566+ jest
567+ .spyOn(njordCommon, 'getNjordClient')
568+ .mockImplementation(() => createClient(Credits, mockTransport));
569+
570+ await saveFixtures(
571+ con,
572+ User,
573+ usersFixture.map((item) => {
574+ return {
575+ ...item,
576+ id: `t-pc-${item.id}`,
577+ username: `t-pc-${item.username}`,
578+ github: undefined,
579+ };
580+ }),
581+ );
582+
583+ await saveFixtures(con, Product, [
584+ {
585+ id: '5329e56b-b121-47cb-9c3c-58c086c1542b',
586+ name: 'Award 1',
587+ image: 'https://daily.dev/award.jpg',
588+ type: ProductType.Award,
589+ value: 42,
590+ },
591+ ]);
592+ });
593+
594+ it('should purchase cores', async () => {
595+ const transaction = await con.getRepository(UserTransaction).save({
596+ processor: UserTransactionProcessor.Paddle,
597+ receiverId: 't-pc-2',
598+ status: UserTransactionStatus.Success,
599+ productId: null,
600+ senderId: null,
601+ value: 42,
602+ valueIncFees: 42,
603+ fee: 0,
604+ request: {},
605+ flags: {
606+ note: 'Test test!',
607+ },
608+ });
609+
610+ await njordCommon.purchaseCores({
611+ transaction,
612+ });
613+
614+ expect(transaction).toMatchObject({
615+ id: expect.any(String),
616+ processor: UserTransactionProcessor.Paddle,
617+ receiverId: 't-pc-2',
618+ status: UserTransactionStatus.Success,
619+ productId: null,
620+ senderId: null,
621+ value: 42,
622+ valueIncFees: 42,
623+ fee: 0,
624+ createdAt: expect.any(Date),
625+ updatedAt: expect.any(Date),
626+ flags: {
627+ note: 'Test test!',
628+ },
629+ } as UserTransaction);
630+
631+ const transactionAfter = await con
632+ .getRepository(UserTransaction)
633+ .findOneByOrFail({
634+ id: transaction.id,
635+ });
636+
637+ expect(transactionAfter.id).toBe(transaction.id);
638+ expect(transactionAfter).toMatchObject({
639+ ...transaction,
640+ valueIncFees: 42,
641+ updatedAt: expect.any(Date),
642+ });
643+ });
644+
645+ it('should throw if transaction has no id', async () => {
646+ const transaction = await con.getRepository(UserTransaction).create({
647+ processor: UserTransactionProcessor.Paddle,
648+ receiverId: 't-pc-2',
649+ status: UserTransactionStatus.Success,
650+ productId: null,
651+ senderId: 't-pc-1',
652+ value: 42,
653+ valueIncFees: 42,
654+ fee: 0,
655+ request: {},
656+ flags: {
657+ note: 'Test test!',
658+ },
659+ });
660+
661+ await expect(() =>
662+ njordCommon.purchaseCores({
663+ transaction,
664+ }),
665+ ).rejects.toThrow(new Error('No transaction id'));
666+ });
667+
668+ it('should throw if transaction has product', async () => {
669+ const transaction = await con.getRepository(UserTransaction).save({
670+ processor: UserTransactionProcessor.Paddle,
671+ receiverId: 't-pc-2',
672+ status: UserTransactionStatus.Success,
673+ productId: '5329e56b-b121-47cb-9c3c-58c086c1542b',
674+ senderId: null,
675+ value: 42,
676+ valueIncFees: 42,
677+ fee: 0,
678+ request: {},
679+ flags: {
680+ note: 'Test test!',
681+ },
682+ });
683+
684+ await expect(() =>
685+ njordCommon.purchaseCores({
686+ transaction,
687+ }),
688+ ).rejects.toThrow(
689+ new Error('Purchase cores transaction can not have product'),
690+ );
691+ });
692+
693+ it('should throw if transaction has sender', async () => {
694+ const transaction = await con.getRepository(UserTransaction).save({
695+ processor: UserTransactionProcessor.Paddle,
696+ receiverId: 't-pc-2',
697+ status: UserTransactionStatus.Success,
698+ productId: null,
699+ senderId: 't-pc-1',
700+ value: 42,
701+ valueIncFees: 42,
702+ fee: 0,
703+ request: {},
704+ flags: {
705+ note: 'Test test!',
706+ },
707+ });
708+
709+ await expect(() =>
710+ njordCommon.purchaseCores({
711+ transaction,
712+ }),
713+ ).rejects.toThrow(
714+ new Error('Purchase cores transaction can not have sender'),
715+ );
716+ });
717+
718+ it('should sign request', async () => {
719+ const mockTransport = createMockNjordTransport();
720+ const mockedClient = createClient(Credits, mockTransport);
721+ const clientSpy = jest.spyOn(mockedClient, 'transfer');
722+ jest
723+ .spyOn(njordCommon, 'getNjordClient')
724+ .mockImplementation(() => mockedClient);
725+
726+ const transaction = await con.getRepository(UserTransaction).save({
727+ processor: UserTransactionProcessor.Paddle,
728+ receiverId: 't-pc-2',
729+ status: UserTransactionStatus.Success,
730+ productId: null,
731+ senderId: null,
732+ value: 42,
733+ valueIncFees: 42,
734+ fee: 0,
735+ request: {},
736+ flags: {
737+ note: 'Test test!',
738+ },
739+ });
740+
741+ await njordCommon.purchaseCores({
742+ transaction,
743+ });
744+
745+ expect(clientSpy).toHaveBeenCalledTimes(1);
746+ expect(clientSpy).toHaveBeenCalledWith(
747+ expect.objectContaining({
748+ idempotencyKey: transaction.id,
749+ transfers: expect.toBeArrayOfSize(1),
750+ }),
751+ expect.objectContaining({
752+ headers: expect.any(Headers),
753+ }),
754+ );
755+ expect(
756+ (clientSpy.mock.calls[0][1]!.headers as Headers).get('authorization'),
757+ ).toStartWith('Bearer ');
758+ });
759+ });
760+
761+ describe('createNjordAuth', () => {
762+ beforeEach(async () => {
763+ jest.clearAllMocks();
764+ });
765+
766+ it('should create auth', async () => {
767+ const payload = new GetBalanceRequest({
768+ account: {
769+ userId: 't-cna-1',
770+ currency: Currency.CORES,
771+ },
772+ });
773+
774+ const result = await njordCommon.createNjordAuth(payload);
775+
776+ expect(result.headers).toBeInstanceOf(Headers);
777+
778+ const headers = result.headers as Headers;
779+
780+ expect(headers.get('authorization')).toStartWith('Bearer ');
781+
782+ const jwt = await verifyJwt(
783+ headers.get('authorization')!.replace('Bearer ', '')!,
784+ );
785+
786+ expect(jwt).toMatchObject({
787+ aud: 'Daily Staging',
788+ client_id: serviceClientId,
789+ iat: expect.any(Number),
790+ iss: 'Daily API Staging',
791+ message_hash:
792+ '87a33e6ae594147d8cd4d22b7b29f026f0ab4b45b66d9ff65ca7b456894f8871',
793+ });
794+ });
795+ });
0 commit comments