Skip to main content
Glama
lamlValidator.test.ts19.4 kB
import { validateLaml, McpSession, LamlValidationResult } from '../lamlValidator.js'; import { parseLaml } from '../lamlParser.js'; // Mock session for testing function createMockSession(): McpSession { const errors: Array<{ code: string; message: string; context?: unknown }> = []; const warnings: Array<{ code: string; message: string; context?: unknown }> = []; return { logger: { addError: (error) => errors.push(error), addWarning: (warning) => warnings.push(warning) }, throwError: (error) => { errors.push(error); throw new Error(error.message); }, _errors: errors, _warnings: warnings } as McpSession & { _errors: unknown[]; _warnings: unknown[] }; } describe('LAML Validator', () => { describe('Basic validation', () => { test('should accept valid LAML document', () => { const validLaml = ` $meta: name: 'testDocument' purpose: 'Test LAML document for validation' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.domain.example'] section1: property: 'value' hasFlag: true `; const session = createMockSession(); const parseResult = parseLaml(validLaml); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect((session as any)._errors).toHaveLength(0); }); test('should fail on invalid YAML', () => { const invalidYaml = 'invalid: yaml: content: [[['; const session = createMockSession(); const parseResult = parseLaml(invalidYaml); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.length).toBeGreaterThan(0); expect((session as any)._errors[0].code).toBe('LAML_YAML_SYNTAX_ERROR'); }); }); describe('$meta section validation', () => { test('should auto-fix missing $meta section', () => { const missingMeta = ` section1: property: 'value' `; const session = createMockSession(); const parseResult = parseLaml(missingMeta); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); // Invalid due to empty domains array expect(result.autoFixedIssues).toContain('Added missing $meta section'); expect(result.fixedDocument).toBeDefined(); // Document is created even if invalid // Should have domains empty error (after auto-fixing domains field) expect((session as any)._errors.some((e: any) => e.code === 'LAML_DOMAINS_EMPTY')).toBe(true); }); test('should auto-fix $meta position', () => { const misplacedMeta = ` section1: property: 'value' $meta: name: 'test' purpose: 'Test document' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.domain'] `; const session = createMockSession(); const parseResult = parseLaml(misplacedMeta); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect(result.autoFixedIssues).toContain('Moved $meta section to first position'); expect(result.fixedDocument).toBeDefined(); }); test('should auto-fix missing required fields in $meta', () => { const incompleteMeta = ` $meta: name: 'test' section1: property: 'value' `; const session = createMockSession(); const parseResult = parseLaml(incompleteMeta); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect(result.autoFixedIssues.some(issue => issue.includes('purpose'))).toBe(true); expect(result.autoFixedIssues.some(issue => issue.includes('version'))).toBe(true); expect(result.autoFixedIssues.some(issue => issue.includes('spec'))).toBe(true); expect(result.autoFixedIssues.some(issue => issue.includes('domains'))).toBe(true); expect((session as any)._errors.some((e: any) => e.code === 'LAML_DOMAINS_EMPTY')).toBe(true); }); test('should create proper LAML value types when auto-fixing', () => { const missingMeta = ` section1: property: 'value' `; const session = createMockSession(); const parseResult = parseLaml(missingMeta); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); // Invalid due to empty domains expect(result.fixedSource).toBeDefined(); // Should be defined when there are auto-fixes, even if invalid // The document should have auto-fix attempts but still be invalid expect(result.autoFixedIssues.length).toBeGreaterThan(0); expect((session as any)._errors.length).toBeGreaterThan(0); // Should have detected the empty domains issue expect((session as any)._errors.some((e: any) => e.code === 'LAML_DOMAINS_EMPTY' || e.code === 'LAML_META_DOMAINS_INVALID_TYPE')).toBe(true); }); test('should error on invalid $meta type', () => { const invalidMetaType = ` $meta: "this should be an object" section1: property: 'value' `; const session = createMockSession(); const parseResult = parseLaml(invalidMetaType); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.some((e: any) => e.code === 'LAML_META_INVALID_TYPE')).toBe(true); }); }); describe('Reference validation', () => { test('should validate correct references', () => { const validReferences = ` $meta: name: 'testReferences' purpose: 'Test reference validation' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.references'] section1: property: 'value' reference: '*section1.property' section2: nested: value: 'test' deepRef: '*section2.nested.value' `; const session = createMockSession(); const parseResult = parseLaml(validReferences); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect((session as any)._errors.filter((e: any) => e.code.includes('REFERENCE')).length).toBe(0); }); test('should error on invalid reference format', () => { const invalidRefFormat = ` $meta: name: 'testInvalidRef' purpose: 'Test invalid reference format' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.references'] section1: property: 'value' badRef: '*invalid-reference.format' `; const session = createMockSession(); const parseResult = parseLaml(invalidRefFormat); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.some((e: any) => e.code === 'LAML_INVALID_REFERENCE_FORMAT')).toBe(true); }); test('should error on non-existent reference', () => { const nonExistentRef = ` $meta: name: 'testNonExistentRef' purpose: 'Test non-existent reference' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.references'] section1: property: 'value' badRef: '*nonExistent.section.property' `; const session = createMockSession(); const parseResult = parseLaml(nonExistentRef); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.some((e: any) => e.code === 'LAML_REFERENCE_NOT_FOUND')).toBe(true); }); test('should warn on deep references', () => { const deepReferences = ` $meta: name: 'testDeepRef' purpose: 'Test deep reference warning' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.references'] section1: level1: level2: level3: level4: 'value' deepRef: '*section1.level1.level2.level3.level4' `; const session = createMockSession(); const parseResult = parseLaml(deepReferences); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); // Deep reference warning has been removed }); }); describe('Value type validation', () => { test('should auto-fix boolean values', () => { const booleanFixes = ` $meta: name: 'testBooleans' purpose: 'Test boolean value fixes' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.types'] flags: hasPermission: 'true' isEnabled: 'false' canExecute: 'yes' shouldRun: 'no' mustValidate: '1' allowsAccess: '0' `; const session = createMockSession(); const parseResult = parseLaml(booleanFixes); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect(result.autoFixedIssues.filter(issue => issue.includes('Fixed boolean value')).length).toBe(6); expect(result.fixedDocument).toBeDefined(); }); test('should error on invalid boolean values', () => { const invalidBooleans = ` $meta: name: 'testInvalidBooleans' purpose: 'Test invalid boolean values' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.types'] flags: hasPermission: 'maybe' isEnabled: 'sometimes' `; const session = createMockSession(); const parseResult = parseLaml(invalidBooleans); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.filter((e: any) => e.code === 'LAML_INVALID_BOOLEAN_VALUE').length).toBe(2); }); test('should warn on improperly formatted literal values', () => { const literalWarnings = ` $meta: name: 'testLiterals' purpose: 'Test literal format warnings' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.types'] values: format: 'user_auth_email' type: 'api-endpoint-config' style: 'CamelCaseFormat' `; const session = createMockSession(); const parseResult = parseLaml(literalWarnings); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect((session as any)._warnings.filter((w: any) => w.code === 'LAML_LITERAL_FORMAT_WARNING').length).toBeGreaterThan(0); }); }); describe('Structure principles validation', () => { test('should warn on conceptual duplication', () => { const conceptualDuplication = ` $meta: name: 'testDuplication' purpose: 'Test conceptual duplication detection' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.structure'] userAuth: method: 'oauth' authentication: type: 'saml' userAuthentication: provider: 'google' `; const session = createMockSession(); const parseResult = parseLaml(conceptualDuplication); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); // Conceptual duplication detection has been removed expect((session as any)._warnings.length).toBeGreaterThanOrEqual(0); }); test('should handle empty document gracefully', () => { const emptyDoc = ''; const session = createMockSession(); const parseResult = parseLaml(emptyDoc); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.length).toBeGreaterThan(0); }); test('should handle non-map root structure', () => { const arrayRoot = ` - item1 - item2 - item3 `; const session = createMockSession(); const parseResult = parseLaml(arrayRoot); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.some((e: any) => e.code === 'LAML_INVALID_ROOT_STRUCTURE')).toBe(true); }); }); describe('Multiline strings validation', () => { test('should handle literal multiline strings (|)', () => { const literalMultiline = ` $meta: name: 'testLiteralMultiline' purpose: 'Test literal multiline string handling' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.multiline'] content: description: | This is a literal multiline string. It preserves line breaks and spacing. Each line is kept as-is. instructions: | Step 1: Do something Step 2: Do something else Step 3: Finish `; const session = createMockSession(); const parseResult = parseLaml(literalMultiline); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect((session as any)._errors).toHaveLength(0); }); test('should handle folded multiline strings (>)', () => { const foldedMultiline = ` $meta: name: 'testFoldedMultiline' purpose: 'Test folded multiline string handling' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.multiline'] content: description: > This is a folded multiline string. Line breaks are converted to spaces. Only paragraph breaks are preserved. longText: > This is another paragraph with multiple lines that will be folded into a single line. `; const session = createMockSession(); const parseResult = parseLaml(foldedMultiline); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); expect((session as any)._errors).toHaveLength(0); }); test('should handle mixed multiline strings with LAML references', () => { const mixedMultiline = ` $meta: name: 'testMixedMultiline' purpose: 'Test mixed multiline with LAML references' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.multiline'] baseRules: format: 'standard' content: description: | This document uses LAML references like this one: '*baseRules.format' and preserves line breaks. summary: > This folded string also contains a LAML reference '*baseRules.format' but line breaks are folded. `; const session = createMockSession(); const parseResult = parseLaml(mixedMultiline); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(true); // Verify LAML references are properly validated even in multiline strings expect((session as any)._errors.filter((e: any) => e.code === 'LAML_INVALID_REFERENCE')).toHaveLength(0); }); test('should validate multiline strings with invalid LAML references', () => { const invalidRefsInMultiline = ` $meta: name: 'testInvalidRefsMultiline' purpose: 'Test invalid LAML references in multiline strings' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.multiline'] content: description: | This contains an invalid reference: '*nonExistent.property' should fail validation. summary: > Another invalid reference '*also.missing' in a folded string. `; const session = createMockSession(); const parseResult = parseLaml(invalidRefsInMultiline); const result = validateLaml(parseResult, session); expect(result.isValid).toBe(false); expect((session as any)._errors.filter((e: any) => e.code === 'LAML_REFERENCE_NOT_FOUND').length).toBe(2); }); }); describe('YAML aliases detection', () => { test('should error on YAML merge keys (<<:)', () => { const withMergeKeys = ` $meta: name: 'testMergeKeys' purpose: 'Test YAML merge key detection' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.aliases'] baseConfig: &base format: 'standard' level: 'info' developmentConfig: <<: *base level: 'debug' productionConfig: <<: *base level: 'warn' `; const session = createMockSession(); const parseResult = parseLaml(withMergeKeys); const result = validateLaml(parseResult, session); // Should fail because YAML merge keys are not allowed in LAML expect(result.isValid).toBe(false); expect((session as any)._errors.filter((e: any) => e.code === 'LAML_YAML_MERGE_KEY_INVALID').length).toBeGreaterThan(0); }); test('should auto-fix simple value aliases', () => { const simpleAliases = ` $meta: name: 'testSimpleAliases' purpose: 'Test simple value aliases' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.aliases'] baseFormat: &format 'standard' content: type: *format style: *format `; const session = createMockSession(); const parseResult = parseLaml(simpleAliases); const result = validateLaml(parseResult, session); // YAML aliases should be auto-converted to LAML references expect(result.isValid).toBe(true); expect(result.autoFixedIssues.some(issue => issue.includes('Converted YAML alias'))).toBe(true); expect((session as any)._errors.filter((e: any) => e.code === 'LAML_YAML_ALIAS_NOT_ALLOWED').length).toBe(0); }); test('should auto-fix complex nested aliases', () => { const nestedAliases = ` $meta: name: 'testNestedAliases' purpose: 'Test complex nested aliases' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.aliases'] defaultAuth: &auth method: 'oauth' provider: 'google' defaultCache: &cache enabled: true ttl: 3600 development: auth: *auth cache: *cache debug: true production: auth: *auth cache: *cache debug: false `; const session = createMockSession(); const parseResult = parseLaml(nestedAliases); const result = validateLaml(parseResult, session); // YAML aliases should be auto-converted to LAML references expect(result.isValid).toBe(true); expect(result.autoFixedIssues.some(issue => issue.includes('Converted YAML alias'))).toBe(true); expect((session as any)._errors.filter((e: any) => e.code === 'LAML_YAML_ALIAS_NOT_ALLOWED').length).toBe(0); }); }); describe('Integration tests', () => { test('should handle complex document with multiple issues', () => { const complexDoc = ` section1: hasFlag: 'true' reference: '*nonExistent.ref' badBool: 'maybe' $meta: name: 'complexTest' purpose: 'Test complex document' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.complex'] userAuth: method: 'oauth' authentication: type: 'saml' `; const session = createMockSession(); const parseResult = parseLaml(complexDoc); const result = validateLaml(parseResult, session); // Should have both auto-fixes and errors expect(result.autoFixedIssues.length).toBeGreaterThan(0); expect((session as any)._errors.length).toBeGreaterThan(0); // Should still be invalid due to critical errors expect(result.isValid).toBe(false); }); test('should return fixed source when auto-fixes are applied', () => { const needsFixes = ` section1: hasFlag: 'true' $meta: name: 'fixTest' purpose: 'Test auto-fix' version: 1.0 spec: '.cursor/rules/g-laml.mdc' domains: ['test.fix'] `; const session = createMockSession(); const parseResult = parseLaml(needsFixes); const result = validateLaml(parseResult, session); expect(result.fixedSource).toBeDefined(); expect(result.fixedDocument).toBeDefined(); expect(result.autoFixedIssues.length).toBeGreaterThan(0); // Fixed source should be parseable and valid const fixedParseResult = parseLaml(result.fixedSource!); expect(fixedParseResult.ast).toBeDefined(); }); }); });

Latest Blog Posts

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/EgorKluch/mcp-laml'

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