Skip to content

Commit 0a96d0a

Browse files
committed
add tests
1 parent 1b35a4f commit 0a96d0a

File tree

16 files changed

+6053
-448
lines changed

16 files changed

+6053
-448
lines changed

.github/workflows/test.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, master, develop]
6+
pull_request:
7+
branches: [main, master, develop]
8+
9+
jobs:
10+
unit-and-integration-tests:
11+
name: Unit & Integration Tests
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: '20'
22+
cache: 'npm'
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
27+
- name: Run unit and integration tests
28+
run: npm run test:run
29+
30+
- name: Generate coverage report
31+
run: npm run test:coverage
32+
33+
- name: Upload coverage to Codecov
34+
uses: codecov/codecov-action@v4
35+
with:
36+
files: ./coverage/lcov.info
37+
flags: unittests
38+
name: codecov-umbrella
39+
fail_ci_if_error: false
40+
41+
- name: Check coverage thresholds
42+
run: |
43+
echo "Checking if coverage meets 80% threshold..."
44+
npm run test:coverage -- --reporter=json --reporter=text
45+
46+
e2e-tests:
47+
name: E2E Tests
48+
runs-on: ubuntu-latest
49+
needs: unit-and-integration-tests
50+
51+
steps:
52+
- name: Checkout code
53+
uses: actions/checkout@v4
54+
55+
- name: Setup Node.js
56+
uses: actions/setup-node@v4
57+
with:
58+
node-version: '20'
59+
cache: 'npm'
60+
61+
- name: Install dependencies
62+
run: npm ci
63+
64+
- name: Install Playwright browsers
65+
run: npx playwright install --with-deps
66+
67+
- name: Run E2E tests
68+
run: npm run test:e2e
69+
env:
70+
NEXT_PUBLIC_USE_MOCK_WALLET: 'true'
71+
NEXT_PUBLIC_DEFAULT_NETWORK: 'testnet'
72+
73+
- name: Upload Playwright report
74+
uses: actions/upload-artifact@v4
75+
if: always()
76+
with:
77+
name: playwright-report
78+
path: playwright-report/
79+
retention-days: 30
80+
81+
lint:
82+
name: Lint
83+
runs-on: ubuntu-latest
84+
85+
steps:
86+
- name: Checkout code
87+
uses: actions/checkout@v4
88+
89+
- name: Setup Node.js
90+
uses: actions/setup-node@v4
91+
with:
92+
node-version: '20'
93+
cache: 'npm'
94+
95+
- name: Install dependencies
96+
run: npm ci
97+
98+
- name: Run linter
99+
run: npm run lint
100+
101+
build:
102+
name: Build
103+
runs-on: ubuntu-latest
104+
needs: [unit-and-integration-tests, lint]
105+
106+
steps:
107+
- name: Checkout code
108+
uses: actions/checkout@v4
109+
110+
- name: Setup Node.js
111+
uses: actions/setup-node@v4
112+
with:
113+
node-version: '20'
114+
cache: 'npm'
115+
116+
- name: Install dependencies
117+
run: npm ci
118+
119+
- name: Build application
120+
run: npm run build
121+
env:
122+
NEXT_PUBLIC_USE_MOCK_WALLET: 'false'
123+
NEXT_PUBLIC_DEFAULT_NETWORK: 'testnet'

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ build
99
.vercel
1010
*.tsbuildinfo
1111
next-env.d.ts
12+
coverage/
13+
playwright-report/
14+
test-results/

