- 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.
283 lines
8.1 KiB
TypeScript
283 lines
8.1 KiB
TypeScript
/**
|
|
* Tests for SPARQL HTTP Client
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import {
|
|
SparqlClient,
|
|
SparqlError,
|
|
createSparqlClient,
|
|
getDefaultClient,
|
|
type SelectResults,
|
|
} from '../../src/lib/sparql/client';
|
|
|
|
describe('SparqlClient', () => {
|
|
let client: SparqlClient;
|
|
|
|
beforeEach(() => {
|
|
client = new SparqlClient({ endpoint: 'http://localhost:7878/query' });
|
|
// Clear fetch mocks
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create client with endpoint', () => {
|
|
expect(client).toBeInstanceOf(SparqlClient);
|
|
});
|
|
|
|
it('should use default timeout if not specified', () => {
|
|
const client = new SparqlClient({ endpoint: 'http://test' });
|
|
expect(client).toBeDefined();
|
|
});
|
|
|
|
it('should accept custom timeout', () => {
|
|
const client = new SparqlClient({
|
|
endpoint: 'http://test',
|
|
timeout: 60000,
|
|
});
|
|
expect(client).toBeDefined();
|
|
});
|
|
|
|
it('should accept custom headers', () => {
|
|
const client = new SparqlClient({
|
|
endpoint: 'http://test',
|
|
headers: { 'X-Custom': 'value' },
|
|
});
|
|
expect(client).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('executeSelect', () => {
|
|
it('should execute SELECT query successfully', async () => {
|
|
const mockResponse: SelectResults = {
|
|
head: { vars: ['s', 'p', 'o'] },
|
|
results: {
|
|
bindings: [
|
|
{
|
|
s: { type: 'uri', value: 'http://example.org/subject' },
|
|
p: { type: 'uri', value: 'http://example.org/predicate' },
|
|
o: { type: 'literal', value: 'object' },
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
});
|
|
|
|
const result = await client.executeSelect('SELECT * WHERE { ?s ?p ?o }');
|
|
|
|
expect(result).toEqual(mockResponse);
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'http://localhost:7878/query',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: expect.objectContaining({
|
|
'Content-Type': 'application/sparql-query',
|
|
Accept: 'application/sparql-results+json',
|
|
}),
|
|
body: 'SELECT * WHERE { ?s ?p ?o }',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle empty results', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
head: { vars: [] },
|
|
results: { bindings: [] },
|
|
}),
|
|
});
|
|
|
|
const result = await client.executeSelect('SELECT * WHERE { ?s ?p ?o }');
|
|
expect(result.results.bindings).toHaveLength(0);
|
|
});
|
|
|
|
it('should throw SparqlError on HTTP error', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 400,
|
|
statusText: 'Bad Request',
|
|
text: async () => 'Parse error at line 1',
|
|
});
|
|
|
|
await expect(
|
|
client.executeSelect('INVALID QUERY')
|
|
).rejects.toThrow(SparqlError);
|
|
});
|
|
|
|
it('should throw SparqlError on network error', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue(new Error('Network failure'));
|
|
|
|
await expect(
|
|
client.executeSelect('SELECT * WHERE { ?s ?p ?o }')
|
|
).rejects.toThrow(SparqlError);
|
|
});
|
|
|
|
it.skip('should timeout long queries', async () => {
|
|
// Note: Testing timeout with mocked fetch is complex due to timing issues
|
|
// This test would work in integration tests with real HTTP calls
|
|
const slowClient = new SparqlClient({
|
|
endpoint: 'http://localhost:7878/query',
|
|
timeout: 100, // 100ms timeout
|
|
});
|
|
|
|
global.fetch = vi.fn().mockImplementation(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
setTimeout(() => resolve({
|
|
ok: true,
|
|
json: async () => ({ head: { vars: [] }, results: { bindings: [] } })
|
|
}), 200); // 200ms delay
|
|
})
|
|
);
|
|
|
|
await expect(
|
|
slowClient.executeSelect('SELECT * WHERE { ?s ?p ?o }')
|
|
).rejects.toThrow(/timeout/i);
|
|
});
|
|
});
|
|
|
|
describe('executeAsk', () => {
|
|
it('should execute ASK query and return true', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ head: {}, boolean: true }),
|
|
});
|
|
|
|
const result = await client.executeAsk('ASK { ?s ?p ?o }');
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should execute ASK query and return false', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ head: {}, boolean: false }),
|
|
});
|
|
|
|
const result = await client.executeAsk('ASK { <http://no> <http://match> <http://here> }');
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('executeConstruct', () => {
|
|
it('should execute CONSTRUCT query and return N-Triples', async () => {
|
|
const ntriples = '<http://s> <http://p> <http://o> .';
|
|
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
text: async () => ntriples,
|
|
});
|
|
|
|
const result = await client.executeConstruct(
|
|
'CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }'
|
|
);
|
|
|
|
expect(result).toBe(ntriples);
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'http://localhost:7878/query',
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({
|
|
Accept: 'application/n-triples',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('testConnection', () => {
|
|
it('should return true for successful connection', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ head: {}, boolean: true }),
|
|
});
|
|
|
|
const result = await client.testConnection();
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false for failed connection', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
|
|
|
|
const result = await client.testConnection();
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getTripleCount', () => {
|
|
it('should return count of triples', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
head: { vars: ['count'] },
|
|
results: {
|
|
bindings: [
|
|
{
|
|
count: {
|
|
type: 'literal',
|
|
value: '12345',
|
|
datatype: 'http://www.w3.org/2001/XMLSchema#integer',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
});
|
|
|
|
const count = await client.getTripleCount();
|
|
expect(count).toBe(12345);
|
|
});
|
|
|
|
it('should return 0 for empty database', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
head: { vars: ['count'] },
|
|
results: { bindings: [] },
|
|
}),
|
|
});
|
|
|
|
const count = await client.getTripleCount();
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('SparqlError', () => {
|
|
it('should create error with message', () => {
|
|
const error = new SparqlError('Test error');
|
|
expect(error.message).toBe('Test error');
|
|
expect(error.name).toBe('SparqlError');
|
|
});
|
|
|
|
it('should include status code and response', () => {
|
|
const error = new SparqlError('Bad request', 400, 'Parse error');
|
|
expect(error.statusCode).toBe(400);
|
|
expect(error.response).toBe('Parse error');
|
|
});
|
|
});
|
|
|
|
describe('createSparqlClient', () => {
|
|
it('should create client from environment', () => {
|
|
import.meta.env.VITE_SPARQL_ENDPOINT = 'http://localhost:7878/query';
|
|
const client = createSparqlClient();
|
|
expect(client).toBeInstanceOf(SparqlClient);
|
|
});
|
|
|
|
it('should throw error if endpoint not configured', () => {
|
|
delete import.meta.env.VITE_SPARQL_ENDPOINT;
|
|
expect(() => createSparqlClient()).toThrow(/VITE_SPARQL_ENDPOINT/);
|
|
});
|
|
});
|
|
|
|
describe('getDefaultClient', () => {
|
|
it('should return singleton client', () => {
|
|
import.meta.env.VITE_SPARQL_ENDPOINT = 'http://localhost:7878/query';
|
|
const client1 = getDefaultClient();
|
|
const client2 = getDefaultClient();
|
|
expect(client1).toBe(client2); // Same instance
|
|
});
|
|
});
|