- 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.
193 lines
6.3 KiB
TypeScript
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);
|
|
});
|
|
});
|