MCP Terminal Server

/** * 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 * as assert from 'assert'; import { describe, it } from 'node:test'; import { extractItems, extractJson, parsePartialJson } from '../src/extract'; describe('extract', () => { describe('extractItems', () => { interface TestStep { chunk: string; want: unknown[]; } interface TestCase { name: string; steps: TestStep[]; } const testCases: TestCase[] = [ { name: 'handles simple array in chunks', steps: [ { chunk: '[', want: [] }, { chunk: '{"a": 1},', want: [{ a: 1 }] }, { chunk: '{"b": 2}', want: [{ b: 2 }] }, { chunk: ']', want: [] }, ], }, { name: 'handles nested objects', steps: [ { chunk: '[{"outer": {', want: [] }, { chunk: '"inner": "value"}},', want: [{ outer: { inner: 'value' } }], }, { chunk: '{"next": true}]', want: [{ next: true }] }, ], }, { name: 'handles escaped characters', steps: [ { chunk: '[{"text": "line1\\n', want: [] }, { chunk: 'line2"},', want: [{ text: 'line1\nline2' }] }, { chunk: '{"text": "tab\\there"}]', want: [{ text: 'tab\there' }] }, ], }, { name: 'ignores content before first array', steps: [ { chunk: 'Here is an array:\n```json\n\n[', want: [] }, { chunk: '{"a": 1},', want: [{ a: 1 }] }, { chunk: '{"b": 2}]\n```\nDid you like my array?', want: [{ b: 2 }] }, ], }, { name: 'handles whitespace', steps: [ { chunk: '[\n ', want: [] }, { chunk: '{"a": 1},\n ', want: [{ a: 1 }] }, { chunk: '{"b": 2}\n]', want: [{ b: 2 }] }, ], }, ]; for (const tc of testCases) { it(tc.name, () => { let text = ''; let cursor = 0; for (const step of tc.steps) { text += step.chunk; const result = extractItems(text, cursor); assert.deepStrictEqual(result.items, step.want); cursor = result.cursor; } }); } }); describe('extractJson', () => { interface TestCase { name: string; input: { text: string; throwOnBadJson?: boolean; }; expected?: unknown; throws?: boolean; } const testCases: TestCase[] = [ { name: 'extracts simple object', input: { text: 'prefix{"a":1}suffix', }, expected: { a: 1 }, }, { name: 'extracts simple array', input: { text: 'prefix[1,2,3]suffix', }, expected: [1, 2, 3], }, { name: 'handles nested structures', input: { text: 'text{"a":{"b":[1,2]}}more', }, expected: { a: { b: [1, 2] } }, }, { name: 'handles strings with braces', input: { text: '{"text": "not {a} json"}', }, expected: { text: 'not {a} json' }, }, { name: 'returns null for invalid JSON without throw', input: { text: 'not json at all', }, expected: null, }, { name: 'throws for invalid JSON with throw flag', input: { text: 'not json at all', throwOnBadJson: true, }, throws: true, }, ]; for (const tc of testCases) { it(tc.name, () => { if (tc.throws) { assert.throws(() => { extractJson(tc.input.text, true); }); } else { const result = extractJson( tc.input.text, (tc.input.throwOnBadJson || false) as any ); assert.deepStrictEqual(result, tc.expected); } }); } }); describe('parsePartialJson', () => { interface TestCase { name: string; input: string; expected: unknown; } const testCases: TestCase[] = [ { name: 'parses complete object', input: '{"a":1,"b":2}', expected: { a: 1, b: 2 }, }, { name: 'parses partial object', input: '{"a":1,"b":', expected: { a: 1 }, }, { name: 'parses partial array', input: '[1,2,3,', expected: [1, 2, 3], }, { name: 'parses nested partial structures', input: '{"a":{"b":1,"c":]}}', expected: { a: { b: 1 } }, }, ]; for (const tc of testCases) { it(tc.name, () => { const result = parsePartialJson(tc.input); assert.deepStrictEqual(result, tc.expected); }); } }); });