MCP Terminal Server
by dillip285
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
afterEach,
beforeEach,
describe,
expect,
it,
jest,
} from '@jest/globals';
import fs from 'fs';
import * as uuid from 'uuid';
import { LocalFileDatasetStore } from '../../src/eval/localFileDatasetStore';
import {
CreateDatasetRequestSchema,
UpdateDatasetRequestSchema,
} from '../../src/types/apis';
import { Dataset, DatasetStore } from '../../src/types/eval';
const FAKE_TIME = new Date('2024-02-03T12:05:33.243Z');
const SAMPLE_DATASET_1_V1 = [
{
input: 'Cats are evil',
reference: 'Sorry no reference',
},
{
input: 'Dogs are beautiful',
},
];
const SAMPLE_DATASET_1_WITH_IDS = [
{
testCaseId: '1',
input: 'Cats are evil',
reference: 'Sorry no reference',
},
{
testCaseId: '2',
input: 'Dogs are beautiful',
},
];
const SAMPLE_DATASET_1_V2 = [
{
input: 'Cats are evil',
reference: 'Sorry no reference',
},
{
input: 'Dogs are angels',
},
{
input: 'Dogs are also super cute',
},
];
const SAMPLE_DATASET_ID_1 = 'dataset-1-123456';
const SAMPLE_DATASET_METADATA_1_V1 = {
datasetId: SAMPLE_DATASET_ID_1,
size: 2,
version: 1,
datasetType: 'UNKNOWN',
createTime: FAKE_TIME.toString(),
updateTime: FAKE_TIME.toString(),
};
const SAMPLE_DATASET_METADATA_1_V2 = {
datasetId: SAMPLE_DATASET_ID_1,
size: 3,
version: 2,
datasetType: 'UNKNOWN',
createTime: FAKE_TIME.toString(),
updateTime: FAKE_TIME.toString(),
};
const CREATE_DATASET_REQUEST = CreateDatasetRequestSchema.parse({
data: SAMPLE_DATASET_1_V1,
datasetType: 'UNKNOWN',
});
const CREATE_DATASET_REQUEST_WITH_SCHEMA = CreateDatasetRequestSchema.parse({
data: SAMPLE_DATASET_1_V1,
datasetType: 'UNKNOWN',
schema: {
inputSchema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
referenceSchema: {
type: 'number',
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
targetAction: '/flow/my-flow',
});
const UPDATE_DATASET_REQUEST = UpdateDatasetRequestSchema.parse({
data: SAMPLE_DATASET_1_V2,
datasetId: SAMPLE_DATASET_ID_1,
});
const SAMPLE_DATASET_ID_2 = 'dataset-2-123456';
const SAMPLE_DATASET_METADATA_2 = {
datasetId: SAMPLE_DATASET_ID_2,
size: 5,
version: 1,
createTime: FAKE_TIME.toString(),
updateTime: FAKE_TIME.toString(),
};
jest.mock('process', () => {
return {
cwd: jest.fn(() => 'store-root'),
};
});
jest.mock('uuid');
const uuidSpy = jest.spyOn(uuid, 'v4');
const TEST_CASE_ID = 'test-case-1234-1234-1234';
jest.mock('../../src/utils', () => ({
generateTestCaseId: jest.fn(() => TEST_CASE_ID),
}));
jest.useFakeTimers({ advanceTimers: true });
jest.setSystemTime(FAKE_TIME);
describe('localFileDatasetStore', () => {
let DatasetStore: DatasetStore;
beforeEach(() => {
// For storeRoot setup
fs.existsSync = jest.fn(() => true);
uuidSpy.mockReturnValueOnce('12345678');
LocalFileDatasetStore.reset();
DatasetStore = LocalFileDatasetStore.getDatasetStore() as DatasetStore;
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('createDataset', () => {
it('writes and updates index for new dataset', async () => {
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify({}) as any)
);
fs.existsSync = jest.fn(() => false);
const dataset: Dataset = SAMPLE_DATASET_1_V1.map((s) => ({
testCaseId: TEST_CASE_ID,
...s,
}));
const datasetMetadata = await DatasetStore.createDataset({
...CREATE_DATASET_REQUEST,
datasetId: SAMPLE_DATASET_ID_1,
});
expect(fs.promises.writeFile).toHaveBeenCalledTimes(2);
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
1,
expect.stringContaining(`datasets/${SAMPLE_DATASET_ID_1}.json`),
JSON.stringify(dataset)
);
const metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
};
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
2,
expect.stringContaining('datasets/index.json'),
JSON.stringify(metadataMap)
);
expect(datasetMetadata).toMatchObject(SAMPLE_DATASET_METADATA_1_V1);
});
it('writes and updates index for new dataset, testCaseIds are provided', async () => {
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify({}) as any)
);
fs.existsSync = jest.fn(() => false);
const dataset: Dataset = SAMPLE_DATASET_1_V1.map((s, i) => ({
testCaseId: TEST_CASE_ID + `index${i}`,
...s,
}));
const datasetMetadata = await DatasetStore.createDataset({
...CREATE_DATASET_REQUEST,
data: dataset,
datasetId: SAMPLE_DATASET_ID_1,
});
expect(fs.promises.writeFile).toHaveBeenCalledTimes(2);
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
1,
expect.stringContaining(`datasets/${SAMPLE_DATASET_ID_1}.json`),
JSON.stringify(dataset)
);
const metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
};
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
2,
expect.stringContaining('datasets/index.json'),
JSON.stringify(metadataMap)
);
expect(datasetMetadata).toMatchObject(SAMPLE_DATASET_METADATA_1_V1);
});
it('creates new dataset, with schema', async () => {
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify({}) as any)
);
fs.existsSync = jest.fn(() => false);
const dataset: Dataset = SAMPLE_DATASET_1_V1.map((s) => ({
testCaseId: TEST_CASE_ID,
...s,
}));
const datasetMetadata = await DatasetStore.createDataset({
...CREATE_DATASET_REQUEST_WITH_SCHEMA,
datasetId: SAMPLE_DATASET_ID_1,
});
expect(datasetMetadata.schema).toMatchObject({
inputSchema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
referenceSchema: {
type: 'number',
$schema: 'http://json-schema.org/draft-07/schema#',
},
});
expect(datasetMetadata.targetAction).toEqual('/flow/my-flow');
});
it('fails request if dataset already exists', async () => {
fs.existsSync = jest.fn(() => true);
expect(async () => {
await DatasetStore.createDataset(CREATE_DATASET_REQUEST);
}).rejects.toThrow();
expect(fs.promises.writeFile).toBeCalledTimes(0);
});
it('fails if datasetId is invalid', async () => {
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify({}) as any)
);
fs.existsSync = jest.fn(() => false);
expect(async () => {
await DatasetStore.createDataset({
...CREATE_DATASET_REQUEST,
datasetId: 'ultracool!@#$@#$%%%!#$%datasetid',
});
}).rejects.toThrow('Invalid datasetId');
});
it('does not fail if datasetId starts with capitals', async () => {
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify({}) as any)
);
fs.existsSync = jest.fn(() => false);
expect(async () => {
await DatasetStore.createDataset({
...CREATE_DATASET_REQUEST,
datasetId: 'UltracoolId',
});
}).not.toThrow();
});
});
describe('updateDataset', () => {
it('succeeds for existing dataset -- append', async () => {
fs.existsSync = jest.fn(() => true);
let metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
const dataset: Dataset = [...SAMPLE_DATASET_1_WITH_IDS];
const getDatasetSpy = jest
.spyOn(DatasetStore, 'getDataset')
.mockImplementation(() => Promise.resolve(dataset));
const datasetMetadata = await DatasetStore.updateDataset({
data: [
{
input: 'A new information on cat dog',
},
],
datasetId: SAMPLE_DATASET_ID_1,
});
expect(fs.promises.writeFile).toHaveBeenCalledTimes(2);
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
1,
expect.stringContaining(`datasets/${SAMPLE_DATASET_ID_1}.json`),
JSON.stringify([
...dataset,
{
testCaseId: TEST_CASE_ID,
input: 'A new information on cat dog',
},
])
);
const updatedMetadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V2,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
2,
expect.stringContaining('datasets/index.json'),
JSON.stringify(updatedMetadataMap)
);
expect(getDatasetSpy).toHaveBeenCalledTimes(1);
expect(datasetMetadata).toMatchObject(SAMPLE_DATASET_METADATA_1_V2);
});
it('succeeds for existing dataset -- append and replace', async () => {
fs.existsSync = jest.fn(() => true);
let metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
const dataset: Dataset = [...SAMPLE_DATASET_1_WITH_IDS];
const getDatasetSpy = jest
.spyOn(DatasetStore, 'getDataset')
.mockImplementation(() => Promise.resolve(dataset));
const datasetMetadata = await DatasetStore.updateDataset({
data: [
{
input: 'A new information on cat dog',
},
{
testCaseId: '1',
input: 'Other information on hot dog',
},
],
datasetId: SAMPLE_DATASET_ID_1,
});
expect(fs.promises.writeFile).toHaveBeenCalledTimes(2);
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
1,
expect.stringContaining(`datasets/${SAMPLE_DATASET_ID_1}.json`),
JSON.stringify([
{
testCaseId: '1',
input: 'Other information on hot dog',
},
{
testCaseId: '2',
input: 'Dogs are beautiful',
},
{
testCaseId: TEST_CASE_ID,
input: 'A new information on cat dog',
},
])
);
const updatedMetadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V2,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
expect(fs.promises.writeFile).toHaveBeenNthCalledWith(
2,
expect.stringContaining('datasets/index.json'),
JSON.stringify(updatedMetadataMap)
);
expect(getDatasetSpy).toHaveBeenCalledTimes(1);
expect(datasetMetadata).toMatchObject(SAMPLE_DATASET_METADATA_1_V2);
});
it('succeeds for existing dataset -- with schema', async () => {
fs.existsSync = jest.fn(() => true);
let metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
// For index file reads
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
fs.promises.writeFile = jest.fn(async () => Promise.resolve(undefined));
fs.promises.appendFile = jest.fn(async () => Promise.resolve(undefined));
const datasetMetadata = await DatasetStore.updateDataset({
datasetId: SAMPLE_DATASET_ID_1,
schema: {
inputSchema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
targetAction: '/flow/my-flow-2',
});
expect(datasetMetadata.schema).toMatchObject({
inputSchema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
});
expect(datasetMetadata.targetAction).toEqual('/flow/my-flow-2');
});
it('fails for non existing dataset', async () => {
fs.existsSync = jest.fn(() => false);
expect(async () => {
await DatasetStore.updateDataset(UPDATE_DATASET_REQUEST);
}).rejects.toThrow();
expect(fs.promises.writeFile).toBeCalledTimes(0);
});
});
describe('listDatasets', () => {
it('succeeds for zero datasets', async () => {
fs.existsSync = jest.fn(() => false);
const metadatas = await DatasetStore.listDatasets();
expect(metadatas).toMatchObject([]);
});
it('succeeds for existing datasets', async () => {
fs.existsSync = jest.fn(() => true);
const metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
const metadatas = await DatasetStore.listDatasets();
expect(metadatas).toMatchObject([
SAMPLE_DATASET_METADATA_1_V1,
SAMPLE_DATASET_METADATA_2,
]);
});
});
describe('getDataset', () => {
it('succeeds for existing dataset', async () => {
const dataset: Dataset = SAMPLE_DATASET_1_V1.map((s) => ({
...s,
testCaseId: 'id-x',
}));
fs.existsSync = jest.fn(() => true);
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(dataset) as any)
);
const fetchedDataset = await DatasetStore.getDataset(SAMPLE_DATASET_ID_1);
expect(fetchedDataset).toMatchObject(dataset);
});
it('fails for non existing dataset', async () => {
// TODO: Implement this.
});
});
describe('deleteDataset', () => {
it('deletes dataset and updates index', async () => {
fs.promises.rm = jest.fn(async () => Promise.resolve());
let metadataMap = {
[SAMPLE_DATASET_ID_1]: SAMPLE_DATASET_METADATA_1_V1,
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
await DatasetStore.deleteDataset(SAMPLE_DATASET_ID_1);
expect(fs.promises.rm).toHaveBeenCalledWith(
expect.stringContaining(`datasets/${SAMPLE_DATASET_ID_1}.json`)
);
let updatedMetadataMap = {
[SAMPLE_DATASET_ID_2]: SAMPLE_DATASET_METADATA_2,
};
expect(fs.promises.writeFile).toHaveBeenCalledWith(
expect.stringContaining('datasets/index.json'),
JSON.stringify(updatedMetadataMap)
);
});
});
describe('generateDatasetId', () => {
it('returns ID if present', async () => {
const id = await (
DatasetStore as LocalFileDatasetStore
).generateDatasetId('some-id');
expect(id).toBe('some-id');
});
it('returns full ID if nothing is provided', async () => {
const id = await (
DatasetStore as LocalFileDatasetStore
).generateDatasetId();
expect(id).toBe('12345678');
});
it('throws if no unique ID is generated', async () => {
let metadataMap = {
['some-id']: SAMPLE_DATASET_METADATA_1_V1,
};
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
expect(async () => {
await (DatasetStore as LocalFileDatasetStore).generateDatasetId(
'some-id'
);
}).rejects.toThrow('Dataset ID not unique');
});
it('throws if datasetId is too long', async () => {
expect(async () => {
await (DatasetStore as LocalFileDatasetStore).generateDatasetId(
'toolongdatasetid'.repeat(5)
);
}).rejects.toThrow('Invalid datasetId');
});
it('throws if datasetId is invalid', async () => {
expect(async () => {
await (DatasetStore as LocalFileDatasetStore).generateDatasetId(
'my@#$%@#$%datasetid'
);
}).rejects.toThrow('Invalid datasetId');
});
it('throws if UUID is not unique', async () => {
let metadataMap = {
['12345678']: SAMPLE_DATASET_METADATA_1_V1,
};
fs.promises.readFile = jest.fn(async () =>
Promise.resolve(JSON.stringify(metadataMap) as any)
);
expect(async () => {
await (DatasetStore as LocalFileDatasetStore).generateDatasetId();
}).rejects.toThrow('Dataset ID not unique');
});
});
});