Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
67eba61
Init Testing setup
treeoflife2 Nov 6, 2025
dfbbd68
update lock file
g1nt0ki Nov 6, 2025
872b333
better validation and move jest config to the api-tests folder
treeoflife2 Nov 7, 2025
20e3bd7
rollback global jest.config change
treeoflife2 Nov 7, 2025
3aa844f
minor fix
g1nt0ki Nov 7, 2025
f092c03
remove cli support (use ui-tool)
g1nt0ki Nov 7, 2025
75c74e9
report test failures to discord
g1nt0ki Nov 7, 2025
14b1b87
add json reporter
g1nt0ki Nov 10, 2025
3350861
obol token data
Define101 Nov 5, 2025
67e6ffd
Coins decimals type (#10945)
waynebruce0x Nov 5, 2025
90643fe
update osmosis twitter handle
g1nt0ki Nov 5, 2025
fa89a4a
feat: add erc4626 sUSDr address on Sei (#10947)
FabienCoutant Nov 5, 2025
d66a215
asseto aoabt add Avalanche's token (#10939)
Silas-Zhu Nov 5, 2025
5b80f46
add new listing: enable dimensions
realdealshaman Nov 5, 2025
604e7ac
move some listings to data5
realdealshaman Nov 5, 2025
56c5015
add new listings
realdealshaman Nov 5, 2025
51bd029
add EthStrategy tvl
realdealshaman Nov 6, 2025
6f5bf34
Mappings (#10950)
waynebruce0x Nov 6, 2025
4ca269e
rely on slug form bridges-server data (#10936)
vrtnd Nov 6, 2025
bf5ff2d
ssui (#10955)
waynebruce0x Nov 6, 2025
ae80367
add audit link
realdealshaman Nov 6, 2025
fe24dbe
add tvl、volume、fee three meters for dipcoin spot and dipcoin perps. a…
swifth Nov 6, 2025
e0966ad
update new dipcoin info
realdealshaman Nov 6, 2025
1755407
add new listings
realdealshaman Nov 6, 2025
529342a
add new listings
realdealshaman Nov 6, 2025
d030db7
fix module
realdealshaman Nov 6, 2025
a402874
chore: add fees & dexs dimension adapters to FlowSwap v3 (#10953)
leonimella Nov 6, 2025
66740d2
enable dimensions adapters
realdealshaman Nov 6, 2025
b73935b
V2 Chain Assets (#10820)
waynebruce0x Nov 7, 2025
eca98c6
ssui (#10958)
waynebruce0x Nov 7, 2025
14f2e2f
supra FA (#10959)
waynebruce0x Nov 7, 2025
f4212c6
price sui-LST tokens (#10960)
g1nt0ki Nov 7, 2025
2816887
delist perp volume ostrich (#10962)
noateden Nov 7, 2025
ffb2f5c
add new listing
realdealshaman Nov 8, 2025
05f7d33
enable dimensions adapters
realdealshaman Nov 8, 2025
23bd36e
Lombard/ BTC.b as new listing (#10956)
VaitaR Nov 8, 2025
863b34d
fix listings
realdealshaman Nov 8, 2025
8a0757e
add astro perp listing
realdealshaman Nov 8, 2025
0fc1942
update listing name
realdealshaman Nov 8, 2025
bb38191
add b-lucky listing
realdealshaman Nov 8, 2025
d133d02
enable dzsol fees
realdealshaman Nov 8, 2025
e7f4139
Enabled grvt perps OI (#10963)
treeoflife2 Nov 8, 2025
47a1825
disable bitlayer-bridge por (#10964)
noateden Nov 8, 2025
d89a15d
track bifrost vol & chain fees
g1nt0ki Nov 8, 2025
e44a1e6
ignore ohm
g1nt0ki Nov 8, 2025
9252c73
fix icon
realdealshaman Nov 8, 2025
5ea3c0b
enable rho x OI
realdealshaman Nov 8, 2025
a1cad69
add token
realdealshaman Nov 8, 2025
0874b28
fix lombard vaults icon
realdealshaman Nov 8, 2025
798ed2f
fix icon
realdealshaman Nov 8, 2025
eabe8fa
add audit link
realdealshaman Nov 9, 2025
94ad0e5
link stablecoins
realdealshaman Nov 9, 2025
272fa48
link stablecoins
realdealshaman Nov 9, 2025
f1cd074
new listing
realdealshaman Nov 9, 2025
3193403
add stablecoin
realdealshaman Nov 9, 2025
35be7c9
add warning max apy
Define101 Nov 10, 2025
8742b05
remove gtbtc price until confirm collateral
Define101 Nov 10, 2025
45096bd
Merge branch 'master' of https://github.com/DefiLlama/defillama-serve…
treeoflife2 Nov 13, 2025
b7e304f
tvl endpoint tests
treeoflife2 Nov 13, 2025
3236e9e
fix tvl tests
treeoflife2 Nov 13, 2025
9fd6e35
remove duplicate
treeoflife2 Nov 13, 2025
6c703d5
revert gitignore change
treeoflife2 Nov 13, 2025
2d23a39
stablecoins tests
treeoflife2 Nov 14, 2025
7eb2df2
add tests for activeUsers
treeoflife2 Nov 15, 2025
79a700a
remaining test categories
treeoflife2 Nov 20, 2025
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
159 changes: 159 additions & 0 deletions defi/api-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# DefiLlama API Testing Framework

Type-safe API testing framework for DefiLlama endpoints using Jest and TypeScript.

## Architecture

```
api-tests/
├── src/
│ ├── tvl/ # TVL category
│ │ ├── setup.ts # Shared client initialization
│ │ ├── types.ts # Category-specific types
│ │ ├── protocols.test.ts
│ │ └── charts.test.ts
│ ├── stablecoins/ # Add tests here
│ └── ...
└── utils/
├── config/ # API client & endpoints config
├── testHelpers.ts # Common assertions
└── validators.ts # Response validators
```

Uses parent `defi` folder configurations (jest.config.js, tsconfig.json, .gitignore, .env).

## Category Pattern

Each category follows this structure:

**setup.ts** - Initialize and cache API client
```typescript
import { createApiClient, ApiClient } from '../../utils/config/apiClient';
import { TVL_ENDPOINTS } from '../../utils/config/endpoints';

let apiClient: ApiClient | null = null;

export function initializeTvlTests(): ApiClient {
if (!apiClient) {
apiClient = createApiClient(TVL_ENDPOINTS.BASE_URL);
}
return apiClient;
}

export { TVL_ENDPOINTS };
```

**types.ts** - Category-specific TypeScript types
```typescript
export interface Protocol {
id: string;
name: string;
tvl: number;
}
```

**\*.test.ts** - Test files using setup and types
```typescript
import { initializeTvlTests, TVL_ENDPOINTS } from './setup';
import { Protocol } from './types';

describe('TVL API - Protocols', () => {
const apiClient = initializeTvlTests();

it('should return protocols', async () => {
const response = await apiClient.get<Protocol[]>(TVL_ENDPOINTS.PROTOCOLS);
expect(response.status).toBe(200);
});
});
```

## Environment Setup

Add these variables to `defi/.env`:

```bash
BASE_API_URL='https://api.llama.fi'
BETA_API_URL='https://api.llama.fi'
BETA_COINS_URL='https://coins.llama.fi'
BETA_STABLECOINS_URL='https://stablecoins.llama.fi'
BETA_YIELDS_URL='https://yields.llama.fi'
BETA_BRIDGES_URL='https://bridges.llama.fi'
BETA_PRO_API_URL='https://pro-api.llama.fi'
```

## Running Tests

From `defi` directory:
```bash
npm run test:api # Run all API tests
npm run test:api:watch # Watch mode
npm run test:api:coverage # With coverage
```

## Adding New Category

1. **Create folder**: `src/your-category/`

2. **Add endpoint** in `utils/config/endpoints.ts`:
```typescript
export const YOUR_ENDPOINTS = {
BASE_URL: BASE_URLS.YOUR_CATEGORY,
ENDPOINT: '/path',
} as const;
```

3. **Create `setup.ts`**:
```typescript
import { createApiClient, ApiClient } from '../../utils/config/apiClient';
import { YOUR_ENDPOINTS } from '../../utils/config/endpoints';

let apiClient: ApiClient | null = null;

export function initializeYourTests(): ApiClient {
if (!apiClient) {
apiClient = createApiClient(YOUR_ENDPOINTS.BASE_URL);
}
return apiClient;
}

export { YOUR_ENDPOINTS };
```

4. **Create `types.ts`** with category-specific types

5. **Write tests** using the setup and types

## Available Helpers

**utils/testHelpers.ts**
- `expectSuccessfulResponse(response)` - Check 2xx status
- `expectArrayResponse(response)` - Validate array response
- `expectNonEmptyArray(data)` - Check array has items
- `expectValidNumber(value)` - Validate number
- `expectNonNegativeNumber(value)` - Check value >= 0
- `expectValidTimestamp(timestamp)` - Validate Unix timestamp

**utils/validators.ts**
- `validateProtocol(protocol)` - Validate protocol structure
- `validateChartDataPoint(point)` - Validate chart data
- `validateArray(data, validator)` - Validate array with custom validator

## CI/CD

**Jenkins**
```groovy
stage('API Tests') {
steps {
sh 'cd defi && npm run test:api'
}
}
```

**GitHub Actions**
```yaml
- run: cd defi && npm run test:api
```

## API Docs

https://api-docs.defillama.com/
177 changes: 177 additions & 0 deletions defi/api-tests/src/tvl/protocols.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { initializeTvlTests, TVL_ENDPOINTS } from './setup';
import { Protocol, isProtocolArray } from './types';
import {
expectSuccessfulResponse,
expectArrayResponse,
expectNonEmptyArray,
expectArrayItemsHaveKeys,
expectValidNumber,
expectNonNegativeNumber,
expectNonEmptyString,
} from '../../utils/testHelpers';
import { validateProtocol, validateArray } from '../../utils/validators';
import { ApiResponse } from '../../utils/config/apiClient';

describe('TVL API - Protocols', () => {
const apiClient = initializeTvlTests();
let protocolsResponse: ApiResponse<Protocol[]>;

beforeAll(async () => {
protocolsResponse = await apiClient.get<Protocol[]>(TVL_ENDPOINTS.PROTOCOLS);
});

describe('GET /protocols', () => {
it('should return list of all protocols', () => {
expectSuccessfulResponse(protocolsResponse);
expectArrayResponse(protocolsResponse);
expectNonEmptyArray(protocolsResponse.data);
expect(isProtocolArray(protocolsResponse.data)).toBe(true);
});

it('should return protocols with required fields', () => {
const requiredFields = ['id', 'name', 'symbol', 'chains', 'slug'];
expectArrayItemsHaveKeys(protocolsResponse.data, requiredFields);
});

it('should return protocols with valid data types', () => {
const sampleSize = Math.min(10, protocolsResponse.data.length);
const sampleProtocols = protocolsResponse.data.slice(0, sampleSize);

sampleProtocols.forEach((protocol) => {
expect(validateProtocol(protocol)).toBe(true);
expectNonEmptyString(protocol.name);
expectNonEmptyString(protocol.symbol);
if (protocol.tvl !== null) {
expectNonNegativeNumber(protocol.tvl);
}
expect(Array.isArray(protocol.chains)).toBe(true);
});
});

it('should return protocols with valid TVL values', () => {
const protocolsWithTvl = protocolsResponse.data.filter((p) => p.tvl !== null && p.tvl >= 0);

expect(protocolsWithTvl.length).toBeGreaterThan(0);

protocolsWithTvl.forEach((protocol) => {
expectValidNumber(protocol.tvl!);
expectNonNegativeNumber(protocol.tvl!);
expect(protocol.tvl!).toBeLessThan(10_000_000_000_000);
});
});

it('should return protocols with valid chain TVLs structure', () => {
const protocolsWithChainTvls = protocolsResponse.data.filter(
(p) => p.chainTvls && Object.keys(p.chainTvls).length > 0
);
expect(protocolsWithChainTvls.length).toBeGreaterThan(0);

protocolsWithChainTvls.slice(0, 5).forEach((protocol) => {
expect(typeof protocol.chainTvls).toBe('object');

Object.entries(protocol.chainTvls).forEach(([chain, tvl]) => {
expectNonEmptyString(chain);
expectValidNumber(tvl);
expectNonNegativeNumber(tvl);
});

if ('borrowed' in protocol.chainTvls) {
expectValidNumber(protocol.chainTvls.borrowed!);
expectNonNegativeNumber(protocol.chainTvls.borrowed!);
}
});
});

it('should handle protocols with borrowed amounts', () => {
const protocolsWithBorrowed = protocolsResponse.data.filter(
(p) => p.chainTvls && 'borrowed' in p.chainTvls
);

if (protocolsWithBorrowed.length > 0) {
protocolsWithBorrowed.slice(0, 3).forEach((protocol) => {
const borrowed = protocol.chainTvls.borrowed;
if (borrowed !== undefined) {
expectValidNumber(borrowed);
expectNonNegativeNumber(borrowed);
}

const borrowedChains = Object.keys(protocol.chainTvls).filter(
(key) => key.endsWith('-borrowed')
);
borrowedChains.forEach((chain) => {
const chainTvl = protocol.chainTvls[chain as any];
expectValidNumber(chainTvl);
expectNonNegativeNumber(chainTvl);
});
});
}
});

it('should return protocols ordered by TVL descending', () => {
const protocolsWithTvl = protocolsResponse.data.filter((p) => p.tvl !== null);

const sampleSize = Math.min(100, protocolsWithTvl.length);
for (let i = 0; i < sampleSize - 1; i++) {
expect(protocolsWithTvl[i].tvl!).toBeGreaterThanOrEqual(protocolsWithTvl[i + 1].tvl!);
}
});

it('should have consistent data structure', () => {
const allValid = validateArray(protocolsResponse.data, validateProtocol);
expect(allValid).toBe(true);
});

it('should return protocols with categories', () => {
const protocolsWithCategory = protocolsResponse.data.filter((p) => p.category);
expect(protocolsWithCategory.length).toBeGreaterThan(0);
});

it('should return protocols with valid percentage changes', () => {
const protocolsWithChanges = protocolsResponse.data.filter(
(p) => p.change_1h !== null || p.change_1d !== null || p.change_7d !== null
);

expect(protocolsWithChanges.length).toBeGreaterThan(0);

protocolsWithChanges.slice(0, 10).forEach((protocol) => {
if (protocol.change_1h !== null && protocol.change_1h !== undefined) {
expectValidNumber(protocol.change_1h);
}
if (protocol.change_1d !== null && protocol.change_1d !== undefined) {
expectValidNumber(protocol.change_1d);
}
if (protocol.change_7d !== null && protocol.change_7d !== undefined) {
expectValidNumber(protocol.change_7d);
}
});
});

it('should handle protocols with parent protocol slug', () => {
const protocolsWithParent = protocolsResponse.data.filter((p) => p.parentProtocolSlug);

if (protocolsWithParent.length > 0) {
protocolsWithParent.slice(0, 5).forEach((protocol) => {
expectNonEmptyString(protocol.parentProtocolSlug!);
expect(protocol.parentProtocolSlug).not.toBe(protocol.slug);
});
}
});

it('should validate complete protocol structure', () => {
const sampleProtocol = protocolsResponse.data[0];

expect(sampleProtocol).toHaveProperty('id');
expect(sampleProtocol).toHaveProperty('name');
expect(sampleProtocol).toHaveProperty('symbol');
expect(sampleProtocol).toHaveProperty('chains');
expect(sampleProtocol).toHaveProperty('slug');

expectNonEmptyString(sampleProtocol.id);
expectNonEmptyString(sampleProtocol.name);
expectNonEmptyString(sampleProtocol.symbol);
expectNonEmptyString(sampleProtocol.slug);
expect(Array.isArray(sampleProtocol.chains)).toBe(true);
expect(typeof sampleProtocol.chainTvls).toBe('object');
});
});
});
21 changes: 21 additions & 0 deletions defi/api-tests/src/tvl/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createApiClient, ApiClient } from '../../utils/config/apiClient';
import { TVL_ENDPOINTS } from '../../utils/config/endpoints';

let apiClient: ApiClient | null = null;

export function initializeTvlTests(): ApiClient {
if (!apiClient) {
apiClient = createApiClient(TVL_ENDPOINTS.BASE_URL);
}
return apiClient;
}

export function getTvlClient(): ApiClient {
if (!apiClient) {
return initializeTvlTests();
}
return apiClient;
}

export { TVL_ENDPOINTS };

Loading