const mockFetch = global.fetch as jest.MockedFunction<typeof fetch>;
describe("Fibrous API Endpoints", () => {
const API_BASE = "https://api.fibrous.finance";
const GRAPH_API_BASE = "https://graph.fibrous.finance";
beforeEach(() => {
mockFetch.mockClear();
});
describe("Health Check Endpoints", () => {
const healthEndpoints = ["/health", "/base/health", "/starknet/health", "/scroll/health"];
test.each(healthEndpoints)("should return healthy status for %s", async (endpoint) => {
const mockResponse = {
success: true,
status: "healthy",
timestamp: new Date().toISOString(),
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: "OK",
json: async () => mockResponse,
} as Response);
const response = await fetch(`${API_BASE}${endpoint}`);
const data = await response.json();
expect(response.ok).toBe(true);
expect(data.success).toBe(true);
expect(data.status).toBe("healthy");
});
test("should handle health check failures gracefully", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 503,
statusText: "Service Unavailable",
json: async () => ({ error: "Service temporarily unavailable" }),
} as Response);
const response = await fetch(`${API_BASE}/health`);
expect(response.ok).toBe(false);
expect(response.status).toBe(503);
});
});
describe("Route Endpoints", () => {
const chains = ["base", "starknet", "scroll"];
test.each(chains)("should handle route requests for %s chain", async (chain) => {
const mockRoute = {
success: true,
inputToken: {
address: "0x123",
symbol: "ETH",
decimals: 18,
name: "Ethereum",
},
outputToken: {
address: "0x456",
symbol: "USDC",
decimals: 6,
name: "USD Coin",
},
outputAmount: "1000000000",
route: [],
time: 0.1,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockRoute,
} as Response);
const url = `${API_BASE}/${chain}/route?amount=1000000000000000000&tokenInAddress=0x123&tokenOutAddress=0x456`;
const response = await fetch(url);
const data = await response.json();
expect(response.ok).toBe(true);
expect(data.success).toBe(true);
expect(data.inputToken.symbol).toBe("ETH");
});
test("should handle invalid route parameters", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 400,
json: async () => ({
success: false,
error: "Invalid parameters",
}),
} as Response);
const url = `${API_BASE}/base/route?amount=invalid&tokenInAddress=&tokenOutAddress=`;
const response = await fetch(url);
const data = await response.json();
expect(response.ok).toBe(false);
expect(data.success).toBe(false);
});
});
describe("Token Endpoints", () => {
test("should fetch supported tokens", async () => {
const mockTokens = [
{
address: "0x123",
symbol: "ETH",
name: "Ethereum",
decimals: 18,
isBase: true,
isNative: true,
price: 2000,
},
{
address: "0x456",
symbol: "USDC",
name: "USD Coin",
decimals: 6,
isBase: true,
isNative: false,
price: 1,
},
];
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockTokens,
} as Response);
const response = await fetch(`${GRAPH_API_BASE}/base/tokens`);
const data = await response.json();
expect(response.ok).toBe(true);
expect(Array.isArray(data)).toBe(true);
expect(data).toHaveLength(2);
expect(data[0].symbol).toBe("ETH");
});
test("should fetch specific token by address", async () => {
const mockToken = {
address: "0x123",
symbol: "ETH",
name: "Ethereum",
decimals: 18,
isBase: true,
isNative: true,
price: 2000,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockToken,
} as Response);
const response = await fetch(`${GRAPH_API_BASE}/base/tokens/0x123`);
const data = await response.json();
expect(response.ok).toBe(true);
expect(data.address).toBe("0x123");
expect(data.symbol).toBe("ETH");
});
});
describe("Protocol Endpoints", () => {
test("should fetch supported protocols", async () => {
const mockProtocols = [
{ amm_name: "Uniswap", protocol: 1 },
{ amm_name: "Sushiswap", protocol: 2 },
{ amm_name: "Curve", protocol: 3 },
];
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockProtocols,
} as Response);
const response = await fetch(`${GRAPH_API_BASE}/base/protocols`);
const data = await response.json();
expect(response.ok).toBe(true);
expect(Array.isArray(data)).toBe(true);
expect(data).toHaveLength(3);
expect(data[0].amm_name).toBe("Uniswap");
});
});
describe("Error Handling", () => {
test("should handle network errors", async () => {
mockFetch.mockRejectedValueOnce(new Error("Network error"));
await expect(fetch(`${API_BASE}/health`)).rejects.toThrow("Network error");
});
test("should handle timeout errors", async () => {
mockFetch.mockImplementationOnce(
() =>
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 100))
);
await expect(fetch(`${API_BASE}/health`)).rejects.toThrow("Timeout");
});
test("should handle 404 errors", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
statusText: "Not Found",
json: async () => ({ error: "Endpoint not found" }),
} as Response);
const response = await fetch(`${API_BASE}/invalid-endpoint`);
expect(response.ok).toBe(false);
expect(response.status).toBe(404);
});
test("should handle rate limiting", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 429,
statusText: "Too Many Requests",
headers: new Headers({
"Retry-After": "60",
}),
json: async () => ({ error: "Rate limit exceeded" }),
} as Response);
const response = await fetch(`${API_BASE}/base/route`);
expect(response.ok).toBe(false);
expect(response.status).toBe(429);
});
});
describe("Headers and Authentication", () => {
test("should send correct headers", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({}),
} as Response);
await fetch(`${API_BASE}/health`, {
headers: {
"Content-Type": "application/json",
"User-Agent": "fibrous-mcp/1.0.0",
},
});
expect(mockFetch).toHaveBeenCalledWith(
`${API_BASE}/health`,
expect.objectContaining({
headers: expect.objectContaining({
"Content-Type": "application/json",
"User-Agent": "fibrous-mcp/1.0.0",
}),
})
);
});
});
});