Skip to main content
Glama
contract-tests.test.ts11 kB
/** * Contract Tests (Fase 3) * * Validates that tools conform to their published contracts/schemas. * Ensures backward compatibility and proper error responses. */ import { describe, it, expect } from '@jest/globals'; import { ValidationError, ErrorCode } from '../../src/types/index'; // ============================================================================== // SCHEMA VALIDATION TESTS // ============================================================================== describe('Tool Contract Tests', () => { describe('bereken_hypotheek_starter', () => { it('should accept minimal valid input', () => { const input = { inkomen_aanvrager: 50000, geboortedatum_aanvrager: '1990-05-15', heeft_partner: false, verplichtingen_pm: 0 }; // This should not throw expect(() => validateInput(input)).not.toThrow(); }); it('should accept input with partner', () => { const input = { inkomen_aanvrager: 50000, geboortedatum_aanvrager: '1990-05-15', heeft_partner: true, inkomen_partner: 40000, geboortedatum_partner: '1992-08-20', verplichtingen_pm: 250 }; expect(() => validateInput(input)).not.toThrow(); }); it('should accept optional session_id', () => { const input = { inkomen_aanvrager: 50000, geboortedatum_aanvrager: '1990-05-15', heeft_partner: false, verplichtingen_pm: 0, session_id: '550e8400-e29b-41d4-a716-446655440000' }; expect(() => validateInput(input)).not.toThrow(); }); it('should reject invalid inkomen_aanvrager type', () => { const input = { inkomen_aanvrager: '50000', // String instead of number geboortedatum_aanvrager: '1990-05-15', heeft_partner: false }; expect(() => validateInput(input)).toThrow(); }); it('should reject missing required fields', () => { const input = { inkomen_aanvrager: 50000 // Missing: geboortedatum_aanvrager, heeft_partner }; expect(() => validateInput(input)).toThrow(); }); }); describe('bereken_hypotheek_doorstromer', () => { it('should accept valid doorstromer input', () => { const input = { inkomen_aanvrager: 65000, geboortedatum_aanvrager: '1985-03-15', heeft_partner: false, verplichtingen_pm: 0, waarde_huidige_woning: 350000, bestaande_hypotheek: { leningdelen: [{ huidige_schuld: 180000, huidige_rente: 0.032, resterende_looptijd_in_maanden: 240, rentevasteperiode_maanden: 120, hypotheekvorm: 'annuiteit' }] } }; expect(() => validateDoorstromerInput(input)).not.toThrow(); }); it('should accept multiple leningdelen', () => { const input = { inkomen_aanvrager: 70000, geboortedatum_aanvrager: '1980-01-01', heeft_partner: false, verplichtingen_pm: 0, waarde_huidige_woning: 400000, bestaande_hypotheek: { leningdelen: [ { huidige_schuld: 150000, huidige_rente: 0.025, resterende_looptijd_in_maanden: 180, rentevasteperiode_maanden: 60, hypotheekvorm: 'annuiteit' }, { huidige_schuld: 50000, huidige_rente: 0.04, resterende_looptijd_in_maanden: 120, rentevasteperiode_maanden: 0, hypotheekvorm: 'aflossingsvrij' } ] } }; expect(() => validateDoorstromerInput(input)).not.toThrow(); }); it('should reject missing waarde_huidige_woning', () => { const input = { inkomen_aanvrager: 65000, geboortedatum_aanvrager: '1985-03-15', heeft_partner: false, verplichtingen_pm: 0, // Missing: waarde_huidige_woning bestaande_hypotheek: { leningdelen: [{ huidige_schuld: 180000, huidige_rente: 0.032, resterende_looptijd_in_maanden: 240, rentevasteperiode_maanden: 120, hypotheekvorm: 'annuiteit' }] } }; expect(() => validateDoorstromerInput(input)).toThrow(); }); it('should reject invalid hypotheekvorm', () => { const input = { inkomen_aanvrager: 65000, geboortedatum_aanvrager: '1985-03-15', heeft_partner: false, verplichtingen_pm: 0, waarde_huidige_woning: 350000, bestaande_hypotheek: { leningdelen: [{ huidige_schuld: 180000, huidige_rente: 0.032, resterende_looptijd_in_maanden: 240, rentevasteperiode_maanden: 120, hypotheekvorm: 'invalid_type' // Invalid! }] } }; expect(() => validateDoorstromerInput(input)).toThrow(); }); }); }); // ============================================================================== // ERROR RESPONSE TESTS // ============================================================================== describe('Error Response Contracts', () => { it('should return structured error for validation failure', () => { const error = new ValidationError( ErrorCode.AGE_OUT_OF_RANGE, 'Leeftijd moet tussen 18 en 75 zijn', 'geboortedatum_aanvrager', '2010-01-01' ); const structured = error.toStructured('test-session-id'); expect(structured).toHaveProperty('code'); expect(structured).toHaveProperty('message'); expect(structured).toHaveProperty('field'); expect(structured).toHaveProperty('correlation_id'); expect(structured.code).toBe(ErrorCode.AGE_OUT_OF_RANGE); }); it('should include retry_after_ms for rate limit errors', () => { // This would come from actual tool call error const errorResponse = { code: ErrorCode.API_RATE_LIMIT, message: 'Rate limit exceeded', retry_after_ms: 30000 }; expect(errorResponse.retry_after_ms).toBeGreaterThan(0); }); it('should include field information for validation errors', () => { const error = new ValidationError( ErrorCode.INCOME_OUT_OF_RANGE, 'Inkomen te hoog', 'inkomen_aanvrager', 1500000 ); const structured = error.toStructured(); expect(structured.field).toBe('inkomen_aanvrager'); expect(structured.details).toHaveProperty('value'); }); }); // ============================================================================== // BACKWARD COMPATIBILITY TESTS // ============================================================================== describe('Backward Compatibility', () => { it('should still accept hypotheekvorm as string (not just enum)', () => { // v3.0 accepted strings, v4.0 should still accept for compatibility const input = { huidige_schuld: 180000, huidige_rente: 0.032, resterende_looptijd_in_maanden: 240, rentevasteperiode_maanden: 120, hypotheekvorm: 'annuiteit' // String, not Hypotheekvorm enum }; // Should not throw expect(() => validateLeningdeel(input)).not.toThrow(); }); it('should accept both "annuiteit" and "annuïteit" spellings', () => { // Common user mistake - we should handle gracefully const input1 = { hypotheekvorm: 'annuiteit' }; const input2 = { hypotheekvorm: 'annuïteit' }; // Both should work (second gets normalized) expect(() => normalizeHypotheekvorm(input1.hypotheekvorm)).not.toThrow(); // Note: annuïteit should throw in v4 strict mode, but we document the breaking change }); }); // ============================================================================== // RESPONSE SCHEMA TESTS // ============================================================================== describe('Response Schema Validation', () => { it('should return consistent structure for maximaal berekening', () => { const mockResponse = { resultaat: [ { maximaal_bedrag: 220000, bruto_maandlasten_nieuwe_lening: 1015, resultaat_omschrijving: 'Met NHG', gebruikte_hypotheekgegevens: { nhg_toegepast: true, energielabel: 'B', opzet_nieuwe_hypotheek: [] } } ] }; // Validate structure expect(mockResponse).toHaveProperty('resultaat'); expect(Array.isArray(mockResponse.resultaat)).toBe(true); expect(mockResponse.resultaat[0]).toHaveProperty('maximaal_bedrag'); expect(mockResponse.resultaat[0]).toHaveProperty('bruto_maandlasten_nieuwe_lening'); }); it('should return consistent structure for opzet berekening', () => { const mockResponse = { resultaat: { Benodigd_bedrag: { Woning_koopsom: 300000, Kosten: 15000 }, Financiering: { Hypotheek: 300000, Eigen_geld: 15000 }, bruto_maandlasten_nieuwe_lening: 1385 } }; // Validate structure expect(mockResponse).toHaveProperty('resultaat'); expect(mockResponse.resultaat).toHaveProperty('Benodigd_bedrag'); expect(mockResponse.resultaat).toHaveProperty('Financiering'); }); }); // ============================================================================== // HELPER FUNCTIONS (would import from actual code) // ============================================================================== function validateInput(input: any): void { // Simplified validation - actual code in validation/schemas.ts if (typeof input.inkomen_aanvrager !== 'number') { throw new Error('inkomen_aanvrager must be number'); } if (typeof input.geboortedatum_aanvrager !== 'string') { throw new Error('geboortedatum_aanvrager must be string'); } if (typeof input.heeft_partner !== 'boolean') { throw new Error('heeft_partner must be boolean'); } } function validateDoorstromerInput(input: any): void { validateInput(input); if (typeof input.waarde_huidige_woning !== 'number') { throw new Error('waarde_huidige_woning required'); } if (!input.bestaande_hypotheek || !input.bestaande_hypotheek.leningdelen) { throw new Error('bestaande_hypotheek.leningdelen required'); } // Validate each leningdeel using the same logic as validateLeningdeel input.bestaande_hypotheek.leningdelen.forEach((ld: any) => validateLeningdeel(ld)); } function validateLeningdeel(input: any): void { const validTypes = ['annuiteit', 'lineair', 'aflossingsvrij']; if (!validTypes.includes(input.hypotheekvorm)) { throw new Error('Invalid hypotheekvorm'); } } function normalizeHypotheekvorm(value: string): string { // Would call actual normalization function return value.toLowerCase().replace('ï', 'i'); }

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/pace8/Test'

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