__mocks__/hathorCoreAPI.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { vi } from 'vitest'
2+
import { ContractState, BlueprintInfo, ContractHistory } from '@/types/hathor'
3+
import { Network } from '@/lib/config'
4+
5+
export const mockBlueprintInfo: BlueprintInfo = {
6+
id: 'test-blueprint-id',
7+
name: 'HathorDice',
8+
attributes: {
9+
public_methods: ['place_bet', 'add_liquidity', 'remove_liquidity', 'claim_balance'],
10+
},
11+
}
12+
13+
export const mockContractStates: Record<string, ContractState> = {
14+
'contract-htr': {
15+
token_uid: '00',
16+
max_bet_amount: 10000n,
17+
house_edge_basis_points: 190,
18+
random_bit_length: 16,
19+
available_tokens: 100000000n,
20+
total_liquidity_provided: 100000000n,
21+
},
22+
'contract-usdc': {
23+
token_uid: '01',
24+
max_bet_amount: 5000n,
25+
house_edge_basis_points: 250,
26+
random_bit_length: 20,
27+
available_tokens: 50000000n,
28+
total_liquidity_provided: 50000000n,
29+
},
30+
}
31+
32+
export const mockTransactions = [
33+
{
34+
tx_id: '0000000000000001',
35+
timestamp: Date.now() - 60000,
36+
nc_method: 'place_bet',
37+
nc_caller: 'WYBwT3xLpDnHNtYZiU52oanupVeDKhAvNp',
38+
first_block: '0000000000000abc',
39+
is_voided: false,
40+
nc_args_decoded: {
41+
threshold: 32768,
42+
},
43+
nc_events: [
44+
{
45+
type: 'BetPlaced',
46+
data: '{"user":"WYBwT3xLpDnHNtYZiU52oanupVeDKhAvNp","amount":1000,"threshold":32768,"result":12345,"won":true,"payout":1900}',
47+
},
48+
],
49+
},
50+
{
51+
tx_id: '0000000000000002',
52+
timestamp: Date.now() - 120000,
53+
nc_method: 'place_bet',
54+
nc_caller: 'WYBwT3xLpDnHNtYZiU52oanupVeDKhAvNp',
55+
first_block: '0000000000000abd',
56+
is_voided: false,
57+
nc_args_decoded: {
58+
threshold: 16384,
59+
},
60+
nc_events: [
61+
{
62+
type: 'BetPlaced',
63+
data: '{"user":"WYBwT3xLpDnHNtYZiU52oanupVeDKhAvNp","amount":500,"threshold":16384,"result":50000,"won":false,"payout":0}',
64+
},
65+
],
66+
},
67+
]
68+
69+
export const mockContractHistory: ContractHistory = {
70+
transactions: mockTransactions,
71+
total: mockTransactions.length,
72+
hasMore: false,
73+
}
74+
75+
export class MockHathorCoreAPI {
76+
private baseUrl: string
77+
public network: Network
78+
79+
constructor(network: Network) {
80+
this.network = network
81+
this.baseUrl = `https://mock-${network}.hathor.network/v1a`
82+
}
83+
84+
async getBlueprintInfo(blueprintId: string): Promise<BlueprintInfo> {
85+
return mockBlueprintInfo
86+
}
87+
88+
async getContractState(contractId: string): Promise<ContractState> {
89+
return mockContractStates[contractId] || mockContractStates['contract-htr']
90+
}
91+
92+
async getContractHistory(
93+
contractId: string,
94+
limit: number = 50,
95+
after?: string
96+
): Promise<ContractHistory> {
97+
// Simulate pagination
98+
const allTransactions = mockTransactions
99+
const startIndex = after ? allTransactions.findIndex(tx => tx.tx_id === after) + 1 : 0
100+
const transactions = allTransactions.slice(startIndex, startIndex + limit)
101+
102+
return {
103+
transactions,
104+
total: transactions.length,
105+
hasMore: startIndex + limit < allTransactions.length,
106+
}
107+
}
108+
109+
async getTransaction(txId: string): Promise<any> {
110+
const tx = mockTransactions.find(t => t.tx_id === txId)
111+
if (!tx) {
112+
throw new Error(`Transaction not found: ${txId}`)
113+
}
114+
return tx
115+
}
116+
117+
async callViewFunction(
118+
contractId: string,
119+
method: string,
120+
args: any[] = [],
121+
callerAddress?: string
122+
): Promise<any> {
123+
const callKey = `${method}(${args.map(arg => JSON.stringify(arg)).join(', ')})`
124+
125+
// Mock different view function responses
126+
if (method === 'get_address_balance') {
127+
return {
128+
calls: {
129+
[callKey]: {
130+
value: 5000,
131+
},
132+
},
133+
}
134+
}
135+
136+
if (method === 'calculate_address_maximum_liquidity_removal') {
137+
return {
138+
calls: {
139+
[callKey]: {
140+
value: 10000,
141+
},
142+
},
143+
}
144+
}
145+
146+
return {
147+
calls: {
148+
[callKey]: {
149+
value: 0,
150+
},
151+
},
152+
}
153+
}
154+
155+
async getMaximumLiquidityRemoval(contractId: string, callerAddress: string): Promise<bigint> {
156+
const result = await this.callViewFunction(
157+
contractId,
158+
'calculate_address_maximum_liquidity_removal',
159+
[callerAddress],
160+
callerAddress
161+
)
162+
const callKey = Object.keys(result.calls)[0]
163+
if (callKey && result.calls[callKey]?.value !== undefined) {
164+
return BigInt(result.calls[callKey].value)
165+
}
166+
return 0n
167+
}
168+
169+
async getClaimableBalance(contractId: string, callerAddress: string): Promise<bigint> {
170+
const result = await this.callViewFunction(contractId, 'get_address_balance', [callerAddress], callerAddress)
171+
const callKey = Object.keys(result.calls)[0]
172+
if (callKey && result.calls[callKey]?.value !== undefined) {
173+
return BigInt(result.calls[callKey].value)
174+
}
175+
return 0n
176+
}
177+
}
178+
179+
// Create mock instance
180+
export const createMockHathorCoreAPI = (network: Network = 'testnet') => {
181+
return new MockHathorCoreAPI(network)
182+
}
183+
184+
// Vitest mock factory
185+
export const mockHathorCoreAPIFactory = () => {
186+
const mockInstance = createMockHathorCoreAPI()
187+
188+
return {
189+
HathorCoreAPI: vi.fn().mockImplementation((network: Network) => {
190+
const instance = createMockHathorCoreAPI(network)
191+
return {
192+
getBlueprintInfo: vi.fn().mockImplementation(instance.getBlueprintInfo.bind(instance)),
193+
getContractState: vi.fn().mockImplementation(instance.getContractState.bind(instance)),
194+
getContractHistory: vi.fn().mockImplementation(instance.getContractHistory.bind(instance)),
195+
getTransaction: vi.fn().mockImplementation(instance.getTransaction.bind(instance)),
196+
callViewFunction: vi.fn().mockImplementation(instance.callViewFunction.bind(instance)),
197+
getMaximumLiquidityRemoval: vi.fn().mockImplementation(instance.getMaximumLiquidityRemoval.bind(instance)),
198+
getClaimableBalance: vi.fn().mockImplementation(instance.getClaimableBalance.bind(instance)),
199+
}
200+
}),
201+
}
202+
}

0 commit comments

Comments
 (0)