Skip to main content
Glama

mcp-gsheets

validators.test.tsβ€’27.5 kB
import { describe, it, expect } from 'vitest'; import { validateSpreadsheetId, validateRange, validateGetValuesInput, validateUpdateValuesInput, validateAppendValuesInput, validateClearValuesInput, validateBatchGetValuesInput, validateBatchUpdateValuesInput, validateCreateSpreadsheetInput, validateInsertSheetInput, validateDeleteSheetInput, validateDuplicateSheetInput, validateUpdateSheetPropertiesInput, validateCopyToInput, validateFormatCellsInput, validateUpdateBordersInput, validateMergeCellsInput, validateUnmergeCellsInput, validateAddConditionalFormattingInput, validateInsertRowsInput, } from '../../../src/utils/validators'; import { testSpreadsheetIds, testRanges, testValues, testInputs, testErrors } from '../../fixtures/test-data'; describe('validateSpreadsheetId', () => { it('should accept valid spreadsheet IDs', () => { testSpreadsheetIds.valid.forEach((id) => { expect(validateSpreadsheetId(id)).toBe(true); }); }); it('should reject invalid spreadsheet IDs', () => { testSpreadsheetIds.invalid.forEach((id) => { expect(validateSpreadsheetId(id)).toBe(false); }); }); it('should handle edge cases', () => { expect(validateSpreadsheetId('')).toBe(false); expect(validateSpreadsheetId(' ')).toBe(false); expect(validateSpreadsheetId('a'.repeat(100))).toBe(true); // long valid ID }); }); describe('validateRange', () => { describe('simple ranges', () => { it('should accept valid simple ranges', () => { testRanges.valid.simple.forEach((range) => { expect(validateRange(range)).toBe(true); }); }); it('should accept valid ranges with colon', () => { testRanges.valid.withRange.forEach((range) => { expect(validateRange(range)).toBe(true); }); }); }); describe('ranges with sheet names', () => { it('should accept ranges with standard sheet names', () => { expect(validateRange('Sheet1!A1')).toBe(true); expect(validateRange('MySheet!A1:B10')).toBe(true); }); it('should handle sheet names with special characters', () => { expect(validateRange("'Sheet with Spaces'!A1")).toBe(true); expect(validateRange("'Übungen & Aufgaben'!A1:Z99")).toBe(true); expect(validateRange("'2024-Budget (Final)'!A:A")).toBe(true); expect(validateRange("'Sheet #1'!1:100")).toBe(true); expect(validateRange("'Tom''s Data'!A1")).toBe(true); // escaped apostrophe }); it('should handle edge cases with sheet names', () => { // Check fixture data to see what edge cases are expected to pass const validEdgeCases = [ "'Sheet with Spaces'!A1", "'2024-Budget'!A1:Z99" ]; validEdgeCases.forEach((range) => { expect(validateRange(range)).toBe(true); }); }); }); describe('invalid ranges', () => { it('should reject ranges with multiple exclamation marks', () => { expect(validateRange('Sheet1!A1!B2')).toBe(false); expect(validateRange('Sheet1!!A1')).toBe(false); }); it('should reject empty sheet names', () => { expect(validateRange('!A1')).toBe(false); expect(validateRange(' !A1')).toBe(false); }); it('should reject invalid cell references', () => { expect(validateRange('Sheet1!ABC')).toBe(false); // no row number expect(validateRange('Sheet1!123')).toBe(false); // no column letter expect(validateRange('Sheet1!1A')).toBe(false); // wrong order }); }); describe('case sensitivity', () => { it('should accept both uppercase and lowercase column letters', () => { expect(validateRange('a1')).toBe(true); expect(validateRange('A1')).toBe(true); expect(validateRange('aA1:Bb10')).toBe(true); }); }); }); describe('validateGetValuesInput', () => { it('should validate correct input with all fields', () => { const result = validateGetValuesInput(testInputs.getValues.valid); expect(result).toEqual(testInputs.getValues.valid); }); it('should validate minimal input and add defaults', () => { const result = validateGetValuesInput(testInputs.getValues.minimal); expect(result.spreadsheetId).toBe(testInputs.getValues.minimal.spreadsheetId); expect(result.range).toBe(testInputs.getValues.minimal.range); expect(result.majorDimension).toBe('ROWS'); expect(result.valueRenderOption).toBe('FORMATTED_VALUE'); }); it('should throw error for missing required fields', () => { expect(() => validateGetValuesInput({})).toThrow('spreadsheetId is required'); expect(() => validateGetValuesInput({ spreadsheetId: 'test' })).toThrow('range is required'); }); it('should throw error for invalid types', () => { expect(() => validateGetValuesInput({ spreadsheetId: 123, range: 'A1' })) .toThrow('spreadsheetId is required and must be a string'); expect(() => validateGetValuesInput({ spreadsheetId: 'test', range: ['A1'] })) .toThrow('range is required and must be a string'); }); it('should throw error for invalid spreadsheet ID format', () => { expect(() => validateGetValuesInput({ spreadsheetId: 'invalid id with spaces', range: 'A1', })).toThrow('Invalid spreadsheet ID format'); }); it('should throw error for invalid range format', () => { expect(() => validateGetValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'invalid range', })).toThrow('Invalid range format'); }); it('should handle null and undefined values', () => { expect(() => validateGetValuesInput(null)).toThrow(); expect(() => validateGetValuesInput(undefined)).toThrow(); expect(() => validateGetValuesInput({ spreadsheetId: null, range: 'A1' })) .toThrow('spreadsheetId is required'); expect(() => validateGetValuesInput({ spreadsheetId: 'test', range: undefined })) .toThrow('range is required'); }); }); describe('validateUpdateValuesInput', () => { it('should validate correct input with all fields', () => { const result = validateUpdateValuesInput(testInputs.updateValues.valid); expect(result).toEqual(testInputs.updateValues.valid); }); it('should validate minimal input and add defaults', () => { const result = validateUpdateValuesInput(testInputs.updateValues.minimal); expect(result.valueInputOption).toBe('USER_ENTERED'); }); it('should throw error for missing values array', () => { expect(() => validateUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', })).toThrow('values is required and must be an array'); }); it('should throw error for invalid values type', () => { expect(() => validateUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', values: 'not an array', })).toThrow('values is required and must be an array'); }); it('should accept empty array for values', () => { const result = validateUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', values: [], }); expect(result.values).toEqual([]); }); it('should accept complex nested arrays', () => { const result = validateUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1:C3', values: testValues.mixed, }); expect(result.values).toEqual(testValues.mixed); }); }); describe('validateAppendValuesInput', () => { it('should validate correct input with all fields', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1', values: [['test']], valueInputOption: 'RAW', insertDataOption: 'INSERT_ROWS', }; const result = validateAppendValuesInput(input); expect(result).toEqual(input); }); it('should add default options', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', values: [['test']], }; const result = validateAppendValuesInput(input); expect(result.valueInputOption).toBe('USER_ENTERED'); expect(result.insertDataOption).toBe('OVERWRITE'); }); }); describe('validateClearValuesInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1:B10', }; const result = validateClearValuesInput(input); expect(result).toEqual(input); }); it('should throw error for missing fields', () => { expect(() => validateClearValuesInput({})).toThrow('spreadsheetId is required'); expect(() => validateClearValuesInput({ spreadsheetId: 'test' })).toThrow('range is required'); }); }); describe('validateBatchGetValuesInput', () => { it('should validate correct input with multiple ranges', () => { const result = validateBatchGetValuesInput(testInputs.batchGetValues.valid); expect(result).toEqual(testInputs.batchGetValues.valid); }); it('should validate minimal input', () => { const result = validateBatchGetValuesInput(testInputs.batchGetValues.minimal); expect(result.ranges).toEqual(['A1']); expect(result.majorDimension).toBe('ROWS'); expect(result.valueRenderOption).toBe('FORMATTED_VALUE'); }); it('should throw error for empty ranges array', () => { expect(() => validateBatchGetValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', ranges: [], })).toThrow('ranges is required and must be a non-empty array'); }); it('should throw error for invalid ranges', () => { expect(() => validateBatchGetValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', ranges: ['A1', 'invalid range'], })).toThrow('Invalid range format: invalid range'); }); it('should throw error for non-array ranges', () => { expect(() => validateBatchGetValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', ranges: 'A1,B2', })).toThrow('ranges is required and must be a non-empty array'); }); }); describe('validateBatchUpdateValuesInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', data: [ { range: 'A1:B2', values: [['A1', 'B1'], ['A2', 'B2']] }, { range: 'C1:D2', values: [['C1', 'D1'], ['C2', 'D2']] }, ], valueInputOption: 'RAW', }; const result = validateBatchUpdateValuesInput(input); expect(result).toEqual(input); }); it('should add default valueInputOption', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', data: [{ range: 'A1', values: [['test']] }], }; const result = validateBatchUpdateValuesInput(input); expect(result.valueInputOption).toBe('USER_ENTERED'); }); it('should throw error for empty data array', () => { expect(() => validateBatchUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', data: [], })).toThrow('data is required and must be a non-empty array'); }); it('should throw error for invalid data items', () => { expect(() => validateBatchUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', data: [{ range: 'A1' }], // missing values })).toThrow('Each data item must have range and values properties'); expect(() => validateBatchUpdateValuesInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', data: [{ values: [['test']] }], // missing range })).toThrow('Each data item must have range and values properties'); }); }); describe('validateCreateSpreadsheetInput', () => { it('should validate correct input with sheets', () => { const result = validateCreateSpreadsheetInput(testInputs.createSpreadsheet.valid); expect(result).toEqual(testInputs.createSpreadsheet.valid); }); it('should validate minimal input', () => { const result = validateCreateSpreadsheetInput(testInputs.createSpreadsheet.minimal); expect(result.title).toBe('Simple Test'); expect(result.sheets).toBeUndefined(); }); it('should throw error for missing title', () => { expect(() => validateCreateSpreadsheetInput({})).toThrow('title is required'); expect(() => validateCreateSpreadsheetInput({ title: null })).toThrow('title is required'); expect(() => validateCreateSpreadsheetInput({ title: 123 })).toThrow('title is required and must be a string'); }); }); describe('validateInsertSheetInput', () => { it('should validate correct input with all fields', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', title: 'New Sheet', index: 1, rowCount: 500, columnCount: 50, }; const result = validateInsertSheetInput(input); expect(result).toEqual(input); }); it('should add default dimensions', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', title: 'New Sheet', }; const result = validateInsertSheetInput(input); expect(result.rowCount).toBe(1000); expect(result.columnCount).toBe(26); }); it('should throw error for missing required fields', () => { expect(() => validateInsertSheetInput({})).toThrow('spreadsheetId is required'); expect(() => validateInsertSheetInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', })).toThrow('title is required'); }); }); describe('validateDeleteSheetInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, }; const result = validateDeleteSheetInput(input); expect(result).toEqual(input); }); it('should accept sheetId of 0', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 0, }; const result = validateDeleteSheetInput(input); expect(result.sheetId).toBe(0); }); it('should throw error for missing sheetId', () => { expect(() => validateDeleteSheetInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', })).toThrow('sheetId is required'); }); it('should throw error for invalid sheetId type', () => { expect(() => validateDeleteSheetInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: '123', })).toThrow('sheetId is required and must be a number'); }); }); describe('validateDuplicateSheetInput', () => { it('should validate correct input with all fields', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, insertSheetIndex: 2, newSheetName: 'Copy of Sheet', }; const result = validateDuplicateSheetInput(input); expect(result).toEqual(input); }); it('should validate minimal input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 0, }; const result = validateDuplicateSheetInput(input); expect(result).toEqual(input); }); }); describe('validateUpdateSheetPropertiesInput', () => { it('should validate correct input with all properties', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, title: 'Updated Title', gridProperties: { rowCount: 2000, columnCount: 50 }, tabColor: { red: 1.0, green: 0.5, blue: 0.0 }, }; const result = validateUpdateSheetPropertiesInput(input); expect(result).toEqual(input); }); it('should validate minimal input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 0, }; const result = validateUpdateSheetPropertiesInput(input); expect(result).toEqual(input); }); }); describe('validateCopyToInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, destinationSpreadsheetId: '1a2B3c4D5e6F7g8H9i0J1k2L3m4N5o6P7q8R9s0T1u2V', }; const result = validateCopyToInput(input); expect(result).toEqual(input); }); it('should throw error for missing destinationSpreadsheetId', () => { expect(() => validateCopyToInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, })).toThrow('destinationSpreadsheetId is required'); }); it('should throw error for invalid destination spreadsheet ID', () => { expect(() => validateCopyToInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', sheetId: 123, destinationSpreadsheetId: 'invalid id!', })).toThrow('Invalid destination spreadsheet ID format'); }); }); describe('validateFormatCellsInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1:B10', format: { backgroundColor: { red: 1.0, green: 0.9, blue: 0.9 }, textFormat: { bold: true, fontSize: 12 }, }, }; const result = validateFormatCellsInput(input); expect(result).toEqual(input); }); it('should throw error for missing format', () => { expect(() => validateFormatCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', })).toThrow('format is required'); }); it('should throw error for invalid format type', () => { expect(() => validateFormatCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', format: 'invalid', })).toThrow('format is required and must be an object'); }); it('should accept empty format object', () => { const result = validateFormatCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', format: {}, }); expect(result.format).toEqual({}); }); }); describe('validateUpdateBordersInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1:B10', borders: { top: { style: 'SOLID', width: 1, color: { red: 0, green: 0, blue: 0 } }, bottom: { style: 'SOLID', width: 1, color: { red: 0, green: 0, blue: 0 } }, }, }; const result = validateUpdateBordersInput(input); expect(result).toEqual(input); }); it('should throw error for missing borders', () => { expect(() => validateUpdateBordersInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1', })).toThrow('borders is required'); }); }); describe('validateMergeCellsInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1:B2', mergeType: 'MERGE_ALL', }; const result = validateMergeCellsInput(input); expect(result).toEqual(input); }); it('should accept all valid merge types', () => { const validTypes = ['MERGE_ALL', 'MERGE_COLUMNS', 'MERGE_ROWS']; validTypes.forEach((mergeType) => { const result = validateMergeCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1:B2', mergeType, }); expect(result.mergeType).toBe(mergeType); }); }); it('should throw error for invalid merge type', () => { expect(() => validateMergeCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1:B2', mergeType: 'INVALID_TYPE', })).toThrow('Invalid mergeType. Must be one of: MERGE_ALL, MERGE_COLUMNS, MERGE_ROWS'); }); it('should throw error for missing mergeType', () => { expect(() => validateMergeCellsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'A1:B2', })).toThrow('mergeType is required'); }); }); describe('validateUnmergeCellsInput', () => { it('should validate correct input', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A1:B2', }; const result = validateUnmergeCellsInput(input); expect(result).toEqual(input); }); }); describe('validateAddConditionalFormattingInput', () => { it('should validate correct input with boolean rule', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [ { ranges: ['Sheet1!A1:A10'], booleanRule: { condition: { type: 'NUMBER_GREATER', values: [{ userEnteredValue: '100' }] }, format: { backgroundColor: { red: 1, green: 0, blue: 0 } }, }, }, ], }; const result = validateAddConditionalFormattingInput(input); expect(result).toEqual(input); }); it('should validate correct input with gradient rule', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [ { ranges: ['Sheet1!B1:B10'], gradientRule: { minpoint: { color: { red: 1, green: 1, blue: 1 }, type: 'MIN' }, maxpoint: { color: { red: 0, green: 1, blue: 0 }, type: 'MAX' }, }, }, ], }; const result = validateAddConditionalFormattingInput(input); expect(result).toEqual(input); }); it('should validate multiple rules with multiple ranges', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [ { ranges: ['Sheet1!A1:A10', 'Sheet1!C1:C10'], booleanRule: { condition: { type: 'TEXT_CONTAINS', values: [{ userEnteredValue: 'test' }] }, format: { textFormat: { bold: true } }, }, }, { ranges: ['Sheet2!A:A'], gradientRule: { minpoint: { color: { red: 1, green: 0, blue: 0 }, type: 'NUMBER', value: '0' }, maxpoint: { color: { red: 0, green: 1, blue: 0 }, type: 'NUMBER', value: '100' }, }, }, ], }; const result = validateAddConditionalFormattingInput(input); expect(result).toEqual(input); }); it('should throw error for empty rules array', () => { expect(() => validateAddConditionalFormattingInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [], })).toThrow('rules is required and must be a non-empty array'); }); it('should throw error for rule without ranges', () => { expect(() => validateAddConditionalFormattingInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [{ booleanRule: {} }], })).toThrow('Each rule must have a non-empty ranges array'); }); it('should throw error for empty ranges in rule', () => { expect(() => validateAddConditionalFormattingInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [{ ranges: [], booleanRule: {} }], })).toThrow('Each rule must have a non-empty ranges array'); }); it('should throw error for invalid range in rule', () => { expect(() => validateAddConditionalFormattingInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [ { ranges: ['Sheet1!A1', 'invalid range'], booleanRule: {}, }, ], })).toThrow('Invalid range format: invalid range'); }); it('should throw error for rule without booleanRule or gradientRule', () => { expect(() => validateAddConditionalFormattingInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', rules: [{ ranges: ['A1:A10'] }], })).toThrow('Each rule must have either booleanRule or gradientRule'); }); }); describe('validateInsertRowsInput', () => { it('should accept valid input with minimal parameters', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', }; const result = validateInsertRowsInput(input); expect(result).toEqual({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', rows: 1, position: 'BEFORE', inheritFromBefore: false, values: undefined, valueInputOption: 'USER_ENTERED', }); }); it('should accept valid input with all parameters', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A10', rows: 3, position: 'AFTER', inheritFromBefore: true, values: [['a', 'b'], ['c', 'd']], valueInputOption: 'RAW', }; const result = validateInsertRowsInput(input); expect(result).toEqual(input); }); it('should reject invalid spreadsheet ID', () => { expect(() => validateInsertRowsInput({ spreadsheetId: 'invalid id!', range: 'Sheet1!A5', })).toThrow('Invalid spreadsheet ID format'); }); it('should reject invalid range', () => { expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'invalid range', })).toThrow(); }); it('should reject invalid rows count', () => { expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', rows: 0, })).toThrow('Rows must be a positive number'); expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', rows: -1, })).toThrow('Rows must be a positive number'); expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', rows: 'not a number', })).toThrow('Rows must be a positive number'); }); it('should reject invalid position', () => { expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', position: 'INVALID', })).toThrow('Position must be either BEFORE or AFTER'); }); it('should reject invalid values format', () => { expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', values: 'not an array', })).toThrow('Values must be a 2D array'); expect(() => validateInsertRowsInput({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', values: ['not', '2D', 'array'], })).toThrow('Values must be a 2D array'); }); it('should apply default values correctly', () => { const input = { spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range: 'Sheet1!A5', rows: 5, }; const result = validateInsertRowsInput(input); expect(result.position).toBe('BEFORE'); expect(result.inheritFromBefore).toBe(false); expect(result.valueInputOption).toBe('USER_ENTERED'); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freema/mcp-gsheets'

If you have feedback or need assistance with the MCP directory API, please join our Discord server