glam/frontend/tests/unit/sparql-validator.test.ts
kempersc 2761857b0d Add scripts for converting OWL/Turtle ontology to Mermaid and PlantUML diagrams
- Implemented `owl_to_mermaid.py` to convert OWL/Turtle files into Mermaid class diagrams.
- Implemented `owl_to_plantuml.py` to convert OWL/Turtle files into PlantUML class diagrams.
- Added two new PlantUML files for custodian multi-aspect diagrams.
2025-11-22 23:01:13 +01:00

326 lines
9.1 KiB
TypeScript

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: <http://schema.org/>
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: <http://schema.org/>
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: <http://schema.org/>
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: <http://schema.org/>
PREFIX unused: <http://example.org/>
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 <http://example.org/museum1>';
const result = validateSparqlQuery(query);
expect(result.isValid).toBe(true);
});
it('should warn about SELECT variables not in WHERE', () => {
const query = `
PREFIX schema: <http://schema.org/>
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: <http://schema.org/>
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: <http://schema.org/>
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: <http://schema.org/>
PREFIX owl: <http://www.w3.org/2002/07/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 <http://example.org/graph>';
expect(isSafeQuery(query)).toBe(false);
});
it('should reject CLEAR queries', () => {
const query = 'CLEAR GRAPH <http://example.org/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 <http://example.org/data.ttl>';
expect(isSafeQuery(query)).toBe(false);
});
});
});