Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
148 changes: 148 additions & 0 deletions tests/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -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<MyFixture> {
// 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<MyFixture> {
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';
86 changes: 86 additions & 0 deletions tests/fixtures/accounts/account-builder.ts
Original file line number Diff line number Diff line change
@@ -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<AccountFixture> {
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;
}
}
73 changes: 73 additions & 0 deletions tests/fixtures/accounts/account-fixtures.ts
Original file line number Diff line number Diff line change
@@ -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[];
2 changes: 2 additions & 0 deletions tests/fixtures/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './account-fixtures';
export * from './account-builder';
55 changes: 55 additions & 0 deletions tests/fixtures/builders/fixture-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Base fixture builder class
* Provides common functionality for all fixture builders
*/
export abstract class FixtureBuilder<T> {
protected data: Partial<T> = {};

/**
* Build the fixture
*/
abstract build(): T;

/**
* Reset the builder to defaults
*/
reset(): this {
this.data = {};
return this;
}

/**
* Set a specific value
*/
set<K extends keyof T>(key: K, value: T[K]): this {
this.data[key] = value;
return this;
}

/**
* Merge with another builder
*/
merge(other: Partial<T>): 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[];
}
7 changes: 7 additions & 0 deletions tests/fixtures/builders/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading