Skip to content

Commit

Permalink
feat: integration tests (#2380)
Browse files Browse the repository at this point in the history
* feat: setup basic test env

* feat: seed integration environment

- cmd to start integration environment and run tests
- seeds environment on test run
- extracts common MASE functionality into new mock-account-servicing-lib

* refactor(localenv,integration): move graphql url to config from seed

- moves graphql url to config from seed
- also removes self section which only contained unused properties

* refactor: move testenv configuration into new dir

* refactor: cleanup env vars

* feat: add webhook server, fix env vars

* chore: comment

* feat: start grant request test

* fix: eslint errors

* fix: docker compose env vars

* feat: add --build arg

* fix: incoming-payment grant initiation request

Grant request was failing with invalid signature. This was fixed by directing hte backend to use the same private key being used to create the open payments client.

* fix: ts error

* feat: implement tests through Create Quote

* chore: upgrade op client

* feat: partially implemented grant request outgoing payment test

* feat: rework to host.docker.internal

- switches hostname to host.docker.internal to resolve url mismatch problem
- finishes grant request outgoing payment test
- cleans up some configuration and test variables

* feat: add p2p flow test

* feat: add continuation step with consent mocking

* fix: rm obsolete type cast to any and comment

* feat: add create ougoing payment test

* fix: bad pnpm-lock merge

* feat: build deps in mock ase job

* feat: get non existant wallet address test

* fix: update open payments pkg

* chore: fix lint warnings

* feat: implement continuation polling

* chore: test cleanup

* refactor: generate gql in tests instead of import from lib

* chore: rm old comment

* chore: use latest http-singature-utils to match other deps

* chore: pnpm i

* chore: use latest koa-bodyparser

* chore: rm engine strict

* chore: revert rm engine strict

- fixes netlify ci failure but ultimately not the correct fix

* chore(integration): dont ignore env, rm example env

* refactor: use docker healthcheck for running tests

* chore: move healthcheck to last started docker container

* feat: use latest open payments pkg, no body requird on continuation

* chore: pnpm i, fix broken lockfile (no apollo version)

* refactor: move webhook event enum to types

* refactor: move run integration script to test/integration

* Update packages/mock-account-servicing-lib/package.json

Co-authored-by: Sabine Schaller <[email protected]>

* refactor: simplify mock-account-servicing-entity ci step

* feat(integration): exit run-tests early if containers fail to start

* feat: use pino logger

* refactor: rename mock account servicing lib

* refactor: rename class files to camel case

* fix: import correct body parser

* refactor: poll instead of delay

* refactor: simplify call to poll condition only

* fix: update filenames

* refactor: change filename name to kebab case

---------

Co-authored-by: Sabine Schaller <[email protected]>
  • Loading branch information
BlairCurrey and sabineschaller authored Mar 19, 2024
1 parent 2c930ee commit 642e7a2
Show file tree
Hide file tree
Showing 50 changed files with 7,214 additions and 924 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter mock-account-servicing-entity typecheck
- run: pnpm --filter mock-account-servicing-entity build
- run: pnpm --filter mock-account-servicing-entity typecheck

token-introspection:
runs-on: ubuntu-22.04
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ build

# Env variables
.env
!test/**/.env

# AWS Lambda
*.zip
1 change: 1 addition & 0 deletions localenv/cloud-nine-wallet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ services:
OPEN_PAYMENTS_URL: ${CLOUD_NINE_OPEN_PAYMENTS_URL:-https://cloud-nine-wallet-backend}
AUTH_SERVER_DOMAIN: ${CLOUD_NINE_AUTH_SERVER_DOMAIN:-http://localhost:3006}
TESTNET_AUTOPEER_URL: ${TESTNET_AUTOPEER_URL}
GRAPHQL_URL: http://cloud-nine-wallet-backend:3001/graphql
volumes:
- ../cloud-nine-wallet/seed.yml:/workspace/seed.yml
- ../cloud-nine-wallet/private-key.pem:/workspace/private-key.pem
Expand Down
4 changes: 0 additions & 4 deletions localenv/cloud-nine-wallet/seed.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
self:
graphqlUrl: http://cloud-nine-wallet-backend:3001/graphql
mapHostname: 'primary-mase'
openPaymentPublishedPort: 3000
assets:
- code: USD
scale: 2
Expand Down
1 change: 1 addition & 0 deletions localenv/happy-life-bank/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
SEED_FILE_LOCATION: /workspace/seed.yml
KEY_FILE: /workspace/private-key.pem
OPEN_PAYMENTS_URL: ${HAPPY_LIFE_BANK_OPEN_PAYMENTS_URL:-https://happy-life-bank-backend}
GRAPHQL_URL: http://happy-life-bank-backend:3001/graphql
volumes:
- ../happy-life-bank/seed.yml:/workspace/seed.yml
- ../happy-life-bank/private-key.pem:/workspace/private-key.pem
Expand Down
4 changes: 0 additions & 4 deletions localenv/happy-life-bank/seed.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
self:
graphqlUrl: http://happy-life-bank-backend:3001/graphql
mapHostname: 'primary-mase'
openPaymentPublishedPort: 4000
assets:
- code: USD
scale: 2
Expand Down
5 changes: 5 additions & 0 deletions localenv/mock-account-servicing-entity/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ FROM base AS prod-deps

COPY package.json pnpm-workspace.yaml .npmrc ./
COPY localenv/mock-account-servicing-entity/package.json ./localenv/mock-account-servicing-entity/package.json
COPY packages/mock-account-service-lib/package.json ./packages/mock-account-service-lib/package.json

RUN pnpm clean
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
Expand All @@ -36,6 +37,7 @@ FROM base AS builder

COPY package.json pnpm-workspace.yaml .npmrc tsconfig.json tsconfig.build.json ./
COPY localenv/mock-account-servicing-entity ./localenv/mock-account-servicing-entity
COPY packages/mock-account-service-lib ./packages/mock-account-service-lib

RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install \
Expand All @@ -54,9 +56,12 @@ COPY localenv/happy-life-bank/seed.yml ./localenv/happy-life-bank/seed.yml
COPY --from=prod-deps /home/rafiki/node_modules ./node_modules
COPY --from=prod-deps /home/rafiki/localenv/mock-account-servicing-entity/node_modules ./localenv/mock-account-servicing-entity/node_modules
COPY --from=prod-deps /home/rafiki/localenv/mock-account-servicing-entity/package.json ./localenv/mock-account-servicing-entity/package.json
COPY --from=prod-deps /home/rafiki/packages/mock-account-service-lib/node_modules ./packages/mock-account-service-lib/node_modules
COPY --from=prod-deps /home/rafiki/packages/mock-account-service-lib/package.json ./packages/mock-account-service-lib/package.json

COPY --from=builder /home/rafiki/localenv/mock-account-servicing-entity/build ./localenv/mock-account-servicing-entity/build
COPY --from=builder /home/rafiki/localenv/mock-account-servicing-entity/public ./localenv/mock-account-servicing-entity/public
COPY --from=builder /home/rafiki/packages/mock-account-service-lib/dist ./packages/mock-account-service-lib/dist

WORKDIR /home/rafiki/localenv/mock-account-servicing-entity
CMD ["sh", "./node_modules/.bin/remix-serve", "./build/index.js"]
13 changes: 11 additions & 2 deletions localenv/mock-account-servicing-entity/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import type { EntryContext } from '@remix-run/node'
import { createReadableStreamFromReadable } from '@remix-run/node'
import { RemixServer } from '@remix-run/react'
import { renderToPipeableStream } from 'react-dom/server'
import { runSeed } from './lib/run_seed.server'
import { setupFromSeed } from 'mock-account-service-lib'
import { CONFIG } from './lib/parse_config.server'
import { apolloClient } from './lib/apolloClient'
import { mockAccounts } from './lib/accounts.server'

declare global {
// eslint-disable-next-line no-var
Expand All @@ -27,7 +30,13 @@ async function callWithRetry(fn: () => any, depth = 0): Promise<void> {
}

if (!global.__seeded) {
callWithRetry(runSeed)
callWithRetry(async () => {
console.log('setting up from seed...')
return setupFromSeed(CONFIG, apolloClient, mockAccounts, {
logLevel: 'debug',
pinoPretty: true
})
})
.then(() => {
global.__seeded = true
})
Expand Down
272 changes: 1 addition & 271 deletions localenv/mock-account-servicing-entity/app/lib/accounts.server.ts
Original file line number Diff line number Diff line change
@@ -1,273 +1,3 @@
export interface Account {
id: string
name: string
path: string
walletAddressID: string
walletAddress: string
debitsPending: bigint
debitsPosted: bigint
creditsPending: bigint
creditsPosted: bigint
assetCode: string
assetScale: number
assetId: string
}

export interface AccountsServer {
clearAccounts(): Promise<void>
setWalletAddress(
id: string,
walletID: string,
walletAddress: string
): Promise<void>
create(
id: string,
path: string,
name: string,
assetCode: string,
assetScale: number,
assetId: string
): Promise<void>
listAll(): Promise<Account[]>
get(id: string): Promise<Account | undefined>
getByWalletAddressId(walletAddressId: string): Promise<Account | undefined>
getByPath(path: string): Promise<Account | undefined>
getByWalletAddressUrl(walletAddressUrl: string): Promise<Account | undefined>
voidPendingDebit(id: string, amount: bigint): Promise<void>
voidPendingCredit(id: string, amount: bigint): Promise<void>
pendingDebit(id: string, amount: bigint): Promise<void>
pendingCredit(id: string, amount: bigint): Promise<void>
debit(id: string, amount: bigint, clearPending: boolean): Promise<void>
credit(id: string, amount: bigint, clearPending: boolean): Promise<void>
}

export class AccountProvider implements AccountsServer {
accounts = new Map<string, Account>()

async clearAccounts(): Promise<void> {
this.accounts.clear()
}

async setWalletAddress(
id: string,
walletID: string,
walletAddress: string
): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account already exists')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

acc.walletAddress = walletAddress
acc.walletAddressID = walletID
}

async create(
id: string,
path: string,
name: string,
assetCode: string,
assetScale: number,
assetId: string
): Promise<void> {
if (this.accounts.has(id)) {
throw new Error('account already exists')
}
this.accounts.set(id, {
id,
name,
path,
walletAddress: '',
walletAddressID: '',
creditsPending: BigInt(0),
creditsPosted: BigInt(0),
debitsPending: BigInt(0),
debitsPosted: BigInt(0),
assetCode,
assetScale,
assetId
})
}

async listAll(): Promise<Account[]> {
return [...this.accounts.values()]
}

async get(id: string): Promise<Account | undefined> {
return this.accounts.get(id)
}

async getByWalletAddressId(
walletAddressId: string
): Promise<Account | undefined> {
for (const acc of this.accounts.values()) {
if (acc.walletAddressID == walletAddressId) {
return acc
}
}
}

async getByWalletAddressUrl(
walletAddressUrl: string
): Promise<Account | undefined> {
return (await this.listAll()).find(
(acc) => acc.walletAddress === walletAddressUrl
)
}

async getByPath(path: string): Promise<Account | undefined> {
return (await this.listAll()).find((acc) => acc.path === path)
}

async credit(
id: string,
amount: bigint,
clearPending: boolean
): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid credit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

if (clearPending && acc.creditsPending - amount < 0) {
throw new Error('invalid amount, credits pending cannot be less than 0')
}

acc.creditsPosted += amount
if (clearPending) {
acc.creditsPending -= amount
}
}

async debit(
id: string,
amount: bigint,
clearPending: boolean
): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid debit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

if (
(clearPending && acc.debitsPending - amount < 0) ||
acc.debitsPosted + amount < 0
) {
throw new Error('invalid amount, debits pending cannot be less than 0')
}

if (
!clearPending &&
acc.creditsPosted < acc.debitsPosted + acc.debitsPending + amount
) {
throw new Error('invalid debit, insufficient funds')
}
acc.debitsPosted += amount
if (clearPending) {
acc.debitsPending -= amount
}
}

async pendingCredit(id: string, amount: bigint): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid pending credit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

acc.creditsPending += amount
}

async pendingDebit(id: string, amount: bigint): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid pending debit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

if (acc.creditsPosted < acc.debitsPosted + acc.debitsPending + amount) {
throw new Error('invalid pending debit amount, insufficient funds')
}

acc.debitsPending += amount
}

async voidPendingDebit(id: string, amount: bigint): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid void pending debit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

if (acc.debitsPending - amount < 0) {
throw new Error('invalid amount, debits pending cannot be less than 0')
}

acc.debitsPending -= amount
}

async voidPendingCredit(id: string, amount: bigint): Promise<void> {
if (!this.accounts.has(id)) {
throw new Error('account does not exist')
}
if (amount < 0) {
throw new Error('invalid void pending credit amount')
}

const acc = this.accounts.get(id)

if (!acc) {
throw new Error()
}

if (acc.debitsPending - amount < 0) {
throw new Error('invalid amount, credits pending cannot be less than 0')
}

acc.creditsPending -= amount
}
}
import { AccountProvider } from 'mock-account-service-lib'

export const mockAccounts = new AccountProvider()
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CONFIG } from './parse_config.server'
import { onError } from '@apollo/client/link/error'

const httpLink = createHttpLink({
uri: CONFIG.seed.self.graphqlUrl
uri: CONFIG.graphqlUrl
})

const errorLink = onError(({ graphQLErrors }) => {
Expand Down
Loading

0 comments on commit 642e7a2

Please sign in to comment.