mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create base ConvertingPatchHandler abstract class
This commit is contained in:
parent
c18b8526cc
commit
25f33ee4cd
@ -250,6 +250,7 @@ export * from './storage/mapping/FixedContentTypeMapper';
|
||||
export * from './storage/mapping/SubdomainExtensionBasedMapper';
|
||||
|
||||
// Storage/Patch
|
||||
export * from './storage/patch/ConvertingPatchHandler';
|
||||
export * from './storage/patch/PatchHandler';
|
||||
export * from './storage/patch/SparqlUpdatePatchHandler';
|
||||
|
||||
|
89
src/storage/patch/ConvertingPatchHandler.ts
Normal file
89
src/storage/patch/ConvertingPatchHandler.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import type { RepresentationConverter } from '../conversion/RepresentationConverter';
|
||||
import type { ResourceStore } from '../ResourceStore';
|
||||
import type { PatchHandlerArgs } from './PatchHandler';
|
||||
import { PatchHandler } from './PatchHandler';
|
||||
|
||||
/**
|
||||
* An abstract patch handler.
|
||||
*
|
||||
* A `ConvertingPatchHandler` converts a document to its `intermediateType`,
|
||||
* handles the patch operation, and then converts back to its original type.
|
||||
* This abstract class covers all of the above except of handling the patch operation,
|
||||
* for which the abstract `patch` function has to be implemented.
|
||||
*
|
||||
* In case there is no resource yet and a new one needs to be created,
|
||||
* the `patch` function will be called without a Representation
|
||||
* and the result will be converted to the `defaultType`.
|
||||
*/
|
||||
export abstract class ConvertingPatchHandler extends PatchHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly converter: RepresentationConverter;
|
||||
protected readonly intermediateType: string;
|
||||
protected readonly defaultType: string;
|
||||
|
||||
/**
|
||||
* @param converter - Converter that will be used to generate intermediate Representation.
|
||||
* @param intermediateType - Content-type of the intermediate Representation.
|
||||
* @param defaultType - Content-type in case a new resource gets created.
|
||||
*/
|
||||
protected constructor(converter: RepresentationConverter, intermediateType: string, defaultType: string) {
|
||||
super();
|
||||
this.converter = converter;
|
||||
this.intermediateType = intermediateType;
|
||||
this.defaultType = defaultType;
|
||||
}
|
||||
|
||||
public async handle(input: PatchHandlerArgs): Promise<ResourceIdentifier[]> {
|
||||
const { source, identifier } = input;
|
||||
const { representation, contentType } = await this.toIntermediate(source, identifier);
|
||||
|
||||
const patched = await this.patch(input, representation);
|
||||
|
||||
// Convert back to the original type and write the result
|
||||
const converted = await this.converter.handleSafe({
|
||||
representation: patched,
|
||||
identifier,
|
||||
preferences: { type: { [contentType]: 1 }},
|
||||
});
|
||||
return source.setRepresentation(identifier, converted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the resource from the source and converts it to the intermediate type if it was found.
|
||||
* Also returns the contentType that should be used when converting back before setting the representation.
|
||||
*/
|
||||
protected async toIntermediate(source: ResourceStore, identifier: ResourceIdentifier):
|
||||
Promise<{ representation?: Representation; contentType: string }> {
|
||||
let converted: Representation | undefined;
|
||||
let contentType: string;
|
||||
try {
|
||||
const representation = await source.getRepresentation(identifier, {});
|
||||
contentType = representation.metadata.contentType!;
|
||||
const preferences = { type: { [this.intermediateType]: 1 }};
|
||||
converted = await this.converter.handleSafe({ representation, identifier, preferences });
|
||||
} catch (error: unknown) {
|
||||
// Solid, §5.1: "When a successful PUT or PATCH request creates a resource,
|
||||
// the server MUST use the effective request URI to assign the URI to that resource."
|
||||
// https://solid.github.io/specification/protocol#resource-type-heuristics
|
||||
if (!NotFoundHttpError.isInstance(error)) {
|
||||
throw error;
|
||||
}
|
||||
contentType = this.defaultType;
|
||||
this.logger.debug(`Patching new resource ${identifier.path}`);
|
||||
}
|
||||
return { representation: converted, contentType };
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the given representation based on the patch arguments.
|
||||
* In case representation is not defined a new Representation should be created.
|
||||
* @param input - Arguments that were passed to the initial `handle` call.
|
||||
* @param representation - Representation acquired from the source and converted to the intermediate type.
|
||||
*/
|
||||
protected abstract patch(input: PatchHandlerArgs, representation?: Representation): Promise<Representation>;
|
||||
}
|
@ -7,34 +7,26 @@ import { Algebra } from 'sparqlalgebrajs';
|
||||
import type { Patch } from '../../ldp/http/Patch';
|
||||
import type { SparqlUpdatePatch } from '../../ldp/http/SparqlUpdatePatch';
|
||||
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import { INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import type { RepresentationConverter } from '../conversion/RepresentationConverter';
|
||||
import type { ResourceStore } from '../ResourceStore';
|
||||
import { ConvertingPatchHandler } from './ConvertingPatchHandler';
|
||||
import type { PatchHandlerArgs } from './PatchHandler';
|
||||
import { PatchHandler } from './PatchHandler';
|
||||
|
||||
/**
|
||||
* PatchHandler that supports specific types of SPARQL updates.
|
||||
* Currently all DELETE/INSERT types are supported that have empty where bodies and no variables.
|
||||
* Supports application/sparql-update PATCH requests on RDF resources.
|
||||
*
|
||||
* Will try to keep the content-type and metadata of the original resource intact.
|
||||
* In case this PATCH would create a new resource, it will have content-type `defaultType`.
|
||||
* Only DELETE/INSERT updates without variables are supported.
|
||||
*/
|
||||
export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
export class SparqlUpdatePatchHandler extends ConvertingPatchHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly converter: RepresentationConverter;
|
||||
private readonly defaultType: string;
|
||||
|
||||
public constructor(converter: RepresentationConverter, defaultType = 'text/turtle') {
|
||||
super();
|
||||
this.converter = converter;
|
||||
this.defaultType = defaultType;
|
||||
super(converter, INTERNAL_QUADS, defaultType);
|
||||
}
|
||||
|
||||
public async canHandle({ patch }: PatchHandlerArgs): Promise<void> {
|
||||
@ -45,7 +37,7 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
|
||||
public async handle(input: PatchHandlerArgs): Promise<ResourceIdentifier[]> {
|
||||
// Verify the patch
|
||||
const { source, identifier, patch } = input;
|
||||
const { patch } = input;
|
||||
const op = (patch as SparqlUpdatePatch).algebra;
|
||||
|
||||
// In case of a NOP we can skip everything
|
||||
@ -55,7 +47,8 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
|
||||
this.validateUpdate(op);
|
||||
|
||||
return this.applyPatch(source, identifier, op);
|
||||
// Only start conversion if we know the operation is valid
|
||||
return super.handle(input);
|
||||
}
|
||||
|
||||
private isSparqlUpdate(patch: Patch): patch is SparqlUpdatePatch {
|
||||
@ -126,51 +119,27 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
/**
|
||||
* Apply the given algebra operation to the given identifier.
|
||||
*/
|
||||
private async applyPatch(source: ResourceStore, identifier: ResourceIdentifier, op: Algebra.Operation):
|
||||
Promise<ResourceIdentifier[]> {
|
||||
// These are used to make sure we keep the original content-type and metadata
|
||||
let contentType: string;
|
||||
protected async patch(input: PatchHandlerArgs, representation?: Representation): Promise<Representation> {
|
||||
const { identifier, patch } = input;
|
||||
const result = new Store<BaseQuad>();
|
||||
let metadata: RepresentationMetadata;
|
||||
|
||||
const result = new Store<BaseQuad>();
|
||||
try {
|
||||
// Read the quads of the current representation
|
||||
const representation = await source.getRepresentation(identifier, {});
|
||||
contentType = representation.metadata.contentType ?? this.defaultType;
|
||||
const preferences = { type: { [INTERNAL_QUADS]: 1 }};
|
||||
const quads = await this.converter.handleSafe({ representation, identifier, preferences });
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
metadata = quads.metadata;
|
||||
|
||||
const importEmitter = result.import(quads.data);
|
||||
if (representation) {
|
||||
({ metadata } = representation);
|
||||
const importEmitter = result.import(representation.data);
|
||||
await new Promise((resolve, reject): void => {
|
||||
importEmitter.on('end', resolve);
|
||||
importEmitter.on('error', reject);
|
||||
});
|
||||
this.logger.debug(`${result.size} quads in ${identifier.path}.`);
|
||||
} catch (error: unknown) {
|
||||
// Solid, §5.1: "When a successful PUT or PATCH request creates a resource,
|
||||
// the server MUST use the effective request URI to assign the URI to that resource."
|
||||
// https://solid.github.io/specification/protocol#resource-type-heuristics
|
||||
if (!NotFoundHttpError.isInstance(error)) {
|
||||
throw error;
|
||||
}
|
||||
contentType = this.defaultType;
|
||||
} else {
|
||||
metadata = new RepresentationMetadata(identifier, INTERNAL_QUADS);
|
||||
this.logger.debug(`Patching new resource ${identifier.path}.`);
|
||||
}
|
||||
|
||||
this.applyOperation(result, op);
|
||||
this.applyOperation(result, (patch as SparqlUpdatePatch).algebra);
|
||||
this.logger.debug(`${result.size} quads will be stored to ${identifier.path}.`);
|
||||
|
||||
// Convert back to the original type and write the result
|
||||
const patched = new BasicRepresentation(result.match() as Readable, metadata);
|
||||
const converted = await this.converter.handleSafe({
|
||||
representation: patched,
|
||||
identifier,
|
||||
preferences: { type: { [contentType]: 1 }},
|
||||
});
|
||||
return source.setRepresentation(identifier, converted);
|
||||
return new BasicRepresentation(result.match() as Readable, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
|
118
test/unit/storage/patch/ConvertingPatchHandlers.test.ts
Normal file
118
test/unit/storage/patch/ConvertingPatchHandlers.test.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import type { Patch } from '../../../../src/ldp/http/Patch';
|
||||
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../../../src/ldp/representation/Representation';
|
||||
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
|
||||
import type {
|
||||
RepresentationConverter,
|
||||
RepresentationConverterArgs,
|
||||
} from '../../../../src/storage/conversion/RepresentationConverter';
|
||||
import { ConvertingPatchHandler } from '../../../../src/storage/patch/ConvertingPatchHandler';
|
||||
import type { PatchHandlerArgs } from '../../../../src/storage/patch/PatchHandler';
|
||||
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
|
||||
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
||||
|
||||
class SimpleConvertingPatchHandler extends ConvertingPatchHandler {
|
||||
private readonly type: string;
|
||||
|
||||
public constructor(converter: RepresentationConverter, intermediateType: string, defaultType: string) {
|
||||
super(converter, intermediateType, defaultType);
|
||||
this.type = intermediateType;
|
||||
}
|
||||
|
||||
public async patch(input: PatchHandlerArgs, representation?: Representation): Promise<Representation> {
|
||||
return representation ?
|
||||
new BasicRepresentation('patched', representation.metadata) :
|
||||
new BasicRepresentation('patched', input.identifier, this.type);
|
||||
}
|
||||
}
|
||||
|
||||
describe('A ConvertingPatchHandler', (): void => {
|
||||
const intermediateType = 'internal/quads';
|
||||
const defaultType = 'text/turtle';
|
||||
const identifier: ResourceIdentifier = { path: 'http://test.com/foo' };
|
||||
const patch: Patch = new BasicRepresentation([], 'type/patch');
|
||||
const representation: Representation = new BasicRepresentation([], 'application/trig');
|
||||
let source: jest.Mocked<ResourceStore>;
|
||||
let args: PatchHandlerArgs;
|
||||
let converter: jest.Mocked<RepresentationConverter>;
|
||||
let handler: jest.Mocked<SimpleConvertingPatchHandler>;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
converter = {
|
||||
handleSafe: jest.fn(async({ preferences }: RepresentationConverterArgs): Promise<any> =>
|
||||
new BasicRepresentation('converted', Object.keys(preferences.type!)[0])),
|
||||
} as any;
|
||||
|
||||
source = {
|
||||
getRepresentation: jest.fn().mockResolvedValue(representation),
|
||||
setRepresentation: jest.fn(async(id: ResourceIdentifier): Promise<ResourceIdentifier[]> => [ id ]),
|
||||
} as any;
|
||||
|
||||
args = { patch, identifier, source };
|
||||
|
||||
handler = new SimpleConvertingPatchHandler(converter, intermediateType, defaultType) as any;
|
||||
jest.spyOn(handler, 'patch');
|
||||
});
|
||||
|
||||
it('converts the representation before calling the patch function.', async(): Promise<void> => {
|
||||
await expect(handler.handle(args)).resolves.toEqual([ identifier ]);
|
||||
|
||||
// Convert input
|
||||
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.getRepresentation).toHaveBeenLastCalledWith(identifier, { });
|
||||
expect(converter.handleSafe).toHaveBeenCalledTimes(2);
|
||||
expect(converter.handleSafe).toHaveBeenCalledWith({
|
||||
representation: await source.getRepresentation.mock.results[0].value,
|
||||
identifier,
|
||||
preferences: { type: { [intermediateType]: 1 }},
|
||||
});
|
||||
|
||||
// Patch
|
||||
expect(handler.patch).toHaveBeenCalledTimes(1);
|
||||
expect(handler.patch).toHaveBeenLastCalledWith(args, await converter.handleSafe.mock.results[0].value);
|
||||
|
||||
// Convert back
|
||||
expect(converter.handleSafe).toHaveBeenLastCalledWith({
|
||||
representation: await handler.patch.mock.results[0].value,
|
||||
identifier,
|
||||
preferences: { type: { 'application/trig': 1 }},
|
||||
});
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.setRepresentation)
|
||||
.toHaveBeenLastCalledWith(identifier, await converter.handleSafe.mock.results[1].value);
|
||||
expect(source.setRepresentation.mock.calls[0][1].metadata.contentType).toBe(representation.metadata.contentType);
|
||||
});
|
||||
|
||||
it('expects the patch function to create a new representation if there is none.', async(): Promise<void> => {
|
||||
source.getRepresentation.mockRejectedValueOnce(new NotFoundHttpError());
|
||||
|
||||
await expect(handler.handle(args)).resolves.toEqual([ identifier ]);
|
||||
|
||||
// Try to get input
|
||||
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.getRepresentation).toHaveBeenLastCalledWith(identifier, { });
|
||||
|
||||
// Patch
|
||||
expect(handler.patch).toHaveBeenCalledTimes(1);
|
||||
expect(handler.patch).toHaveBeenLastCalledWith(args, undefined);
|
||||
|
||||
// Convert new Representation to default type
|
||||
expect(converter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(converter.handleSafe).toHaveBeenLastCalledWith({
|
||||
representation: await handler.patch.mock.results[0].value,
|
||||
identifier,
|
||||
preferences: { type: { [defaultType]: 1 }},
|
||||
});
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.setRepresentation)
|
||||
.toHaveBeenLastCalledWith(identifier, await converter.handleSafe.mock.results[0].value);
|
||||
expect(source.setRepresentation.mock.calls[0][1].metadata.contentType).toBe(defaultType);
|
||||
});
|
||||
|
||||
it('rethrows the error if something goes wrong getting the representation.', async(): Promise<void> => {
|
||||
const error = new Error('bad data');
|
||||
source.getRepresentation.mockRejectedValueOnce(error);
|
||||
|
||||
await expect(handler.handle(args)).rejects.toThrow(error);
|
||||
});
|
||||
});
|
@ -15,13 +15,13 @@ import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError
|
||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
let converter: RepresentationConverter;
|
||||
let converter: jest.Mocked<RepresentationConverter>;
|
||||
let handler: SparqlUpdatePatchHandler;
|
||||
let source: ResourceStore;
|
||||
let source: jest.Mocked<ResourceStore>;
|
||||
let startQuads: Quad[];
|
||||
const dummyType = 'internal/not-quads';
|
||||
const defaultType = 'internal/not-quads';
|
||||
const identifier = { path: 'http://test.com/foo' };
|
||||
const fullfilledDataInsert = 'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 . }';
|
||||
const fulfilledDataInsert = 'INSERT DATA { :s1 :p1 :o1 . :s2 :p2 :o2 . }';
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
startQuads = [ quad(
|
||||
@ -37,17 +37,14 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
converter = {
|
||||
handleSafe: jest.fn(async({ representation, preferences }: RepresentationConverterArgs): Promise<any> =>
|
||||
new BasicRepresentation(representation.data, Object.keys(preferences.type!)[0])),
|
||||
} as unknown as RepresentationConverter;
|
||||
} as any;
|
||||
|
||||
source = {
|
||||
getRepresentation: jest.fn(async(): Promise<any> => new BasicRepresentation(startQuads, dummyType)),
|
||||
getRepresentation: jest.fn(async(): Promise<any> => new BasicRepresentation(startQuads, defaultType)),
|
||||
setRepresentation: jest.fn(),
|
||||
modifyResource: jest.fn(async(): Promise<any> => {
|
||||
throw new Error('noModify');
|
||||
}),
|
||||
} as unknown as ResourceStore;
|
||||
} as any;
|
||||
|
||||
handler = new SparqlUpdatePatchHandler(converter, dummyType);
|
||||
handler = new SparqlUpdatePatchHandler(converter, defaultType);
|
||||
});
|
||||
|
||||
async function basicChecks(quads: Quad[]): Promise<boolean> {
|
||||
@ -55,24 +52,24 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
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,
|
||||
representation: await source.getRepresentation.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 }},
|
||||
preferences: { type: { [defaultType]: 1 }},
|
||||
});
|
||||
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
const setParams = (source.setRepresentation as jest.Mock).mock.calls[0];
|
||||
const setParams = source.setRepresentation.mock.calls[0];
|
||||
expect(setParams[0]).toEqual(identifier);
|
||||
expect(setParams[1]).toEqual(expect.objectContaining({
|
||||
binary: true,
|
||||
metadata: expect.any(RepresentationMetadata),
|
||||
}));
|
||||
expect(setParams[1].metadata.contentType).toEqual(dummyType);
|
||||
expect(setParams[1].metadata.contentType).toEqual(defaultType);
|
||||
await expect(arrayifyStream(setParams[1].data)).resolves.toBeRdfIsomorphic(quads);
|
||||
return true;
|
||||
}
|
||||
@ -101,7 +98,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
});
|
||||
|
||||
it('handles INSERT DATA updates.', async(): Promise<void> => {
|
||||
await handle(fullfilledDataInsert);
|
||||
await handle(fulfilledDataInsert);
|
||||
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/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')) ],
|
||||
@ -202,15 +199,13 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
});
|
||||
|
||||
it('throws the error returned by the store if there is one.', async(): Promise<void> => {
|
||||
source.getRepresentation = jest.fn(async(): Promise<any> => {
|
||||
throw new Error('error');
|
||||
});
|
||||
await expect(handle(fullfilledDataInsert)).rejects.toThrow('error');
|
||||
source.getRepresentation.mockRejectedValueOnce(new Error('error'));
|
||||
await expect(handle(fulfilledDataInsert)).rejects.toThrow('error');
|
||||
});
|
||||
|
||||
it('creates a new resource if it does not exist yet.', async(): Promise<void> => {
|
||||
startQuads = [];
|
||||
(source.getRepresentation as jest.Mock).mockRejectedValueOnce(new NotFoundHttpError());
|
||||
source.getRepresentation.mockRejectedValueOnce(new NotFoundHttpError());
|
||||
const query = 'INSERT DATA { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1>. }';
|
||||
await handle(query);
|
||||
|
||||
@ -218,37 +213,26 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
expect(converter.handleSafe).toHaveBeenLastCalledWith({
|
||||
representation: expect.objectContaining({ binary: false, metadata: expect.any(RepresentationMetadata) }),
|
||||
identifier,
|
||||
preferences: { type: { [dummyType]: 1 }},
|
||||
preferences: { type: { [defaultType]: 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);
|
||||
const setParams = source.setRepresentation.mock.calls[0];
|
||||
expect(setParams[1].metadata.contentType).toEqual(defaultType);
|
||||
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/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());
|
||||
source.getRepresentation.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];
|
||||
const setParams = source.setRepresentation.mock.calls[0];
|
||||
expect(setParams[1].metadata.contentType).toEqual('text/turtle');
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user