Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

1240 lines
40 KiB
TypeScript

import { afterAll, beforeEach, describe, expect, it, Mock, Mocked, MockedClass, vi } from 'vitest';
import { evaluate } from '../../src/evaluator';
import logger from '../../src/logger';
import Eval from '../../src/models/eval';
import { testProviderConnectivity, testProviderSession } from '../../src/node/testProvider';
import { neverGenerateRemote } from '../../src/redteam/remoteGeneration';
import { doRemoteGrading } from '../../src/remoteGrading';
import { ResultFailureReason } from '../../src/types/index';
import { fetchWithProxy } from '../../src/util/fetch/index';
import { mockGlobal } from '../util/utils';
import type { EvaluateResult, EvaluateSummaryV3 } from '../../src/types/index';
import type { ApiProvider } from '../../src/types/providers';
// Mock dependencies
vi.mock('../../src/evaluator');
vi.mock('../../src/logger');
vi.mock('../../src/models/eval');
vi.mock('../../src/redteam/remoteGeneration');
vi.mock('../../src/remoteGrading');
vi.mock('../../src/util/fetch/index');
vi.mock('../../src/globalConfig/cloud', async (importOriginal) => {
return {
...(await importOriginal()),
cloudConfig: {
getApiHost: vi.fn(() => 'https://api.promptfoo.app'),
getApiKey: vi.fn(() => 'test-api-key'),
isEnabled: vi.fn(() => true),
},
};
});
const restoreCrypto = mockGlobal('crypto', {
...crypto,
randomUUID: vi.fn(() => 'test-uuid-1234'),
} as Crypto);
afterAll(() => {
restoreCrypto();
});
describe('Provider Test Functions', () => {
let mockProvider: ApiProvider;
let mockEvalRecord: Mocked<Eval>;
let mockSummary: EvaluateSummaryV3;
beforeEach(() => {
vi.clearAllMocks();
// Reset fetchWithProxy mock to default behavior
(fetchWithProxy as Mock).mockReset();
// Setup mock provider
mockProvider = {
id: vi.fn(() => 'test-provider'),
callApi: vi.fn(),
config: {},
getSessionId: vi.fn(),
} as any;
// Setup mock Eval record
mockEvalRecord = {
toEvaluateSummary: vi.fn(),
} as any;
// Setup default mock summary
mockSummary = {
results: [],
stats: {
successes: 0,
failures: 0,
errors: 0,
tokenUsage: {
total: 0,
prompt: 0,
completion: 0,
cached: 0,
numRequests: 0,
completionDetails: {
reasoning: 0,
acceptedPrediction: 0,
rejectedPrediction: 0,
},
assertions: {
prompt: 0,
completion: 0,
total: 0,
numRequests: 0,
},
},
},
prompts: [],
timestamp: new Date().toISOString(),
version: 3,
};
// Mock Eval.constructor
(Eval as MockedClass<typeof Eval>).mockImplementation(function () {
return mockEvalRecord;
});
mockEvalRecord.toEvaluateSummary.mockResolvedValue(mockSummary);
// Mock evaluate to resolve successfully by default
(evaluate as Mock).mockResolvedValue(mockEvalRecord);
// Default mock for neverGenerateRemote
(neverGenerateRemote as Mock).mockReturnValue(false);
// Default mock for doRemoteGrading
(doRemoteGrading as Mock).mockResolvedValue({
pass: true,
score: 1,
reason: 'The system correctly remembered the previous question',
});
});
describe('testProviderConnectivity', () => {
describe('successful connectivity tests', () => {
it('should successfully test connectivity with agent endpoint analysis', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello! How can I help you today?',
raw: 'Hello! How can I help you today?',
metadata: {
transformedRequest: { body: { message: 'Hello World!' } },
http: {
status: 200,
statusText: 'OK',
headers: { 'content-type': 'application/json' },
},
},
sessionId: 'session-123',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null, // No grading result since we don't use assertions
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock successful agent endpoint response
const mockAgentResponse = {
message: 'Provider is working correctly',
changes_needed: false,
};
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue(mockAgentResponse),
});
const result = await testProviderConnectivity({ provider: mockProvider });
// Verify evaluation was called WITHOUT assertions
expect(evaluate).toHaveBeenCalledWith(
expect.objectContaining({
providers: [mockProvider],
prompts: [{ raw: 'Hello World!', label: 'Connectivity Test' }],
tests: [
expect.objectContaining({
vars: { sessionId: 'test-uuid-1234' },
}),
],
}),
mockEvalRecord,
expect.objectContaining({
maxConcurrency: 1,
showProgressBar: false,
}),
);
// Verify agent endpoint was called with correct payload
expect(fetchWithProxy).toHaveBeenCalledWith(
expect.stringContaining('/api/v1/providers/test'),
expect.objectContaining({
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer test-api-key',
},
body: JSON.stringify({
config: mockProvider.config,
providerResponse: mockResult.response!.raw,
parsedResponse: mockResult.response!.output,
error: null,
headers: { 'content-type': 'application/json' },
}),
}),
);
// Verify result structure
expect(result).toEqual({
success: true,
message: 'Provider is working correctly',
error: undefined,
providerResponse: mockResult.response,
transformedRequest: { body: { message: 'Hello World!' } },
sessionId: 'session-123',
analysis: undefined,
});
expect(logger.debug).toHaveBeenCalledWith('[testProviderConnectivity] Running evaluation', {
providerId: mockProvider.id,
});
});
it('should handle connectivity test with remote grading disabled', async () => {
(neverGenerateRemote as Mock).mockReturnValue(true);
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello! How can I help you today?',
metadata: {
transformedRequest: { body: { message: 'Hello World!' } },
},
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
const result = await testProviderConnectivity({ provider: mockProvider });
// Verify evaluation was called without assertions
expect(evaluate).toHaveBeenCalledWith(
expect.objectContaining({
tests: [
expect.objectContaining({
vars: { sessionId: 'test-uuid-1234' },
}),
],
}),
mockEvalRecord,
expect.any(Object),
);
// Verify fetchWithProxy was NOT called when remote grading is disabled
expect(fetchWithProxy).not.toHaveBeenCalled();
// Verify result for disabled remote grading
expect(result).toEqual({
success: true,
message:
'Provider test completed. Remote grading disabled - please review the response manually.',
error: undefined,
providerResponse: mockResult.response,
transformedRequest: { body: { message: 'Hello World!' } },
sessionId: 'test-uuid-1234',
});
});
it('should extract session ID from provider getSessionId method', async () => {
mockProvider.getSessionId = vi.fn(() => 'provider-session-id');
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock successful agent response
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
message: 'Test passed',
changes_needed: false,
}),
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result.sessionId).toBe('provider-session-id');
});
it('should extract session ID from response if provider method not available', async () => {
mockProvider.getSessionId = undefined;
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
sessionId: 'response-session-id',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock successful agent response
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
message: 'Test passed',
changes_needed: false,
}),
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result.sessionId).toBe('response-session-id');
});
it('should fall back to generated session ID if not found elsewhere', async () => {
mockProvider.getSessionId = undefined;
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock successful agent response
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
message: 'Test passed',
changes_needed: false,
}),
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result.sessionId).toBe('test-uuid-1234');
});
});
describe('failed connectivity tests', () => {
it('should handle provider call failure', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
error: 'Connection timeout',
metadata: {
transformedRequest: { url: 'http://example.com' },
},
},
error: 'Connection timeout',
failureReason: ResultFailureReason.ERROR,
success: false,
score: 0,
latencyMs: 5000,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.failures = 1;
// Mock agent endpoint response for error case
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
message: 'Provider call failed: Connection timeout',
error: 'Connection timeout',
}),
});
const result = await testProviderConnectivity({ provider: mockProvider });
// Should call agent endpoint even when there's an error for analysis
expect(fetchWithProxy).toHaveBeenCalledWith(
expect.stringContaining('/api/v1/providers/test'),
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
config: mockProvider.config,
providerResponse: undefined,
parsedResponse: undefined,
error: 'Connection timeout',
headers: undefined,
}),
}),
);
expect(result).toEqual({
success: false,
message: 'Provider call failed: Connection timeout',
error: 'Connection timeout',
providerResponse: mockResult.response,
transformedRequest: { url: 'http://example.com' },
sessionId: 'test-uuid-1234',
analysis: undefined,
});
});
it('should handle agent endpoint failure with analysis', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Error: Internal server error',
raw: 'Error: Internal server error',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock agent response with changes needed
const mockAgentResponse = {
message: 'Provider returned an error message instead of a valid response',
changes_needed: true,
changes_needed_reason: 'Provider returned an error message instead of a valid response',
changes_needed_suggestions: [
'Check your API configuration and ensure the endpoint is working',
],
};
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue(mockAgentResponse),
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Provider returned an error message instead of a valid response',
error: undefined,
providerResponse: mockResult.response,
transformedRequest: undefined,
sessionId: 'test-uuid-1234',
analysis: {
changes_needed: true,
changes_needed_reason: 'Provider returned an error message instead of a valid response',
changes_needed_suggestions: [
'Check your API configuration and ensure the endpoint is working',
],
},
});
});
it('should handle agent endpoint returning error status', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
metadata: {
transformedRequest: { body: { message: 'Hello World!' } },
},
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock agent endpoint returning error status
(fetchWithProxy as Mock).mockResolvedValue({
ok: false,
statusText: 'Internal Server Error',
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Error evaluating the results. Please review the provider response manually.',
error: 'Remote evaluation failed',
providerResponse: mockResult.response,
transformedRequest: { body: { message: 'Hello World!' } },
sessionId: 'test-uuid-1234',
});
expect(logger.error).toHaveBeenCalledWith(
'[testProviderConnectivity] Error calling agent helper',
{
error: 'Internal Server Error',
providerId: mockProvider.id,
},
);
});
it('should handle agent endpoint throwing an exception', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
metadata: {
transformedRequest: { body: { message: 'Hello World!' } },
},
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock agent endpoint throwing exception
(fetchWithProxy as Mock).mockRejectedValue(new Error('Network error'));
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Error evaluating the results. Please review the provider response manually.',
error: 'Network error',
providerResponse: mockResult.response,
transformedRequest: { body: { message: 'Hello World!' } },
sessionId: 'test-uuid-1234',
});
expect(logger.error).toHaveBeenCalledWith(
'[testProviderConnectivity] Error calling agent helper',
{
error: 'Network error',
providerId: mockProvider.id,
},
);
});
it('should handle agent endpoint returning error in response', async () => {
const mockResult: EvaluateResult = {
promptIdx: 0,
testIdx: 0,
testCase: {} as any,
promptId: 'test-prompt-id',
provider: { id: 'test-provider' },
prompt: { raw: 'Hello World!', label: 'Connectivity Test' },
vars: { sessionId: 'test-uuid-1234' },
response: {
output: 'Hello!',
raw: 'Hello!',
},
error: null,
failureReason: ResultFailureReason.NONE,
success: true,
score: 1,
latencyMs: 100,
gradingResult: null,
namedScores: {},
};
mockSummary.results = [mockResult];
mockSummary.stats.successes = 1;
// Mock agent response with error field
const mockAgentResponse = {
error: 'Unable to analyze the provider response',
message: 'Analysis failed',
};
(fetchWithProxy as Mock).mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue(mockAgentResponse),
});
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Analysis failed',
error: 'Unable to analyze the provider response',
providerResponse: mockResult.response,
transformedRequest: undefined,
sessionId: 'test-uuid-1234',
analysis: undefined,
});
});
it('should handle evaluation throwing an error', async () => {
const evalError = new Error('Evaluation failed');
(evaluate as Mock).mockRejectedValue(evalError);
const result = await testProviderConnectivity({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Error evaluating the provider. Please review the error details.',
error: 'Evaluation failed',
});
expect(logger.error).toHaveBeenCalledWith(
'[testProviderConnectivity] Error during evaluation',
{
error: 'Evaluation failed',
providerId: mockProvider.id,
},
);
});
});
});
describe('testProviderSession', () => {
describe('successful session tests', () => {
it('should successfully test session with client-side session ID', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi to return successful responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output:
'I can help you with various tasks like answering questions, providing information, etc.',
})
.mockResolvedValueOnce({
output: 'You asked me what I can help you with.',
});
const result = await testProviderSession({ provider: mockProvider });
// Verify callApi was called twice with correct prompts and session ID
expect(mockProvider.callApi).toHaveBeenCalledTimes(2);
expect(mockProvider.callApi).toHaveBeenNthCalledWith(
1,
'What can you help me with?',
expect.objectContaining({
vars: { sessionId: 'test-uuid-1234' },
prompt: {
raw: 'What can you help me with?',
label: 'Session Test - Request 1',
},
}),
);
expect(mockProvider.callApi).toHaveBeenNthCalledWith(
2,
'What was the last thing I asked you?',
expect.objectContaining({
vars: { sessionId: 'test-uuid-1234' },
prompt: {
raw: 'What was the last thing I asked you?',
label: 'Session Test - Request 2',
},
}),
);
// Verify doRemoteGrading was called
expect(doRemoteGrading).toHaveBeenCalledWith(
expect.objectContaining({
task: 'llm-rubric',
rubric: expect.stringContaining('maintains session state'),
output: 'You asked me what I can help you with.',
}),
);
// Verify successful result
expect(result).toEqual(
expect.objectContaining({
success: true,
message: expect.any(String),
reason: 'The system correctly remembered the previous question',
details: {
sessionId: 'test-uuid-1234',
sessionSource: 'client',
request1: {
prompt: 'What can you help me with?',
sessionId: 'test-uuid-1234',
},
response1:
'I can help you with various tasks like answering questions, providing information, etc.',
request2: {
prompt: 'What was the last thing I asked you?',
sessionId: 'test-uuid-1234',
},
response2: 'You asked me what I can help you with.',
},
}),
);
});
it('should successfully test session with server-side session ID', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
sessionParser: 'response.sessionId',
};
const serverSessionId = 'server-generated-session-123';
mockProvider.getSessionId = vi.fn(() => serverSessionId);
// Mock callApi to return successful responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help with many things!',
sessionId: serverSessionId,
})
.mockResolvedValueOnce({
output: 'You asked what I can help you with.',
sessionId: serverSessionId,
});
const result = await testProviderSession({ provider: mockProvider });
// Verify callApi was called twice
expect(mockProvider.callApi).toHaveBeenCalledTimes(2);
// First call should have no sessionId in vars (server will generate it)
expect(mockProvider.callApi).toHaveBeenNthCalledWith(
1,
'What can you help me with?',
expect.objectContaining({
vars: {}, // No sessionId for first request in server mode
prompt: {
raw: 'What can you help me with?',
label: 'Session Test - Request 1',
},
}),
);
// Second call should use extracted sessionId
expect(mockProvider.callApi).toHaveBeenNthCalledWith(
2,
'What was the last thing I asked you?',
expect.objectContaining({
vars: { sessionId: serverSessionId },
prompt: {
raw: 'What was the last thing I asked you?',
label: 'Session Test - Request 2',
},
}),
);
expect(result).toEqual({
success: true,
message:
'Session management is working correctly! The provider remembered information across requests.',
reason: 'The system correctly remembered the previous question',
details: {
sessionId: serverSessionId,
sessionSource: 'server',
request1: { prompt: 'What can you help me with?', sessionId: undefined },
response1: 'I can help with many things!',
request2: {
prompt: 'What was the last thing I asked you?',
sessionId: serverSessionId,
},
response2: 'You asked what I can help you with.',
},
});
});
it('should handle session test with remote grading disabled', async () => {
(neverGenerateRemote as Mock).mockReturnValue(true);
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help with many things!',
})
.mockResolvedValueOnce({
output: 'You asked what I can help you with.',
});
const result = await testProviderSession({ provider: mockProvider });
// Verify doRemoteGrading was NOT called
expect(doRemoteGrading).not.toHaveBeenCalled();
expect(result).toEqual({
success: false,
message:
'Session test completed. Remote grading is disabled - please examine the results yourself.',
reason: 'Manual review required - remote grading is disabled',
details: {
sessionId: 'test-uuid-1234',
sessionSource: 'client',
request1: { prompt: 'What can you help me with?', sessionId: 'test-uuid-1234' },
response1: 'I can help with many things!',
request2: {
prompt: 'What was the last thing I asked you?',
sessionId: 'test-uuid-1234',
},
response2: 'You asked what I can help you with.',
},
});
});
});
describe('failed session tests', () => {
it('should fail when session memory is not working', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help with many things!',
})
.mockResolvedValueOnce({
output: "I don't have access to previous messages.",
});
// Mock doRemoteGrading to return failure
(doRemoteGrading as Mock).mockResolvedValue({
pass: false,
score: 0,
reason: 'The system did not remember the previous question',
});
const result = await testProviderSession({ provider: mockProvider });
expect(result).toEqual(
expect.objectContaining({
success: false,
message: expect.any(String),
reason: 'The system did not remember the previous question',
details: {
sessionId: 'test-uuid-1234',
sessionSource: 'client',
request1: {
prompt: 'What can you help me with?',
sessionId: 'test-uuid-1234',
},
response1: 'I can help with many things!',
request2: {
prompt: 'What was the last thing I asked you?',
sessionId: 'test-uuid-1234',
},
response2: "I don't have access to previous messages.",
},
}),
);
});
it('should warn when client session config is missing sessionId variable', async () => {
// Config without {{sessionId}}
mockProvider.config = {
headers: {
'X-Custom-Header': 'test',
},
};
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
})
.mockResolvedValueOnce({
output: 'You asked about help.',
});
const result = await testProviderSession({ provider: mockProvider });
// Should log warning but still proceed with test
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining('{{sessionId}} not found'),
expect.any(Object),
);
// Test should still run
expect(mockProvider.callApi).toHaveBeenCalledTimes(2);
expect(result.success).toBeDefined();
});
it('should skip validation when skipConfigValidation is true', async () => {
// Config without {{sessionId}}
mockProvider.config = {
headers: {
'X-Custom-Header': 'test',
},
};
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
})
.mockResolvedValueOnce({
output: 'You asked about help.',
});
const result = await testProviderSession({
provider: mockProvider,
options: { skipConfigValidation: true },
});
// Should NOT log warning
expect(logger.warn).not.toHaveBeenCalled();
// Test should still run
expect(mockProvider.callApi).toHaveBeenCalledTimes(2);
expect(result.success).toBeDefined();
});
it('should warn when server session config is missing parser', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock getSessionId to provide sessionId even without parser
mockProvider.getSessionId = vi.fn(() => 'manual-session-id');
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
})
.mockResolvedValueOnce({
output: 'You asked about help.',
});
await testProviderSession({
provider: mockProvider,
sessionConfig: { sessionSource: 'server' },
});
// Should log warning about missing session parser
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining('no session parser configured'),
expect.any(Object),
);
// Test should still run if getSessionId provides a session
expect(mockProvider.callApi).toHaveBeenCalledTimes(2);
});
it('should fail when server session ID extraction fails', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
sessionParser: 'response.sessionId',
};
// First response doesn't have sessionId
(mockProvider.callApi as Mock).mockResolvedValueOnce({
output: 'I can help!',
// No sessionId in response
});
mockProvider.getSessionId = vi.fn(() => undefined as any);
const result = await testProviderSession({ provider: mockProvider });
expect(result).toEqual({
success: false,
message:
'Session extraction failed: The session parser did not extract a session ID from the server response',
reason:
"The session parser expression did not return a valid session ID. Check that the parser matches your server's response format.",
details: {
sessionId: 'Not extracted',
sessionSource: 'server',
request1: {
prompt: 'What can you help me with?',
},
response1: 'I can help!',
},
});
});
it('should handle first request error', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi to return error on first request
(mockProvider.callApi as Mock).mockResolvedValueOnce({
error: 'Connection failed',
});
const result = await testProviderSession({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'First request failed: Connection failed',
error: 'Connection failed',
details: {
sessionId: 'test-uuid-1234',
sessionSource: 'client',
request1: { prompt: 'What can you help me with?', sessionId: 'test-uuid-1234' },
response1: 'Connection failed',
},
});
});
it('should handle second request error', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi - first succeeds, second fails
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
})
.mockResolvedValueOnce({
error: 'Connection timeout',
});
const result = await testProviderSession({ provider: mockProvider });
expect(result).toEqual({
success: false,
message: 'Second request failed: Connection timeout',
error: 'Connection timeout',
details: {
sessionId: 'test-uuid-1234',
sessionSource: 'client',
request1: { prompt: 'What can you help me with?', sessionId: 'test-uuid-1234' },
response1: 'I can help!',
request2: {
prompt: 'What was the last thing I asked you?',
sessionId: 'test-uuid-1234',
},
response2: 'Connection timeout',
},
});
});
});
describe('session configuration', () => {
it('should update provider config with session settings', () => {
// This will be called internally during testProviderSession
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
expect(mockProvider.config).toBeDefined();
});
it('should detect server session from sessionParser in provider config', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
sessionParser: 'response.sessionId',
};
const serverSessionId = 'server-session-id';
mockProvider.getSessionId = vi.fn(() => serverSessionId);
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
sessionId: serverSessionId,
})
.mockResolvedValueOnce({
output: 'You asked about help.',
sessionId: serverSessionId,
});
const result = await testProviderSession({ provider: mockProvider });
expect(result.details?.sessionSource).toBe('server');
expect(result.details?.sessionId).toBe(serverSessionId);
});
});
describe('edge cases', () => {
it('should handle empty response outputs gracefully', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi with empty outputs
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: '',
})
.mockResolvedValueOnce({
output: '',
});
// Mock doRemoteGrading to fail for empty outputs
(doRemoteGrading as Mock).mockResolvedValue({
pass: false,
score: 0,
reason: 'Empty response cannot demonstrate session memory',
});
const result = await testProviderSession({ provider: mockProvider });
expect(result.success).toBe(false);
expect(result.details?.response1).toBe('');
expect(result.details?.response2).toBe('');
});
it('should handle grading failure gracefully', async () => {
mockProvider.config = {
headers: {
'X-Session-ID': '{{sessionId}}',
},
};
// Mock callApi responses
(mockProvider.callApi as Mock)
.mockResolvedValueOnce({
output: 'I can help!',
})
.mockResolvedValueOnce({
output: 'You asked about help.',
});
// Mock doRemoteGrading to throw error
(doRemoteGrading as Mock).mockRejectedValue(new Error('Grading service unavailable'));
const result = await testProviderSession({ provider: mockProvider });
expect(result.success).toBe(false);
expect(result.message).toContain('Failed to evaluate session');
expect(result.error).toContain('Grading service unavailable');
});
});
});
});