Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 1658 gatehub add tests for gatehub module #1671

Merged
merged 26 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b4d07a7
wallet backend add tests for gatehub module
adrianboros Oct 2, 2024
3fe347a
remove commented code
adrianboros Oct 2, 2024
d85d925
more test for gatehub module service
adrianboros Oct 2, 2024
32cf0d1
Merge branch 'main' into 1658-gatehub-add-tests-for-gatehub-module
adrianboros Oct 3, 2024
25435d5
more tests for rafiki service, better webhook input validation and bu…
adrianboros Oct 7, 2024
1a45251
Merge branch 'main' into 1658-gatehub-add-tests-for-gatehub-module
adrianboros Oct 7, 2024
be1ce81
remove not needed interfaces
adrianboros Oct 7, 2024
8be89bb
Merge branch 'main' into 1658-gatehub-add-tests-for-gatehub-module
adrianboros Nov 4, 2024
75e6316
feat(wallet/backend,boutique/backend): DEV env - add hot reload for w…
adrianboros Nov 4, 2024
8777fef
fix: Fix images in emails (#1786)
Tymmmy Nov 5, 2024
f4f9340
fix(deps): update dependency awilix to v12 (#1711)
renovate[bot] Nov 5, 2024
b84a07f
fix(deps): update radix-ui-primitives monorepo (#1704)
renovate[bot] Nov 5, 2024
1e4f163
chore(deps): update dependency @eslint/compat to ^1.2.2 (#1787)
renovate[bot] Nov 5, 2024
344958e
chore(deps): update dependency @types/jest to ^29.5.14 (#1788)
renovate[bot] Nov 5, 2024
8e2cae1
chore(deps): update dependency eslint-plugin-react to ^7.37.2 (#1789)
renovate[bot] Nov 5, 2024
d29686f
chore(deps): update dependency tailwindcss to ^3.4.14 (#1790)
renovate[bot] Nov 5, 2024
656466e
chore(deps): update dependency vite to ^5.4.10 (#1792)
renovate[bot] Nov 5, 2024
271e814
chore(deps): update dependency typescript to ^5.6.3 (#1791)
renovate[bot] Nov 5, 2024
1428330
fix: windows compat issues with pnpm dev cmd & env var syntax (#1743)
DarianM Nov 5, 2024
9b1b5f2
fix the gatehub failing tests
Nov 7, 2024
4b3836d
Merge branch 'main' into 1658-gatehub-add-tests-for-gatehub-module
Nov 7, 2024
0063390
format
Nov 7, 2024
c36a993
fix prettier format win env
Nov 10, 2024
a17c53c
added test cases for gatehub service
Nov 10, 2024
9efef2c
Merge branch 'main' into 1658-gatehub-add-tests-for-gatehub-module
Nov 22, 2024
df8a6b1
update test: return undefined when gatehub user is not found in addU…
Nov 22, 2024
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
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@
"localRoot": "${workspaceFolder}/packages/wallet/backend",
"remoteRoot": "/home/testnet/packages/wallet/backend",
"protocol": "inspector"
},
{
// debugs jest test file in wallet backend
"type": "node",
"request": "launch",
"name": "Wallet Backend-Jest Test File",
"program": "${workspaceFolder}/packages/wallet/backend/node_modules/jest/bin/jest.js",
"args": [
"--runTestsByPath",
"${relativeFile}",
"--config",
"packages/wallet/backend/jest.config.json"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"windows": {
"program": "${workspaceFolder}/packages/wallet/backend/node_modules/jest/bin/jest.js"
}
}
]
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"localenv:start:lite": "cross-env DEV_MODE=lite pnpm compose up -d --build",
"localenv:stop": "pnpm compose down",
"preinstall": "npx only-allow pnpm",
"prettier:write": "prettier --config '.prettierrc.js' --write .",
"prettier:check": "prettier --config '.prettierrc.js' --check .",
"prettier:write": "prettier --config \".prettierrc.js\" --write .",
"prettier:check": "prettier --config \".prettierrc.js\" --check .",
"prod": "pnpm compose:prod up -d --build",
"prod:down": "pnpm compose:prod down",
"wallet:backend": "pnpm --filter @wallet/backend --",
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/backend/src/rafiki/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RatesService } from '@/rates/service'
import { ratesSchema } from '@/rates/validation'
import { validate } from '@/shared/validate'
import { RafikiService } from './service'
import { webhookSchema } from './validation'
import { webhookBodySchema } from './validation'
import { RatesResponse } from '@wallet/shared'
interface IRafikiController {
getRates: (
Expand Down Expand Up @@ -39,7 +39,7 @@ export class RafikiController implements IRafikiController {

onWebHook = async (req: Request, res: Response, next: NextFunction) => {
try {
const wh = await validate(webhookSchema, req)
const wh = await validate(webhookBodySchema, req)

await this.rafikiService.onWebHook(wh.body)
res.status(200).send()
Expand Down
55 changes: 49 additions & 6 deletions packages/wallet/backend/src/rafiki/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import MessageType from '@/socket/messageType'
import { BadRequest } from '@shared/backend'
import { GateHubClient } from '@/gatehub/client'
import { TransactionTypeEnum } from '@/gatehub/consts'
import {
WebhookType,
incomingPaymentWebhookSchema,
incomingPaymentCompletedWebhookSchema,
outgoingPaymentWebhookSchema,
walletAddressWebhookSchema,
validateInput
} from './validation'

export enum EventType {
IncomingPaymentCreated = 'incoming_payment.created',
Expand Down Expand Up @@ -69,8 +77,11 @@ type Fee = {

export type Fees = Record<string, Fee>

const isValidEventType = (value: string): value is EventType => {
return Object.values(EventType).includes(value as EventType)
}
interface IRafikiService {
onWebHook: (wh: WebHook) => Promise<void>
onWebHook: (wh: WebhookType) => Promise<void>
}

export class RafikiService implements IRafikiService {
Expand All @@ -84,12 +95,19 @@ export class RafikiService implements IRafikiService {
private walletAddressService: WalletAddressService
) {}

public async onWebHook(wh: WebHook): Promise<void> {
public async onWebHook(wh: WebhookType): Promise<void> {
this.logger.info(
`received webhook of type : ${wh.type} for : ${
wh.type === EventType.WalletAddressNotFound ? '' : `${wh.data.id}}`
wh.type === EventType.WalletAddressNotFound ? '' : `${wh.id}}`
}`
)
if (!isValidEventType(wh.type)) {
throw new BadRequest(`unknown event type, ${wh.type}`)
}
const isValid = await this.isValidInput(wh)
if (!isValid) {
throw new BadRequest(`Invalid Input for ${wh.type}`)
}
switch (wh.type) {
case EventType.OutgoingPaymentCreated:
await this.handleOutgoingPaymentCreated(wh)
Expand All @@ -112,11 +130,35 @@ export class RafikiService implements IRafikiService {
case EventType.WalletAddressNotFound:
this.logger.warn(`${EventType.WalletAddressNotFound} received`)
break
default:
throw new BadRequest(`unknown event type, ${wh.type}`)
}
}

private async isValidInput(wh: WebhookType) {
let validInput = false

switch (wh.type) {
case EventType.OutgoingPaymentCreated:
case EventType.OutgoingPaymentCompleted:
case EventType.OutgoingPaymentFailed:
validInput = await validateInput(outgoingPaymentWebhookSchema, wh)
break
case EventType.IncomingPaymentCompleted:
validInput = await validateInput(
incomingPaymentCompletedWebhookSchema,
wh
)
break
case EventType.IncomingPaymentCreated:
case EventType.IncomingPaymentExpired:
validInput = await validateInput(incomingPaymentWebhookSchema, wh)
break
case EventType.WalletAddressNotFound:
validInput = await validateInput(walletAddressWebhookSchema, wh)
break
}
return validInput
}

private parseAmount(amount: AmountJSON): Amount {
return { ...amount, value: BigInt(amount.value) }
}
Expand All @@ -126,7 +168,8 @@ export class RafikiService implements IRafikiService {
if (
[
EventType.OutgoingPaymentCreated,
EventType.OutgoingPaymentCompleted
EventType.OutgoingPaymentCompleted,
EventType.OutgoingPaymentFailed
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug fix here: We have to parse debitAmount even for OutgoingPaymentFailed event otherwise it throws a bad request error - few lines below - and the event is not handled at all

].includes(wh.type)
) {
amount = this.parseAmount(wh.data.debitAmount as AmountJSON)
Expand Down
137 changes: 129 additions & 8 deletions packages/wallet/backend/src/rafiki/validation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { z } from 'zod'
import { EventType, PaymentType } from './service'

export const webhookSchema = z.object({
body: z.object({
id: z.string({ required_error: 'id is required' }),
type: z.nativeEnum(EventType),
data: z.record(z.string(), z.any())
})
})

const quoteAmountSchema = z.object({
value: z.coerce.bigint(),
assetCode: z.string(),
Expand All @@ -31,3 +23,132 @@ export const quoteSchema = z.object({
expiresAt: z.string()
})
})

const amountSchema = z.object({
value: z.coerce.number(),
assetCode: z.string(),
assetScale: z.number()
})

const incomingPaymentCompletedSchema = z.object({
id: z.string(),
walletAddressId: z.string(),
createdAt: z.string(),
expiresAt: z.string(),
incomingAmount: amountSchema,
receivedAmount: amountSchema,
completed: z.boolean(),
updatedAt: z.string(),
metadata: z.object({
description: z.string()
})
})
const incomingPaymentSchema = z.object({
id: z.string(),
walletAddressId: z.string(),
createdAt: z.string(),
expiresAt: z.string(),
receivedAmount: amountSchema,
completed: z.boolean(),
updatedAt: z.string(),
metadata: z.object({
description: z.string()
})
})
const outgoingPaymentSchema = z.object({
id: z.string(),
walletAddressId: z.string(),
client: z.string(),
state: z.string(),
receiver: z.string(),
debitAmount: amountSchema,
receiveAmount: amountSchema,
sentAmount: amountSchema,
stateAttempts: z.number(),
createdAt: z.string(),
updatedAt: z.string(),
balance: z.string(),
metadata: z.object({
description: z.string()
})
})
const outgoingPaymentCreatedSchema = z.object({
id: z.string(),
walletAddressId: z.string(),
client: z.string(),
state: z.string(),
receiver: z.string(),
debitAmount: amountSchema,
receiveAmount: amountSchema,
sentAmount: amountSchema,
stateAttempts: z.number(),
createdAt: z.string(),
updatedAt: z.string(),
balance: z.string(),
metadata: z.object({
description: z.string()
})
})
export const incomingPaymentCompletedWebhookSchema = z.object({
id: z.string({ required_error: 'id is required' }),
type: z.nativeEnum(EventType),
data: incomingPaymentCompletedSchema
})
export const incomingPaymentWebhookSchema = z.object({
id: z.string({ required_error: 'id is required' }),
type: z.nativeEnum(EventType),
data: incomingPaymentSchema
})
export const outgoingPaymentCreatedWebhookSchema = z.object({
id: z.string({ required_error: 'id is required' }),
type: z.nativeEnum(EventType),
data: outgoingPaymentCreatedSchema
})
export const outgoingPaymentWebhookSchema = z.object({
id: z.string(),
type: z.nativeEnum(EventType),
data: outgoingPaymentSchema
})
export const walletAddressWebhookSchema = z.object({
id: z.string(),
type: z.nativeEnum(EventType),
data: z.object({
walletAddressUrl: z.string()
})
})
export const webhookSchema = z.union([
incomingPaymentCompletedWebhookSchema,
incomingPaymentWebhookSchema,
outgoingPaymentWebhookSchema,
walletAddressWebhookSchema
])
export const incomingPaymentWebhookBodySchema = z.object({
body: incomingPaymentWebhookSchema
})
export const webhookBodySchema = z.object({
body: webhookSchema
})
export type WebhookType = z.infer<typeof webhookSchema>

export async function validateInput<Z extends z.AnyZodObject>(
schema: Z,
input: WebhookType
): Promise<boolean> {
try {
const res = await schema.safeParseAsync(input)
if (!res.success) {
const errors: Record<string, string> = {}
res.error.issues.forEach((i) => {
if (i.path.length > 1) {
errors[i.path[1]] = i.message
} else {
errors[i.path[0]] = i.message
}
})
return false
}
} catch (error) {
return false
}
return true
}
Loading
Loading