Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/point-of-sale/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const baseConfig = require('../../jest.config.base.js')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageName = require('./package.json').name

module.exports = {
...baseConfig,
clearMocks: true,
testTimeout: 30000,
roots: [`<rootDir>/packages/${packageName}`],
setupFiles: [`<rootDir>/packages/${packageName}/jest.env.js`],
globalSetup: `<rootDir>/packages/${packageName}/jest.setup.ts`,
globalTeardown: `<rootDir>/packages/${packageName}/jest.teardown.js`,
testRegex: `(packages/${packageName}/.*/__tests__/.*|\\.(test|spec))\\.tsx?$`,
testEnvironment: `<rootDir>/packages/${packageName}/jest.custom-environment.ts`,
moduleDirectories: [
`node_modules`,
`packages/${packageName}/node_modules`,
`<rootDir>/node_modules`
],
modulePaths: [
`node_modules`,
`<rootDir>/packages/${packageName}/src/`,
`<rootDir>/node_modules`
],
id: packageName,
displayName: packageName,
rootDir: '../..'
}
9 changes: 9 additions & 0 deletions packages/point-of-sale/jest.custom-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TestEnvironment } from 'jest-environment-node'
import nock from 'nock'

export default class CustomEnvironment extends TestEnvironment {
constructor(config, context) {
super(config, context)
this.global.nock = nock
}
}
18 changes: 18 additions & 0 deletions packages/point-of-sale/jest.env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
process.env.LOG_LEVEL = 'silent'
process.env.INSTANCE_NAME = 'Rafiki'
process.env.KEY_ID = 'myKey'
process.env.OPEN_PAYMENTS_URL = 'http://127.0.0.1:3000'
process.env.ILP_CONNECTOR_URL = 'http://127.0.0.1:3002'
process.env.ILP_ADDRESS = 'test.rafiki'
process.env.AUTH_SERVER_GRANT_URL = 'http://127.0.0.1:3006'
process.env.AUTH_SERVER_INTROSPECTION_URL = 'http://127.0.0.1:3007/'
process.env.AUTH_SERVICE_API_URL = 'http://127.0.0.1:3011'
process.env.WEBHOOK_URL = 'http://127.0.0.1:4001/webhook'
process.env.STREAM_SECRET = '2/PxuRFV9PAp0yJlnAifJ+1OxujjjI16lN+DBnLNRLA='
process.env.USE_TIGERBEETLE = false
process.env.ENABLE_TELEMETRY = false
process.env.AUTH_ADMIN_API_URL = 'http://127.0.0.1:3003/graphql'
process.env.AUTH_ADMIN_API_SECRET = 'test-secret'
process.env.OPERATOR_TENANT_ID = 'cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d'
process.env.API_SECRET = 'KQEXlZO65jUJXakXnLxGO7dk387mt71G9tZ42rULSNU='
process.env.EXCHANGE_RATES_URL = 'http://example.com/rates'
88 changes: 88 additions & 0 deletions packages/point-of-sale/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { knex } from 'knex'
import { GenericContainer, Wait } from 'testcontainers'
require('./jest.env') // set environment variables

const POSTGRES_PORT = 5432
const REDIS_PORT = 6379

