diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..b02992f --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,148 @@ +# SDK Fixture Framework + +## Overview + +The fixture framework provides deterministic, reusable test fixtures for SDK integration tests. + +## Why Use Fixtures? + +- **Deterministic**: Same data every time +- **Reusable**: Share across tests +- **Type-safe**: Full TypeScript support +- **Composable**: Build complex fixtures from simple ones + +## Available Fixtures + +### Accounts +- `valid`: A valid, funded account +- `empty`: An account with no balance +- `lowBalance`: An account with a small balance +- `highBalance`: An account with a large balance +- `notFound`: An account that does not exist +- `pending`: An account with a pending transaction +- `frozen`: A frozen account + +### Payments +- `success`: A successful payment +- `pending`: A pending payment +- `failed`: A failed payment +- `withMemo`: A payment with a memo +- `usdc`: A USDC payment + +### Transactions +- `success`: A successful transaction +- `pending`: A pending transaction +- `failed`: A failed transaction +- `withMemo`: A transaction with memo + +### Network +- `success`: A successful network response +- `timeout`: A network timeout +- `serverError`: A 500 error +- `notFound`: A 404 error +- `forbidden`: A 403 error +- `rateLimited`: A 429 error + +### Soroban +- `success`: A successful contract call +- `error`: A contract call with error +- `timeout`: A contract call timeout +- `unsupported`: An unsupported feature call + +### Vault +- `success`: A successful vault operation +- `pending`: A pending vault operation +- `failed`: A failed vault operation +- `lock`: A lock operation +- `unlock`: An unlock operation + +## Usage Examples + +### Basic Usage + +```typescript +import { accountFixtures, paymentFixtures } from '../fixtures'; + +describe('Account tests', () => { + it('should handle valid account', () => { + const account = accountFixtures.valid; + expect(account.balance).toBe('1000.00'); + }); + + it('should handle empty account', () => { + const account = accountFixtures.empty; + expect(account.balance).toBe('0.00'); + }); +}); +import { AccountBuilder } from '../fixtures/builders'; + +describe('Custom account tests', () => { + it('should create custom account', () => { + const account = new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withBalance('500.00') + .withSequence(123456789) + .build(); + + expect(account.balance).toBe('500.00'); + }); +}); +import { accountFixtures, paymentFixtures } from '../fixtures'; + +describe('Integration tests', () => { + it('should handle full flow', () => { + const sender = accountFixtures.valid; + const payment = paymentFixtures.success; + + expect(sender.balance).toBe('1000.00'); + expect(payment.from).toBe(sender.id); + }); +}); +// tests/fixtures/my-domain/my-builder.ts +export class MyBuilder extends FixtureBuilder { + // Implement builder methods +} +// tests/fixtures/my-domain/my-fixtures.ts +export const myFixtures = { + valid: new MyBuilder().build(), + error: new MyBuilder().withError('error').build(), +}; +// tests/fixtures/my-domain/index.ts +export * from './my-fixtures'; +export * from './my-builder'; +// tests/fixtures/index.ts +export * from './my-domain'; +const account = await getAccount('G...'); +expect(account.balance).toBeDefined(); +const account = accountFixtures.valid; +expect(account.balance).toBe('1000.00'); +export class MyBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + id: 'default_id', + name: 'default_name', + }; + } + + withId(id: string): this { + this.data.id = id; + return this; + } + + // ... other builder methods + + build(): MyFixture { + return { + id: this.data.id!, + name: this.data.name!, + }; + } +} +export const myFixtures = { + valid: new MyBuilder().build(), + error: new MyBuilder().withId('error').build(), +}; +// tests/fixtures/my-domain/index.ts +export * from './my-fixtures'; +export * from './my-builder'; diff --git a/tests/fixtures/accounts/account-builder.ts b/tests/fixtures/accounts/account-builder.ts new file mode 100644 index 0000000..0ba377e --- /dev/null +++ b/tests/fixtures/accounts/account-builder.ts @@ -0,0 +1,86 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface AccountFixture { + id: string; + balance: string; + sequence: number; + exists: boolean; + frozen: boolean; + pendingTransaction: boolean; + createdAt: Date; + updatedAt: Date; +} + +export class AccountBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + id: 'GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + balance: '0.00', + sequence: 0, + exists: true, + frozen: false, + pendingTransaction: false, + createdAt: new Date(), + updatedAt: new Date(), + }; + } + + withId(id: string): this { + this.data.id = id; + return this; + } + + withBalance(balance: string): this { + this.data.balance = balance; + return this; + } + + withSequence(sequence: number): this { + this.data.sequence = sequence; + return this; + } + + withExists(exists: boolean): this { + this.data.exists = exists; + return this; + } + + withFrozen(frozen: boolean): this { + this.data.frozen = frozen; + return this; + } + + withPendingTransaction(pending: boolean): this { + this.data.pendingTransaction = pending; + return this; + } + + build(): AccountFixture { + return { + id: this.data.id!, + balance: this.data.balance!, + sequence: this.data.sequence!, + exists: this.data.exists!, + frozen: this.data.frozen!, + pendingTransaction: this.data.pendingTransaction!, + createdAt: this.data.createdAt!, + updatedAt: this.data.updatedAt!, + }; + } + + validate(): boolean { + return !!this.data.id && this.data.id.startsWith('G'); + } + + getErrors(): string[] { + const errors: string[] = []; + if (!this.data.id) { + errors.push('Account ID is required'); + } + if (!this.data.id?.startsWith('G')) { + errors.push('Account ID must start with G'); + } + return errors; + } +} diff --git a/tests/fixtures/accounts/account-fixtures.ts b/tests/fixtures/accounts/account-fixtures.ts new file mode 100644 index 0000000..ca9c689 --- /dev/null +++ b/tests/fixtures/accounts/account-fixtures.ts @@ -0,0 +1,73 @@ +import { AccountBuilder } from './account-builder'; + +/** + * Baseline account fixtures + */ +export const accountFixtures = { + /** + * A valid, funded account + */ + valid: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withBalance('1000.00') + .withSequence(123456789) + .build(), + + /** + * An account with no balance + */ + empty: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567891') + .withBalance('0.00') + .withSequence(123456789) + .build(), + + /** + * An account with a small balance + */ + lowBalance: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567892') + .withBalance('0.01') + .withSequence(123456789) + .build(), + + /** + * An account with a large balance + */ + highBalance: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567893') + .withBalance('1000000.00') + .withSequence(123456789) + .build(), + + /** + * An account that does not exist + */ + notFound: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567899') + .withExists(false) + .build(), + + /** + * An account with a pending transaction + */ + pending: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567894') + .withBalance('1000.00') + .withSequence(123456789) + .withPendingTransaction(true) + .build(), + + /** + * A frozen account + */ + frozen: new AccountBuilder() + .withId('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567895') + .withBalance('1000.00') + .withSequence(123456789) + .withFrozen(true) + .build(), +}; + +export type AccountFixtureType = keyof typeof accountFixtures; +export const accountFixtureNames = Object.keys(accountFixtures) as AccountFixtureType[]; diff --git a/tests/fixtures/accounts/index.ts b/tests/fixtures/accounts/index.ts new file mode 100644 index 0000000..c689a1d --- /dev/null +++ b/tests/fixtures/accounts/index.ts @@ -0,0 +1,2 @@ +export * from './account-fixtures'; +export * from './account-builder'; diff --git a/tests/fixtures/builders/fixture-builder.ts b/tests/fixtures/builders/fixture-builder.ts new file mode 100644 index 0000000..8b07d56 --- /dev/null +++ b/tests/fixtures/builders/fixture-builder.ts @@ -0,0 +1,55 @@ +/** + * Base fixture builder class + * Provides common functionality for all fixture builders + */ +export abstract class FixtureBuilder { + protected data: Partial = {}; + + /** + * Build the fixture + */ + abstract build(): T; + + /** + * Reset the builder to defaults + */ + reset(): this { + this.data = {}; + return this; + } + + /** + * Set a specific value + */ + set(key: K, value: T[K]): this { + this.data[key] = value; + return this; + } + + /** + * Merge with another builder + */ + merge(other: Partial): this { + this.data = { ...this.data, ...other }; + return this; + } + + /** + * Clone the builder + */ + clone(): this { + const clone = new (this.constructor as any)(); + clone.data = { ...this.data }; + return clone; + } + + /** + * Validate the fixture + */ + abstract validate(): boolean; + + /** + * Get validation errors + */ + abstract getErrors(): string[]; +} diff --git a/tests/fixtures/builders/index.ts b/tests/fixtures/builders/index.ts new file mode 100644 index 0000000..455ba1a --- /dev/null +++ b/tests/fixtures/builders/index.ts @@ -0,0 +1,7 @@ +export * from './fixture-builder'; +export * from './account-builder'; +export * from './payment-builder'; +export * from './transaction-builder'; +export * from './network-builder'; +export * from './soroban-builder'; +export * from './vault-builder'; diff --git a/tests/fixtures/index.ts b/tests/fixtures/index.ts index 63d1054..36a6144 100644 --- a/tests/fixtures/index.ts +++ b/tests/fixtures/index.ts @@ -1,16 +1,7 @@ -export { fundedAccount, unfundedAccount, accountNotFound } from './accounts'; -export { transactionList, failedTransaction, transactionNotFound } from './transactions'; -export { - paymentList, - paymentNotFound, - makeHorizon404Error, - makeHorizonResultCodeError, - neverSettlingPromise, -} from './payments'; -export { - successfulPaymentSummary, - failedPaymentSummary, - pendingTransactionSummary, - unknownTransactionSummary, - transactionSummaryFixtures, -} from './transactionSummary'; +export * from './accounts'; +export * from './payments'; +export * from './transactions'; +export * from './network'; +export * from './soroban'; +export * from './vault'; +export * from './builders'; diff --git a/tests/fixtures/network/index.ts b/tests/fixtures/network/index.ts new file mode 100644 index 0000000..078b6ca --- /dev/null +++ b/tests/fixtures/network/index.ts @@ -0,0 +1,2 @@ +export * from './network-fixtures'; +export * from './network-builder'; diff --git a/tests/fixtures/network/network-builder.ts b/tests/fixtures/network/network-builder.ts new file mode 100644 index 0000000..98b8d80 --- /dev/null +++ b/tests/fixtures/network/network-builder.ts @@ -0,0 +1,74 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface NetworkFixture { + status: number; + data?: any; + error?: string; + headers?: Record; + timeout: boolean; + latency: number; +} + +export class NetworkBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + status: 200, + timeout: false, + latency: 0, + }; + } + + withStatus(status: number): this { + this.data.status = status; + return this; + } + + withData(data: any): this { + this.data.data = data; + return this; + } + + withError(error: string): this { + this.data.error = error; + return this; + } + + withHeaders(headers: Record): this { + this.data.headers = headers; + return this; + } + + withTimeout(timeout: boolean): this { + this.data.timeout = timeout; + return this; + } + + withLatency(latency: number): this { + this.data.latency = latency; + return this; + } + + build(): NetworkFixture { + return { + status: this.data.status!, + data: this.data.data, + error: this.data.error, + headers: this.data.headers, + timeout: this.data.timeout!, + latency: this.data.latency!, + }; + } + + validate(): boolean { + return this.data.status !== undefined; + } + + getErrors(): string[] { + const errors: string[] = []; + if (this.data.status === undefined) { + errors.push('Status is required'); + } + return errors; + } +} diff --git a/tests/fixtures/network/network-fixtures.ts b/tests/fixtures/network/network-fixtures.ts new file mode 100644 index 0000000..30bca96 --- /dev/null +++ b/tests/fixtures/network/network-fixtures.ts @@ -0,0 +1,59 @@ +import { NetworkBuilder } from './network-builder'; + +/** + * Baseline network response fixtures + */ +export const networkFixtures = { + /** + * A successful network response + */ + success: new NetworkBuilder() + .withStatus(200) + .withData({ success: true, result: 'success' }) + .build(), + + /** + * A network timeout + */ + timeout: new NetworkBuilder() + .withStatus(504) + .withError('Timeout') + .withTimeout(true) + .build(), + + /** + * A network error (500) + */ + serverError: new NetworkBuilder() + .withStatus(500) + .withError('Internal Server Error') + .build(), + + /** + * A network error (404) + */ + notFound: new NetworkBuilder() + .withStatus(404) + .withError('Not Found') + .build(), + + /** + * A network error (403) + */ + forbidden: new NetworkBuilder() + .withStatus(403) + .withError('Forbidden') + .build(), + + /** + * A network error (429) - rate limited + */ + rateLimited: new NetworkBuilder() + .withStatus(429) + .withError('Rate Limited') + .withHeaders({ 'Retry-After': '60' }) + .build(), +}; + +export type NetworkFixtureType = keyof typeof networkFixtures; +export const networkFixtureNames = Object.keys(networkFixtures) as NetworkFixtureType[]; diff --git a/tests/fixtures/payments/index.ts b/tests/fixtures/payments/index.ts new file mode 100644 index 0000000..00b958a --- /dev/null +++ b/tests/fixtures/payments/index.ts @@ -0,0 +1,2 @@ +export * from './payment-fixtures'; +export * from './payment-builder'; diff --git a/tests/fixtures/payments/payment-builder.ts b/tests/fixtures/payments/payment-builder.ts new file mode 100644 index 0000000..bd961f1 --- /dev/null +++ b/tests/fixtures/payments/payment-builder.ts @@ -0,0 +1,109 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface PaymentFixture { + from: string; + to: string; + amount: string; + asset: string; + assetIssuer?: string; + memo?: string; + status: 'pending' | 'completed' | 'failed'; + txHash?: string; + error?: string; + createdAt: Date; + updatedAt: Date; +} + +export class PaymentBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + from: 'GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + to: 'GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW', + amount: '0.00', + asset: 'XLM', + status: 'pending', + createdAt: new Date(), + updatedAt: new Date(), + }; + } + + withFrom(from: string): this { + this.data.from = from; + return this; + } + + withTo(to: string): this { + this.data.to = to; + return this; + } + + withAmount(amount: string): this { + this.data.amount = amount; + return this; + } + + withAsset(asset: string): this { + this.data.asset = asset; + return this; + } + + withAssetIssuer(issuer: string): this { + this.data.assetIssuer = issuer; + return this; + } + + withMemo(memo: string): this { + this.data.memo = memo; + return this; + } + + withStatus(status: 'pending' | 'completed' | 'failed'): this { + this.data.status = status; + return this; + } + + withTxHash(txHash: string): this { + this.data.txHash = txHash; + return this; + } + + withError(error: string): this { + this.data.error = error; + return this; + } + + build(): PaymentFixture { + return { + from: this.data.from!, + to: this.data.to!, + amount: this.data.amount!, + asset: this.data.asset!, + assetIssuer: this.data.assetIssuer, + memo: this.data.memo, + status: this.data.status!, + txHash: this.data.txHash, + error: this.data.error, + createdAt: this.data.createdAt!, + updatedAt: this.data.updatedAt!, + }; + } + + validate(): boolean { + return !!this.data.from && !!this.data.to && Number(this.data.amount) > 0; + } + + getErrors(): string[] { + const errors: string[] = []; + if (!this.data.from) { + errors.push('From address is required'); + } + if (!this.data.to) { + errors.push('To address is required'); + } + if (!this.data.amount || Number(this.data.amount) <= 0) { + errors.push('Amount must be greater than 0'); + } + return errors; + } +} diff --git a/tests/fixtures/payments/payment-fixtures.ts b/tests/fixtures/payments/payment-fixtures.ts new file mode 100644 index 0000000..bc857e0 --- /dev/null +++ b/tests/fixtures/payments/payment-fixtures.ts @@ -0,0 +1,68 @@ +import { PaymentBuilder } from './payment-builder'; + +/** + * Baseline payment fixtures + */ +export const paymentFixtures = { + /** + * A successful payment + */ + success: new PaymentBuilder() + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('completed') + .withTxHash('0x1234567890abcdef1234567890abcdef12345678') + .build(), + + /** + * A pending payment + */ + pending: new PaymentBuilder() + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('pending') + .build(), + + /** + * A failed payment + */ + failed: new PaymentBuilder() + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('failed') + .withError('Insufficient balance') + .build(), + + /** + * A payment with a memo + */ + withMemo: new PaymentBuilder() + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withMemo('Payment for services') + .withStatus('completed') + .build(), + + /** + * A payment with USDC asset + */ + usdc: new PaymentBuilder() + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('USDC') + .withAssetIssuer('GUSDC1234567890') + .withStatus('completed') + .build(), +}; + +export type PaymentFixtureType = keyof typeof paymentFixtures; +export const paymentFixtureNames = Object.keys(paymentFixtures) as PaymentFixtureType[]; diff --git a/tests/fixtures/soroban/index.ts b/tests/fixtures/soroban/index.ts new file mode 100644 index 0000000..d744386 --- /dev/null +++ b/tests/fixtures/soroban/index.ts @@ -0,0 +1,2 @@ +export * from './soroban-fixtures'; +export * from './soroban-builder'; diff --git a/tests/fixtures/soroban/soroban-builder.ts b/tests/fixtures/soroban/soroban-builder.ts new file mode 100644 index 0000000..428804d --- /dev/null +++ b/tests/fixtures/soroban/soroban-builder.ts @@ -0,0 +1,85 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface SorobanFixture { + contractId: string; + method: string; + params?: any[]; + result?: any; + error?: string; + timeout: boolean; + gasUsed: number; +} + +export class SorobanBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + contractId: 'CA1234567890ABCDEF', + method: 'call', + timeout: false, + gasUsed: 1000, + }; + } + + withContractId(contractId: string): this { + this.data.contractId = contractId; + return this; + } + + withMethod(method: string): this { + this.data.method = method; + return this; + } + + withParams(params: any[]): this { + this.data.params = params; + return this; + } + + withResult(result: any): this { + this.data.result = result; + return this; + } + + withError(error: string): this { + this.data.error = error; + return this; + } + + withTimeout(timeout: boolean): this { + this.data.timeout = timeout; + return this; + } + + withGasUsed(gasUsed: number): this { + this.data.gasUsed = gasUsed; + return this; + } + + build(): SorobanFixture { + return { + contractId: this.data.contractId!, + method: this.data.method!, + params: this.data.params, + result: this.data.result, + error: this.data.error, + timeout: this.data.timeout!, + gasUsed: this.data.gasUsed!, + }; + } + + validate(): boolean { + return !!this.data.contractId && !!this.data.method; + } + + getErrors(): string[] { + const errors: string[] = []; + if (!this.data.contractId) { + errors.push('Contract ID is required'); + } + if (!this.data.method) { + errors.push('Method name is required'); + } + return errors; + } +} diff --git a/tests/fixtures/soroban/soroban-fixtures.ts b/tests/fixtures/soroban/soroban-fixtures.ts new file mode 100644 index 0000000..f2c01f2 --- /dev/null +++ b/tests/fixtures/soroban/soroban-fixtures.ts @@ -0,0 +1,46 @@ +import { SorobanBuilder } from './soroban-builder'; + +/** + * Baseline Soroban call fixtures + */ +export const sorobanFixtures = { + /** + * A successful contract call + */ + success: new SorobanBuilder() + .withContractId('CA1234567890ABCDEF') + .withMethod('deposit') + .withResult({ success: true, amount: '100.00' }) + .build(), + + /** + * A contract call with error + */ + error: new SorobanBuilder() + .withContractId('CA1234567890ABCDEF') + .withMethod('deposit') + .withResult({ success: false, error: 'Contract error' }) + .withError('Contract execution failed') + .build(), + + /** + * A contract call timeout + */ + timeout: new SorobanBuilder() + .withContractId('CA1234567890ABCDEF') + .withMethod('deposit') + .withTimeout(true) + .build(), + + /** + * An unsupported feature call + */ + unsupported: new SorobanBuilder() + .withContractId('CA1234567890ABCDEF') + .withMethod('unsupported') + .withError('Unsupported feature') + .build(), +}; + +export type SorobanFixtureType = keyof typeof sorobanFixtures; +export const sorobanFixtureNames = Object.keys(sorobanFixtures) as SorobanFixtureType[]; diff --git a/tests/fixtures/transactions/index.ts b/tests/fixtures/transactions/index.ts new file mode 100644 index 0000000..18a1086 --- /dev/null +++ b/tests/fixtures/transactions/index.ts @@ -0,0 +1,2 @@ +export * from './transaction-fixtures'; +export * from './transaction-builder'; diff --git a/tests/fixtures/transactions/transaction-builder.ts b/tests/fixtures/transactions/transaction-builder.ts new file mode 100644 index 0000000..fd88e9c --- /dev/null +++ b/tests/fixtures/transactions/transaction-builder.ts @@ -0,0 +1,117 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface TransactionFixture { + hash: string; + from: string; + to: string; + amount: string; + asset: string; + assetIssuer?: string; + memo?: string; + status: 'pending' | 'completed' | 'failed'; + fee?: string; + error?: string; + createdAt: Date; + updatedAt: Date; +} + +export class TransactionBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + hash: '0x' + '0'.repeat(64), + from: 'GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + to: 'GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW', + amount: '0.00', + asset: 'XLM', + status: 'pending', + createdAt: new Date(), + updatedAt: new Date(), + }; + } + + withHash(hash: string): this { + this.data.hash = hash; + return this; + } + + withFrom(from: string): this { + this.data.from = from; + return this; + } + + withTo(to: string): this { + this.data.to = to; + return this; + } + + withAmount(amount: string): this { + this.data.amount = amount; + return this; + } + + withAsset(asset: string): this { + this.data.asset = asset; + return this; + } + + withAssetIssuer(issuer: string): this { + this.data.assetIssuer = issuer; + return this; + } + + withMemo(memo: string): this { + this.data.memo = memo; + return this; + } + + withStatus(status: 'pending' | 'completed' | 'failed'): this { + this.data.status = status; + return this; + } + + withFee(fee: string): this { + this.data.fee = fee; + return this; + } + + withError(error: string): this { + this.data.error = error; + return this; + } + + build(): TransactionFixture { + return { + hash: this.data.hash!, + from: this.data.from!, + to: this.data.to!, + amount: this.data.amount!, + asset: this.data.asset!, + assetIssuer: this.data.assetIssuer, + memo: this.data.memo, + status: this.data.status!, + fee: this.data.fee, + error: this.data.error, + createdAt: this.data.createdAt!, + updatedAt: this.data.updatedAt!, + }; + } + + validate(): boolean { + return !!this.data.hash && !!this.data.from && !!this.data.to; + } + + getErrors(): string[] { + const errors: string[] = []; + if (!this.data.hash) { + errors.push('Transaction hash is required'); + } + if (!this.data.from) { + errors.push('From address is required'); + } + if (!this.data.to) { + errors.push('To address is required'); + } + return errors; + } +} diff --git a/tests/fixtures/transactions/transaction-fixtures.ts b/tests/fixtures/transactions/transaction-fixtures.ts new file mode 100644 index 0000000..3edb041 --- /dev/null +++ b/tests/fixtures/transactions/transaction-fixtures.ts @@ -0,0 +1,59 @@ +import { TransactionBuilder } from './transaction-builder'; + +/** + * Baseline transaction fixtures + */ +export const transactionFixtures = { + /** + * A successful transaction + */ + success: new TransactionBuilder() + .withHash('0x1234567890abcdef1234567890abcdef12345678') + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('completed') + .build(), + + /** + * A pending transaction + */ + pending: new TransactionBuilder() + .withHash('0x1234567890abcdef1234567890abcdef12345679') + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('pending') + .build(), + + /** + * A failed transaction + */ + failed: new TransactionBuilder() + .withHash('0x1234567890abcdef1234567890abcdef12345680') + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withStatus('failed') + .withError('Transaction failed') + .build(), + + /** + * A transaction with memo + */ + withMemo: new TransactionBuilder() + .withHash('0x1234567890abcdef1234567890abcdef12345681') + .withFrom('GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + .withTo('GXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVW') + .withAmount('100.00') + .withAsset('XLM') + .withMemo('Payment for services') + .withStatus('completed') + .build(), +}; + +export type TransactionFixtureType = keyof typeof transactionFixtures; +export const transactionFixtureNames = Object.keys(transactionFixtures) as TransactionFixtureType[]; diff --git a/tests/fixtures/vault/index.ts b/tests/fixtures/vault/index.ts new file mode 100644 index 0000000..823506b --- /dev/null +++ b/tests/fixtures/vault/index.ts @@ -0,0 +1,2 @@ +export * from './vault-fixtures'; +export * from './vault-builder'; diff --git a/tests/fixtures/vault/vault-builder.ts b/tests/fixtures/vault/vault-builder.ts new file mode 100644 index 0000000..67290d9 --- /dev/null +++ b/tests/fixtures/vault/vault-builder.ts @@ -0,0 +1,91 @@ +import { FixtureBuilder } from '../builders/fixture-builder'; + +export interface VaultFixture { + userId: string; + amount: string; + action: 'deposit' | 'withdraw' | 'lock' | 'unlock'; + status: 'pending' | 'completed' | 'failed'; + lockDuration?: number; + lockId?: string; + error?: string; + createdAt: Date; + updatedAt: Date; +} + +export class VaultBuilder extends FixtureBuilder { + constructor() { + super(); + this.data = { + userId: 'user_123', + amount: '0.00', + action: 'deposit', + status: 'pending', + createdAt: new Date(), + updatedAt: new Date(), + }; + } + + withUserId(userId: string): this { + this.data.userId = userId; + return this; + } + + withAmount(amount: string): this { + this.data.amount = amount; + return this; + } + + withAction(action: 'deposit' | 'withdraw' | 'lock' | 'unlock'): this { + this.data.action = action; + return this; + } + + withStatus(status: 'pending' | 'completed' | 'failed'): this { + this.data.status = status; + return this; + } + + withLockDuration(duration: number): this { + this.data.lockDuration = duration; + return this; + } + + withLockId(lockId: string): this { + this.data.lockId = lockId; + return this; + } + + withError(error: string): this { + this.data.error = error; + return this; + } + + build(): VaultFixture { + return { + userId: this.data.userId!, + amount: this.data.amount!, + action: this.data.action!, + status: this.data.status!, + lockDuration: this.data.lockDuration, + lockId: this.data.lockId, + error: this.data.error, + createdAt: this.data.createdAt!, + updatedAt: this.data.updatedAt!, + }; + } + + validate(): boolean { + return !!this.data.userId && Number(this.data.amount) >= 0; + } + + getErrors(): string[] { + const errors: string[] = []; + if (!this.data.userId) { + errors.push('User ID is required'); + } + if (!this.data.amount || Number(this.data.amount) < 0) { + errors.push('Amount must be non-negative'); + } + return errors; + } +} diff --git a/tests/fixtures/vault/vault-fixtures.ts b/tests/fixtures/vault/vault-fixtures.ts new file mode 100644 index 0000000..4262bb2 --- /dev/null +++ b/tests/fixtures/vault/vault-fixtures.ts @@ -0,0 +1,62 @@ +import { VaultBuilder } from './vault-builder'; + +/** + * Baseline vault fixtures + */ +export const vaultFixtures = { + /** + * A successful vault operation + */ + success: new VaultBuilder() + .withUserId('user_123') + .withAmount('1000.00') + .withAction('deposit') + .withStatus('completed') + .build(), + + /** + * A pending vault operation + */ + pending: new VaultBuilder() + .withUserId('user_123') + .withAmount('1000.00') + .withAction('deposit') + .withStatus('pending') + .build(), + + /** + * A failed vault operation + */ + failed: new VaultBuilder() + .withUserId('user_123') + .withAmount('1000.00') + .withAction('deposit') + .withStatus('failed') + .withError('Insufficient balance') + .build(), + + /** + * A lock operation + */ + lock: new VaultBuilder() + .withUserId('user_123') + .withAmount('500.00') + .withAction('lock') + .withLockDuration(3600) + .withStatus('completed') + .build(), + + /** + * An unlock operation + */ + unlock: new VaultBuilder() + .withUserId('user_123') + .withAmount('500.00') + .withAction('unlock') + .withLockId('lock_123') + .withStatus('completed') + .build(), +}; + +export type VaultFixtureType = keyof typeof vaultFixtures; +export const vaultFixtureNames = Object.keys(vaultFixtures) as VaultFixtureType[];