- 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.
326 lines
9.1 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|