Skip to main content
Glama

Open Food Facts MCP Server

by caleb-conner
validation.test.ts19.4 kB
import { describe, it, expect } from '@jest/globals'; import { ProductSchema, ProductResponseSchema, SearchResponseSchema, OpenFoodFactsConfig } from '../src/types.js'; describe('Schema Validation', () => { describe('ProductSchema', () => { it('should validate a complete product', () => { const validProduct = { code: '3017620422003', product_name: 'Nutella', brands: 'Ferrero', categories: 'Sweet spreads, Chocolate spreads', ingredients_text: 'Sugar, palm oil, hazelnuts', nutriments: { 'energy-kcal_100g': 539, 'fat_100g': 30.9, 'proteins_100g': 6.3, }, nutriscore_grade: 'e', nova_group: 4, ecoscore_grade: 'd', image_url: 'https://example.com/image.jpg', image_front_url: 'https://example.com/front.jpg', quantity: '400g', packaging: 'Plastic jar', labels: 'No palm oil', countries: 'France', manufacturing_places: 'France', stores: 'Carrefour', created_datetime: '2012-08-24T14:56:20Z', last_modified_datetime: 1699185045, }; const result = ProductSchema.safeParse(validProduct); expect(result.success).toBe(true); if (result.success) { expect(result.data.code).toBe('3017620422003'); expect(result.data.product_name).toBe('Nutella'); } }); it('should validate a minimal product with only code', () => { const minimalProduct = { code: '123456789', }; const result = ProductSchema.safeParse(minimalProduct); expect(result.success).toBe(true); }); it('should reject product without code', () => { const invalidProduct = { product_name: 'Test Product', brands: 'Test Brand', }; const result = ProductSchema.safeParse(invalidProduct); expect(result.success).toBe(false); }); it('should reject product with invalid code type', () => { const invalidProduct = { code: 123456789, // Should be string product_name: 'Test Product', }; const result = ProductSchema.safeParse(invalidProduct); expect(result.success).toBe(false); }); it('should handle numeric nova_group', () => { const productWithNumericNova = { code: '123456789', nova_group: 4, }; const result = ProductSchema.safeParse(productWithNumericNova); expect(result.success).toBe(true); if (result.success) { expect(result.data.nova_group).toBe(4); } }); it('should handle string nova_group', () => { const productWithStringNova = { code: '123456789', nova_group: '4', }; const result = ProductSchema.safeParse(productWithStringNova); expect(result.success).toBe(true); if (result.success) { expect(result.data.nova_group).toBe('4'); } }); it('should handle numeric datetime values', () => { const productWithNumericDates = { code: '123456789', created_datetime: 1692882980, last_modified_datetime: 1699185045, }; const result = ProductSchema.safeParse(productWithNumericDates); expect(result.success).toBe(true); }); it('should handle string datetime values', () => { const productWithStringDates = { code: '123456789', created_datetime: '2012-08-24T14:56:20Z', last_modified_datetime: '2023-11-15T10:30:45Z', }; const result = ProductSchema.safeParse(productWithStringDates); expect(result.success).toBe(true); }); it('should validate nutriments with mixed types', () => { const productWithMixedNutriments = { code: '123456789', nutriments: { 'energy-kcal_100g': 539, // number 'fat_100g': '30.9', // string 'proteins_100g': 6.3, // number 'salt_100g': '0.107', // string }, }; const result = ProductSchema.safeParse(productWithMixedNutriments); expect(result.success).toBe(true); if (result.success) { expect(result.data.nutriments?.['energy-kcal_100g']).toBe(539); expect(result.data.nutriments?.['fat_100g']).toBe('30.9'); } }); it('should reject invalid nutriment values', () => { const productWithInvalidNutriments = { code: '123456789', nutriments: { 'energy-kcal_100g': true, // Invalid type 'fat_100g': 30.9, }, }; const result = ProductSchema.safeParse(productWithInvalidNutriments); expect(result.success).toBe(false); }); }); describe('ProductResponseSchema', () => { it('should validate successful product response', () => { const validResponse = { status: 1, status_verbose: 'product found', product: { code: '3017620422003', product_name: 'Nutella', }, }; const result = ProductResponseSchema.safeParse(validResponse); expect(result.success).toBe(true); if (result.success) { expect(result.data.status).toBe(1); expect(result.data.product?.code).toBe('3017620422003'); } }); it('should validate product not found response', () => { const notFoundResponse = { status: 0, status_verbose: 'product not found', }; const result = ProductResponseSchema.safeParse(notFoundResponse); expect(result.success).toBe(true); if (result.success) { expect(result.data.status).toBe(0); expect(result.data.product).toBeUndefined(); } }); it('should reject response without status', () => { const invalidResponse = { status_verbose: 'product found', product: { code: '123456789' }, }; const result = ProductResponseSchema.safeParse(invalidResponse); expect(result.success).toBe(false); }); it('should reject response without status_verbose', () => { const invalidResponse = { status: 1, product: { code: '123456789' }, }; const result = ProductResponseSchema.safeParse(invalidResponse); expect(result.success).toBe(false); }); it('should reject response with invalid status type', () => { const invalidResponse = { status: '1', // Should be number status_verbose: 'product found', product: { code: '123456789' }, }; const result = ProductResponseSchema.safeParse(invalidResponse); expect(result.success).toBe(false); }); it('should reject response with invalid product', () => { const invalidResponse = { status: 1, status_verbose: 'product found', product: { // Missing required 'code' field product_name: 'Test Product', }, }; const result = ProductResponseSchema.safeParse(invalidResponse); expect(result.success).toBe(false); }); }); describe('SearchResponseSchema', () => { it('should validate complete search response', () => { const validSearchResponse = { count: 150, page: 1, page_count: 8, page_size: 20, products: [ { code: '3017620422003', product_name: 'Nutella', }, { code: '3229820787015', product_name: 'Greek Yogurt', }, ], }; const result = SearchResponseSchema.safeParse(validSearchResponse); expect(result.success).toBe(true); if (result.success) { expect(result.data.count).toBe(150); expect(result.data.products).toHaveLength(2); } }); it('should validate empty search response', () => { const emptySearchResponse = { count: 0, page: 1, page_count: 0, page_size: 20, products: [], }; const result = SearchResponseSchema.safeParse(emptySearchResponse); expect(result.success).toBe(true); if (result.success) { expect(result.data.count).toBe(0); expect(result.data.products).toHaveLength(0); } }); it('should reject search response with missing required fields', () => { const invalidSearchResponse = { count: 150, // Missing page, page_count, page_size products: [], }; const result = SearchResponseSchema.safeParse(invalidSearchResponse); expect(result.success).toBe(false); }); it('should reject search response with invalid field types', () => { const invalidSearchResponse = { count: '150', // Should be number page: 1, page_count: 8, page_size: 20, products: [], }; const result = SearchResponseSchema.safeParse(invalidSearchResponse); expect(result.success).toBe(false); }); it('should reject search response with non-array products', () => { const invalidSearchResponse = { count: 150, page: 1, page_count: 8, page_size: 20, products: 'not-an-array', }; const result = SearchResponseSchema.safeParse(invalidSearchResponse); expect(result.success).toBe(false); }); it('should reject search response with invalid products', () => { const invalidSearchResponse = { count: 1, page: 1, page_count: 1, page_size: 20, products: [ { // Missing required 'code' field product_name: 'Invalid Product', }, ], }; const result = SearchResponseSchema.safeParse(invalidSearchResponse); expect(result.success).toBe(false); }); it('should validate search response with products containing optional fields', () => { const searchResponseWithOptionals = { count: 1, page: 1, page_count: 1, page_size: 20, products: [ { code: '3017620422003', product_name: 'Nutella', brands: 'Ferrero', nutriscore_grade: 'e', nova_group: 4, }, ], }; const result = SearchResponseSchema.safeParse(searchResponseWithOptionals); expect(result.success).toBe(true); if (result.success) { expect(result.data.products[0].nutriscore_grade).toBe('e'); expect(result.data.products[0].nova_group).toBe(4); } }); }); describe('Edge Cases and Boundary Values', () => { it('should handle extremely long strings', () => { const productWithLongStrings = { code: '123456789', product_name: 'A'.repeat(1000), ingredients_text: 'B'.repeat(10000), categories: 'C'.repeat(500), }; const result = ProductSchema.safeParse(productWithLongStrings); expect(result.success).toBe(true); }); it('should handle empty strings', () => { const productWithEmptyStrings = { code: '123456789', product_name: '', brands: '', categories: '', }; const result = ProductSchema.safeParse(productWithEmptyStrings); expect(result.success).toBe(true); }); it('should handle very large numbers in nutriments', () => { const productWithLargeNutriments = { code: '123456789', nutriments: { 'energy-kcal_100g': 999999, 'fat_100g': 100.999, 'proteins_100g': Number.MAX_SAFE_INTEGER, }, }; const result = ProductSchema.safeParse(productWithLargeNutriments); expect(result.success).toBe(true); }); it('should handle zero and negative numbers in nutriments', () => { const productWithZeroNutriments = { code: '123456789', nutriments: { 'energy-kcal_100g': 0, 'fat_100g': -1, // Negative values might exist in real data 'fiber_100g': 0, }, }; const result = ProductSchema.safeParse(productWithZeroNutriments); expect(result.success).toBe(true); }); it('should handle floating point precision issues', () => { const productWithPrecisionNumbers = { code: '123456789', nutriments: { 'fat_100g': 0.1 + 0.2, // 0.30000000000000004 'proteins_100g': 1.23456789, 'salt_100g': 0.000001, }, }; const result = ProductSchema.safeParse(productWithPrecisionNumbers); expect(result.success).toBe(true); }); it('should handle special characters and Unicode', () => { const productWithUnicode = { code: '123456789', product_name: 'Café au lait 🥛 ñoño', brands: 'Société Générale®', ingredients_text: 'Ingrédients: café, lait, sucre β-carotène', countries: '中国, 日本, Россия', }; const result = ProductSchema.safeParse(productWithUnicode); expect(result.success).toBe(true); }); }); describe('Configuration Validation', () => { it('should validate valid OpenFoodFactsConfig', () => { const validConfig: OpenFoodFactsConfig = { baseUrl: 'https://world.openfoodfacts.net', userAgent: 'OpenFoodFactsMCP/1.0 (test)', rateLimits: { products: 100, search: 10, facets: 2, }, }; // Since there's no explicit schema for config, we test the structure expect(validConfig.baseUrl).toMatch(/^https?:\/\//); expect(validConfig.userAgent).toContain('OpenFoodFactsMCP'); expect(validConfig.rateLimits.products).toBeGreaterThan(0); expect(validConfig.rateLimits.search).toBeGreaterThan(0); expect(validConfig.rateLimits.facets).toBeGreaterThan(0); }); it('should identify invalid URLs in config', () => { const configWithInvalidUrl = { baseUrl: 'not-a-url', userAgent: 'OpenFoodFactsMCP/1.0 (test)', rateLimits: { products: 100, search: 10, facets: 2 }, }; expect(configWithInvalidUrl.baseUrl).not.toMatch(/^https?:\/\//); }); it('should identify invalid rate limits', () => { const configWithInvalidRates = { baseUrl: 'https://world.openfoodfacts.net', userAgent: 'OpenFoodFactsMCP/1.0 (test)', rateLimits: { products: 0, // Invalid: should be positive search: -5, // Invalid: should be positive facets: 2, }, }; expect(configWithInvalidRates.rateLimits.products).not.toBeGreaterThan(0); expect(configWithInvalidRates.rateLimits.search).not.toBeGreaterThan(0); }); }); describe('Type Coercion and Transformation', () => { it('should handle numeric strings that could be coerced', () => { const productWithNumericStrings = { code: '123456789', nova_group: '4', // String that represents number created_datetime: '1692882980', // String timestamp nutriments: { 'energy-kcal_100g': '539', // String number }, }; const result = ProductSchema.safeParse(productWithNumericStrings); expect(result.success).toBe(true); if (result.success) { expect(result.data.nova_group).toBe('4'); // Should remain string expect(result.data.nutriments?.['energy-kcal_100g']).toBe('539'); // Should remain string } }); it('should not coerce invalid string types', () => { const productWithInvalidTypes = { code: 123456789, // Number instead of string product_name: true, // Boolean instead of string }; const result = ProductSchema.safeParse(productWithInvalidTypes); expect(result.success).toBe(false); }); }); describe('Real-world API Response Simulation', () => { it('should validate typical OFF API response structure', () => { // Simulate a real Open Food Facts API response const realWorldResponse = { status: 1, status_verbose: 'product found', product: { code: '3017620422003', url: 'https://world.openfoodfacts.org/product/3017620422003/nutella-ferrero', creator: 'openfoodfacts-contributors', created_t: 1345821380, created_datetime: '2012-08-24T14:56:20Z', last_modified_t: 1699185045, last_modified_datetime: 1699185045, product_name: 'Nutella', generic_name: 'Pâte à tartiner aux noisettes et au cacao', quantity: '400 g', packaging: 'Plastique, Pot, Couvercle, Carton', brands: 'Ferrero', categories: 'Aliments et boissons à base de végétaux, Aliments d\'origine végétale', origins: 'France', manufacturing_places: 'France', labels: '', emb_codes: '', link: '', purchase_places: 'France', stores: 'Magasins U, Carrefour, Leclerc', countries: 'France', ingredients_text: 'Sucre, huile de palme, NOISETTES 13%, lait écrémé en poudre 8,7%', allergens: 'Lait, Fruits à coque', traces: 'Gluten', serving_size: '', no_nutriments: '0', additives_n: 3, additives_tags: ['en:e322', 'en:e476', 'en:vanillin'], nutriscore_score: 26, nutriscore_grade: 'e', nova_group: 4, ecoscore_score: 27, ecoscore_grade: 'd', nutriments: { 'energy-kcal_100g': 539, 'energy_100g': 2255, 'fat_100g': 30.9, 'saturated-fat_100g': 10.6, 'carbohydrates_100g': 57.5, 'sugars_100g': 56.3, 'fiber_100g': 0, 'proteins_100g': 6.3, 'salt_100g': 0.107, 'sodium_100g': 0.043, 'calcium_100g': 0.16, 'iron_100g': 4.2, }, image_url: 'https://images.openfoodfacts.org/images/products/301/762/042/2003/front_fr.4.400.jpg', image_front_url: 'https://images.openfoodfacts.org/images/products/301/762/042/2003/front_fr.4.400.jpg', image_ingredients_url: 'https://images.openfoodfacts.org/images/products/301/762/042/2003/ingredients_fr.7.400.jpg', image_nutrition_url: 'https://images.openfoodfacts.org/images/products/301/762/042/2003/nutrition_fr.8.400.jpg', }, }; const result = ProductResponseSchema.safeParse(realWorldResponse); expect(result.success).toBe(true); }); it('should validate typical search API response', () => { const realWorldSearchResponse = { count: 1642, page: 1, page_count: 83, page_size: 20, skip: 0, products: [ { code: '3017620422003', product_name: 'Nutella', brands: 'Ferrero', categories: 'Sweet spreads', nutriscore_grade: 'e', nova_group: '4', image_url: 'https://images.openfoodfacts.org/images/products/301/762/042/2003/front_fr.4.400.jpg', }, { code: '8000500037676', product_name: 'Nutella B-ready', brands: 'Ferrero', categories: 'Snacks', nutriscore_grade: 'e', nova_group: '4', }, ], }; const result = SearchResponseSchema.safeParse(realWorldSearchResponse); expect(result.success).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