UNPKG

ai-cant-even

Version:

A satirical AI-powered utility that's confidently wrong about basic math operations

494 lines (408 loc) 15.1 kB
import { AiCantEven, aiCantEven, Confidence, Logic, Provider } from '../index'; import { AiSdkClient } from '../ai-sdk-client'; // Mock the AI SDK modules jest.mock('@ai-sdk/anthropic', () => ({ createAnthropic: jest.fn(() => jest.fn(() => 'mocked-anthropic-model')), })); jest.mock('@ai-sdk/openai', () => ({ createOpenAI: jest.fn(() => jest.fn(() => 'mocked-openai-model')), })); jest.mock('ai', () => ({ generateText: jest.fn(), })); // Mock console.error to avoid noise in tests const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); describe('AiCantEven', () => { beforeEach(() => { jest.clearAllMocks(); delete process.env.ANTHROPIC_API_KEY; delete process.env.OPENAI_API_KEY; }); afterAll(() => { mockConsoleError.mockRestore(); }); describe('Constructor and Configuration', () => { it('should create instance with default configuration', () => { const ai = new AiCantEven(); expect(ai).toBeInstanceOf(AiCantEven); }); it('should create instance with custom configuration', () => { const config = { confidence: Confidence.SMUG, logic: Logic.PSEUDOMATH, provider: Provider.ANTHROPIC, apiKey: 'test-key', model: 'claude-3-sonnet', }; const ai = new AiCantEven(config); expect(ai).toBeInstanceOf(AiCantEven); }); it('should initialize with partial configuration', () => { const ai = new AiCantEven({ confidence: Confidence.SNARKY }); expect(ai).toBeInstanceOf(AiCantEven); }); it('should handle empty configuration object', () => { const ai = new AiCantEven({}); expect(ai).toBeInstanceOf(AiCantEven); }); }); describe('Fluent API Methods', () => { let ai: AiCantEven; beforeEach(() => { ai = new AiCantEven(); }); it('should support withConfidence method chaining', () => { const result = ai.withConfidence(Confidence.SMUG); expect(result).toBe(ai); }); it('should support withLogic method chaining', () => { const result = ai.withLogic(Logic.VISUAL); expect(result).toBe(ai); }); it('should support withModel method chaining', () => { const result = ai.withModel('gpt-4'); expect(result).toBe(ai); }); it('should support withApiKey method chaining', () => { const result = ai.withApiKey('test-key'); expect(result).toBe(ai); }); it('should support withApiKey with endpoint', () => { const result = ai.withApiKey('test-key', 'https://custom-endpoint.com'); expect(result).toBe(ai); }); it('should support method chaining combination', () => { const result = ai .withConfidence(Confidence.OVERTHINK) .withLogic(Logic.NONSEQUITUR) .withModel('claude-3-haiku') .withApiKey('test-key'); expect(result).toBe(ai); }); }); describe('Math Operation Methods - Without API Key (Fallback)', () => { let ai: AiCantEven; beforeEach(() => { ai = new AiCantEven(); }); it('should return fallback response for isEven', async () => { const response = await ai.isEven(4); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isOdd', async () => { const response = await ai.isOdd(3); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isGreaterThan', async () => { const response = await ai.isGreaterThan(10, 5); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isLessThan', async () => { const response = await ai.isLessThan(3, 8); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for areEqual', async () => { const response = await ai.areEqual(5, 5); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isPrime', async () => { const response = await ai.isPrime(17); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isPositive', async () => { const response = await ai.isPositive(-3); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isDivisibleBy', async () => { const response = await ai.isDivisibleBy(15, 3); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isNumber', async () => { const response = await ai.isNumber('42'); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); it('should return fallback response for isInteger', async () => { const response = await ai.isInteger(3.14); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); }); }); describe('Math Operation Methods - With API Key', () => { let ai: AiCantEven; const { generateText } = require('ai'); beforeEach(() => { ai = new AiCantEven({ provider: Provider.ANTHROPIC, apiKey: 'test-key', }); generateText.mockResolvedValue({ text: 'Mocked AI response' }); }); it('should call AI SDK for isEven with API key', async () => { const response = await ai.isEven(4); expect(generateText).toHaveBeenCalled(); expect(response).toBe('Mocked AI response'); }); it('should call AI SDK for operations with comparison values', async () => { await ai.isGreaterThan(10, 5); expect(generateText).toHaveBeenCalledWith( expect.objectContaining({ model: 'mocked-anthropic-model', messages: expect.arrayContaining([ expect.objectContaining({ role: 'system' }), expect.objectContaining({ role: 'user', content: expect.stringContaining('Is 10 greater than 5?') }), ]), }) ); }); it('should reset temporary settings after each operation', async () => { await ai.withConfidence(Confidence.SMUG).isEven(4); await ai.isOdd(3); expect(generateText).toHaveBeenCalledTimes(2); const [firstCall, secondCall] = generateText.mock.calls; expect(firstCall[0].messages[1].content).toContain('SMUG'); expect(secondCall[0].messages[1].content).toContain('OVERWHELMED'); }); it('should use temporary model for single operation', async () => { await ai.withModel('custom-model').isEven(4); expect(generateText).toHaveBeenCalled(); }); }); describe('Error Handling', () => { let ai: AiCantEven; const { generateText } = require('ai'); beforeEach(() => { ai = new AiCantEven({ provider: Provider.ANTHROPIC, apiKey: 'test-key', }); }); it('should handle API errors gracefully', async () => { generateText.mockRejectedValue(new Error('API Error')); const response = await ai.isEven(4); expect(typeof response).toBe('string'); expect(response.length).toBeGreaterThan(0); expect(mockConsoleError).toHaveBeenCalledWith( 'Error calling anthropic API:', expect.any(Error) ); }); it('should return fallback response on API failure', async () => { generateText.mockRejectedValue(new Error('Network error')); const response = await ai.isPrime(17); expect(response).toMatch(/can't even|Error 418|neural networks|existential|Math\.exe/); }); }); describe('aiCantEven Factory Function', () => { it('should create instance with no config', () => { const ai = aiCantEven(); expect(ai).toBeInstanceOf(AiCantEven); }); it('should create instance with provider and API key', () => { const ai = aiCantEven({ provider: Provider.ANTHROPIC, apiKey: 'test-key', }); expect(ai).toBeInstanceOf(AiCantEven); }); it('should use ANTHROPIC_API_KEY environment variable', () => { process.env.ANTHROPIC_API_KEY = 'env-anthropic-key'; const ai = aiCantEven(); expect(ai).toBeInstanceOf(AiCantEven); }); it('should use OPENAI_API_KEY environment variable', () => { process.env.OPENAI_API_KEY = 'env-openai-key'; const ai = aiCantEven(); expect(ai).toBeInstanceOf(AiCantEven); }); it('should prioritize ANTHROPIC_API_KEY over OPENAI_API_KEY', () => { process.env.ANTHROPIC_API_KEY = 'env-anthropic-key'; process.env.OPENAI_API_KEY = 'env-openai-key'; const ai = aiCantEven(); expect(ai).toBeInstanceOf(AiCantEven); }); it('should override environment variables with explicit config', () => { process.env.ANTHROPIC_API_KEY = 'env-key'; const ai = aiCantEven({ provider: Provider.OPENAI, apiKey: 'explicit-key', }); expect(ai).toBeInstanceOf(AiCantEven); }); }); describe('Type Exports', () => { it('should export Confidence enum', () => { expect(Confidence.OVERWHELMED).toBe('OVERWHELMED'); expect(Confidence.OVERTHINK).toBe('OVERTHINK'); expect(Confidence.SMUG).toBe('SMUG'); expect(Confidence.SNARKY).toBe('SNARKY'); }); it('should export Logic enum', () => { expect(Logic.SIMPLE).toBe('SIMPLE'); expect(Logic.NONSEQUITUR).toBe('NONSEQUITUR'); expect(Logic.PSEUDOMATH).toBe('PSEUDOMATH'); expect(Logic.VISUAL).toBe('VISUAL'); }); it('should export Provider enum', () => { expect(Provider.ANTHROPIC).toBe('anthropic'); expect(Provider.OPENAI).toBe('openai'); }); }); }); describe('AiSdkClient', () => { const { generateText } = require('ai'); const { createAnthropic } = require('@ai-sdk/anthropic'); const { createOpenAI } = require('@ai-sdk/openai'); beforeEach(() => { jest.clearAllMocks(); }); describe('Constructor', () => { it('should initialize with Anthropic provider', () => { new AiSdkClient(Provider.ANTHROPIC, 'test-key'); expect(createAnthropic).toHaveBeenCalledWith({ apiKey: 'test-key', }); }); it('should initialize with OpenAI provider', () => { new AiSdkClient(Provider.OPENAI, 'test-key'); expect(createOpenAI).toHaveBeenCalledWith({ apiKey: 'test-key', }); }); it('should initialize with custom endpoint', () => { new AiSdkClient( Provider.ANTHROPIC, 'test-key', 'https://custom-endpoint.com' ); expect(createAnthropic).toHaveBeenCalledWith({ apiKey: 'test-key', baseURL: 'https://custom-endpoint.com', }); }); it('should initialize with custom model', () => { new AiSdkClient( Provider.ANTHROPIC, 'test-key', undefined, 'claude-3-opus' ); expect(createAnthropic).toHaveBeenCalled(); }); it('should throw error for unsupported provider', () => { expect(() => { new AiSdkClient('unsupported' as Provider, 'test-key'); }).toThrow('Unsupported provider: unsupported'); }); }); describe('generateResponse', () => { let client: AiSdkClient; beforeEach(() => { client = new AiSdkClient(Provider.ANTHROPIC, 'test-key'); }); it('should generate response successfully', async () => { generateText.mockResolvedValue({ text: 'AI response' }); const params = { confidence: Confidence.SMUG, logic: Logic.SIMPLE, operation: 'isEven', value: 4, }; const response = await client.generateResponse(params); expect(generateText).toHaveBeenCalledWith({ model: 'mocked-anthropic-model', messages: [ { role: 'system', content: expect.stringContaining('You are an AI') }, { role: 'user', content: expect.stringContaining('Is 4 even?') }, ], temperature: 0.9, maxTokens: 100, }); expect(response).toEqual({ text: 'AI response', success: true, }); }); it('should handle comparison operations', async () => { generateText.mockResolvedValue({ text: 'Comparison response' }); const params = { confidence: Confidence.OVERTHINK, logic: Logic.VISUAL, operation: 'isGreaterThan', value: 10, comparisonValue: 5, }; await client.generateResponse(params); expect(generateText).toHaveBeenCalledWith( expect.objectContaining({ messages: expect.arrayContaining([ expect.objectContaining({ role: 'user', content: expect.stringContaining('Is 10 greater than 5?'), }), ]), }) ); }); it('should handle API errors with fallback', async () => { generateText.mockRejectedValue(new Error('API Error')); const params = { confidence: Confidence.SNARKY, logic: Logic.PSEUDOMATH, operation: 'isPrime', value: 17, }; const response = await client.generateResponse(params); expect(response.success).toBe(false); expect(typeof response.text).toBe('string'); expect(response.text.length).toBeGreaterThan(0); }); it('should include confidence and logic in prompt', async () => { generateText.mockResolvedValue({ text: 'Response' }); const params = { confidence: Confidence.OVERWHELMED, logic: Logic.NONSEQUITUR, operation: 'isOdd', value: 7, }; await client.generateResponse(params); const userMessage = generateText.mock.calls[0][0].messages[1].content; expect(userMessage).toContain('OVERWHELMED'); expect(userMessage).toContain('NONSEQUITUR'); }); it('should handle unknown operations', async () => { generateText.mockResolvedValue({ text: 'Unknown operation response' }); const params = { confidence: Confidence.SMUG, logic: Logic.SIMPLE, operation: 'unknownOperation', value: 42, }; await client.generateResponse(params); const userMessage = generateText.mock.calls[0][0].messages[1].content; expect(userMessage).toContain('What can you tell me about 42?'); }); it('should trim response text', async () => { generateText.mockResolvedValue({ text: ' Trimmed response ' }); const params = { confidence: Confidence.SNARKY, logic: Logic.VISUAL, operation: 'isInteger', value: 3.14, }; const response = await client.generateResponse(params); expect(response.text).toBe('Trimmed response'); }); }); });