feat: add implementations of pod-related interfaces

This commit is contained in:
Joachim Van Herwegen
2020-11-27 13:43:44 +01:00
parent 39745ccf22
commit 9653deec7f
9 changed files with 310 additions and 1 deletions

View File

@@ -0,0 +1,67 @@
import { Readable } from 'stream';
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
import type { Agent } from '../../../src/pods/agent/Agent';
import type { IdentifierGenerator } from '../../../src/pods/generate/IdentifierGenerator';
import type { Resource, ResourcesGenerator } from '../../../src/pods/generate/ResourcesGenerator';
import { GeneratedPodManager } from '../../../src/pods/GeneratedPodManager';
import type { ResourceStore } from '../../../src/storage/ResourceStore';
import { ConflictHttpError } from '../../../src/util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
describe('A GeneratedPodManager', (): void => {
const base = 'http://test.com/';
let agent: Agent;
let store: ResourceStore;
let generatorData: Resource[];
const idGenerator: IdentifierGenerator = {
generate: (slug: string): ResourceIdentifier => ({ path: `${base}${slug}/` }),
};
let resGenerator: ResourcesGenerator;
let manager: GeneratedPodManager;
beforeEach(async(): Promise<void> => {
agent = {
login: 'user',
name: 'first last',
webId: 'http://secure/webId',
};
store = {
getRepresentation: jest.fn((): any => {
throw new NotFoundHttpError();
}),
setRepresentation: jest.fn(),
} as any;
generatorData = [
{ identifier: { path: '/path/' }, representation: '/' as any },
{ identifier: { path: '/path/a/' }, representation: '/a/' as any },
{ identifier: { path: '/path/a/b' }, representation: '/a/b' as any },
];
resGenerator = {
generate: jest.fn(async function* (): any {
yield* generatorData;
}),
};
manager = new GeneratedPodManager(store, idGenerator, resGenerator);
});
it('throws an error if the generate identifier is not available.', async(): Promise<void> => {
(store.getRepresentation as jest.Mock).mockImplementationOnce((): any => ({
data: Readable.from([]),
metadata: new RepresentationMetadata(),
binary: true,
}));
const result = manager.createPod(agent);
await expect(result).rejects.toThrow(`There already is a resource at ${base}user/`);
await expect(result).rejects.toThrow(ConflictHttpError);
});
it('generates an identifier and writes containers before writing the resources in them.', async(): Promise<void> => {
await expect(manager.createPod(agent)).resolves.toEqual({ path: `${base}${agent.login}/` });
expect(store.setRepresentation).toHaveBeenCalledTimes(3);
expect(store.setRepresentation).toHaveBeenNthCalledWith(1, { path: '/path/' }, '/');
expect(store.setRepresentation).toHaveBeenNthCalledWith(2, { path: '/path/a/' }, '/a/');
expect(store.setRepresentation).toHaveBeenNthCalledWith(3, { path: '/path/a/b' }, '/a/b');
});
});

View File

@@ -0,0 +1,66 @@
import type { Representation } from '../../../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import { AgentJsonParser } from '../../../../src/pods/agent/AgentJsonParser';
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
import { guardedStreamFrom } from '../../../../src/util/StreamUtil';
describe('An AgentJsonParser', (): void => {
let metadata: RepresentationMetadata;
let representation: Representation;
const parser = new AgentJsonParser();
beforeEach(async(): Promise<void> => {
metadata = new RepresentationMetadata();
metadata.contentType = 'application/json';
representation = {
binary: true,
data: guardedStreamFrom([]),
metadata,
};
});
it('only supports JSON data.', async(): Promise<void> => {
metadata.contentType = undefined;
const result = parser.canHandle(representation);
await expect(result).rejects.toThrow(NotImplementedHttpError);
await expect(result).rejects.toThrow('Only JSON data is supported');
metadata.contentType = 'application/json';
await expect(parser.canHandle(representation)).resolves.toBeUndefined();
metadata.contentType = 'application/ld+json';
await expect(parser.canHandle(representation)).resolves.toBeUndefined();
});
it('errors if required keys are missing.', async(): Promise<void> => {
representation.data = guardedStreamFrom([ JSON.stringify({ login: 'login' }) ]);
const result = parser.handle(representation);
await expect(result).rejects.toThrow(BadRequestHttpError);
await expect(result).rejects.toThrow('Input data is missing Agent key webId');
});
it('errors if unknown keys are present.', async(): Promise<void> => {
representation.data = guardedStreamFrom([ JSON.stringify({
login: 'login',
webId: 'webId',
name: 'name',
unknown: 'unknown',
}) ]);
const result = parser.handle(representation);
await expect(result).rejects.toThrow(BadRequestHttpError);
await expect(result).rejects.toThrow('unknown is not a valid Agent key');
});
it('generates a User object.', async(): Promise<void> => {
representation.data = guardedStreamFrom([ JSON.stringify({
login: 'login',
webId: 'webId',
name: 'name',
}) ]);
await expect(parser.handle(representation)).resolves
.toEqual({
login: 'login',
webId: 'webId',
name: 'name',
});
});
});

View File

@@ -0,0 +1,14 @@
import { SuffixIdentifierGenerator } from '../../../../src/pods/generate/SuffixIdentifierGenerator';
describe('A SuffixIdentifierGenerator', (): void => {
const base = 'http://test.com/';
const generator = new SuffixIdentifierGenerator(base);
it('generates identifiers by appending the slug.', async(): Promise<void> => {
expect(generator.generate('slug')).toEqual({ path: `${base}slug/` });
});
it('converts non-alphanumerics to dashes.', async(): Promise<void> => {
expect(generator.generate('sàl/u㋡g')).toEqual({ path: `${base}s-l-u-g/` });
});
});