mcp
This commit is contained in:
@@ -9,7 +9,7 @@ describe('AssistantService', () => {
|
||||
].join(' ');
|
||||
const originalFetch = global.fetch;
|
||||
const originalApiKey = process.env.MISTRAL_API_KEY;
|
||||
const originalAgentId = process.env.MISTRAL_AGENT_ID;
|
||||
const originalModel = process.env.MISTRAL_MODEL;
|
||||
let chatLogsRepository: {
|
||||
create: jest.Mock;
|
||||
find: jest.Mock;
|
||||
@@ -26,7 +26,7 @@ describe('AssistantService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.MISTRAL_API_KEY = 'test-key';
|
||||
process.env.MISTRAL_AGENT_ID = 'agent-listify';
|
||||
process.env.MISTRAL_MODEL = 'mistral-large-test';
|
||||
global.fetch = jest.fn();
|
||||
chatLogsRepository = {
|
||||
create: jest.fn((input) => input),
|
||||
@@ -51,10 +51,10 @@ describe('AssistantService', () => {
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
process.env.MISTRAL_API_KEY = originalApiKey;
|
||||
process.env.MISTRAL_AGENT_ID = originalAgentId;
|
||||
process.env.MISTRAL_MODEL = originalModel;
|
||||
});
|
||||
|
||||
it('forwards messages to the configured Mistral agent', async () => {
|
||||
it('starts a Mistral conversation with the configured model', async () => {
|
||||
const providerResponse = {
|
||||
choices: [
|
||||
{
|
||||
@@ -78,7 +78,7 @@ describe('AssistantService', () => {
|
||||
});
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://api.mistral.ai/v1/agents/completions',
|
||||
'https://api.mistral.ai/v1/conversations',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -86,12 +86,12 @@ describe('AssistantService', () => {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
agent_id: 'agent-listify',
|
||||
messages: [
|
||||
model: 'mistral-large-test',
|
||||
inputs: [
|
||||
{ role: 'assistant', content: 'Hallo' },
|
||||
{ role: 'user', content: 'Hallo' },
|
||||
{ role: 'system', content: connectorSystemMessage },
|
||||
],
|
||||
instructions: connectorSystemMessage,
|
||||
tools: [
|
||||
{
|
||||
type: 'connector',
|
||||
@@ -99,9 +99,6 @@ describe('AssistantService', () => {
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
response_format: {
|
||||
type: 'text',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -117,11 +114,16 @@ describe('AssistantService', () => {
|
||||
expect.objectContaining({
|
||||
userId: 'user-1',
|
||||
provider: 'mistral',
|
||||
endpoint: 'https://api.mistral.ai/v1/agents/completions',
|
||||
agentId: 'agent-listify',
|
||||
endpoint: 'https://api.mistral.ai/v1/conversations',
|
||||
agentId: null,
|
||||
statusCode: 200,
|
||||
requestPayload: expect.objectContaining({
|
||||
agent_id: 'agent-listify',
|
||||
model: 'mistral-large-test',
|
||||
inputs: [
|
||||
{ role: 'assistant', content: 'Hallo' },
|
||||
{ role: 'user', content: 'Hallo' },
|
||||
],
|
||||
instructions: connectorSystemMessage,
|
||||
tools: [{ type: 'connector', connector_id: 'listify' }],
|
||||
}),
|
||||
responsePayload: providerResponse,
|
||||
@@ -131,6 +133,64 @@ describe('AssistantService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts assistant content and created list actions from conversation outputs', async () => {
|
||||
const createdList = {
|
||||
id: 'list-1',
|
||||
ownerId: 'user-1',
|
||||
accessRole: 'owner',
|
||||
name: 'Einkauf',
|
||||
kind: 'shopping',
|
||||
items: [],
|
||||
collaborators: [],
|
||||
createdAt: '2026-06-12T00:00:00.000Z',
|
||||
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||
};
|
||||
const providerResponse = {
|
||||
conversation_id: 'conv-1',
|
||||
outputs: [
|
||||
{
|
||||
type: 'tool.execution',
|
||||
tool_name: 'listify_create_list',
|
||||
result: { list: createdList },
|
||||
},
|
||||
{
|
||||
type: 'message.output',
|
||||
role: 'assistant',
|
||||
content: 'Ich habe die Liste Einkauf angelegt.',
|
||||
},
|
||||
],
|
||||
};
|
||||
mockMistralResponse(providerResponse);
|
||||
|
||||
const result = await service.chat('user-1', {
|
||||
messages: [{ role: 'user', content: 'Erstelle eine Einkaufsliste' }],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'Ich habe die Liste Einkauf angelegt.',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: 'list.created',
|
||||
listId: 'list-1',
|
||||
list: createdList,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(listRealtimeService.publishSnapshot).toHaveBeenCalledWith(
|
||||
'user-1',
|
||||
createdList,
|
||||
);
|
||||
expect(chatLogsRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
responsePayload: providerResponse,
|
||||
assistantContent: 'Ich habe die Liste Einkauf angelegt.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('answers open list read requests locally without calling Mistral', async () => {
|
||||
listsService.listLists.mockResolvedValue([
|
||||
{
|
||||
@@ -262,15 +322,9 @@ describe('AssistantService', () => {
|
||||
});
|
||||
|
||||
const payload = getMistralRequestPayload();
|
||||
const contextMessage = payload.messages.at(-2);
|
||||
const contextContent = contextMessage?.content ?? '';
|
||||
const contextContent = payload.instructions;
|
||||
|
||||
expect(contextMessage).toEqual(
|
||||
expect.objectContaining({
|
||||
role: 'system',
|
||||
content: expect.stringContaining('Aktueller Listify-Kontext:'),
|
||||
}),
|
||||
);
|
||||
expect(contextContent).toContain('Aktueller Listify-Kontext:');
|
||||
expect(contextContent).toContain(
|
||||
'Der User befindet sich auf einer Listendetailseite.',
|
||||
);
|
||||
@@ -287,10 +341,7 @@ describe('AssistantService', () => {
|
||||
expect(contextContent).not.toContain('ada@example.com');
|
||||
expect(contextContent).not.toContain('grace@example.com');
|
||||
expect(contextContent).not.toContain('checkedByUserId');
|
||||
expect(payload.messages.at(-1)).toEqual({
|
||||
role: 'system',
|
||||
content: connectorSystemMessage,
|
||||
});
|
||||
expect(contextContent).toContain(connectorSystemMessage);
|
||||
});
|
||||
|
||||
it('adds list items locally when the user writes onto the current list', async () => {
|
||||
@@ -390,7 +441,7 @@ describe('AssistantService', () => {
|
||||
statusCode: 502,
|
||||
responsePayload: providerResponse,
|
||||
assistantContent: null,
|
||||
errorMessage: 'Mistral agent request failed.',
|
||||
errorMessage: 'Mistral conversation request failed.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -720,40 +771,20 @@ describe('AssistantService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('fails clearly when the agent id is missing', async () => {
|
||||
delete process.env.MISTRAL_AGENT_ID;
|
||||
|
||||
await expect(
|
||||
service.chat('user-1', {
|
||||
messages: [{ role: 'user', content: 'Hallo' }],
|
||||
}),
|
||||
).rejects.toThrow(ServiceUnavailableException);
|
||||
expect(chatLogsRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
userId: 'user-1',
|
||||
agentId: null,
|
||||
statusCode: null,
|
||||
responsePayload: null,
|
||||
assistantContent: null,
|
||||
errorMessage: 'Mistral agent id is not configured.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('lists the latest chat logs for the current user', async () => {
|
||||
chatLogsRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'log-1',
|
||||
userId: 'user-1',
|
||||
provider: 'mistral',
|
||||
endpoint: 'https://api.mistral.ai/v1/agents/completions',
|
||||
agentId: 'agent-listify',
|
||||
endpoint: 'https://api.mistral.ai/v1/conversations',
|
||||
agentId: null,
|
||||
statusCode: 502,
|
||||
durationMs: 123,
|
||||
requestPayload: { messages: [] },
|
||||
requestPayload: { inputs: [] },
|
||||
responsePayload: { message: 'connector failed' },
|
||||
assistantContent: null,
|
||||
errorMessage: 'Mistral agent request failed.',
|
||||
errorMessage: 'Mistral conversation request failed.',
|
||||
createdAt: new Date('2026-06-24T08:00:00.000Z'),
|
||||
},
|
||||
]);
|
||||
@@ -769,14 +800,14 @@ describe('AssistantService', () => {
|
||||
{
|
||||
id: 'log-1',
|
||||
provider: 'mistral',
|
||||
endpoint: 'https://api.mistral.ai/v1/agents/completions',
|
||||
agentId: 'agent-listify',
|
||||
endpoint: 'https://api.mistral.ai/v1/conversations',
|
||||
agentId: null,
|
||||
statusCode: 502,
|
||||
durationMs: 123,
|
||||
requestPayload: { messages: [] },
|
||||
requestPayload: { inputs: [] },
|
||||
responsePayload: { message: 'connector failed' },
|
||||
assistantContent: null,
|
||||
errorMessage: 'Mistral agent request failed.',
|
||||
errorMessage: 'Mistral conversation request failed.',
|
||||
createdAt: '2026-06-24T08:00:00.000Z',
|
||||
},
|
||||
]);
|
||||
@@ -792,7 +823,8 @@ function mockMistralResponse(response: object, ok = true, status = 200): void {
|
||||
}
|
||||
|
||||
function getMistralRequestPayload(): {
|
||||
messages: Array<{ role: string; content: string }>;
|
||||
inputs: Array<{ role: string; content: string }>;
|
||||
instructions: string;
|
||||
} {
|
||||
const [, init] = jest.mocked(global.fetch).mock.calls.at(-1) ?? [];
|
||||
const body = init?.body;
|
||||
@@ -802,6 +834,7 @@ function getMistralRequestPayload(): {
|
||||
}
|
||||
|
||||
return JSON.parse(body) as {
|
||||
messages: Array<{ role: string; content: string }>;
|
||||
inputs: Array<{ role: string; content: string }>;
|
||||
instructions: string;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user