/** * 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 = ` . "Test Record" . . `; 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 = ` "Test Label" . "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 . # Another comment "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 = ` . `; 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: . @prefix rdfs: . @prefix rico: . rdf:type rico:Record ; rdfs:label "Test Record" ; rico:isOrWasConstituentOf . `; 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: . 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 = ` . `; const result = parseNTriples(malformed); // Should parse the valid triple expect(result.nodes).toHaveLength(1); }); });