Skip to main content
Glama

Dodo Payments

Official
by dodopayments
options.test.ts16.2 kB
import { parseCLIOptions, parseQueryOptions } from '../src/options'; import { Filter } from '../src/tools'; import { parseEmbeddedJSON } from '../src/compat'; // Mock process.argv const mockArgv = (args: string[]) => { const originalArgv = process.argv; process.argv = ['node', 'test.js', ...args]; return () => { process.argv = originalArgv; }; }; describe('parseCLIOptions', () => { it('should parse basic filter options', () => { const cleanup = mockArgv([ '--tool=test-tool', '--resource=test-resource', '--operation=read', '--tag=test-tag', ]); const result = parseCLIOptions(); expect(result.filters).toEqual([ { type: 'tag', op: 'include', value: 'test-tag' }, { type: 'resource', op: 'include', value: 'test-resource' }, { type: 'tool', op: 'include', value: 'test-tool' }, { type: 'operation', op: 'include', value: 'read' }, ] as Filter[]); expect(result.capabilities).toEqual({}); expect(result.list).toBe(false); cleanup(); }); it('should parse exclusion filters', () => { const cleanup = mockArgv([ '--no-tool=exclude-tool', '--no-resource=exclude-resource', '--no-operation=write', '--no-tag=exclude-tag', ]); const result = parseCLIOptions(); expect(result.filters).toEqual([ { type: 'tag', op: 'exclude', value: 'exclude-tag' }, { type: 'resource', op: 'exclude', value: 'exclude-resource' }, { type: 'tool', op: 'exclude', value: 'exclude-tool' }, { type: 'operation', op: 'exclude', value: 'write' }, ] as Filter[]); expect(result.capabilities).toEqual({}); cleanup(); }); it('should parse client presets', () => { const cleanup = mockArgv(['--client=openai-agents']); const result = parseCLIOptions(); expect(result.client).toEqual('openai-agents'); cleanup(); }); it('should parse individual capabilities', () => { const cleanup = mockArgv([ '--capability=top-level-unions', '--capability=valid-json', '--capability=refs', '--capability=unions', '--capability=tool-name-length=40', ]); const result = parseCLIOptions(); expect(result.capabilities).toEqual({ topLevelUnions: true, validJson: true, refs: true, unions: true, toolNameLength: 40, }); cleanup(); }); it('should handle list option', () => { const cleanup = mockArgv(['--list']); const result = parseCLIOptions(); expect(result.list).toBe(true); cleanup(); }); it('should handle multiple filters of the same type', () => { const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); const result = parseCLIOptions(); expect(result.filters).toEqual([ { type: 'resource', op: 'include', value: 'res1' }, { type: 'resource', op: 'include', value: 'res2' }, { type: 'tool', op: 'include', value: 'tool1' }, { type: 'tool', op: 'include', value: 'tool2' }, ] as Filter[]); cleanup(); }); it('should handle comma-separated values in array options', () => { const cleanup = mockArgv([ '--tool=tool1,tool2', '--resource=res1,res2', '--capability=top-level-unions,valid-json,unions', ]); const result = parseCLIOptions(); expect(result.filters).toEqual([ { type: 'resource', op: 'include', value: 'res1' }, { type: 'resource', op: 'include', value: 'res2' }, { type: 'tool', op: 'include', value: 'tool1' }, { type: 'tool', op: 'include', value: 'tool2' }, ] as Filter[]); expect(result.capabilities).toEqual({ topLevelUnions: true, validJson: true, unions: true, }); cleanup(); }); it('should handle invalid tool-name-length format', () => { const cleanup = mockArgv(['--capability=tool-name-length=invalid']); // Mock console.error to prevent output during test const originalError = console.error; console.error = jest.fn(); expect(() => parseCLIOptions()).toThrow(); console.error = originalError; cleanup(); }); it('should handle unknown capability', () => { const cleanup = mockArgv(['--capability=unknown-capability']); // Mock console.error to prevent output during test const originalError = console.error; console.error = jest.fn(); expect(() => parseCLIOptions()).toThrow(); console.error = originalError; cleanup(); }); }); describe('parseQueryOptions', () => { const defaultOptions = { client: undefined, includeDynamicTools: undefined, includeAllTools: undefined, filters: [], capabilities: { topLevelUnions: true, validJson: true, refs: true, unions: true, formats: true, toolNameLength: undefined, }, }; it('should parse basic filter options from query string', () => { const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; const result = parseQueryOptions(defaultOptions, query); expect(result.filters).toEqual([ { type: 'resource', op: 'include', value: 'test-resource' }, { type: 'operation', op: 'include', value: 'read' }, { type: 'tag', op: 'include', value: 'test-tag' }, { type: 'tool', op: 'include', value: 'test-tool' }, ]); expect(result.capabilities).toEqual({ topLevelUnions: true, validJson: true, refs: true, unions: true, formats: true, toolNameLength: undefined, }); }); it('should parse exclusion filters from query string', () => { const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; const result = parseQueryOptions(defaultOptions, query); expect(result.filters).toEqual([ { type: 'resource', op: 'exclude', value: 'exclude-resource' }, { type: 'operation', op: 'exclude', value: 'write' }, { type: 'tag', op: 'exclude', value: 'exclude-tag' }, { type: 'tool', op: 'exclude', value: 'exclude-tool' }, ]); }); it('should parse client option from query string', () => { const query = 'client=openai-agents'; const result = parseQueryOptions(defaultOptions, query); expect(result.client).toBe('openai-agents'); }); it('should parse client capabilities from query string', () => { const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; const result = parseQueryOptions(defaultOptions, query); expect(result.capabilities).toEqual({ topLevelUnions: true, validJson: true, refs: true, unions: true, formats: true, toolNameLength: 40, }); }); it('should parse no-capability options from query string', () => { const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; const result = parseQueryOptions(defaultOptions, query); expect(result.capabilities).toEqual({ topLevelUnions: false, validJson: true, refs: false, unions: true, formats: false, toolNameLength: undefined, }); }); it('should parse tools options from query string', () => { const query = 'tools=dynamic&tools=all'; const result = parseQueryOptions(defaultOptions, query); expect(result.includeDynamicTools).toBe(true); expect(result.includeAllTools).toBe(true); }); it('should parse no-tools options from query string', () => { const query = 'tools=dynamic&tools=all&no_tools=dynamic'; const result = parseQueryOptions(defaultOptions, query); expect(result.includeDynamicTools).toBe(false); expect(result.includeAllTools).toBe(true); }); it('should handle array values in query string', () => { const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; const result = parseQueryOptions(defaultOptions, query); expect(result.filters).toEqual([ { type: 'resource', op: 'include', value: 'res1' }, { type: 'resource', op: 'include', value: 'res2' }, { type: 'tool', op: 'include', value: 'tool1' }, { type: 'tool', op: 'include', value: 'tool2' }, ]); }); it('should merge with default options', () => { const defaultWithFilters = { ...defaultOptions, filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], client: 'cursor' as const, includeDynamicTools: true, }; const query = 'tool=new-tool&resource=new-resource'; const result = parseQueryOptions(defaultWithFilters, query); expect(result.filters).toEqual([ { type: 'tag', op: 'include', value: 'existing-tag' }, { type: 'resource', op: 'include', value: 'new-resource' }, { type: 'tool', op: 'include', value: 'new-tool' }, ]); expect(result.client).toBe('cursor'); expect(result.includeDynamicTools).toBe(true); }); it('should override client from default options', () => { const defaultWithClient = { ...defaultOptions, client: 'cursor' as const, }; const query = 'client=openai-agents'; const result = parseQueryOptions(defaultWithClient, query); expect(result.client).toBe('openai-agents'); }); it('should merge capabilities with default options', () => { const defaultWithCapabilities = { ...defaultOptions, capabilities: { topLevelUnions: false, validJson: false, refs: true, unions: true, formats: true, toolNameLength: 30, }, }; const query = 'capability=top-level-unions&no_capability=refs'; const result = parseQueryOptions(defaultWithCapabilities, query); expect(result.capabilities).toEqual({ topLevelUnions: true, validJson: false, refs: false, unions: true, formats: true, toolNameLength: 30, }); }); it('should handle empty query string', () => { const query = ''; const result = parseQueryOptions(defaultOptions, query); expect(result).toEqual(defaultOptions); }); it('should handle invalid query string gracefully', () => { const query = 'invalid=value&operation=invalid-operation'; // Should throw due to Zod validation for invalid operation expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); }); it('should preserve default undefined values when not specified', () => { const defaultWithUndefined = { ...defaultOptions, client: undefined, includeDynamicTools: undefined, includeAllTools: undefined, }; const query = 'tool=test-tool'; const result = parseQueryOptions(defaultWithUndefined, query); expect(result.client).toBeUndefined(); expect(result.includeDynamicTools).toBeFalsy(); expect(result.includeAllTools).toBeFalsy(); }); it('should handle complex query with mixed include and exclude filters', () => { const query = 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; const result = parseQueryOptions(defaultOptions, query); expect(result.filters).toEqual([ { type: 'resource', op: 'include', value: 'include-res' }, { type: 'operation', op: 'include', value: 'read' }, { type: 'tag', op: 'include', value: 'include-tag' }, { type: 'tool', op: 'include', value: 'include-tool' }, { type: 'resource', op: 'exclude', value: 'exclude-res' }, { type: 'tag', op: 'exclude', value: 'exclude-tag' }, { type: 'tool', op: 'exclude', value: 'exclude-tool' }, ]); }); }); describe('parseEmbeddedJSON', () => { it('should not change non-string values', () => { const args = { numberProp: 42, booleanProp: true, objectProp: { nested: 'value' }, arrayProp: [1, 2, 3], nullProp: null, undefinedProp: undefined, }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).toBe(args); // Should return original object since no changes made expect(result['numberProp']).toBe(42); expect(result['booleanProp']).toBe(true); expect(result['objectProp']).toEqual({ nested: 'value' }); expect(result['arrayProp']).toEqual([1, 2, 3]); expect(result['nullProp']).toBe(null); expect(result['undefinedProp']).toBe(undefined); }); it('should parse valid JSON objects in string properties', () => { const args = { jsonObjectString: '{"key": "value", "number": 123}', regularString: 'not json', }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).not.toBe(args); // Should return new object since changes were made expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); expect(result['regularString']).toBe('not json'); }); it('should leave invalid JSON in string properties unchanged', () => { const args = { invalidJson1: '{"key": value}', // Missing quotes around value invalidJson2: '{key: "value"}', // Missing quotes around key invalidJson3: '{"key": "value",}', // Trailing comma invalidJson4: 'just a regular string', emptyString: '', }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).toBe(args); // Should return original object since no changes made expect(result['invalidJson1']).toBe('{"key": value}'); expect(result['invalidJson2']).toBe('{key: "value"}'); expect(result['invalidJson3']).toBe('{"key": "value",}'); expect(result['invalidJson4']).toBe('just a regular string'); expect(result['emptyString']).toBe(''); }); it('should not parse JSON primitives in string properties', () => { const args = { numberString: '123', floatString: '45.67', negativeNumberString: '-89', booleanTrueString: 'true', booleanFalseString: 'false', nullString: 'null', jsonArrayString: '[1, 2, 3, "test"]', regularString: 'not json', }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).toBe(args); // Should return original object since no changes made expect(result['numberString']).toBe('123'); expect(result['floatString']).toBe('45.67'); expect(result['negativeNumberString']).toBe('-89'); expect(result['booleanTrueString']).toBe('true'); expect(result['booleanFalseString']).toBe('false'); expect(result['nullString']).toBe('null'); expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); expect(result['regularString']).toBe('not json'); }); it('should handle mixed valid objects and other JSON types', () => { const args = { validObject: '{"success": true}', invalidObject: '{"missing": quote}', validNumber: '42', validArray: '[1, 2, 3]', keepAsString: 'hello world', nonString: 123, }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).not.toBe(args); // Should return new object since some changes were made expect(result['validObject']).toEqual({ success: true }); expect(result['invalidObject']).toBe('{"missing": quote}'); expect(result['validNumber']).toBe('42'); // Not parsed, remains string expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string expect(result['keepAsString']).toBe('hello world'); expect(result['nonString']).toBe(123); }); it('should return original object when no strings are present', () => { const args = { number: 42, boolean: true, object: { key: 'value' }, }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).toBe(args); // Should return original object since no changes made }); it('should return original object when all strings are invalid JSON', () => { const args = { string1: 'hello', string2: 'world', string3: 'not json at all', }; const schema = {}; const result = parseEmbeddedJSON(args, schema); expect(result).toBe(args); // Should return original object since no changes made }); });

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/dodopayments/dodopayments-node'

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