Skip to content

Commit

Permalink
Merge pull request #135 from Archie-Finance/feature/websockets
Browse files Browse the repository at this point in the history
feature/Websocket event api
  • Loading branch information
FurlanLuka authored Oct 13, 2022
2 parents 2b5a660 + 19aa781 commit b3eea17
Show file tree
Hide file tree
Showing 100 changed files with 1,792 additions and 1,173 deletions.
27 changes: 23 additions & 4 deletions apps/ltv-api/integration/ltv_updated_flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { DateTime } from 'luxon';
import {
COLLATERAL_SALE_LTV_LIMIT,
LTV_MARGIN_CALL_LIMIT,
LTV_UPDATED_TOPIC,
MARGIN_CALL_STARTED_TOPIC,
} from '@archie/api/ltv-api/constants';
import { MARGIN_CALL_LIQUIDATION_AFTER_HOURS } from '@archie/api/ltv-api/constants';
Expand Down Expand Up @@ -70,7 +71,7 @@ describe('Ltv api tests', () => {
);
const liquidationAmount = '15000';
const afterLiquidationLtv = 60;
let liquidationId: string;
let liquidationId: string | undefined;

it(`should keep ltv at 0 as credit line was not created yet`, async () => {
await app.get(LtvQueueController).ledgerUpdated(ledgerUpdatedPayload);
Expand All @@ -79,6 +80,10 @@ describe('Ltv api tests', () => {
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);

expect(queueStub.publish).toHaveBeenCalledWith(LTV_UPDATED_TOPIC, {
userId: user.id,
ltv: 0,
});
expect(response.body).toStrictEqual<LtvDto>({
ltv: 0,
status: LtvStatus.good,
Expand All @@ -103,6 +108,7 @@ describe('Ltv api tests', () => {
});

it(`should create margin call and liquidate once ltv reaches 90%`, async () => {
const expectedLiquidationCommandPublishSequence = 3;
const creditBalanceUpdatedPayload = creditBalanceUpdatedFactory({
utilizationAmount: ledgerValue * 0.9,
});
Expand Down Expand Up @@ -137,7 +143,11 @@ describe('Ltv api tests', () => {
createdAt: expect.any(String),
},
]);
expect(queueStub.publish).nthCalledWith(1, MARGIN_CALL_STARTED_TOPIC, {
expect(queueStub.publish).nthCalledWith(1, LTV_UPDATED_TOPIC, {
userId: user.id,
ltv: 90,
});
expect(queueStub.publish).nthCalledWith(2, MARGIN_CALL_STARTED_TOPIC, {
userId: user.id,
startedAt: expect.any(String),
ltv: 90,
Expand All @@ -150,18 +160,24 @@ describe('Ltv api tests', () => {
collateralBalance: ledgerValue,
});
expect(queueStub.publish).nthCalledWith(
2,
expectedLiquidationCommandPublishSequence,
INITIATE_LEDGER_ASSET_LIQUIDATION_COMMAND,
{
userId: user.id,
amount: liquidationAmount,
liquidationId: expect.any(String),
},
);
liquidationId = queueStub.publish.mock.calls[1][1].liquidationId;
liquidationId =
queueStub.publish.mock.calls[
expectedLiquidationCommandPublishSequence - 1
][1].liquidationId;
});

it(`should not mark margin call as completed if only collateral balance update is received`, async () => {
if (liquidationId === undefined) {
throw new Error('Liquidation id is not found');
}
const ledgerUpdatedDueToLiquidationEvent =
ledgerAccountUpdatedPayloadFactory({
action: {
Expand All @@ -187,6 +203,9 @@ describe('Ltv api tests', () => {
});

it(`should mark margin call as completed after also credit balance update is received`, async () => {
if (liquidationId === undefined) {
throw new Error('Liquidation id is not found');
}
const creditUtilizationUpdatedDueToLiquidationEvent =
creditBalanceUpdatedFactory({
paymentDetails: {
Expand Down
109 changes: 49 additions & 60 deletions apps/onboarding-api/integration/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
createTestingModule,
generateUserAccessToken,
initializeTestingModule,
queueStub,
TestDatabase,
user,
} from '@archie/test/integration';
import { AppModule } from '../src/app.module';
import * as request from 'supertest';
Expand All @@ -18,6 +20,8 @@ import {
import { OnboardingQueueController } from '@archie/api/onboarding-api/onboarding';
import { creditLineCreatedDataFactory } from '@archie/api/credit-line-api/test-data';
import { cardActivatedDataFactory } from '@archie/api/credit-api/test-data';
import { ONBOARDING_UPDATED_TOPIC } from '@archie/api/onboarding-api/constants';
import { onboardingUpdatedPayloadFactory } from '@archie/api/onboarding-api/test-data';

describe('Onboarding service tests', () => {
let app: INestApplication;
Expand All @@ -43,6 +47,9 @@ describe('Onboarding service tests', () => {
describe('Create and update onboarding record for user', () => {
beforeAll(setup);
afterAll(cleanup);
afterEach(() => {
queueStub.publish.mockReset();
});

it('should throw an error because onboarding record was not created yet', async () => {
const kycSubmittedPayload = kycSubmittedDataFactory();
Expand Down Expand Up @@ -76,22 +83,13 @@ describe('Onboarding service tests', () => {
await app
.get(OnboardingQueueController)
.kycSubmittedEventHandler(kycSubmittedPayload);
});

it('should onboarding record with kyc stage completed', async () => {
const response = await request(app.getHttpServer())
.get('/v1/onboarding')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);

expect(response.body).toStrictEqual({
kycStage: true,
emailVerificationStage: false,
collateralizationStage: false,
cardActivationStage: false,
mfaEnrollmentStage: false,
completed: false,
});
expect(queueStub.publish).toHaveBeenCalledWith(
ONBOARDING_UPDATED_TOPIC,
onboardingUpdatedPayloadFactory({
kycStage: true,
}),
);
});

it('should complete the mfa stage', async () => {
Expand All @@ -100,22 +98,14 @@ describe('Onboarding service tests', () => {
await app
.get(OnboardingQueueController)
.mfaEnrollmentEventHandler(mfaEnrolledPayload);
});

it('should onboarding record with kyc and mfa stage completed', async () => {
const response = await request(app.getHttpServer())
.get('/v1/onboarding')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);

expect(response.body).toStrictEqual({
kycStage: true,
emailVerificationStage: false,
collateralizationStage: false,
cardActivationStage: false,
mfaEnrollmentStage: true,
completed: false,
});
expect(queueStub.publish).toHaveBeenCalledWith(
ONBOARDING_UPDATED_TOPIC,
onboardingUpdatedPayloadFactory({
kycStage: true,
mfaEnrollmentStage: true,
}),
);
});

it('should complete the email verification stage', async () => {
Expand All @@ -124,22 +114,15 @@ describe('Onboarding service tests', () => {
await app
.get(OnboardingQueueController)
.emailVerifiedEventHandler(emailVerifiedPayload);
});

it('should onboarding record with kyc, email verification and mfa stage completed', async () => {
const response = await request(app.getHttpServer())
.get('/v1/onboarding')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);

expect(response.body).toStrictEqual({
kycStage: true,
emailVerificationStage: true,
collateralizationStage: false,
cardActivationStage: false,
mfaEnrollmentStage: true,
completed: false,
});
expect(queueStub.publish).toHaveBeenCalledWith(
ONBOARDING_UPDATED_TOPIC,
onboardingUpdatedPayloadFactory({
kycStage: true,
mfaEnrollmentStage: true,
emailVerificationStage: true,
}),
);
});

it('should complete the collateralization stage', async () => {
Expand All @@ -148,22 +131,16 @@ describe('Onboarding service tests', () => {
await app
.get(OnboardingQueueController)
.collateralReceivedEventHandler(creditLineCreatedPayload);
});

it('should onboarding record with kyc, email verification, collateralization and mfa stage completed', async () => {
const response = await request(app.getHttpServer())
.get('/v1/onboarding')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);

expect(response.body).toStrictEqual({
kycStage: true,
emailVerificationStage: true,
collateralizationStage: true,
cardActivationStage: false,
mfaEnrollmentStage: true,
completed: false,
});
expect(queueStub.publish).toHaveBeenCalledWith(
ONBOARDING_UPDATED_TOPIC,
onboardingUpdatedPayloadFactory({
kycStage: true,
mfaEnrollmentStage: true,
emailVerificationStage: true,
collateralizationStage: true,
}),
);
});

it('should complete the card activation stage', async () => {
Expand All @@ -172,6 +149,18 @@ describe('Onboarding service tests', () => {
await app
.get(OnboardingQueueController)
.cardActivatedEventHandler(creditLineCreatedPayload);

expect(queueStub.publish).toHaveBeenCalledWith(
ONBOARDING_UPDATED_TOPIC,
onboardingUpdatedPayloadFactory({
kycStage: true,
mfaEnrollmentStage: true,
emailVerificationStage: true,
collateralizationStage: true,
cardActivationStage: true,
completed: true,
}),
);
});

it('should return all stages completed', async () => {
Expand Down
18 changes: 18 additions & 0 deletions apps/websocket-event-api/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions apps/websocket-event-api/Dockerfile.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:16-alpine
WORKDIR /service

COPY dist/apps/websocket-event-api/ /service
COPY node_modules/ /service/node_modules
COPY package-lock.json /service/
COPY package.json /service/

USER node

CMD ["node", "main.js"]
29 changes: 29 additions & 0 deletions apps/websocket-event-api/Dockerfile.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM node:16-alpine as builder
WORKDIR /service

ARG LOCAL

COPY . /service

ENV NX_DAEMON=false

RUN npm i
RUN nx run websocket-event-api:build --skip-nx-cache
RUN if [ "$LOCAL" != "" ] ; then cp apps/websocket-event-api/.env dist/apps/websocket-event-api ; fi
RUN npm prune --production

FROM node:16-alpine

ARG LOCAL

WORKDIR /service

COPY --from=builder /service/package*.json /service/
COPY --from=builder /service/node_modules/ /service/node_modules/
COPY --from=builder /service/dist/apps/websocket-event-api /service/dist/

RUN if [ "$LOCAL" != "" ] ; then cp ./dist/.env ./ ; fi

USER node

CMD ["node", "dist/main.js"]
23 changes: 23 additions & 0 deletions apps/websocket-event-api/chart/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
24 changes: 24 additions & 0 deletions apps/websocket-event-api/chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v2
name: archie-websocket-event-api
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
Loading

0 comments on commit b3eea17

Please sign in to comment.