Skip to main content
Glama

Open Food Facts MCP Server

by caleb-conner
e2e.test.ts17.7 kB
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from '@jest/globals'; import { OpenFoodFactsClient } from '../src/client.js'; import { ToolHandlers } from '../src/handlers.js'; import { OpenFoodFactsConfig } from '../src/types.js'; import { createMockServer, MockOpenFoodFactsServer } from './utils/mock-server.js'; import { createTestConfig, expectValidMCPResponse, expectValidErrorResponse, expectNutritionalInfo, expectProperFormatting } from './utils/test-helpers.js'; import { mockNutellaProduct, mockHealthyYogurtProduct, mockVeganProduct, mockNutellaProductResponse, mockHealthyYogurtProductResponse, mockVeganProductResponse, mockBarcodes } from './fixtures/products.js'; /** * End-to-End tests that simulate real usage scenarios */ describe('End-to-End Scenarios', () => { let mockServer: MockOpenFoodFactsServer; let client: OpenFoodFactsClient; let handlers: ToolHandlers; let config: OpenFoodFactsConfig; beforeAll(() => { config = createTestConfig(); client = new OpenFoodFactsClient(config); handlers = new ToolHandlers(client); }); beforeEach(() => { mockServer = createMockServer(); }); afterEach(() => { mockServer.cleanup(); }); describe('Complete Product Lookup Workflow', () => { it('should handle complete product analysis workflow', async () => { mockServer .mockGetProduct(mockBarcodes.nutella, mockNutellaProductResponse) .mockGetProduct(mockBarcodes.yogurt, mockHealthyYogurtProductResponse); // Step 1: Basic product lookup const productResult = await handlers.handleGetProduct(mockBarcodes.nutella); expectValidMCPResponse(productResult); expectNutritionalInfo(productResult.content[0].text, mockNutellaProduct); expectProperFormatting(productResult.content[0].text); expect(productResult.content[0].text).toContain('**Nutella**'); expect(productResult.content[0].text).toContain('Nutri-Score: E'); // Step 2: Detailed nutritional analysis const analysisResult = await handlers.handleAnalyzeProduct(mockBarcodes.nutella); expectValidMCPResponse(analysisResult); expect(analysisResult.content[0].text).toContain('**Nutritional Analysis: Nutella**'); expect(analysisResult.content[0].text).toContain('Very poor nutritional quality'); expect(analysisResult.content[0].text).toContain('Ultra-processed foods'); expect(analysisResult.content[0].text).toContain('high'); // Step 3: Product comparison const comparisonResult = await handlers.handleCompareProducts( [mockBarcodes.nutella, mockBarcodes.yogurt], 'nutrition' ); expectValidMCPResponse(comparisonResult); expect(comparisonResult.content[0].text).toContain('**Product Comparison (nutrition)**'); expect(comparisonResult.content[0].text).toContain('Nutella'); expect(comparisonResult.content[0].text).toContain('Greek Yogurt'); expect(comparisonResult.content[0].text).toContain('Nutri-Score: E'); expect(comparisonResult.content[0].text).toContain('Nutri-Score: B'); expect(mockServer.isDone()).toBe(true); }); it('should handle search to analysis workflow', async () => { const searchResponse = { count: 2, page: 1, page_count: 1, page_size: 20, products: [mockNutellaProduct, mockHealthyYogurtProduct], }; mockServer .mockSearchAny(searchResponse) .mockGetProduct(mockBarcodes.nutella, mockNutellaProductResponse); // Step 1: Search for products const searchResult = await handlers.handleSearchProducts({ search: 'chocolate', nutrition_grades: 'a,b,c,d,e', }); expectValidMCPResponse(searchResult); expect(searchResult.content[0].text).toContain('Found 2 products'); expect(searchResult.content[0].text).toContain('Nutella'); expect(searchResult.content[0].text).toContain('Greek Yogurt'); // Step 2: Analyze specific product found in search const analysisResult = await handlers.handleAnalyzeProduct(mockBarcodes.nutella); expectValidMCPResponse(analysisResult); expect(analysisResult.content[0].text).toContain('**Nutritional Analysis: Nutella**'); expect(mockServer.isDone()).toBe(true); }); }); describe('Dietary Recommendations Workflow', () => { it('should provide complete dietary guidance workflow', async () => { const veganSearchResponse = { count: 1, page: 1, page_count: 1, page_size: 20, products: [mockVeganProduct], }; mockServer .mockSearchAny(veganSearchResponse) .mockGetProduct(mockBarcodes.oatDrink, mockVeganProductResponse); // Step 1: Get dietary suggestions const suggestionsResult = await handlers.handleGetProductSuggestions({ category: 'beverages', dietary_preferences: ['vegan', 'organic'], min_nutriscore: 'b', max_results: 5, }); expectValidMCPResponse(suggestionsResult); expect(suggestionsResult.content[0].text).toContain('Product suggestions in beverages'); expect(suggestionsResult.content[0].text).toContain('Oat Drink Original'); // Step 2: Analyze suggested product const analysisResult = await handlers.handleAnalyzeProduct(mockBarcodes.oatDrink); expectValidMCPResponse(analysisResult); expect(analysisResult.content[0].text).toContain('**Nutritional Analysis: Oat Drink Original**'); expect(analysisResult.content[0].text).toContain('Good nutritional quality'); expect(mockServer.isDone()).toBe(true); }); it('should handle healthy alternatives workflow', async () => { const healthySearchResponse = { count: 2, page: 1, page_count: 1, page_size: 20, products: [mockHealthyYogurtProduct, mockVeganProduct], }; mockServer .mockSearchAny(healthySearchResponse) .mockGetProduct(mockBarcodes.yogurt, mockHealthyYogurtProductResponse) .mockGetProduct(mockBarcodes.oatDrink, mockVeganProductResponse); // Step 1: Search for healthy alternatives const searchResult = await handlers.handleSearchProducts({ categories: 'dairy', nutrition_grades: 'a,b', nova_groups: '1,2', }); expectValidMCPResponse(searchResult); expect(searchResult.content[0].text).toContain('Greek Yogurt'); expect(searchResult.content[0].text).toContain('Oat Drink'); // Step 2: Compare healthy options const comparisonResult = await handlers.handleCompareProducts( [mockBarcodes.yogurt, mockBarcodes.oatDrink], 'nutrition' ); expectValidMCPResponse(comparisonResult); expect(comparisonResult.content[0].text).toContain('Greek Yogurt Natural'); expect(comparisonResult.content[0].text).toContain('Oat Drink Original'); expect(mockServer.isDone()).toBe(true); }); }); describe('Error Handling Scenarios', () => { it('should gracefully handle mixed success/failure scenarios', async () => { mockServer .mockGetProduct(mockBarcodes.nutella, mockNutellaProductResponse) .mockGetProductNotFound(mockBarcodes.notFound) .mockGetProductError('error123', 500); // Test successful lookup const successResult = await handlers.handleGetProduct(mockBarcodes.nutella); expectValidMCPResponse(successResult); expect(successResult.content[0].text).toContain('Nutella'); // Test not found const notFoundResult = await handlers.handleGetProduct(mockBarcodes.notFound); expectValidMCPResponse(notFoundResult); expect(notFoundResult.content[0].text).toContain('Product not found'); // Test server error - should be handled by client try { await handlers.handleGetProduct('error123'); } catch (error) { expect(error).toBeInstanceOf(Error); expect((error as Error).message).toContain('Failed to fetch'); } expect(mockServer.isDone()).toBe(true); }); it('should handle network failures gracefully', async () => { mockServer.mockGetProductNetworkError(mockBarcodes.nutella); try { await handlers.handleGetProduct(mockBarcodes.nutella); } catch (error) { expect(error).toBeInstanceOf(Error); } expect(mockServer.isDone()).toBe(true); }); it('should handle empty search results appropriately', async () => { const emptySearchResponse = { count: 0, page: 1, page_count: 0, page_size: 20, products: [], }; mockServer.mockSearchAny(emptySearchResponse); const result = await handlers.handleSearchProducts({ search: 'nonexistent_product_xyz', }); expectValidMCPResponse(result); expect(result.content[0].text).toBe('No products found matching your search criteria.'); expect(mockServer.isDone()).toBe(true); }); }); describe('Performance and Rate Limiting Scenarios', () => { it('should handle batch operations efficiently', async () => { const barcodes = [mockBarcodes.nutella, mockBarcodes.yogurt, mockBarcodes.oatDrink]; mockServer .mockGetProduct(mockBarcodes.nutella, mockNutellaProductResponse) .mockGetProduct(mockBarcodes.yogurt, mockHealthyYogurtProductResponse) .mockGetProduct(mockBarcodes.oatDrink, mockVeganProductResponse); const startTime = Date.now(); const result = await handlers.handleCompareProducts(barcodes, 'nutrition'); const duration = Date.now() - startTime; expectValidMCPResponse(result); expect(result.content[0].text).toContain('**Product Comparison (nutrition)**'); expect(result.content[0].text).toContain('Nutella'); expect(result.content[0].text).toContain('Greek Yogurt'); expect(result.content[0].text).toContain('Oat Drink'); // Should complete reasonably quickly (allowing for network delays in tests) expect(duration).toBeLessThan(5000); expect(mockServer.isDone()).toBe(true); }); it('should handle rate limit scenarios', async () => { const rateLimitedClient = new OpenFoodFactsClient({ ...config, rateLimits: { products: 1, search: 1, facets: 1 }, }); const rateLimitedHandlers = new ToolHandlers(rateLimitedClient); mockServer.mockGetProduct(mockBarcodes.nutella, mockNutellaProductResponse); // First request should succeed const result1 = await rateLimitedHandlers.handleGetProduct(mockBarcodes.nutella); expectValidMCPResponse(result1); // Second request should fail due to rate limit try { await rateLimitedHandlers.handleGetProduct(mockBarcodes.yogurt); fail('Should have thrown rate limit error'); } catch (error) { expect((error as Error).message).toContain('Rate limit exceeded'); } }); }); describe('Complex Multi-Step Workflows', () => { it('should handle comprehensive product research workflow', async () => { // Setup mocks for a complete product research session const chocolateSearchResponse = { count: 100, page: 1, page_count: 5, page_size: 20, products: [mockNutellaProduct, mockHealthyYogurtProduct], }; const healthyChocolateResponse = { count: 10, page: 1, page_count: 1, page_size: 20, products: [mockHealthyYogurtProduct], }; mockServer .mockSearch({ search: 'chocolate' }, chocolateSearchResponse) .mockSearch({ categories: 'chocolates', nutrition_grades: 'a,b' }, healthyChocolateResponse) .mockMultipleProducts([ mockBarcodes.nutella, mockBarcodes.yogurt ]); // Step 1: Initial search for chocolate products const initialSearch = await handlers.handleSearchProducts({ search: 'chocolate', }); expectValidMCPResponse(initialSearch); expect(initialSearch.content[0].text).toContain('Found 100 products'); // Step 2: Refine search for healthier options const refinedSearch = await handlers.handleSearchProducts({ categories: 'chocolates', nutrition_grades: 'a,b', }); expectValidMCPResponse(refinedSearch); expect(refinedSearch.content[0].text).toContain('Found 10 products'); // Step 3: Compare products from search results const comparison = await handlers.handleCompareProducts( [mockBarcodes.nutella, mockBarcodes.yogurt], 'nutrition' ); expectValidMCPResponse(comparison); expect(comparison.content[0].text).toContain('**Product Comparison (nutrition)**'); // Step 4: Detailed analysis of the better option const detailedAnalysis = await handlers.handleAnalyzeProduct(mockBarcodes.yogurt); expectValidMCPResponse(detailedAnalysis); expect(detailedAnalysis.content[0].text).toContain('Good nutritional quality'); expect(mockServer.isDone()).toBe(true); }); it('should handle dietary planning workflow', async () => { // Mock responses for dietary planning scenario const beverageResponse = { count: 20, page: 1, page_count: 1, page_size: 20, products: [mockVeganProduct], }; const snackResponse = { count: 15, page: 1, page_count: 1, page_size: 20, products: [mockHealthyYogurtProduct], }; mockServer .mockSearch({ categories: 'beverages' }, beverageResponse) .mockSearch({ categories: 'snacks' }, snackResponse) .mockGetProduct(mockBarcodes.oatDrink, mockVeganProductResponse) .mockGetProduct(mockBarcodes.yogurt, mockHealthyYogurtProductResponse); // Step 1: Find vegan beverages const beverageSuggestions = await handlers.handleGetProductSuggestions({ category: 'beverages', dietary_preferences: ['vegan'], max_results: 5, }); expectValidMCPResponse(beverageSuggestions); expect(beverageSuggestions.content[0].text).toContain('Product suggestions in beverages'); // Step 2: Find healthy snacks const snackSuggestions = await handlers.handleGetProductSuggestions({ category: 'snacks', dietary_preferences: ['high-protein'], min_nutriscore: 'c', max_results: 3, }); expectValidMCPResponse(snackSuggestions); expect(snackSuggestions.content[0].text).toContain('Product suggestions in snacks'); // Step 3: Compare final selections const finalComparison = await handlers.handleCompareProducts( [mockBarcodes.oatDrink, mockBarcodes.yogurt], 'nutrition' ); expectValidMCPResponse(finalComparison); expect(finalComparison.content[0].text).toContain('Oat Drink Original'); expect(finalComparison.content[0].text).toContain('Greek Yogurt Natural'); expect(mockServer.isDone()).toBe(true); }); }); describe('Edge Cases and Boundary Conditions', () => { it('should handle products with minimal data', async () => { const minimalProductResponse = { status: 1, status_verbose: 'product found', product: { code: 'minimal123', product_name: 'Minimal Product', }, }; mockServer.mockGetProduct('minimal123', minimalProductResponse as any); const result = await handlers.handleGetProduct('minimal123'); expectValidMCPResponse(result); expect(result.content[0].text).toContain('**Minimal Product**'); expect(result.content[0].text).not.toContain('**Scores:**'); expect(result.content[0].text).not.toContain('**Nutrition**'); expect(mockServer.isDone()).toBe(true); }); it('should handle products with extreme nutritional values', async () => { const extremeProduct = { status: 1, status_verbose: 'product found', product: { code: 'extreme123', product_name: 'Extreme Product', nutriments: { 'energy-kcal_100g': 999, 'fat_100g': 99.9, 'sugars_100g': 99.9, 'salt_100g': 10.0, }, nutriscore_grade: 'e', nova_group: 4, }, }; mockServer.mockGetProduct('extreme123', extremeProduct as any); const result = await handlers.handleAnalyzeProduct('extreme123'); expectValidMCPResponse(result); expect(result.content[0].text).toContain('**Nutritional Analysis: Extreme Product**'); expect(result.content[0].text).toContain('(high)'); expect(result.content[0].text).toContain('Very poor nutritional quality'); expect(mockServer.isDone()).toBe(true); }); it('should handle large search result sets', async () => { const largeSearchResponse = { count: 5000, page: 1, page_count: 250, page_size: 20, products: Array(20).fill(mockNutellaProduct), }; mockServer.mockSearchAny(largeSearchResponse); const result = await handlers.handleSearchProducts({ search: 'popular_category', }); expectValidMCPResponse(result); expect(result.content[0].text).toContain('Found 5000 products'); expect(result.content[0].text).toContain('page 1 of 250'); expect(mockServer.isDone()).toBe(true); }); }); });

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