mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Pass ResourceStore as param to PatchHandler
This way the chain of ResourceStores is a bit easier to configure. This commit also updates the SparqlUpdatePatchHandler to keep the metadata and content-type of the resource that is being modified.
This commit is contained in:
@@ -6,6 +6,8 @@ import { translate } from 'sparqlalgebrajs';
|
||||
import type { SparqlUpdatePatch } from '../../../../src/ldp/http/SparqlUpdatePatch';
|
||||
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
|
||||
import type { RepresentationConverterArgs,
|
||||
RepresentationConverter } from '../../../../src/storage/conversion/RepresentationConverter';
|
||||
import { SparqlUpdatePatchHandler } from '../../../../src/storage/patch/SparqlUpdatePatchHandler';
|
||||
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
|
||||
import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
|
||||
@@ -13,9 +15,12 @@ import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError
|
||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
let converter: RepresentationConverter;
|
||||
let handler: SparqlUpdatePatchHandler;
|
||||
let source: ResourceStore;
|
||||
let startQuads: Quad[];
|
||||
const dummyType = 'internal/not-quads';
|
||||
const identifier = { path: 'http://test.com/foo' };
|
||||
const fullfilledDataInsert = 'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 . }';
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
@@ -29,43 +34,60 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
namedNode('http://test.com/startO2'),
|
||||
) ];
|
||||
|
||||
converter = {
|
||||
handleSafe: jest.fn(async({ representation, preferences }: RepresentationConverterArgs): Promise<any> =>
|
||||
new BasicRepresentation(representation.data, Object.keys(preferences.type!)[0])),
|
||||
} as unknown as RepresentationConverter;
|
||||
|
||||
source = {
|
||||
getRepresentation: jest.fn(async(): Promise<any> => new BasicRepresentation(startQuads, 'internal/quads', false)),
|
||||
getRepresentation: jest.fn(async(): Promise<any> => new BasicRepresentation(startQuads, dummyType)),
|
||||
setRepresentation: jest.fn(),
|
||||
modifyResource: jest.fn(async(): Promise<any> => {
|
||||
throw new Error('noModify');
|
||||
}),
|
||||
} as unknown as ResourceStore;
|
||||
|
||||
handler = new SparqlUpdatePatchHandler(source);
|
||||
handler = new SparqlUpdatePatchHandler(converter, dummyType);
|
||||
});
|
||||
|
||||
async function basicChecks(quads: Quad[]): Promise<boolean> {
|
||||
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.getRepresentation).toHaveBeenLastCalledWith(
|
||||
{ path: 'path' }, { type: { [INTERNAL_QUADS]: 1 }},
|
||||
);
|
||||
expect(source.getRepresentation).toHaveBeenLastCalledWith(identifier, { });
|
||||
expect(converter.handleSafe).toHaveBeenCalledTimes(2);
|
||||
expect(converter.handleSafe).toHaveBeenCalledWith({
|
||||
representation: await (source.getRepresentation as jest.Mock).mock.results[0].value,
|
||||
identifier,
|
||||
preferences: { type: { [INTERNAL_QUADS]: 1 }},
|
||||
});
|
||||
expect(converter.handleSafe).toHaveBeenLastCalledWith({
|
||||
representation: expect.objectContaining({ binary: false, metadata: expect.any(RepresentationMetadata) }),
|
||||
identifier,
|
||||
preferences: { type: { [dummyType]: 1 }},
|
||||
});
|
||||
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
const setParams = (source.setRepresentation as jest.Mock).mock.calls[0];
|
||||
expect(setParams[0]).toEqual({ path: 'path' });
|
||||
expect(setParams[0]).toEqual(identifier);
|
||||
expect(setParams[1]).toEqual(expect.objectContaining({
|
||||
binary: false,
|
||||
binary: true,
|
||||
metadata: expect.any(RepresentationMetadata),
|
||||
}));
|
||||
expect(setParams[1].metadata.contentType).toEqual(INTERNAL_QUADS);
|
||||
expect(setParams[1].metadata.contentType).toEqual(dummyType);
|
||||
await expect(arrayifyStream(setParams[1].data)).resolves.toBeRdfIsomorphic(quads);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handle(query: string): Promise<void> {
|
||||
const sparqlPrefix = 'prefix : <http://test.com/>\n';
|
||||
await handler.handle({ identifier: { path: 'path' },
|
||||
patch: { algebra: translate(sparqlPrefix.concat(query), { quads: true }) } as SparqlUpdatePatch });
|
||||
await handler.handle({
|
||||
source,
|
||||
identifier,
|
||||
patch: { algebra: translate(sparqlPrefix.concat(query), { quads: true }) } as SparqlUpdatePatch,
|
||||
});
|
||||
}
|
||||
|
||||
it('only accepts SPARQL updates.', async(): Promise<void> => {
|
||||
const input = { identifier: { path: 'path' },
|
||||
patch: { algebra: {}} as SparqlUpdatePatch };
|
||||
const input = { source, identifier, patch: { algebra: {}} as SparqlUpdatePatch };
|
||||
await expect(handler.canHandle(input)).resolves.toBeUndefined();
|
||||
delete (input.patch as any).algebra;
|
||||
await expect(handler.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
|
||||
@@ -181,13 +203,45 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
|
||||
it('creates a new resource if it does not exist yet.', async(): Promise<void> => {
|
||||
startQuads = [];
|
||||
source.getRepresentation = jest.fn((): any => {
|
||||
throw new NotFoundHttpError();
|
||||
});
|
||||
(source.getRepresentation as jest.Mock).mockRejectedValueOnce(new NotFoundHttpError());
|
||||
const query = 'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }';
|
||||
await handle(query);
|
||||
|
||||
expect(converter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(converter.handleSafe).toHaveBeenLastCalledWith({
|
||||
representation: expect.objectContaining({ binary: false, metadata: expect.any(RepresentationMetadata) }),
|
||||
identifier,
|
||||
preferences: { type: { [dummyType]: 1 }},
|
||||
});
|
||||
|
||||
const quads =
|
||||
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')) ];
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
const setParams = (source.setRepresentation as jest.Mock).mock.calls[0];
|
||||
expect(setParams[1].metadata.contentType).toEqual(dummyType);
|
||||
await expect(arrayifyStream(setParams[1].data)).resolves.toBeRdfIsomorphic(quads);
|
||||
});
|
||||
|
||||
it('can handle representations without content-type.', async(): Promise<void> => {
|
||||
(source.getRepresentation as jest.Mock).mockResolvedValueOnce(
|
||||
new BasicRepresentation(startQuads, new RepresentationMetadata()),
|
||||
);
|
||||
await handle(fullfilledDataInsert);
|
||||
expect(await basicChecks(startQuads.concat(
|
||||
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')) ],
|
||||
[ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')),
|
||||
quad(namedNode('http://test.com/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')) ],
|
||||
))).toBe(true);
|
||||
});
|
||||
|
||||
it('defaults to text/turtle if no default type was set.', async(): Promise<void> => {
|
||||
handler = new SparqlUpdatePatchHandler(converter);
|
||||
startQuads = [];
|
||||
(source.getRepresentation as jest.Mock).mockRejectedValueOnce(new NotFoundHttpError());
|
||||
const query = 'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }';
|
||||
await handle(query);
|
||||
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
const setParams = (source.setRepresentation as jest.Mock).mock.calls[0];
|
||||
expect(setParams[1].metadata.contentType).toEqual('text/turtle');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user