Path: blob/main/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as assert from "assert";6import * as sinon from "sinon";7import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../base/test/common/utils.js";8import {9StorageScope10} from "../../../../../platform/storage/common/storage.js";11import { TestStorageService } from "../../../../test/common/workbenchTestServices.js";12import { McpSamplingLog } from "../../common/mcpSamplingLog.js";13import { IMcpServer } from "../../common/mcpTypes.js";1415suite("MCP - Sampling Log", () => {16const ds = ensureNoDisposablesAreLeakedInTestSuite();17const fakeServer: IMcpServer = {18definition: { id: "testServer" },19readDefinitions: () => ({20get: () => ({ collection: { scope: StorageScope.APPLICATION } }),21}),22} as any;2324let log: McpSamplingLog;25let storage: TestStorageService;26let clock: sinon.SinonFakeTimers;2728setup(() => {29storage = ds.add(new TestStorageService());30log = ds.add(new McpSamplingLog(storage));31clock = sinon.useFakeTimers();32clock.setSystemTime(new Date("2023-10-01T00:00:00Z").getTime());33});3435teardown(() => {36clock.restore();37});3839test("logs a single request", async () => {40log.add(41fakeServer,42[{ role: "user", content: { type: "text", text: "test request" } }],43"test response here",44"foobar9000",45);4647// storage.testEmitWillSaveState(WillSaveStateReason.NONE);48await storage.flush();49assert.deepStrictEqual(50(storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any),51[52[53"testServer",54{55head: 19631,56bins: [1, 0, 0, 0, 0, 0, 0],57lastReqs: [58{59request: [{ role: "user", content: { type: "text", text: "test request" } }],60response: "test response here",61at: 1696118400000,62model: "foobar9000",63},64],65},66],67],68);69});7071test("logs multiple requests on the same day", async () => {72// First request73log.add(74fakeServer,75[{ role: "user", content: { type: "text", text: "first request" } }],76"first response",77"foobar9000",78);7980// Advance time by a few hours but stay on the same day81clock.tick(5 * 60 * 60 * 1000); // 5 hours8283// Second request84log.add(85fakeServer,86[{ role: "user", content: { type: "text", text: "second request" } }],87"second response",88"foobar9000",89);9091await storage.flush();92const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];9394// Verify the bin for the current day has 2 requests95assert.strictEqual(data.bins[0], 2);9697// Verify both requests are in the lastReqs array, with the most recent first98assert.strictEqual(data.lastReqs.length, 2);99assert.strictEqual(data.lastReqs[0].request[0].content.text, "second request");100assert.strictEqual(data.lastReqs[1].request[0].content.text, "first request");101});102103test("shifts bins when adding requests on different days", async () => {104// First request on day 1105log.add(106fakeServer,107[{ role: "user", content: { type: "text", text: "day 1 request" } }],108"day 1 response",109"foobar9000",110);111112// Advance time to the next day113clock.tick(24 * 60 * 60 * 1000);114115// Second request on day 2116log.add(117fakeServer,118[{ role: "user", content: { type: "text", text: "day 2 request" } }],119"day 2 response",120"foobar9000",121);122123await storage.flush();124const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];125126// Verify the bins: day 2 should have 1 request, day 1 should have 1 request127assert.strictEqual(data.bins[0], 1); // day 2128assert.strictEqual(data.bins[1], 1); // day 1129130// Advance time by 5 more days131clock.tick(5 * 24 * 60 * 60 * 1000);132133// Request on day 7134log.add(135fakeServer,136[{ role: "user", content: { type: "text", text: "day 7 request" } }],137"day 7 response",138"foobar9000",139);140141await storage.flush();142const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];143144// Verify the bins have shifted correctly145assert.strictEqual(updatedData.bins[0], 1); // day 7146assert.strictEqual(updatedData.bins[5], 1); // day 2147assert.strictEqual(updatedData.bins[6], 1); // day 1148});149150test("limits the number of stored requests", async () => {151// Add more than the maximum number of requests (Constants.SamplingLastNMessage = 30)152for (let i = 0; i < 35; i++) {153log.add(154fakeServer,155[{ role: "user", content: { type: "text", text: `request ${i}` } }],156`response ${i}`,157"foobar9000",158);159}160161await storage.flush();162const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];163164// Verify only the last 30 requests are kept165assert.strictEqual(data.lastReqs.length, 30);166assert.strictEqual(data.lastReqs[0].request[0].content.text, "request 34");167assert.strictEqual(data.lastReqs[29].request[0].content.text, "request 5");168});169170test("handles different content types", async () => {171// Add a request with text content172log.add(173fakeServer,174[{ role: "user", content: { type: "text", text: "text request" } }],175"text response",176"foobar9000",177);178179// Add a request with image content180log.add(181fakeServer,182[{183role: "user",184content: {185type: "image",186data: "base64data",187mimeType: "image/png"188}189}],190"image response",191"foobar9000",192);193194// Add a request with mixed content195log.add(196fakeServer,197[198{ role: "user", content: { type: "text", text: "text and image" } },199{200role: "assistant",201content: {202type: "image",203data: "base64data",204mimeType: "image/jpeg"205}206}207],208"mixed response",209"foobar9000",210);211212await storage.flush();213const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];214215// Verify all requests are stored correctly216assert.strictEqual(data.lastReqs.length, 3);217assert.strictEqual(data.lastReqs[0].request.length, 2); // Mixed content request has 2 messages218assert.strictEqual(data.lastReqs[1].request[0].content.type, "image");219assert.strictEqual(data.lastReqs[2].request[0].content.type, "text");220});221222test("handles multiple servers", async () => {223const fakeServer2: IMcpServer = {224definition: { id: "testServer2" },225readDefinitions: () => ({226get: () => ({ collection: { scope: StorageScope.APPLICATION } }),227}),228} as any;229230log.add(231fakeServer,232[{ role: "user", content: { type: "text", text: "server1 request" } }],233"server1 response",234"foobar9000",235);236237log.add(238fakeServer2,239[{ role: "user", content: { type: "text", text: "server2 request" } }],240"server2 response",241"foobar9000",242);243244await storage.flush();245const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any);246247// Verify both servers have their data stored248assert.strictEqual(storageData.length, 2);249assert.strictEqual(storageData[0][0], "testServer");250assert.strictEqual(storageData[1][0], "testServer2");251});252});253254255