Skip to main content
Glama

Open Food Facts MCP Server

by caleb-conner
test-helpers.ts9.82 kB
import { Product, ProductResponse, SearchResponse } from '../../src/types.js'; /** * Test utilities for Open Food Facts MCP Server tests */ /** * Creates a minimal valid product for testing */ export function createMinimalProduct(code: string, overrides: Partial<Product> = {}): Product { return { code, ...overrides, }; } /** * Creates a complete product with all fields for testing */ export function createCompleteProduct(code: string, overrides: Partial<Product> = {}): Product { return { code, product_name: 'Test Product', brands: 'Test Brand', categories: 'Test Category', ingredients_text: 'Test ingredients', nutriments: { 'energy-kcal_100g': 100, 'fat_100g': 5.0, 'carbohydrates_100g': 15.0, 'sugars_100g': 10.0, 'proteins_100g': 3.0, 'salt_100g': 0.5, 'fiber_100g': 2.0, }, nutriscore_grade: 'b', nova_group: 2, ecoscore_grade: 'c', image_url: 'https://example.com/image.jpg', image_front_url: 'https://example.com/front.jpg', quantity: '100g', packaging: 'Box', labels: 'Organic', countries: 'Test Country', manufacturing_places: 'Test Factory', stores: 'Test Store', created_datetime: '2023-01-01T00:00:00Z', last_modified_datetime: 1672531200, ...overrides, }; } /** * Creates a product response for testing */ export function createProductResponse( status: number, statusVerbose: string, product?: Product ): ProductResponse { return { status, status_verbose: statusVerbose, ...(product && { product }), }; } /** * Creates a search response for testing */ export function createSearchResponse( products: Product[], overrides: Partial<SearchResponse> = {} ): SearchResponse { return { count: products.length, page: 1, page_count: Math.ceil(products.length / 20), page_size: 20, products, ...overrides, }; } /** * Creates a product with specific nutritional profile for testing */ export function createNutritionalProduct( code: string, profile: 'healthy' | 'unhealthy' | 'average', overrides: Partial<Product> = {} ): Product { const profiles = { healthy: { nutriments: { 'energy-kcal_100g': 80, 'fat_100g': 2.0, 'saturated-fat_100g': 0.5, 'carbohydrates_100g': 10.0, 'sugars_100g': 3.0, 'proteins_100g': 8.0, 'salt_100g': 0.1, 'fiber_100g': 5.0, }, nutriscore_grade: 'a', nova_group: 1, ecoscore_grade: 'a', }, unhealthy: { nutriments: { 'energy-kcal_100g': 550, 'fat_100g': 35.0, 'saturated-fat_100g': 15.0, 'carbohydrates_100g': 55.0, 'sugars_100g': 50.0, 'proteins_100g': 6.0, 'salt_100g': 2.0, 'fiber_100g': 0.0, }, nutriscore_grade: 'e', nova_group: 4, ecoscore_grade: 'e', }, average: { nutriments: { 'energy-kcal_100g': 250, 'fat_100g': 12.0, 'saturated-fat_100g': 6.0, 'carbohydrates_100g': 30.0, 'sugars_100g': 20.0, 'proteins_100g': 5.0, 'salt_100g': 1.0, 'fiber_100g': 2.0, }, nutriscore_grade: 'c', nova_group: 3, ecoscore_grade: 'c', }, }; return createCompleteProduct(code, { ...profiles[profile], ...overrides, }); } /** * Creates a product with specific dietary labels for testing */ export function createDietaryProduct( code: string, dietaryPreferences: string[], overrides: Partial<Product> = {} ): Product { const labelMap: Record<string, string[]> = { vegan: ['Vegan', 'Plant-based'], vegetarian: ['Vegetarian'], 'gluten-free': ['Gluten-free', 'Sans gluten'], organic: ['Organic', 'Bio', 'Organic farming'], 'low-fat': ['Low fat', 'Light', 'Reduced fat'], 'low-sugar': ['Sugar-free', 'No added sugar', 'Low sugar'], 'high-protein': ['High protein', 'Rich in protein'], }; const labels = dietaryPreferences .flatMap(pref => labelMap[pref] || []) .join(', '); return createCompleteProduct(code, { labels, categories: 'Plant-based foods', ...overrides, }); } /** * Generates a list of test barcodes */ export function generateTestBarcodes(count: number): string[] { const barcodes: string[] = []; for (let i = 0; i < count; i++) { // Generate 13-digit barcodes starting with different prefixes const prefix = (300 + (i % 100)).toString().padStart(3, '0'); const suffix = i.toString().padStart(10, '0'); barcodes.push(prefix + suffix); } return barcodes; } /** * Creates mock axios response configuration */ export function createAxiosResponse(data: any, status = 200) { return { data, status, statusText: status === 200 ? 'OK' : 'Error', headers: {}, config: {}, }; } /** * Validates that a text response contains expected nutritional information */ export function expectNutritionalInfo(text: string, product: Product): void { if (product.product_name) { expect(text).toContain(product.product_name); } if (product.brands) { expect(text).toContain(product.brands); } if (product.nutriscore_grade) { expect(text).toContain(product.nutriscore_grade.toUpperCase()); } if (product.nova_group) { expect(text).toContain(product.nova_group.toString()); } if (product.ecoscore_grade) { expect(text).toContain(product.ecoscore_grade.toUpperCase()); } } /** * Validates that a text response contains proper formatting */ export function expectProperFormatting(text: string): void { // Should contain markdown-style headers expect(text).toMatch(/\*\*.*\*\*/); // Should not contain undefined or null values expect(text).not.toContain('undefined'); expect(text).not.toContain('null'); // Should not have excessive whitespace expect(text).not.toMatch(/\n\n\n+/); } /** * Creates a rate limit configuration for testing */ export function createRateLimitConfig( products = 100, search = 10, facets = 2 ) { return { products, search, facets, }; } /** * Creates a client configuration for testing */ export function createTestConfig(overrides: Partial<any> = {}) { return { baseUrl: 'https://test.openfoodfacts.net', userAgent: 'OpenFoodFactsMCP/1.0 (test)', rateLimits: createRateLimitConfig(), ...overrides, }; } /** * Simulates network delay for testing */ export function delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Creates test data for comparison scenarios */ export function createComparisonProducts(): Product[] { return [ createNutritionalProduct('1', 'healthy', { product_name: 'Healthy Product', brands: 'Health Brand', }), createNutritionalProduct('2', 'unhealthy', { product_name: 'Unhealthy Product', brands: 'Junk Brand', }), createNutritionalProduct('3', 'average', { product_name: 'Average Product', brands: 'Regular Brand', }), ]; } /** * Creates test data for dietary filtering scenarios */ export function createDietaryProducts(): Product[] { return [ createDietaryProduct('vegan1', ['vegan', 'organic'], { product_name: 'Vegan Organic Product', }), createDietaryProduct('vegetarian1', ['vegetarian', 'gluten-free'], { product_name: 'Vegetarian Gluten-Free Product', }), createDietaryProduct('regular1', [], { product_name: 'Regular Product', }), createDietaryProduct('protein1', ['high-protein', 'low-fat'], { product_name: 'High Protein Low Fat Product', }), ]; } /** * Validates MCP tool response format */ export function expectValidMCPResponse(response: any): void { expect(response).toHaveProperty('content'); expect(Array.isArray(response.content)).toBe(true); expect(response.content.length).toBeGreaterThan(0); response.content.forEach((item: any) => { expect(item).toHaveProperty('type'); expect(item).toHaveProperty('text'); expect(typeof item.text).toBe('string'); expect(item.text.length).toBeGreaterThan(0); }); } /** * Validates error response format */ export function expectValidErrorResponse(response: any): void { expectValidMCPResponse(response); expect(response.isError).toBe(true); expect(response.content[0].text).toContain('Error:'); } /** * Creates mock environment variables for testing */ export function setupTestEnvironment(): void { process.env.NODE_ENV = 'test'; process.env.OPEN_FOOD_FACTS_BASE_URL = 'https://test.openfoodfacts.net'; process.env.OPEN_FOOD_FACTS_USER_AGENT = 'OpenFoodFactsMCP/1.0 (test)'; } /** * Cleans up test environment */ export function cleanupTestEnvironment(): void { delete process.env.OPEN_FOOD_FACTS_BASE_URL; delete process.env.OPEN_FOOD_FACTS_USER_AGENT; } /** * Measures execution time of async functions */ export async function measureTime<T>( fn: () => Promise<T> ): Promise<{ result: T; duration: number }> { const start = Date.now(); const result = await fn(); const duration = Date.now() - start; return { result, duration }; } /** * Retries an async function with exponential backoff */ export async function retryWithBackoff<T>( fn: () => Promise<T>, maxRetries = 3, baseDelay = 100 ): Promise<T> { let lastError: Error; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt === maxRetries) { throw lastError; } const delay = baseDelay * Math.pow(2, attempt); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError!; }

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/caleb-conner/open-food-facts-mcp'

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