const setup = async (globalConfig): Promise<void> => {
const workers = globalConfig.maxWorkers

const setupDatabase = async () => {
if (!process.env.DATABASE_URL) {
const postgresContainer = await new GenericContainer('postgres:15')
.withExposedPorts(POSTGRES_PORT)
.withBindMounts([
{
source: __dirname + '/scripts/init.sh',
target: '/docker-entrypoint-initdb.d/init.sh'
}
])
.withEnvironment({
POSTGRES_PASSWORD: 'password'
})
.withHealthCheck({
test: ['CMD-SHELL', 'pg_isready -d testing'],
interval: 10000,
timeout: 5000,
retries: 5
})
.withWaitStrategy(Wait.forHealthCheck())
.start()

process.env.DATABASE_URL = `postgresql://postgres:password@localhost:${postgresContainer.getMappedPort(
POSTGRES_PORT
)}/testing`

global.__BACKEND_POSTGRES__ = postgresContainer
}

const db = knex({
client: 'postgresql',
connection: process.env.DATABASE_URL,
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'pos_knex_migrations'
}
})

// node pg defaults to returning bigint as string. This ensures it parses to bigint
db.client.driver.types.setTypeParser(
db.client.driver.types.builtins.INT8,
'text',
BigInt
)
await db.migrate.latest({
directory: __dirname + '/migrations'
})

for (let i = 1; i <= workers; i++) {
const workerDatabaseName = `testing_${i}`

await db.raw(`DROP DATABASE IF EXISTS ${workerDatabaseName}`)
await db.raw(`CREATE DATABASE ${workerDatabaseName} TEMPLATE testing`)
}

global.__BACKEND_KNEX__ = db
}

const setupRedis = async () => {
if (!process.env.REDIS_URL) {
const redisContainer = await new GenericContainer('redis:7')
.withExposedPorts(REDIS_PORT)
.start()

global.__BACKEND_REDIS__ = redisContainer
process.env.REDIS_URL = `redis://localhost:${redisContainer.getMappedPort(
REDIS_PORT
)}`
}
}

await Promise.all([setupDatabase(), setupRedis()])
}

export default setup
13 changes: 13 additions & 0 deletions packages/point-of-sale/jest.teardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = async () => {
await global.__BACKEND_KNEX__.migrate.rollback(
{ directory: __dirname + '/migrations' },
true
)
await global.__BACKEND_KNEX__.destroy()
if (global.__BACKEND_POSTGRES__) {
await global.__BACKEND_POSTGRES__.stop()
}
if (global.__BACKEND_REDIS__) {
await global.__BACKEND_REDIS__.stop()
}
}
21 changes: 21 additions & 0 deletions packages/point-of-sale/jest.tigerbeetle-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { startTigerBeetleContainer } from './src/tests/tigerbeetle'
import { StartedTestContainer } from 'testcontainers'

import CustomTestEnvironment from './jest.custom-environment'

export default class TigerBeetleEnvironment extends CustomTestEnvironment {
private tbContainer: StartedTestContainer | undefined

public async setup(): Promise<void> {
await super.setup()
const tbContainer = await startTigerBeetleContainer()

this.tbContainer = tbContainer.container
this.global.tigerBeetlePort = tbContainer.port
}

public async teardown(): Promise<void> {
await super.teardown()
await this.tbContainer?.stop()
}
}
14 changes: 12 additions & 2 deletions packages/point-of-sale/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests --maxWorkers=50%",
"test:ci": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests --maxWorkers=2",
"test:cov": "pnpm test -- --coverage",
"test:sincemain": "pnpm test -- --changedSince=main",
"test:sincemain:cov": "pnpm test:sincemain --coverage",
"knex": "knex",
"dev": "ts-node-dev --inspect=0.0.0.0:9229 --respawn --transpile-only src/index.ts",
"build": "pnpm build:deps && pnpm clean && tsc --build tsconfig.json",
Expand Down Expand Up @@ -35,6 +39,12 @@
"@types/koa-bodyparser": "^4.3.12",
"@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4",
"@types/uuid": "^9.0.8"
"@types/uuid": "^9.0.8",
"nock": "14.0.0-beta.19",
"jest-environment-node": "^29.7.0",
"jest-openapi": "^0.14.2",
"testcontainers": "^10.16.0",
"tmp": "^0.2.3",
"@types/tmp": "^0.2.6"
}
}
8 changes: 8 additions & 0 deletions packages/point-of-sale/scripts/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
DROP DATABASE IF EXISTS TESTING;
CREATE DATABASE testing;
CREATE DATABASE development;
EOSQL
4 changes: 3 additions & 1 deletion packages/point-of-sale/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ export const Config = {
env: envString('NODE_ENV', 'development'),
port: envInt('PORT', 3008),
trustProxy: envBool('TRUST_PROXY', false),
enableManualMigrations: envBool('ENABLE_MANUAl_MIGRATIONS', false)
enableManualMigrations: envBool('ENABLE_MANUAl_MIGRATIONS', false),
dbSchema: undefined as string | undefined,
tigerBeetleClusterId: envInt('TIGERBEETLE_CLUSTER_ID', 0)
}
10 changes: 10 additions & 0 deletions packages/point-of-sale/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Model } from 'objection'
import { Config } from './config/app'
import { App, AppServices } from './app'
import createLogger from 'pino'
import { createMerchantService } from './merchant/service'

