File size: 2,153 Bytes
7dc28be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import type { FastMCP } from 'fastmcp';
import { describe, expect, it } from 'vitest';
import { toJsonSchema } from 'xsschema';
import { register as registerAppendRows } from './appendSpreadsheetRows.js';
import { register as registerAppendTableRows } from './appendTableRows.js';
import { register as registerBatchWrite } from './batchWrite.js';
import { register as registerCreateSpreadsheet } from './createSpreadsheet.js';
import { register as registerWriteSpreadsheet } from './writeSpreadsheet.js';

type ToolConfig = Parameters<FastMCP['addTool']>[0];

function captureTool(register: (server: FastMCP) => void): ToolConfig {
  let captured: ToolConfig | undefined;
  const server = {
    addTool: (tool: ToolConfig) => {
      captured = tool;
    },
  };

  register(server as FastMCP);

  if (!captured) throw new Error('Tool registration did not call addTool.');
  return captured;
}

async function buildInputSchema(tool: ToolConfig) {
  if (!tool.parameters) throw new Error(`${tool.name} has no parameter schema.`);
  return toJsonSchema(tool.parameters) as Promise<any>;
}

function expectNestedArrayItems(schema: any, propertyPath: string[]) {
  const schemaProperty = propertyPath.reduce((current, key) => current?.properties?.[key], schema);

  expect(schemaProperty?.items).toBeDefined();
  expect(schemaProperty.items?.items).toBeDefined();
}

describe('spreadsheet value tool schemas', () => {
  it.each([
    [registerWriteSpreadsheet, ['values']],
    [registerAppendRows, ['values']],
    [registerAppendTableRows, ['values']],
    [registerCreateSpreadsheet, ['initialData']],
    [registerBatchWrite, ['data', 'items', 'values']],
  ] as const)(
    'defines JSON Schema items for nested cell arrays',
    async (register, propertyPath) => {
      const tool = captureTool(register);
      const schema = await buildInputSchema(tool);

      if (propertyPath[1] === 'items') {
        const dataItems = schema.properties?.data?.items;
        expect(dataItems).toBeDefined();
        expectNestedArrayItems(dataItems, ['values']);
        return;
      }

      expectNestedArrayItems(schema, [...propertyPath]);
    }
  );
});