Skip to main content
Glama

Stampchain MCP Server

Official
stampchain-client.test.ts16.6 kB
import { vi, describe, it, expect, beforeEach } from 'vitest'; /** * Tests for StampchainClient API client */ import axios from 'axios'; import { StampchainClient } from '../../api/stampchain-client.js'; import { createMockAxiosResponse, createMockStamp, createMockCollection, createMockToken, } from '../utils/test-helpers.js'; // Get the mocked axios instance vi.mock('axios'); vi.mock('axios-retry'); describe('StampchainClient', () => { let client: StampchainClient; let mockAxiosInstance: { get: ReturnType<typeof vi.fn>; post: ReturnType<typeof vi.fn>; put: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn>; request: ReturnType<typeof vi.fn>; interceptors: { request: { use: ReturnType<typeof vi.fn> }; response: { use: ReturnType<typeof vi.fn> }; }; defaults: { headers: Record<string, string>; baseURL: string; timeout: number; }; }; beforeEach(() => { // Get the mocked axios create return value mockAxiosInstance = { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), request: vi.fn(), interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() }, }, defaults: { headers: {}, baseURL: 'https://test.stampchain.io/api', timeout: 5000, }, }; // Make axios.create return our mock instance vi.mocked(axios.create).mockReturnValue(mockAxiosInstance as never); client = new StampchainClient({ baseURL: 'https://test.stampchain.io/api', timeout: 5000, retries: 1, retryDelay: 100, }); // Reset all mocks vi.clearAllMocks(); }); describe('constructor', () => { it('should create client with default configuration', () => { const defaultClient = new StampchainClient(); expect(defaultClient).toBeInstanceOf(StampchainClient); expect(defaultClient.getApiVersion()).toBe('2.3'); }); it('should create client with custom configuration', () => { const customClient = new StampchainClient({ baseURL: 'https://custom.api.com', timeout: 10000, retries: 5, retryDelay: 500, apiVersion: '2.2', }); expect(customClient).toBeInstanceOf(StampchainClient); expect(customClient.getApiVersion()).toBe('2.2'); }); }); describe('getStamp', () => { it('should fetch stamp data successfully', async () => { const mockStamp = createMockStamp(); mockAxiosInstance.get.mockResolvedValueOnce( createMockAxiosResponse({ data: { stamp: mockStamp }, }) ); const result = await client.getStamp(12345); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps/12345'); expect(result).toEqual(mockStamp); }); it('should handle API errors gracefully', async () => { mockAxiosInstance.get.mockRejectedValueOnce(new Error('Network error')); await expect(client.getStamp(12345)).rejects.toThrow('Network error'); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps/12345'); }); it('should handle 404 errors for non-existent stamps', async () => { mockAxiosInstance.get.mockRejectedValueOnce({ response: { status: 404, data: { error: 'Stamp not found' } }, }); await expect(client.getStamp(99999)).rejects.toMatchObject({ response: { status: 404 }, }); }); }); describe('searchStamps', () => { it('should search stamps with default parameters', async () => { const mockStamps = [createMockStamp(), createMockStamp({ stamp: 12346 })]; mockAxiosInstance.get.mockResolvedValueOnce( createMockAxiosResponse({ data: mockStamps, }) ); const result = await client.searchStamps(); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps', { params: {}, }); expect(result).toEqual(mockStamps); }); it('should search stamps with custom filters', async () => { const mockStamps = [createMockStamp()]; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse({ data: mockStamps })); const params = { creator: 'bc1qtest123456789012345678901234567890', collection_id: 'test-collection', limit: 20, sort_order: 'ASC' as const, }; const result = await client.searchStamps(params); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps', { params }); expect(result).toEqual(mockStamps); }); it('should handle empty search results', async () => { mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse({ data: [] })); const result = await client.searchStamps({ creator: 'nonexistent' }); expect(result).toEqual([]); }); }); describe('getRecentStamps', () => { it('should fetch recent stamps with default limit', async () => { const mockStamps = [createMockStamp(), createMockStamp({ stamp: 12346 })]; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse({ data: mockStamps })); const result = await client.getRecentStamps(); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps', { params: { limit: 20 }, }); expect(result).toEqual(mockStamps); }); it('should fetch recent stamps with custom limit', async () => { const mockStamps = Array.from({ length: 25 }, (_, i) => createMockStamp({ stamp: 12345 + i }) ); mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse({ data: mockStamps })); const result = await client.getRecentStamps(25); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps', { params: { limit: 25 }, }); expect(result).toEqual(mockStamps); }); }); describe('getCollection', () => { it('should fetch collection data successfully', async () => { const mockCollection = createMockCollection(); mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockCollection)); const result = await client.getCollection('test-collection'); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/collections/test-collection'); expect(result).toEqual(mockCollection); }); it('should handle non-existent collections', async () => { mockAxiosInstance.get.mockRejectedValueOnce({ response: { status: 404, data: { error: 'Collection not found' } }, }); await expect(client.getCollection('nonexistent')).rejects.toMatchObject({ response: { status: 404 }, }); }); }); describe('searchCollections', () => { it('should search collections with default parameters', async () => { const mockCollections = [ createMockCollection(), createMockCollection({ collection_id: 'test-2' }), ]; const mockResponse = { data: mockCollections, last_block: 844755, page: 1, limit: 10, totalPages: 1, total: 2, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockResponse)); const result = await client.searchCollections(); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/collections', { params: {}, }); expect(result).toEqual(mockCollections); }); it('should search collections with custom filters', async () => { const mockCollections = [createMockCollection()]; const mockResponse = { data: mockCollections, last_block: 844755, page: 1, limit: 5, totalPages: 1, total: 1, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockResponse)); const params = { creator: 'bc1qtest123456789012345678901234567890', query: 'Test', page_size: 5, }; const result = await client.searchCollections(params); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/collections', { params }); expect(result).toEqual(mockCollections); }); }); describe('getToken', () => { it('should fetch token info successfully', async () => { const mockToken = createMockToken(); mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockToken)); const result = await client.getToken('TEST'); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/src20/TEST'); expect(result).toEqual(mockToken); }); it('should handle case-insensitive token tickers', async () => { const mockToken = createMockToken({ tick: 'TEST' }); mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockToken)); const result = await client.getToken('test'); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/src20/test'); expect(result).toEqual(mockToken); }); }); describe('searchTokens', () => { it('should search tokens with default parameters', async () => { const mockTokens = [createMockToken(), createMockToken({ tick: 'OTHER' })]; const mockResponse = { data: mockTokens, last_block: 844755, page: 1, limit: 10, totalPages: 1, total: 2, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockResponse)); const result = await client.searchTokens(); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/src20', { params: {}, }); expect(result).toEqual(mockTokens); }); it('should search tokens with filters', async () => { const mockTokens = [createMockToken()]; const mockResponse = { data: mockTokens, last_block: 844755, page: 1, limit: 20, totalPages: 1, total: 1, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockResponse)); const params = { deployer: 'bc1qtest123456789012345678901234567890', page_size: 20, }; const result = await client.searchTokens(params); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/src20', { params }); expect(result).toEqual(mockTokens); }); }); describe('error handling', () => { it('should handle network timeouts', async () => { const timeoutError = new Error('timeout of 5000ms exceeded'); timeoutError.name = 'AxiosError'; mockAxiosInstance.get.mockRejectedValueOnce(timeoutError); await expect(client.getStamp(12345)).rejects.toThrow('timeout of 5000ms exceeded'); }); it('should handle server errors', async () => { mockAxiosInstance.get.mockRejectedValueOnce({ response: { status: 500, statusText: 'Internal Server Error', data: { error: 'Database connection failed' }, }, }); await expect(client.getStamp(12345)).rejects.toMatchObject({ response: { status: 500 }, }); }); it('should handle rate limiting', async () => { mockAxiosInstance.get.mockRejectedValueOnce({ response: { status: 429, statusText: 'Too Many Requests', data: { error: 'Rate limit exceeded' }, }, }); await expect(client.getStamp(12345)).rejects.toMatchObject({ response: { status: 429 }, }); }); }); describe('API version management', () => { it('should get current API version', () => { expect(client.getApiVersion()).toBe('2.3'); }); it('should set API version', () => { client.setApiVersion('2.2'); expect(client.getApiVersion()).toBe('2.2'); }); it('should get available versions', async () => { const mockVersions = { current: '2.3', requestedVersion: '2.3', versions: [ { version: '2.3', status: 'current', releaseDate: '2025-01-15' }, { version: '2.2', status: 'supported', releaseDate: '2024-06-01' }, ], }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockVersions)); const result = await client.getAvailableVersions(); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/versions'); expect(result).toEqual(mockVersions); }); it('should test version compatibility', async () => { // Mock axios.create for the temporary client in testVersionCompatibility const tempMockInstance = { get: vi.fn().mockResolvedValueOnce(createMockAxiosResponse({ status: 'OK' })), }; vi.mocked(axios.create).mockReturnValueOnce(tempMockInstance as never); const result = await client.testVersionCompatibility('2.2'); expect(result).toBe(true); }); it('should handle version compatibility failure', async () => { mockAxiosInstance.get.mockRejectedValueOnce(new Error('Version not supported')); const result = await client.testVersionCompatibility('2.1'); expect(result).toBe(false); }); it('should get feature availability', () => { const features = client.getFeatureAvailability(); expect(features).toEqual({ marketData: true, recentSales: true, enhancedFiltering: true, dispenserInfo: true, cacheStatus: true, }); }); it('should get feature availability for older version', () => { client.setApiVersion('2.2'); const features = client.getFeatureAvailability(); expect(features).toEqual({ marketData: false, recentSales: false, enhancedFiltering: false, dispenserInfo: false, cacheStatus: false, }); }); }); describe('recent sales (v2.3)', () => { it('should get recent sales data', async () => { const mockSalesData = { data: [ { tx_hash: 'abcd1234', block_index: 844755, stamp_id: 12345, price_btc: 0.001, price_usd: 50.0, timestamp: 1704067200, buyer_address: 'bc1qtest123', time_ago: '2h ago', }, ], metadata: { dayRange: 30, lastUpdated: 1704067200000, total: 1, }, last_block: 844755, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockSalesData)); const result = await client.getRecentSales({ dayRange: 30, fullDetails: true }); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps/recentSales', { params: { dayRange: 30, fullDetails: true }, }); expect(result).toEqual(mockSalesData); }); it('should handle recent sales with fallback for older API versions', async () => { client.setApiVersion('2.2'); const mockStamps = [createMockStamp()]; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse({ data: mockStamps })); const result = await client.getRecentSales({ dayRange: 7 }); expect(result.data).toHaveLength(1); expect(result.metadata.dayRange).toBe(7); }); }); describe('market data (v2.3)', () => { it('should get market data', async () => { const mockMarketData = { data: [ { floorPrice: 0.001, floorPriceUSD: 50.0, marketCapUSD: 1000000, activityLevel: 'HOT', lastActivityTime: 1704067200, volume24h: 0.5, }, ], last_block: 844755, page: 1, limit: 20, total: 1, }; mockAxiosInstance.get.mockResolvedValueOnce(createMockAxiosResponse(mockMarketData)); const result = await client.getMarketData({ activity_level: 'HOT' }); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps/marketData', { params: { activity_level: 'HOT' }, }); expect(result).toEqual(mockMarketData); }); it('should get stamp market data', async () => { const mockStampMarketData = { floorPrice: 0.001, floorPriceUSD: 50.0, marketCapUSD: 100000, activityLevel: 'WARM', lastActivityTime: 1704067200, volume24h: 0.1, lastSaleTxHash: 'abcd1234', lastSaleBuyerAddress: 'bc1qtest123', }; mockAxiosInstance.get.mockResolvedValueOnce( createMockAxiosResponse({ data: mockStampMarketData, }) ); const result = await client.getStampMarketData(12345); expect(mockAxiosInstance.get).toHaveBeenCalledWith('/stamps/12345/marketData'); expect(result).toEqual(mockStampMarketData); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stampchain-io/stampchain-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server