export function initIocContainer(
config: typeof Config
Expand Down Expand Up @@ -55,6 +56,15 @@ export function initIocContainer(
)
return db
})

container.singleton('merchantService', async (deps) => {
const [logger, knex] = await Promise.all([
deps.use('logger'),
deps.use('knex')
])
return createMerchantService({ logger, knex })
})

return container
}

Expand Down
65 changes: 65 additions & 0 deletions packages/point-of-sale/src/merchant/service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { IocContract } from '@adonisjs/fold'

import { Merchant } from './model'
import { MerchantService } from './service'

import { createTestApp, TestContainer } from '../tests/app'
import { truncateTables } from '../tests/tableManager'
import { Config } from '../config/app'

import { initIocContainer } from '../'
import { AppServices } from '../app'

describe('Merchant Service', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let merchantService: MerchantService

beforeAll(async (): Promise<void> => {
deps = initIocContainer({
...Config
})

appContainer = await createTestApp(deps)
merchantService = await deps.use('merchantService')
})

afterEach(async (): Promise<void> => {
jest.useRealTimers()
await truncateTables(deps)
})

afterAll(async (): Promise<void> => {
await appContainer.shutdown()
})

describe('create', (): void => {
test('creates a merchant', async (): Promise<void> => {
const merchant = await merchantService.create('Test merchant')
expect(merchant).toEqual({ id: merchant.id, name: 'Test merchant' })
})
})

describe('delete', (): void => {
test('soft deletes an existing merchant', async (): Promise<void> => {
const created = await merchantService.create('Test merchant')

const result = await merchantService.delete(created.id)
expect(result).toBe(true)

const deletedMerchant = await Merchant.query()
.findById(created.id)
.whereNotNull('deletedAt')
expect(deletedMerchant).toBeDefined()
expect(deletedMerchant?.deletedAt).toBeDefined()
})

test('returns false for already deleted merchant', async (): Promise<void> => {
const created = await merchantService.create('Test merchant')

await merchantService.delete(created.id)
const secondDelete = await merchantService.delete(created.id)
expect(secondDelete).toBe(false)
})
})
})
48 changes: 48 additions & 0 deletions packages/point-of-sale/src/merchant/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BaseService } from '../shared/baseService'
import { TransactionOrKnex } from 'objection'
import { Merchant } from './model'

export interface MerchantService {
create(name: string): Promise<Merchant>
delete(id: string): Promise<boolean>
}

interface ServiceDependencies extends BaseService {
knex: TransactionOrKnex
}

export async function createMerchantService({
logger,
knex
}: ServiceDependencies): Promise<MerchantService> {
const log = logger.child({
service: 'MerchantService'
})
const deps: ServiceDependencies = {
logger: log,
knex
}

return {
create: (input: string) => createMerchant(deps, input),
delete: (id: string) => deleteMerchant(deps, id)
}
}

async function createMerchant(
deps: ServiceDependencies,
name: string
): Promise<Merchant> {
return await Merchant.query(deps.knex).insert({ name })
}

async function deleteMerchant(
deps: ServiceDependencies,
id: string
): Promise<boolean> {
const deleted = await Merchant.query(deps.knex)
.patch({ deletedAt: new Date() })
.whereNull('deletedAt')
.where('id', id)
return deleted > 0
}
Loading
Loading