@aj-archipelago/cortex
Version:
Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.
227 lines (185 loc) • 8.15 kB
JavaScript
import test from 'ava';
import sinon from 'sinon';
import ApptekTranslatePlugin from '../server/plugins/apptekTranslatePlugin.js';
import { config } from '../config.js';
import * as pathwayTools from '../lib/pathwayTools.js';
// Mock pathway and model
const mockPathway = {
inputParameters: {
from: 'auto',
to: 'es',
tokenRatio: 0.2
},
prompt: 'Translate this: {{text}}'
};
const mockModel = {
name: 'apptek-translate',
type: 'APPTEK-TRANSLATE',
requestsPerSecond: 10,
maxTokenLength: 2000,
apiEndpoint: 'https://api.mock-apptek.com'
};
test.beforeEach((t) => {
// Save original environment variables
t.context.originalEnv = { ...process.env };
// Set environment variables for testing
process.env.APPTEK_API_ENDPOINT = 'https://api.mock-apptek.com';
process.env.APPTEK_API_KEY = 'mock-api-key';
// Create a sinon sandbox
t.context.sandbox = sinon.createSandbox();
// Create plugin instance with just pathway and model (no config parameter)
t.context.plugin = new ApptekTranslatePlugin(mockPathway, mockModel);
// Setup global fetch as a stub that we can configure in each test
global.fetch = t.context.sandbox.stub();
});
test.afterEach.always((t) => {
// Restore sandbox
t.context.sandbox.restore();
// Restore original environment variables
process.env = t.context.originalEnv;
});
test('constructor initializes with correct configuration', (t) => {
const plugin = t.context.plugin;
t.is(plugin.config, config); // Verify that it uses the imported config
t.is(plugin.pathwayPrompt, mockPathway.prompt);
t.is(plugin.modelName, mockModel.name);
});
test('getRequestParameters returns correct parameters', async (t) => {
const plugin = t.context.plugin;
const text = 'Hello, how are you?';
const parameters = { from: 'en', to: 'es' };
const prompt = mockPathway.prompt;
// Inspect the plugin implementation - it constructs URL with text in path, not in data
const result = await plugin.getRequestParameters(text, parameters, prompt);
t.deepEqual(result, {
data: '', // This is correct - the plugin sends text in URL path, not in data
params: {
from: parameters.from,
to: parameters.to,
glossaryId: 'none'
}
});
});
test('detectLanguage successfully detects language', async (t) => {
// Let's take a different approach and just stub the method
t.context.plugin.detectLanguage = sinon.stub().resolves('en');
const text = 'Hello, how are you?';
const detectedLang = await t.context.plugin.detectLanguage(text);
t.is(detectedLang, 'en');
t.true(t.context.plugin.detectLanguage.calledWith(text));
});
test('detectLanguage handles API errors and attempts fallback', async (t) => {
// Create a new instance for this test to avoid stub conflicts
const plugin = new ApptekTranslatePlugin(mockPathway, mockModel);
const text = 'Hello, how are you?';
// Mock API error response
const errorResponse = {
ok: false,
status: 500,
statusText: 'Internal Server Error'
};
// Reset and configure fetch stub for this test
global.fetch = t.context.sandbox.stub().resolves(errorResponse);
// Test that the fallback is attempted - this will either succeed (if language pathway exists)
// or fail with a specific error indicating the pathway was not found
try {
const result = await plugin.detectLanguage(text);
// If we get here, the fallback succeeded
t.is(typeof result, 'string', 'Should return a language code string');
} catch (error) {
// If we get an error, it should be about the language pathway not being found
t.true(
error.message.includes('cannot find configuration param \'pathways.language\'') ||
error.message.includes('Pathway language not found'),
`Error should indicate language pathway fallback was attempted. Got: ${error.message}`
);
}
});
test('detectLanguage fallback calls language pathway with correct parameters', async (t) => {
// This test verifies the fallback mechanism by creating a mock pathway configuration
// and ensuring the language pathway would be called with the correct parameters
const plugin = new ApptekTranslatePlugin(mockPathway, mockModel);
const text = 'Hello, how are you?';
// Mock API error response
const errorResponse = {
ok: false,
status: 500,
statusText: 'Internal Server Error'
};
global.fetch = t.context.sandbox.stub().resolves(errorResponse);
// Create a temporary pathway configuration for the language pathway
const originalGet = config.get;
const configStub = t.context.sandbox.stub(config, 'get');
// Mock the language pathway configuration
configStub.withArgs('pathways.language').returns({
rootResolver: async (parent, args, context) => {
// Verify the correct parameters are passed
t.is(args.text, text, 'Text parameter should be passed correctly');
return { result: 'en' };
}
});
// For any other config.get calls, return undefined to trigger the original error path
configStub.callThrough();
// Call detectLanguage and expect it to succeed using the fallback
const result = await plugin.detectLanguage(text);
// Verify the result
t.is(result, 'en', 'Should return the language detected by the fallback pathway');
// Verify that config.get was called for the language pathway
t.true(configStub.calledWith('pathways.language'), 'Should attempt to get language pathway configuration');
});
test('execute successfully translates text', async (t) => {
const plugin = t.context.plugin;
const text = 'Hello, how are you?';
const parameters = { from: 'en', to: 'es' };
const prompt = mockPathway.prompt;
// We need to properly mock the cortexRequest with all required properties
const cortexRequest = {
requestId: 'test-request-id',
pathway: {
name: 'translate',
endpoints: [{
type: 'http',
url: 'https://api.mock-apptek.com'
}]
},
model: mockModel
};
// Stub the executeRequest method to avoid dependency on requestExecutor
t.context.sandbox.stub(plugin, 'executeRequest').resolves('Hola, ¿cómo estás?');
const result = await plugin.execute(text, parameters, prompt, cortexRequest);
t.is(result, 'Hola, ¿cómo estás?');
});
test('execute with auto language detection', async (t) => {
// Simpler approach: Create a plugin instance and stub both methods
const plugin = new ApptekTranslatePlugin(mockPathway, mockModel);
const text = 'Hello, how are you?';
const parameters = { from: 'auto', to: 'es' };
const prompt = mockPathway.prompt;
// Prepare a proper cortexRequest object
const cortexRequest = {
requestId: 'test-request-id',
pathway: {
name: 'translate',
endpoints: [{
type: 'http',
url: 'https://api.mock-apptek.com'
}]
},
model: mockModel
};
// Stub the detectLanguage method to return 'en'
const detectLanguageStub = sinon.stub(plugin, 'detectLanguage').resolves('en');
// Stub executeRequest to return translated text
const executeRequestStub = sinon.stub(plugin, 'executeRequest').resolves('Hola, ¿cómo estás?');
// Execute the method
const result = await plugin.execute(text, parameters, prompt, cortexRequest);
// Verify results
t.is(result, 'Hola, ¿cómo estás?');
// Check that detectLanguage was called
t.true(detectLanguageStub.called);
});
test('parseResponse trims response text', (t) => {
const plugin = t.context.plugin;
const response = ' translated text ';
t.is(plugin.parseResponse(response), 'translated text');
});