glam/frontend/tests/unit/rdf-parser.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

193 lines
6.3 KiB
TypeScript

/**
* Tests for RDF Parser
*/
import { describe, it, expect } from 'vitest';
import {
parseNTriples,
parseTurtle,
predicateToLabel,
getShortLabel,
inferTypeFromUri,
getReversePredicate,
isBidirectional,
reverseLink,
} from '@/lib/rdf/parser';
import type { GraphLink } from '@/types/rdf';
describe('RDF Parser - N-Triples', () => {
it('should parse simple N-Triples', () => {
const ntriples = `
<http://example.org/record1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.ica.org/standards/RiC/ontology#Record> .
<http://example.org/record1> <http://www.w3.org/2000/01/rdf-schema#label> "Test Record" .
<http://example.org/record1> <https://www.ica.org/standards/RiC/ontology#isOrWasConstituentOf> <http://example.org/recordset1> .
`;
const result = parseNTriples(ntriples);
// 3 nodes: record1, recordset1, and literal node for "Test Record"
expect(result.nodes).toHaveLength(3);
// 2 links: label link and isOrWasConstituentOf link
expect(result.links).toHaveLength(2);
expect(result.error).toBeUndefined();
const record = result.nodes.find((n) => n.id === 'http://example.org/record1');
expect(record).toBeDefined();
expect(record?.type).toBe('Record');
expect(record?.label).toBe('Test Record');
});
it('should handle literals', () => {
const ntriples = `
<http://example.org/record1> <http://www.w3.org/2000/01/rdf-schema#label> "Test Label" .
<http://example.org/record1> <http://purl.org/dc/terms/description> "Test Description" .
`;
const result = parseNTriples(ntriples);
expect(result.nodes.length).toBeGreaterThan(1);
expect(result.links).toHaveLength(2);
const literalNodes = result.nodes.filter((n) => n.type === 'Literal');
expect(literalNodes.length).toBeGreaterThan(0);
});
it('should skip comments and empty lines', () => {
const ntriples = `
# This is a comment
<http://example.org/record1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.ica.org/standards/RiC/ontology#Record> .
# Another comment
<http://example.org/record1> <http://www.w3.org/2000/01/rdf-schema#label> "Test" .
`;
const result = parseNTriples(ntriples);
// 2 nodes: record1 and literal node for "Test"
expect(result.nodes).toHaveLength(2);
// 1 link: label link (type is processed as metadata only)
expect(result.links).toHaveLength(1);
});
it('should mark bidirectional relationships', () => {
const ntriples = `
<http://example.org/record1> <https://www.ica.org/standards/RiC/ontology#isOrWasConstituentOf> <http://example.org/recordset1> .
`;
const result = parseNTriples(ntriples);
expect(result.links).toHaveLength(1);
expect(result.links[0].isBidirectional).toBe(true);
});
});
describe('RDF Parser - Turtle', () => {
it('should parse Turtle with prefixes', () => {
const turtle = `
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rico: <https://www.ica.org/standards/RiC/ontology#> .
<http://example.org/record1> rdf:type rico:Record ;
rdfs:label "Test Record" ;
rico:isOrWasConstituentOf <http://example.org/recordset1> .
`;
const result = parseTurtle(turtle);
expect(result.nodes.length).toBeGreaterThan(0);
expect(result.links.length).toBeGreaterThan(0);
});
it('should handle multiple objects', () => {
const turtle = `
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<http://example.org/record1> rdfs:label "Label 1", "Label 2", "Label 3" .
`;
const result = parseTurtle(turtle);
expect(result.nodes.length).toBeGreaterThan(1);
expect(result.links).toHaveLength(3);
});
});
describe('Helper Functions', () => {
it('should convert predicate to label', () => {
expect(predicateToLabel('http://www.w3.org/2000/01/rdf-schema#label')).toBe('label');
expect(predicateToLabel('https://www.ica.org/standards/RiC/ontology#hasProvenance')).toBe(
'has provenance'
);
expect(predicateToLabel('http://example.org/customPredicate')).toBe('custom predicate');
});
it('should get short label from URI', () => {
expect(getShortLabel('http://example.org/record1')).toBe('record1');
expect(getShortLabel('https://www.ica.org/standards/RiC/ontology#Record')).toBe('Record');
});
it('should infer type from URI', () => {
expect(inferTypeFromUri('https://www.ica.org/standards/RiC/ontology#Record')).toBe('Record');
expect(inferTypeFromUri('https://www.ica.org/standards/RiC/ontology#Person')).toBe('Person');
expect(inferTypeFromUri('https://www.ica.org/standards/RiC/ontology#Place')).toBe('Place');
expect(inferTypeFromUri('http://example.org/UnknownType')).toBe('Resource');
});
it('should check bidirectional predicates', () => {
expect(
isBidirectional('https://www.ica.org/standards/RiC/ontology#isOrWasConstituentOf')
).toBe(true);
expect(isBidirectional('http://www.w3.org/2000/01/rdf-schema#label')).toBe(false);
});
it('should get reverse predicate', () => {
const reversePredicate = getReversePredicate(
'https://www.ica.org/standards/RiC/ontology#isOrWasConstituentOf'
);
expect(reversePredicate).toBe(
'https://www.ica.org/standards/RiC/ontology#hasOrHadConstituent'
);
});
it('should reverse a link', () => {
const link: GraphLink = {
source: 'node1',
target: 'node2',
predicate: 'part of',
value: 1,
originalPredicate: 'https://www.ica.org/standards/RiC/ontology#isOrWasConstituentOf',
isReversed: false,
isBidirectional: true,
};
const reversed = reverseLink(link);
expect(reversed.source).toBe('node2');
expect(reversed.target).toBe('node1');
expect(reversed.isReversed).toBe(true);
});
});
describe('Error Handling', () => {
it('should handle invalid N-Triples gracefully', () => {
const invalid = 'This is not valid RDF';
const result = parseNTriples(invalid);
expect(result.nodes).toHaveLength(0);
expect(result.links).toHaveLength(0);
});
it('should handle malformed triples', () => {
const malformed = `
<http://example.org/record1> <incomplete triple
<http://example.org/record2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.ica.org/standards/RiC/ontology#Record> .
`;
const result = parseNTriples(malformed);
// Should parse the valid triple
expect(result.nodes).toHaveLength(1);
});
});