import { describe, it, expect } from 'vitest'; import { validateSparqlQuery, formatValidationResult, isSafeQuery, type ValidationResult, } from '../../src/lib/sparql/validator'; describe('SPARQL Validator', () => { describe('validateSparqlQuery', () => { it('should validate correct SELECT query', () => { const query = ` PREFIX schema: SELECT ?museum ?name WHERE { ?museum a schema:Museum . ?museum schema:name ?name . } `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should reject empty query', () => { const result = validateSparqlQuery(''); expect(result.isValid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0].message).toContain('empty'); }); it('should reject query without query type', () => { const query = ` PREFIX schema: WHERE { ?museum a schema:Museum . } `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(false); expect(result.errors.some(e => e.message.includes('SELECT'))).toBe(true); }); it('should reject SELECT query without WHERE clause', () => { const query = ` PREFIX schema: SELECT ?museum `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(false); expect(result.errors.some(e => e.message.includes('WHERE'))).toBe(true); }); it('should detect unbalanced braces', () => { const query = ` SELECT ?museum WHERE { ?museum a schema:Museum . `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(false); expect(result.errors.some(e => e.message.includes('braces'))).toBe(true); }); it('should warn about unbalanced parentheses', () => { const query = ` SELECT ?museum WHERE { ?museum a schema:Museum . FILTER(?year > 2000 } `; const result = validateSparqlQuery(query); expect(result.warnings.some(w => w.message.includes('parentheses'))).toBe(true); }); it('should warn about undeclared prefixes', () => { const query = ` SELECT ?museum WHERE { ?museum a schema:Museum . } `; const result = validateSparqlQuery(query); expect(result.warnings.some(w => w.message.includes('schema'))).toBe(true); expect(result.warnings.some(w => w.message.includes('not declared'))).toBe(true); }); it('should warn about unused prefixes', () => { const query = ` PREFIX schema: PREFIX unused: SELECT ?museum WHERE { ?museum a schema:Museum . } `; const result = validateSparqlQuery(query); expect(result.warnings.some(w => w.message.includes('unused'))).toBe(true); }); it('should accept ASK queries without WHERE', () => { const query = 'ASK { ?s ?p ?o }'; const result = validateSparqlQuery(query); // ASK queries don't require WHERE clause expect(result.isValid).toBe(true); }); it('should accept CONSTRUCT queries', () => { const query = ` CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o } `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(true); }); it('should accept DESCRIBE queries', () => { const query = 'DESCRIBE '; const result = validateSparqlQuery(query); expect(result.isValid).toBe(true); }); it('should warn about SELECT variables not in WHERE', () => { const query = ` PREFIX schema: SELECT ?museum ?name ?description WHERE { ?museum a schema:Museum . ?museum schema:name ?name . } `; const result = validateSparqlQuery(query); expect(result.warnings.some(w => w.message.includes('description'))).toBe(true); }); it('should handle SELECT * correctly', () => { const query = ` PREFIX schema: SELECT * WHERE { ?museum a schema:Museum . } `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(true); }); it('should warn about == in FILTER (should be =)', () => { const query = ` PREFIX schema: SELECT ?museum WHERE { ?museum a schema:Museum . FILTER(?year == 2000) } `; const result = validateSparqlQuery(query); expect(result.warnings.some(w => w.message.includes('single ='))).toBe(true); }); it('should validate complex query with OPTIONAL and FILTER', () => { const query = ` PREFIX schema: PREFIX owl: SELECT ?museum ?name ?wikidata WHERE { ?museum a schema:Museum . ?museum schema:name ?name . OPTIONAL { ?museum owl:sameAs ?wikidataURI . FILTER(STRSTARTS(STR(?wikidataURI), "http://www.wikidata.org/")) BIND(STRAFTER(STR(?wikidataURI), "http://www.wikidata.org/entity/") AS ?wikidata) } } ORDER BY ?name LIMIT 100 `; const result = validateSparqlQuery(query); expect(result.isValid).toBe(true); }); }); describe('formatValidationResult', () => { it('should format valid result', () => { const result: ValidationResult = { isValid: true, errors: [], warnings: [], }; const formatted = formatValidationResult(result); expect(formatted).toContain('✓'); expect(formatted).toContain('Valid'); }); it('should format result with errors', () => { const result: ValidationResult = { isValid: false, errors: [ { line: 1, column: 1, message: 'Missing WHERE clause', severity: 'error' }, ], warnings: [], }; const formatted = formatValidationResult(result); expect(formatted).toContain('❌'); expect(formatted).toContain('error'); expect(formatted).toContain('Missing WHERE clause'); }); it('should format result with warnings', () => { const result: ValidationResult = { isValid: true, errors: [], warnings: [ { line: 1, column: 1, message: 'Unused prefix', severity: 'warning' }, ], }; const formatted = formatValidationResult(result); expect(formatted).toContain('⚠️'); expect(formatted).toContain('warning'); expect(formatted).toContain('Unused prefix'); }); it('should format result with both errors and warnings', () => { const result: ValidationResult = { isValid: false, errors: [ { line: 1, column: 1, message: 'Syntax error', severity: 'error' }, ], warnings: [ { line: 2, column: 1, message: 'Unused variable', severity: 'warning' }, ], }; const formatted = formatValidationResult(result); expect(formatted).toContain('❌'); expect(formatted).toContain('⚠️'); expect(formatted).toContain('Syntax error'); expect(formatted).toContain('Unused variable'); }); }); describe('isSafeQuery', () => { it('should accept SELECT queries', () => { const query = 'SELECT ?s WHERE { ?s ?p ?o }'; expect(isSafeQuery(query)).toBe(true); }); it('should accept CONSTRUCT queries', () => { const query = 'CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }'; expect(isSafeQuery(query)).toBe(true); }); it('should reject DELETE queries', () => { const query = 'DELETE { ?s ?p ?o } WHERE { ?s ?p ?o }'; expect(isSafeQuery(query)).toBe(false); }); it('should reject INSERT queries', () => { const query = 'INSERT { ?s ?p ?o } WHERE { ?s ?p ?o }'; expect(isSafeQuery(query)).toBe(false); }); it('should reject DROP queries', () => { const query = 'DROP GRAPH '; expect(isSafeQuery(query)).toBe(false); }); it('should reject CLEAR queries', () => { const query = 'CLEAR GRAPH '; expect(isSafeQuery(query)).toBe(false); }); it('should reject UPDATE queries', () => { const query = 'UPDATE { ?s schema:name "New Name" } WHERE { ?s ?p ?o }'; expect(isSafeQuery(query)).toBe(false); }); it('should reject LOAD queries', () => { const query = 'LOAD '; expect(isSafeQuery(query)).toBe(false); }); }); });