diff --git a/config/presets/pod-management.json b/config/presets/pod-management.json index 6d54c68b9..83b72b42a 100644 --- a/config/presets/pod-management.json +++ b/config/presets/pod-management.json @@ -34,8 +34,8 @@ "PodManagerHttpHandler:_args_requestParser": { "@id": "urn:solid-server:default:RequestParser" }, - "PodManagerHttpHandler:_args_agentParser": { - "@type": "AgentJsonParser" + "PodManagerHttpHandler:_args_podSettingsParser": { + "@type": "PodSettingsJsonParser" }, "PodManagerHttpHandler:_args_manager": { "@id": "urn:solid-server:default:PodManager" diff --git a/src/index.ts b/src/index.ts index a33c6f0ac..5ac0fdaf3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -108,11 +108,6 @@ export * from './logging/LogUtil'; export * from './logging/VoidLoggerFactory'; export * from './logging/WinstonLoggerFactory'; -// Pods/Agent -export * from './pods/agent/Agent'; -export * from './pods/agent/AgentJsonParser'; -export * from './pods/agent/AgentParser'; - // Pods/Generate export * from './pods/generate/HandlebarsTemplateEngine'; export * from './pods/generate/IdentifierGenerator'; @@ -122,6 +117,11 @@ export * from './pods/generate/SuffixIdentifierGenerator'; export * from './pods/generate/TemplateEngine'; export * from './pods/generate/TemplatedResourcesGenerator'; +// Pods/Settings +export * from './pods/settings/PodSettings'; +export * from './pods/settings/PodSettingsJsonParser'; +export * from './pods/settings/PodSettingsParser'; + // Pods export * from './pods/GeneratedPodManager'; export * from './pods/PodManager'; diff --git a/src/pods/GeneratedPodManager.ts b/src/pods/GeneratedPodManager.ts index f0327c92f..6207de9c0 100644 --- a/src/pods/GeneratedPodManager.ts +++ b/src/pods/GeneratedPodManager.ts @@ -2,10 +2,10 @@ import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifie import { getLoggerFor } from '../logging/LogUtil'; import type { ResourceStore } from '../storage/ResourceStore'; import { ConflictHttpError } from '../util/errors/ConflictHttpError'; -import type { Agent } from './agent/Agent'; import type { IdentifierGenerator } from './generate/IdentifierGenerator'; import type { ResourcesGenerator } from './generate/ResourcesGenerator'; import type { PodManager } from './PodManager'; +import type { PodSettings } from './settings/PodSettings'; /** * Pod manager that uses an {@link IdentifierGenerator} and {@link ResourcesGenerator} @@ -29,14 +29,14 @@ export class GeneratedPodManager implements PodManager { * Pod identifiers are created based on the identifier generator. * Will throw an error if the given identifier already has a resource. */ - public async createPod(agent: Agent): Promise { - const podIdentifier = this.idGenerator.generate(agent.login); + public async createPod(settings: PodSettings): Promise { + const podIdentifier = this.idGenerator.generate(settings.login); this.logger.info(`Creating pod ${podIdentifier.path}`); if (await this.store.resourceExists(podIdentifier)) { throw new ConflictHttpError(`There already is a resource at ${podIdentifier.path}`); } - const resources = this.resourcesGenerator.generate(podIdentifier, agent); + const resources = this.resourcesGenerator.generate(podIdentifier, settings); let count = 0; for await (const { identifier, representation } of resources) { await this.store.setRepresentation(identifier, representation); diff --git a/src/pods/PodManager.ts b/src/pods/PodManager.ts index afbd37e00..b01737013 100644 --- a/src/pods/PodManager.ts +++ b/src/pods/PodManager.ts @@ -1,5 +1,5 @@ import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; -import type { Agent } from './agent/Agent'; +import type { PodSettings } from './settings/PodSettings'; /** * Covers all functions related to pod management. @@ -7,9 +7,9 @@ import type { Agent } from './agent/Agent'; */ export interface PodManager { /** - * Creates a pod for the given agent data. - * @param agent - Data of the agent that needs a pod. + * Creates a pod for the given settings. + * @param settings - Settings describing the pod. * @returns {@link ResourceIdentifier} of the newly created pod. */ - createPod: (agent: Agent) => Promise; + createPod: (settings: PodSettings) => Promise; } diff --git a/src/pods/PodManagerHttpHandler.ts b/src/pods/PodManagerHttpHandler.ts index f652f76df..5780dfea7 100644 --- a/src/pods/PodManagerHttpHandler.ts +++ b/src/pods/PodManagerHttpHandler.ts @@ -8,16 +8,16 @@ import { BadRequestHttpError } from '../util/errors/BadRequestHttpError'; import { isNativeError } from '../util/errors/ErrorUtil'; import { InternalServerError } from '../util/errors/InternalServerError'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; -import type { AgentParser } from './agent/AgentParser'; import type { PodManager } from './PodManager'; +import type { PodSettingsParser } from './settings/PodSettingsParser'; export interface PodHttpHandlerArgs { /** The path on which this handler should intercept requests. Should start with a slash. */ requestPath: string; /** Parses the incoming request. */ requestParser: RequestParser; - /** Parses the data stream to an Agent. */ - agentParser: AgentParser; + /** Parses the data stream to PodSettings. */ + podSettingsParser: PodSettingsParser; /** Handles the pod management. */ manager: PodManager; /** Writes the outgoing response. */ @@ -31,7 +31,7 @@ export interface PodHttpHandlerArgs { export class PodManagerHttpHandler extends HttpHandler { private readonly requestPath!: string; private readonly requestParser!: RequestParser; - private readonly agentParser!: AgentParser; + private readonly podSettingsParser!: PodSettingsParser; private readonly manager!: PodManager; private readonly responseWriter!: ResponseWriter; @@ -55,8 +55,8 @@ export class PodManagerHttpHandler extends HttpHandler { if (!op.body) { throw new BadRequestHttpError('A body is required to create a pod'); } - const agent = await this.agentParser.handleSafe(op.body); - const id = await this.manager.createPod(agent); + const settings = await this.podSettingsParser.handleSafe(op.body); + const id = await this.manager.createPod(settings); await this.responseWriter.handleSafe({ response, result: new CreatedResponseDescription(id) }); } catch (error: unknown) { diff --git a/src/pods/agent/Agent.ts b/src/pods/agent/Agent.ts deleted file mode 100644 index 491e5c31a..000000000 --- a/src/pods/agent/Agent.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Agent metadata related to pod generation. - */ -export type Agent = { - login: string; - webId: string; - name?: string; - email?: string; -}; diff --git a/src/pods/agent/AgentJsonParser.ts b/src/pods/agent/AgentJsonParser.ts deleted file mode 100644 index aa305e40d..000000000 --- a/src/pods/agent/AgentJsonParser.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Representation } from '../../ldp/representation/Representation'; -import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; -import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; -import { readableToString } from '../../util/StreamUtil'; -import type { Agent } from './Agent'; -import { AgentParser } from './AgentParser'; -import Dict = NodeJS.Dict; - -const requiredKeys: (keyof Agent)[] = [ 'login', 'webId' ]; -const optionalKeys: (keyof Agent)[] = [ 'name', 'email' ]; -const agentKeys: Set = new Set(requiredKeys.concat(optionalKeys)); - -/** - * A parser that extracts Agent data from a JSON body. - */ -export class AgentJsonParser extends AgentParser { - public async canHandle(input: Representation): Promise { - if (!input.metadata.contentType || !this.isJSON(input.metadata.contentType)) { - throw new NotImplementedHttpError('Only JSON data is supported'); - } - } - - public async handle(input: Representation): Promise { - const result = JSON.parse(await readableToString(input.data)); - this.isValidAgent(result); - return result; - } - - private isJSON(mediaType: string): boolean { - return mediaType === 'application/json' || mediaType.endsWith('+json'); - } - - /** - * Checks if all keys in the object are valid Agent keys and if all required keys are there. - */ - private isValidAgent(data: Dict): asserts data is Agent { - for (const key of Object.keys(data)) { - if (!agentKeys.has(key as keyof Agent)) { - throw new BadRequestHttpError(`${key} is not a valid Agent key`); - } - } - for (const key of requiredKeys) { - if (!data[key]) { - throw new BadRequestHttpError(`Input data is missing Agent key ${key}`); - } - } - } -} diff --git a/src/pods/agent/AgentParser.ts b/src/pods/agent/AgentParser.ts deleted file mode 100644 index 4098fa6cf..000000000 --- a/src/pods/agent/AgentParser.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Representation } from '../../ldp/representation/Representation'; -import { AsyncHandler } from '../../util/handlers/AsyncHandler'; -import type { Agent } from './Agent'; - -/** - * Parser that generates a {@link Agent} from the data in the given {@link Representation}. - */ -export abstract class AgentParser extends AsyncHandler { } diff --git a/src/pods/generate/ResourcesGenerator.ts b/src/pods/generate/ResourcesGenerator.ts index 842e60569..8f5f5e025 100644 --- a/src/pods/generate/ResourcesGenerator.ts +++ b/src/pods/generate/ResourcesGenerator.ts @@ -9,6 +9,7 @@ export interface Resource { /** * Generator used to create resources relative to a given base identifier. + * Note that this is not an AsyncHandler since it returns an AsyncIterable instead of a promise. */ export interface ResourcesGenerator { /** diff --git a/src/pods/settings/PodSettings.ts b/src/pods/settings/PodSettings.ts new file mode 100644 index 000000000..d32999f39 --- /dev/null +++ b/src/pods/settings/PodSettings.ts @@ -0,0 +1,28 @@ +/** + * Metadata related to pod generation. + * Although the optional fields are not that relevant since this extends Dict, + * they give an indication of what is sometimes expected. + */ +export interface PodSettings extends NodeJS.Dict { + /** + * The name of the pod. Will be used for generating the identifier. + */ + login: string; + /** + * The WebId of the owner of this pod. + */ + webId: string; + /** + * Required for dynamic pod configuration. + * Indicates the name of the config to use for the pod. + */ + template?: string; + /** + * Name of the owner. Used in provisioning templates. + */ + name?: string; + /** + * E-mail of the owner. Used in provisioning templates. + */ + email?: string; +} diff --git a/src/pods/settings/PodSettingsJsonParser.ts b/src/pods/settings/PodSettingsJsonParser.ts new file mode 100644 index 000000000..064b6d53c --- /dev/null +++ b/src/pods/settings/PodSettingsJsonParser.ts @@ -0,0 +1,41 @@ +import type { Representation } from '../../ldp/representation/Representation'; +import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; +import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; +import { readableToString } from '../../util/StreamUtil'; +import type { PodSettings } from './PodSettings'; +import { PodSettingsParser } from './PodSettingsParser'; +import Dict = NodeJS.Dict; + +const requiredKeys: (keyof PodSettings)[] = [ 'login', 'webId' ]; + +/** + * A parser that extracts PodSettings data from a JSON body. + */ +export class PodSettingsJsonParser extends PodSettingsParser { + public async canHandle(input: Representation): Promise { + if (!input.metadata.contentType || !this.isJSON(input.metadata.contentType)) { + throw new NotImplementedHttpError('Only JSON data is supported'); + } + } + + public async handle(input: Representation): Promise { + const result = JSON.parse(await readableToString(input.data)); + this.isValid(result); + return result; + } + + private isJSON(mediaType: string): boolean { + return mediaType === 'application/json' || mediaType.endsWith('+json'); + } + + /** + * Checks if all the required PodSettings keys are there. + */ + private isValid(data: Dict): asserts data is PodSettings { + for (const key of requiredKeys) { + if (!data[key]) { + throw new BadRequestHttpError(`Input data is missing key ${key}`); + } + } + } +} diff --git a/src/pods/settings/PodSettingsParser.ts b/src/pods/settings/PodSettingsParser.ts new file mode 100644 index 000000000..564452f09 --- /dev/null +++ b/src/pods/settings/PodSettingsParser.ts @@ -0,0 +1,8 @@ +import type { Representation } from '../../ldp/representation/Representation'; +import { AsyncHandler } from '../../util/handlers/AsyncHandler'; +import type { PodSettings } from './PodSettings'; + +/** + * Parser that generates a {@link PodSettings} from the data in the given {@link Representation}. + */ +export abstract class PodSettingsParser extends AsyncHandler { } diff --git a/test/integration/PodCreation.test.ts b/test/integration/PodCreation.test.ts index 520f69f6c..281bf4e8c 100644 --- a/test/integration/PodCreation.test.ts +++ b/test/integration/PodCreation.test.ts @@ -10,7 +10,7 @@ const baseUrl = `http://localhost:${port}/`; describe('A server with a pod handler', (): void => { let server: Server; - const agent = { login: 'alice', webId: 'http://test.com/#alice', name: 'Alice Bob' }; + const settings = { login: 'alice', webId: 'http://test.com/#alice', name: 'Alice Bob' }; beforeAll(async(): Promise => { const factory = await instantiateFromConfig( @@ -29,8 +29,8 @@ describe('A server with a pod handler', (): void => { }); }); - it('creates a pod when posting an Agent to /pods.', async(): Promise => { - const pod = `${baseUrl}${agent.login}/`; + it('creates a pod when posting PodSettings to /pods.', async(): Promise => { + const pod = `${baseUrl}${settings.login}/`; // Pod should not exist yet let res = await fetch(pod); @@ -42,16 +42,16 @@ describe('A server with a pod handler', (): void => { headers: { 'content-type': 'application/json', }, - body: JSON.stringify(agent), + body: JSON.stringify(settings), }); expect(res.status).toBe(201); - expect(res.headers.get('location')).toBe(`${baseUrl}${agent.login}/`); + expect(res.headers.get('location')).toBe(`${baseUrl}${settings.login}/`); // Check if all resources are created res = await fetch(`${pod}.acl`); expect(res.status).toBe(200); let body = await readableToString(res.body as any); - expect(body).toContain(`acl:agent <${agent.webId}>`); + expect(body).toContain(`acl:agent <${settings.webId}>`); res = await fetch(`${pod}profile/card`); expect(res.status).toBe(200); @@ -59,9 +59,9 @@ describe('A server with a pod handler', (): void => { body = await readableToString(res.body as any); expect(body).toBe(`@prefix foaf: . -<${agent.webId}> +<${settings.webId}> a foaf:Person ; - foaf:name "${agent.name}". + foaf:name "${settings.name}". `); }); }); diff --git a/test/integration/Subdomains.test.ts b/test/integration/Subdomains.test.ts index 528e02f7d..06dd88b4e 100644 --- a/test/integration/Subdomains.test.ts +++ b/test/integration/Subdomains.test.ts @@ -26,7 +26,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown let server: Server; let initializer: Initializer; let factory: HttpServerFactory; - const agent = { login: 'alice', webId: 'http://test.com/#alice', name: 'Alice Bob' }; + const settings = { login: 'alice', webId: 'http://test.com/#alice', name: 'Alice Bob' }; const podHost = `alice.localhost:${port}`; const podUrl = `http://${podHost}/`; @@ -75,7 +75,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown let res = await fetch(`${baseUrl}alice`, { method: 'PUT', headers: { - authorization: `WebID ${agent.webId}`, + authorization: `WebID ${settings.webId}`, 'content-type': 'text/plain', }, body: 'this is new data!', @@ -95,7 +95,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown headers: { 'content-type': 'application/json', }, - body: JSON.stringify(agent), + body: JSON.stringify(settings), }); expect(res.status).toBe(201); expect(res.headers.get('location')).toBe(podUrl); @@ -115,7 +115,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown const res = await fetch(`${baseUrl}.acl`, { headers: { forwarded: `host=${podHost}`, - authorization: `WebID ${agent.webId}`, + authorization: `WebID ${settings.webId}`, }, }); expect(res.status).toBe(200); @@ -125,7 +125,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown let res = await fetch(`${baseUrl}alice`, { headers: { forwarded: `host=${podHost}`, - authorization: `WebID ${agent.webId}`, + authorization: `WebID ${settings.webId}`, }, }); expect(res.status).toBe(404); @@ -134,7 +134,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown method: 'PUT', headers: { forwarded: `host=${podHost}`, - authorization: `WebID ${agent.webId}`, + authorization: `WebID ${settings.webId}`, 'content-type': 'text/plain', }, body: 'this is new data!', @@ -144,7 +144,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown res = await fetch(`${baseUrl}alice`, { headers: { forwarded: `host=${podHost}`, - authorization: `WebID ${agent.webId}`, + authorization: `WebID ${settings.webId}`, }, }); expect(res.status).toBe(200); @@ -157,7 +157,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeUrn, teardown headers: { 'content-type': 'application/json', }, - body: JSON.stringify(agent), + body: JSON.stringify(settings), }); expect(res.status).toBe(409); }); diff --git a/test/unit/pods/GeneratedPodManager.test.ts b/test/unit/pods/GeneratedPodManager.test.ts index 8961313e6..d4524c402 100644 --- a/test/unit/pods/GeneratedPodManager.test.ts +++ b/test/unit/pods/GeneratedPodManager.test.ts @@ -1,14 +1,14 @@ 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 { PodSettings } from '../../../src/pods/settings/PodSettings'; import type { ResourceStore } from '../../../src/storage/ResourceStore'; import { ConflictHttpError } from '../../../src/util/errors/ConflictHttpError'; describe('A GeneratedPodManager', (): void => { const base = 'http://test.com/'; - let agent: Agent; + let settings: PodSettings; let store: jest.Mocked; let generatorData: Resource[]; const idGenerator: IdentifierGenerator = { @@ -18,7 +18,7 @@ describe('A GeneratedPodManager', (): void => { let manager: GeneratedPodManager; beforeEach(async(): Promise => { - agent = { + settings = { login: 'user', name: 'first last', webId: 'http://secure/webId', @@ -42,13 +42,13 @@ describe('A GeneratedPodManager', (): void => { it('throws an error if the generate identifier is not available.', async(): Promise => { store.resourceExists.mockResolvedValueOnce(true); - const result = manager.createPod(agent); + const result = manager.createPod(settings); 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 => { - await expect(manager.createPod(agent)).resolves.toEqual({ path: `${base}${agent.login}/` }); + await expect(manager.createPod(settings)).resolves.toEqual({ path: `${base}${settings.login}/` }); expect(store.setRepresentation).toHaveBeenCalledTimes(3); expect(store.setRepresentation).toHaveBeenNthCalledWith(1, { path: '/path/' }, '/'); diff --git a/test/unit/pods/PodManagerHttpHandler.test.ts b/test/unit/pods/PodManagerHttpHandler.test.ts index d32c01375..97c1f3bba 100644 --- a/test/unit/pods/PodManagerHttpHandler.test.ts +++ b/test/unit/pods/PodManagerHttpHandler.test.ts @@ -2,9 +2,9 @@ import type { RequestParser } from '../../../src/ldp/http/RequestParser'; import { CreatedResponseDescription } from '../../../src/ldp/http/response/CreatedResponseDescription'; import type { ResponseWriter } from '../../../src/ldp/http/ResponseWriter'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; -import type { AgentParser } from '../../../src/pods/agent/AgentParser'; import type { PodManager } from '../../../src/pods/PodManager'; import { PodManagerHttpHandler } from '../../../src/pods/PodManagerHttpHandler'; +import type { PodSettingsParser } from '../../../src/pods/settings/PodSettingsParser'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import type { HttpResponse } from '../../../src/server/HttpResponse'; import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError'; @@ -15,19 +15,19 @@ import { StaticAsyncHandler } from '../../util/StaticAsyncHandler'; describe('A PodManagerHttpHandler', (): void => { const requestPath = '/pods'; let requestParser: RequestParser; - let agentParser: AgentParser; + let podSettingsParser: PodSettingsParser; let manager: PodManager; let responseWriter: ResponseWriter; let handler: PodManagerHttpHandler; beforeEach(async(): Promise => { requestParser = { handleSafe: jest.fn((): any => 'requestParser') } as any; - agentParser = new StaticAsyncHandler(true, 'agentParser' as any); + podSettingsParser = new StaticAsyncHandler(true, 'podSettingsParser' as any); manager = { createPod: jest.fn(), }; responseWriter = { handleSafe: jest.fn((): any => 'response') } as any; - handler = new PodManagerHttpHandler({ requestPath, requestParser, agentParser, manager, responseWriter }); + handler = new PodManagerHttpHandler({ requestPath, requestParser, podSettingsParser, manager, responseWriter }); }); it('only supports requests to /pods.', async(): Promise => { diff --git a/test/unit/pods/agent/AgentJsonParser.test.ts b/test/unit/pods/settings/PodSettingsJsonParser.test.ts similarity index 76% rename from test/unit/pods/agent/AgentJsonParser.test.ts rename to test/unit/pods/settings/PodSettingsJsonParser.test.ts index 6c5ff3290..b119a8b90 100644 --- a/test/unit/pods/agent/AgentJsonParser.test.ts +++ b/test/unit/pods/settings/PodSettingsJsonParser.test.ts @@ -1,15 +1,15 @@ import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation'; import type { Representation } from '../../../../src/ldp/representation/Representation'; import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata'; -import { AgentJsonParser } from '../../../../src/pods/agent/AgentJsonParser'; +import { PodSettingsJsonParser } from '../../../../src/pods/settings/PodSettingsJsonParser'; import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; import { guardedStreamFrom } from '../../../../src/util/StreamUtil'; -describe('An AgentJsonParser', (): void => { +describe('An PodSettingsJsonParser', (): void => { let metadata: RepresentationMetadata; let representation: Representation; - const parser = new AgentJsonParser(); + const parser = new PodSettingsJsonParser(); beforeEach(async(): Promise => { metadata = new RepresentationMetadata('application/json'); @@ -31,19 +31,7 @@ describe('An AgentJsonParser', (): 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 => { - 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'); + await expect(result).rejects.toThrow('Input data is missing key webId'); }); it('generates a User object.', async(): Promise => {