mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Convert IDP input data to JSON
This commit is contained in:
parent
7f3eab0b20
commit
4f1a86dfa0
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"comment": "Converts application/x-www-form-urlencoded to application/json.",
|
||||||
|
"@id": "urn:solid-server:default:FormToJsonConverter",
|
||||||
|
"@type": "FormToJsonConverter"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
|
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/dynamic-json-template.json",
|
"files-scs:config/util/representation-conversion/converters/dynamic-json-template.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/errors.json",
|
"files-scs:config/util/representation-conversion/converters/errors.json",
|
||||||
|
"files-scs:config/util/representation-conversion/converters/form-to-json.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/markdown.json",
|
"files-scs:config/util/representation-conversion/converters/markdown.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
|
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json"
|
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json"
|
||||||
@ -32,7 +33,8 @@
|
|||||||
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
|
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
|
||||||
{ "@id": "urn:solid-server:default:ErrorToQuadConverter" },
|
{ "@id": "urn:solid-server:default:ErrorToQuadConverter" },
|
||||||
{ "@id": "urn:solid-server:default:ErrorToTemplateConverter" },
|
{ "@id": "urn:solid-server:default:ErrorToTemplateConverter" },
|
||||||
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
|
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" },
|
||||||
|
{ "@id": "urn:solid-server:default:FormToJsonConverter" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -158,6 +158,16 @@ export class IdentityProviderHttpHandler extends HttpHandler {
|
|||||||
return provider.callback(request, response);
|
return provider.callback(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDP handlers expect JSON data
|
||||||
|
if (operation.body) {
|
||||||
|
const args = {
|
||||||
|
representation: operation.body,
|
||||||
|
preferences: { type: { [APPLICATION_JSON]: 1 }},
|
||||||
|
identifier: operation.target,
|
||||||
|
};
|
||||||
|
operation.body = await this.converter.handleSafe(args);
|
||||||
|
}
|
||||||
|
|
||||||
const { result, templateFiles } = await this.resolveRoute(operation, route, oidcInteraction);
|
const { result, templateFiles } = await this.resolveRoute(operation, route, oidcInteraction);
|
||||||
const responseDescription =
|
const responseDescription =
|
||||||
await this.handleInteractionResult(operation, request, result, templateFiles, oidcInteraction);
|
await this.handleInteractionResult(operation, request, result, templateFiles, oidcInteraction);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||||
|
import { readJsonStream } from '../../util/StreamUtil';
|
||||||
import { InteractionHandler } from './email-password/handler/InteractionHandler';
|
import { InteractionHandler } from './email-password/handler/InteractionHandler';
|
||||||
import type { InteractionCompleteResult, InteractionHandlerInput } from './email-password/handler/InteractionHandler';
|
import type { InteractionCompleteResult, InteractionHandlerInput } from './email-password/handler/InteractionHandler';
|
||||||
import { getFormDataRequestBody } from './util/FormDataUtil';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple InteractionHttpHandler that sends the session accountId to the InteractionCompleter as webId.
|
* Simple InteractionHttpHandler that sends the session accountId to the InteractionCompleter as webId.
|
||||||
@ -12,7 +12,7 @@ export class SessionHttpHandler extends InteractionHandler {
|
|||||||
throw new NotImplementedHttpError('Only interactions with a valid session are supported.');
|
throw new NotImplementedHttpError('Only interactions with a valid session are supported.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { remember } = await getFormDataRequestBody(operation);
|
const { remember } = await readJsonStream(operation.body!.data);
|
||||||
return {
|
return {
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
details: { webId: oidcInteraction.session.accountId, shouldRemember: Boolean(remember) },
|
details: { webId: oidcInteraction.session.accountId, shouldRemember: Boolean(remember) },
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { getLoggerFor } from '../../../../logging/LogUtil';
|
import { getLoggerFor } from '../../../../logging/LogUtil';
|
||||||
import { ensureTrailingSlash, joinUrl } from '../../../../util/PathUtil';
|
import { ensureTrailingSlash, joinUrl } from '../../../../util/PathUtil';
|
||||||
|
import { readJsonStream } from '../../../../util/StreamUtil';
|
||||||
import type { TemplateEngine } from '../../../../util/templates/TemplateEngine';
|
import type { TemplateEngine } from '../../../../util/templates/TemplateEngine';
|
||||||
import type { EmailSender } from '../../util/EmailSender';
|
import type { EmailSender } from '../../util/EmailSender';
|
||||||
import { getFormDataRequestBody } from '../../util/FormDataUtil';
|
|
||||||
import { throwIdpInteractionError } from '../EmailPasswordUtil';
|
import { throwIdpInteractionError } from '../EmailPasswordUtil';
|
||||||
import type { AccountStore } from '../storage/AccountStore';
|
import type { AccountStore } from '../storage/AccountStore';
|
||||||
import { InteractionHandler } from './InteractionHandler';
|
import { InteractionHandler } from './InteractionHandler';
|
||||||
@ -41,7 +41,7 @@ export class ForgotPasswordHandler extends InteractionHandler {
|
|||||||
public async handle({ operation }: InteractionHandlerInput): Promise<InteractionResponseResult<{ email: string }>> {
|
public async handle({ operation }: InteractionHandlerInput): Promise<InteractionResponseResult<{ email: string }>> {
|
||||||
try {
|
try {
|
||||||
// Validate incoming data
|
// Validate incoming data
|
||||||
const { email } = await getFormDataRequestBody(operation);
|
const { email } = await readJsonStream(operation.body!.data);
|
||||||
assert(typeof email === 'string' && email.length > 0, 'Email required');
|
assert(typeof email === 'string' && email.length > 0, 'Email required');
|
||||||
|
|
||||||
await this.resetPassword(email);
|
await this.resetPassword(email);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import type { KoaContextWithOIDC } from 'oidc-provider';
|
import type { KoaContextWithOIDC } from 'oidc-provider';
|
||||||
import type { Operation } from '../../../../ldp/operations/Operation';
|
import type { Operation } from '../../../../ldp/operations/Operation';
|
||||||
|
import { APPLICATION_JSON } from '../../../../util/ContentTypes';
|
||||||
|
import { NotImplementedHttpError } from '../../../../util/errors/NotImplementedHttpError';
|
||||||
import { AsyncHandler } from '../../../../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../../../../util/handlers/AsyncHandler';
|
||||||
import type { InteractionCompleterParams } from '../../util/InteractionCompleter';
|
import type { InteractionCompleterParams } from '../../util/InteractionCompleter';
|
||||||
|
|
||||||
@ -32,5 +34,12 @@ export interface InteractionCompleteResult {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler used for IDP interactions.
|
* Handler used for IDP interactions.
|
||||||
|
* Only supports JSON data.
|
||||||
*/
|
*/
|
||||||
export abstract class InteractionHandler extends AsyncHandler<InteractionHandlerInput, InteractionHandlerResult> {}
|
export abstract class InteractionHandler extends AsyncHandler<InteractionHandlerInput, InteractionHandlerResult> {
|
||||||
|
public async canHandle({ operation }: InteractionHandlerInput): Promise<void> {
|
||||||
|
if (operation.body?.metadata.contentType !== APPLICATION_JSON) {
|
||||||
|
throw new NotImplementedHttpError('Only application/json data is supported.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import type { Operation } from '../../../../ldp/operations/Operation';
|
import type { Operation } from '../../../../ldp/operations/Operation';
|
||||||
import { getLoggerFor } from '../../../../logging/LogUtil';
|
import { getLoggerFor } from '../../../../logging/LogUtil';
|
||||||
import { getFormDataRequestBody } from '../../util/FormDataUtil';
|
import { readJsonStream } from '../../../../util/StreamUtil';
|
||||||
import { throwIdpInteractionError } from '../EmailPasswordUtil';
|
import { throwIdpInteractionError } from '../EmailPasswordUtil';
|
||||||
import type { AccountStore } from '../storage/AccountStore';
|
import type { AccountStore } from '../storage/AccountStore';
|
||||||
import { InteractionHandler } from './InteractionHandler';
|
import { InteractionHandler } from './InteractionHandler';
|
||||||
@ -28,7 +28,7 @@ export class LoginHandler extends InteractionHandler {
|
|||||||
this.logger.debug(`Logging in user ${email}`);
|
this.logger.debug(`Logging in user ${email}`);
|
||||||
return {
|
return {
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
details: { webId, shouldRemember: Boolean(remember) },
|
details: { webId, shouldRemember: remember },
|
||||||
};
|
};
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
throwIdpInteractionError(err, { email });
|
throwIdpInteractionError(err, { email });
|
||||||
@ -43,7 +43,7 @@ export class LoginHandler extends InteractionHandler {
|
|||||||
private async parseInput(operation: Operation): Promise<{ email: string; password: string; remember: boolean }> {
|
private async parseInput(operation: Operation): Promise<{ email: string; password: string; remember: boolean }> {
|
||||||
const prefilled: Record<string, string> = {};
|
const prefilled: Record<string, string> = {};
|
||||||
try {
|
try {
|
||||||
const { email, password, remember } = await getFormDataRequestBody(operation);
|
const { email, password, remember } = await readJsonStream(operation.body!.data);
|
||||||
assert(typeof email === 'string' && email.length > 0, 'Email required');
|
assert(typeof email === 'string' && email.length > 0, 'Email required');
|
||||||
prefilled.email = email;
|
prefilled.email = email;
|
||||||
assert(typeof password === 'string' && password.length > 0, 'Password required');
|
assert(typeof password === 'string' && password.length > 0, 'Password required');
|
||||||
|
@ -6,8 +6,8 @@ import type { IdentifierGenerator } from '../../../../pods/generate/IdentifierGe
|
|||||||
import type { PodManager } from '../../../../pods/PodManager';
|
import type { PodManager } from '../../../../pods/PodManager';
|
||||||
import type { PodSettings } from '../../../../pods/settings/PodSettings';
|
import type { PodSettings } from '../../../../pods/settings/PodSettings';
|
||||||
import { joinUrl } from '../../../../util/PathUtil';
|
import { joinUrl } from '../../../../util/PathUtil';
|
||||||
|
import { readJsonStream } from '../../../../util/StreamUtil';
|
||||||
import type { OwnershipValidator } from '../../../ownership/OwnershipValidator';
|
import type { OwnershipValidator } from '../../../ownership/OwnershipValidator';
|
||||||
import { getFormDataRequestBody } from '../../util/FormDataUtil';
|
|
||||||
import { assertPassword, throwIdpInteractionError } from '../EmailPasswordUtil';
|
import { assertPassword, throwIdpInteractionError } from '../EmailPasswordUtil';
|
||||||
import type { AccountStore } from '../storage/AccountStore';
|
import type { AccountStore } from '../storage/AccountStore';
|
||||||
import type { InteractionResponseResult, InteractionHandlerInput } from './InteractionHandler';
|
import type { InteractionResponseResult, InteractionHandlerInput } from './InteractionHandler';
|
||||||
@ -186,12 +186,12 @@ export class RegistrationHandler extends InteractionHandler {
|
|||||||
* Parses the input request into a `ParseResult`.
|
* Parses the input request into a `ParseResult`.
|
||||||
*/
|
*/
|
||||||
private async parseInput(operation: Operation): Promise<ParsedInput> {
|
private async parseInput(operation: Operation): Promise<ParsedInput> {
|
||||||
const parsed = await getFormDataRequestBody(operation);
|
const parsed = await readJsonStream(operation.body!.data);
|
||||||
const prefilled: Record<string, string> = {};
|
const prefilled: Record<string, string> = {};
|
||||||
try {
|
try {
|
||||||
for (const [ key, value ] of Object.entries(parsed)) {
|
for (const [ key, value ] of Object.entries(parsed)) {
|
||||||
assert(!Array.isArray(value), `Unexpected multiple values for ${key}.`);
|
assert(!Array.isArray(value), `Unexpected multiple values for ${key}.`);
|
||||||
prefilled[key] = value ? value.trim() : '';
|
prefilled[key] = typeof value === 'string' ? value.trim() : value;
|
||||||
}
|
}
|
||||||
return this.validateInput(prefilled);
|
return this.validateInput(prefilled);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { getLoggerFor } from '../../../../logging/LogUtil';
|
import { getLoggerFor } from '../../../../logging/LogUtil';
|
||||||
import { getFormDataRequestBody } from '../../util/FormDataUtil';
|
import { readJsonStream } from '../../../../util/StreamUtil';
|
||||||
import { assertPassword, throwIdpInteractionError } from '../EmailPasswordUtil';
|
import { assertPassword, throwIdpInteractionError } from '../EmailPasswordUtil';
|
||||||
import type { AccountStore } from '../storage/AccountStore';
|
import type { AccountStore } from '../storage/AccountStore';
|
||||||
import type { InteractionResponseResult, InteractionHandlerInput } from './InteractionHandler';
|
import type { InteractionResponseResult, InteractionHandlerInput } from './InteractionHandler';
|
||||||
@ -25,7 +25,7 @@ export class ResetPasswordHandler extends InteractionHandler {
|
|||||||
// Extract record ID from request URL
|
// Extract record ID from request URL
|
||||||
const recordId = /\/([^/]+)$/u.exec(operation.target.path)?.[1];
|
const recordId = /\/([^/]+)$/u.exec(operation.target.path)?.[1];
|
||||||
// Validate input data
|
// Validate input data
|
||||||
const { password, confirmPassword } = await getFormDataRequestBody(operation);
|
const { password, confirmPassword } = await readJsonStream(operation.body!.data);
|
||||||
assert(
|
assert(
|
||||||
typeof recordId === 'string' && recordId.length > 0,
|
typeof recordId === 'string' && recordId.length > 0,
|
||||||
'Invalid request. Open the link from your email again',
|
'Invalid request. Open the link from your email again',
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import type { ParsedUrlQuery } from 'querystring';
|
|
||||||
import { parse } from 'querystring';
|
|
||||||
import type { Operation } from '../../../ldp/operations/Operation';
|
|
||||||
import { APPLICATION_X_WWW_FORM_URLENCODED } from '../../../util/ContentTypes';
|
|
||||||
import { UnsupportedMediaTypeHttpError } from '../../../util/errors/UnsupportedMediaTypeHttpError';
|
|
||||||
import { readableToString } from '../../../util/StreamUtil';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes in an operation and parses its body as 'application/x-www-form-urlencoded'
|
|
||||||
*/
|
|
||||||
export async function getFormDataRequestBody(operation: Operation): Promise<ParsedUrlQuery> {
|
|
||||||
if (operation.body?.metadata.contentType !== APPLICATION_X_WWW_FORM_URLENCODED) {
|
|
||||||
throw new UnsupportedMediaTypeHttpError();
|
|
||||||
}
|
|
||||||
const body = await readableToString(operation.body.data);
|
|
||||||
return parse(body);
|
|
||||||
}
|
|
@ -44,7 +44,6 @@ export * from './identity/interaction/email-password/EmailPasswordUtil';
|
|||||||
// Identity/Interaction/Util
|
// Identity/Interaction/Util
|
||||||
export * from './identity/interaction/util/BaseEmailSender';
|
export * from './identity/interaction/util/BaseEmailSender';
|
||||||
export * from './identity/interaction/util/EmailSender';
|
export * from './identity/interaction/util/EmailSender';
|
||||||
export * from './identity/interaction/util/FormDataUtil';
|
|
||||||
export * from './identity/interaction/util/IdpInteractionError';
|
export * from './identity/interaction/util/IdpInteractionError';
|
||||||
export * from './identity/interaction/util/InteractionCompleter';
|
export * from './identity/interaction/util/InteractionCompleter';
|
||||||
|
|
||||||
@ -230,6 +229,7 @@ export * from './storage/conversion/ConversionUtil';
|
|||||||
export * from './storage/conversion/DynamicJsonToTemplateConverter';
|
export * from './storage/conversion/DynamicJsonToTemplateConverter';
|
||||||
export * from './storage/conversion/ErrorToQuadConverter';
|
export * from './storage/conversion/ErrorToQuadConverter';
|
||||||
export * from './storage/conversion/ErrorToTemplateConverter';
|
export * from './storage/conversion/ErrorToTemplateConverter';
|
||||||
|
export * from './storage/conversion/FormToJsonConverter';
|
||||||
export * from './storage/conversion/IfNeededConverter';
|
export * from './storage/conversion/IfNeededConverter';
|
||||||
export * from './storage/conversion/MarkdownToHtmlConverter';
|
export * from './storage/conversion/MarkdownToHtmlConverter';
|
||||||
export * from './storage/conversion/PassthroughConverter';
|
export * from './storage/conversion/PassthroughConverter';
|
||||||
|
26
src/storage/conversion/FormToJsonConverter.ts
Normal file
26
src/storage/conversion/FormToJsonConverter.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { parse } from 'querystring';
|
||||||
|
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
|
||||||
|
import type { Representation } from '../../ldp/representation/Representation';
|
||||||
|
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
|
||||||
|
import { APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED } from '../../util/ContentTypes';
|
||||||
|
import { readableToString } from '../../util/StreamUtil';
|
||||||
|
import { CONTENT_TYPE } from '../../util/Vocabularies';
|
||||||
|
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||||
|
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts application/x-www-form-urlencoded data to application/json.
|
||||||
|
* Due to the nature of form data, the result will be a simple key/value JSON object.
|
||||||
|
*/
|
||||||
|
export class FormToJsonConverter extends TypedRepresentationConverter {
|
||||||
|
public constructor() {
|
||||||
|
super(APPLICATION_X_WWW_FORM_URLENCODED, APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||||
|
const body = await readableToString(representation.data);
|
||||||
|
const json = JSON.stringify(parse(body));
|
||||||
|
const metadata = new RepresentationMetadata(representation.metadata, { [CONTENT_TYPE]: APPLICATION_JSON });
|
||||||
|
return new BasicRepresentation(json, metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,17 @@ export async function readableToQuads(stream: Readable): Promise<Store> {
|
|||||||
return quads;
|
return quads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interprets the stream as JSON and converts it to a Dict.
|
||||||
|
* @param stream - Stream of JSON data.
|
||||||
|
*
|
||||||
|
* @returns The parsed object.
|
||||||
|
*/
|
||||||
|
export async function readJsonStream(stream: Readable): Promise<NodeJS.Dict<any>> {
|
||||||
|
const body = await readableToString(stream);
|
||||||
|
return JSON.parse(body);
|
||||||
|
}
|
||||||
|
|
||||||
// These error messages usually indicate expected behaviour so should not give a warning.
|
// These error messages usually indicate expected behaviour so should not give a warning.
|
||||||
// We compare against the error message instead of the code
|
// We compare against the error message instead of the code
|
||||||
// since the second one is from an external library that does not assign an error code.
|
// since the second one is from an external library that does not assign an error code.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { mkdirSync } from 'fs';
|
import { mkdirSync } from 'fs';
|
||||||
import { stringify } from 'querystring';
|
|
||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
import { joinFilePath } from '../../src/util/PathUtil';
|
import { joinFilePath } from '../../src/util/PathUtil';
|
||||||
@ -56,8 +55,8 @@ describe.each(configs)('A dynamic pod server with template config %s', (template
|
|||||||
it('creates a pod with the given config.', async(): Promise<void> => {
|
it('creates a pod with the given config.', async(): Promise<void> => {
|
||||||
const res = await fetch(`${baseUrl}idp/register`, {
|
const res = await fetch(`${baseUrl}idp/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
});
|
});
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
await expect(res.text()).resolves.toContain(podUrl);
|
await expect(res.text()).resolves.toContain(podUrl);
|
||||||
@ -112,8 +111,8 @@ describe.each(configs)('A dynamic pod server with template config %s', (template
|
|||||||
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
||||||
const res = await fetch(`${baseUrl}idp/register`, {
|
const res = await fetch(`${baseUrl}idp/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
});
|
});
|
||||||
// 200 due to there only being a HTML solution right now that only returns 200
|
// 200 due to there only being a HTML solution right now that only returns 200
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { stringify } from 'querystring';
|
|
||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
import { getPort } from '../util/Util';
|
import { getPort } from '../util/Util';
|
||||||
@ -85,8 +84,8 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
|
|||||||
it('creates pods in a subdomain.', async(): Promise<void> => {
|
it('creates pods in a subdomain.', async(): Promise<void> => {
|
||||||
const res = await fetch(`${baseUrl}idp/register`, {
|
const res = await fetch(`${baseUrl}idp/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
});
|
});
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
await expect(res.text()).resolves.toContain(podUrl);
|
await expect(res.text()).resolves.toContain(podUrl);
|
||||||
@ -145,8 +144,8 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
|
|||||||
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
||||||
const res = await fetch(`${baseUrl}idp/register`, {
|
const res = await fetch(`${baseUrl}idp/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
});
|
});
|
||||||
// 200 due to there only being a HTML solution right now that only returns 200
|
// 200 due to there only being a HTML solution right now that only returns 200
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
|
@ -10,8 +10,10 @@ import type { ResponseWriter } from '../../../src/ldp/http/ResponseWriter';
|
|||||||
import type { Operation } from '../../../src/ldp/operations/Operation';
|
import type { Operation } from '../../../src/ldp/operations/Operation';
|
||||||
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
|
||||||
import type { Representation } from '../../../src/ldp/representation/Representation';
|
import type { Representation } from '../../../src/ldp/representation/Representation';
|
||||||
|
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
|
||||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
||||||
|
import { getBestPreference } from '../../../src/storage/conversion/ConversionUtil';
|
||||||
import type {
|
import type {
|
||||||
RepresentationConverter,
|
RepresentationConverter,
|
||||||
RepresentationConverterArgs,
|
RepresentationConverterArgs,
|
||||||
@ -20,7 +22,7 @@ import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpErro
|
|||||||
import { InternalServerError } from '../../../src/util/errors/InternalServerError';
|
import { InternalServerError } from '../../../src/util/errors/InternalServerError';
|
||||||
import { joinUrl } from '../../../src/util/PathUtil';
|
import { joinUrl } from '../../../src/util/PathUtil';
|
||||||
import { readableToString } from '../../../src/util/StreamUtil';
|
import { readableToString } from '../../../src/util/StreamUtil';
|
||||||
import { SOLID_HTTP, SOLID_META } from '../../../src/util/Vocabularies';
|
import { CONTENT_TYPE, SOLID_HTTP, SOLID_META } from '../../../src/util/Vocabularies';
|
||||||
|
|
||||||
describe('An IdentityProviderHttpHandler', (): void => {
|
describe('An IdentityProviderHttpHandler', (): void => {
|
||||||
const apiVersion = '0.1';
|
const apiVersion = '0.1';
|
||||||
@ -45,7 +47,9 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
handleSafe: jest.fn(async(req: HttpRequest): Promise<Operation> => ({
|
handleSafe: jest.fn(async(req: HttpRequest): Promise<Operation> => ({
|
||||||
target: { path: joinUrl(baseUrl, req.url!) },
|
target: { path: joinUrl(baseUrl, req.url!) },
|
||||||
method: req.method!,
|
method: req.method!,
|
||||||
body: new BasicRepresentation('', req.headers['content-type'] ?? 'text/plain'),
|
body: req.method === 'GET' ?
|
||||||
|
undefined :
|
||||||
|
new BasicRepresentation('', req.headers['content-type'] ?? 'text/plain'),
|
||||||
preferences: { type: { 'text/html': 1 }},
|
preferences: { type: { 'text/html': 1 }},
|
||||||
})),
|
})),
|
||||||
} as any;
|
} as any;
|
||||||
@ -77,7 +81,12 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
converter = {
|
converter = {
|
||||||
handleSafe: jest.fn((input: RepresentationConverterArgs): Representation => input.representation),
|
handleSafe: jest.fn((input: RepresentationConverterArgs): Representation => {
|
||||||
|
// Just find the best match;
|
||||||
|
const type = getBestPreference(input.preferences.type!, { '*/*': 1 })!;
|
||||||
|
const metadata = new RepresentationMetadata(input.representation.metadata, { [CONTENT_TYPE]: type.value });
|
||||||
|
return new BasicRepresentation(input.representation.data, metadata);
|
||||||
|
}),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
interactionCompleter = { handleSafe: jest.fn().mockResolvedValue('http://test.com/idp/auth') } as any;
|
interactionCompleter = { handleSafe: jest.fn().mockResolvedValue('http://test.com/idp/auth') } as any;
|
||||||
@ -116,7 +125,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
expect(JSON.parse(await readableToString(result.data!)))
|
expect(JSON.parse(await readableToString(result.data!)))
|
||||||
.toEqual({ apiVersion, errorMessage: '', prefilled: {}, authenticating: false });
|
.toEqual({ apiVersion, errorMessage: '', prefilled: {}, authenticating: false });
|
||||||
expect(result.statusCode).toBe(200);
|
expect(result.statusCode).toBe(200);
|
||||||
expect(result.metadata?.contentType).toBe('application/json');
|
expect(result.metadata?.contentType).toBe('text/html');
|
||||||
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
|
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,16 +133,17 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
request.url = '/idp/routeResponse';
|
request.url = '/idp/routeResponse';
|
||||||
request.method = 'POST';
|
request.method = 'POST';
|
||||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||||
const operation = await requestParser.handleSafe.mock.results[0].value;
|
const operation: Operation = await requestParser.handleSafe.mock.results[0].value;
|
||||||
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(1);
|
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(routes.response.handler.handleSafe).toHaveBeenLastCalledWith({ operation });
|
expect(routes.response.handler.handleSafe).toHaveBeenLastCalledWith({ operation });
|
||||||
|
expect(operation.body?.metadata.contentType).toBe('application/json');
|
||||||
|
|
||||||
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
|
const { response: mockResponse, result } = responseWriter.handleSafe.mock.calls[0][0];
|
||||||
expect(mockResponse).toBe(response);
|
expect(mockResponse).toBe(response);
|
||||||
expect(JSON.parse(await readableToString(result.data!))).toEqual({ apiVersion, key: 'val', authenticating: false });
|
expect(JSON.parse(await readableToString(result.data!))).toEqual({ apiVersion, key: 'val', authenticating: false });
|
||||||
expect(result.statusCode).toBe(200);
|
expect(result.statusCode).toBe(200);
|
||||||
expect(result.metadata?.contentType).toBe('application/json');
|
expect(result.metadata?.contentType).toBe('text/html');
|
||||||
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.responseTemplates['text/html']);
|
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.responseTemplates['text/html']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,10 +165,11 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
request.method = 'POST';
|
request.method = 'POST';
|
||||||
errorHandler.handleSafe.mockResolvedValueOnce({ statusCode: 400 });
|
errorHandler.handleSafe.mockResolvedValueOnce({ statusCode: 400 });
|
||||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||||
const operation = await requestParser.handleSafe.mock.results[0].value;
|
const operation: Operation = await requestParser.handleSafe.mock.results[0].value;
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation });
|
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation });
|
||||||
expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(0);
|
expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(0);
|
||||||
|
expect(operation.body?.metadata.contentType).toBe('application/json');
|
||||||
|
|
||||||
const error = expect.objectContaining({
|
const error = expect.objectContaining({
|
||||||
message: 'This action can only be performed as part of an OIDC authentication flow.',
|
message: 'This action can only be performed as part of an OIDC authentication flow.',
|
||||||
@ -176,9 +187,11 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
const oidcInteraction = { session: { accountId: 'account' }} as any;
|
const oidcInteraction = { session: { accountId: 'account' }} as any;
|
||||||
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction);
|
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction);
|
||||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||||
const operation = await requestParser.handleSafe.mock.results[0].value;
|
const operation: Operation = await requestParser.handleSafe.mock.results[0].value;
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
||||||
|
expect(operation.body?.metadata.contentType).toBe('application/json');
|
||||||
|
|
||||||
expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(1);
|
expect(interactionCompleter.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(interactionCompleter.handleSafe).toHaveBeenLastCalledWith({ request, webId: 'webId' });
|
expect(interactionCompleter.handleSafe).toHaveBeenLastCalledWith({ request, webId: 'webId' });
|
||||||
const location = await interactionCompleter.handleSafe.mock.results[0].value;
|
const location = await interactionCompleter.handleSafe.mock.results[0].value;
|
||||||
@ -195,10 +208,11 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
const oidcInteraction = { prompt: { name: 'other' }};
|
const oidcInteraction = { prompt: { name: 'other' }};
|
||||||
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction as any);
|
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction as any);
|
||||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||||
const operation = await requestParser.handleSafe.mock.results[0].value;
|
const operation: Operation = await requestParser.handleSafe.mock.results[0].value;
|
||||||
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(0);
|
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(0);
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
expect(routes.complete.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
||||||
|
expect(operation.body?.metadata.contentType).toBe('application/json');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the default route for requests to the root IDP without (matching) prompt.', async(): Promise<void> => {
|
it('uses the default route for requests to the root IDP without (matching) prompt.', async(): Promise<void> => {
|
||||||
@ -207,9 +221,10 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
const oidcInteraction = { prompt: { name: 'notSupported' }};
|
const oidcInteraction = { prompt: { name: 'notSupported' }};
|
||||||
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction as any);
|
provider.interactionDetails.mockResolvedValueOnce(oidcInteraction as any);
|
||||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||||
const operation = await requestParser.handleSafe.mock.results[0].value;
|
const operation: Operation = await requestParser.handleSafe.mock.results[0].value;
|
||||||
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(1);
|
expect(routes.response.handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(routes.response.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
expect(routes.response.handler.handleSafe).toHaveBeenLastCalledWith({ operation, oidcInteraction });
|
||||||
|
expect(operation.body?.metadata.contentType).toBe('application/json');
|
||||||
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(0);
|
expect(routes.complete.handler.handleSafe).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -226,7 +241,7 @@ describe('An IdentityProviderHttpHandler', (): void => {
|
|||||||
expect(JSON.parse(await readableToString(result.data!)))
|
expect(JSON.parse(await readableToString(result.data!)))
|
||||||
.toEqual({ apiVersion, errorMessage: 'handle error', prefilled: { name: 'name' }, authenticating: false });
|
.toEqual({ apiVersion, errorMessage: 'handle error', prefilled: { name: 'name' }, authenticating: false });
|
||||||
expect(result.statusCode).toBe(200);
|
expect(result.statusCode).toBe(200);
|
||||||
expect(result.metadata?.contentType).toBe('application/json');
|
expect(result.metadata?.contentType).toBe('text/html');
|
||||||
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
|
expect(result.metadata?.get(SOLID_META.template)?.value).toBe(routes.response.viewTemplates['text/html']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { Interaction } from '../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
import type { Interaction } from '../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
||||||
import { SessionHttpHandler } from '../../../../src/identity/interaction/SessionHttpHandler';
|
import { SessionHttpHandler } from '../../../../src/identity/interaction/SessionHttpHandler';
|
||||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||||
import { createPostFormOperation } from './email-password/handler/Util';
|
import { createPostJsonOperation } from './email-password/handler/Util';
|
||||||
|
|
||||||
describe('A SessionHttpHandler', (): void => {
|
describe('A SessionHttpHandler', (): void => {
|
||||||
const webId = 'http://test.com/id#me';
|
const webId = 'http://test.com/id#me';
|
||||||
@ -22,7 +22,7 @@ describe('A SessionHttpHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an InteractionCompleteResult when done.', async(): Promise<void> => {
|
it('returns an InteractionCompleteResult when done.', async(): Promise<void> => {
|
||||||
const operation = createPostFormOperation({ remember: true });
|
const operation = createPostJsonOperation({ remember: true });
|
||||||
await expect(handler.handle({ operation, oidcInteraction })).resolves.toEqual({
|
await expect(handler.handle({ operation, oidcInteraction })).resolves.toEqual({
|
||||||
details: { webId, shouldRemember: true },
|
details: { webId, shouldRemember: true },
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
|
@ -5,7 +5,7 @@ import type { AccountStore } from '../../../../../../src/identity/interaction/em
|
|||||||
import type { EmailSender } from '../../../../../../src/identity/interaction/util/EmailSender';
|
import type { EmailSender } from '../../../../../../src/identity/interaction/util/EmailSender';
|
||||||
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
||||||
import type { TemplateEngine } from '../../../../../../src/util/templates/TemplateEngine';
|
import type { TemplateEngine } from '../../../../../../src/util/templates/TemplateEngine';
|
||||||
import { createPostFormOperation } from './Util';
|
import { createPostJsonOperation } from './Util';
|
||||||
|
|
||||||
describe('A ForgotPasswordHandler', (): void => {
|
describe('A ForgotPasswordHandler', (): void => {
|
||||||
let operation: Operation;
|
let operation: Operation;
|
||||||
@ -20,7 +20,7 @@ describe('A ForgotPasswordHandler', (): void => {
|
|||||||
let handler: ForgotPasswordHandler;
|
let handler: ForgotPasswordHandler;
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email });
|
operation = createPostJsonOperation({ email });
|
||||||
|
|
||||||
accountStore = {
|
accountStore = {
|
||||||
generateForgotPasswordRecord: jest.fn().mockResolvedValue(recordId),
|
generateForgotPasswordRecord: jest.fn().mockResolvedValue(recordId),
|
||||||
@ -44,9 +44,9 @@ describe('A ForgotPasswordHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('errors on non-string emails.', async(): Promise<void> => {
|
it('errors on non-string emails.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({});
|
operation = createPostJsonOperation({});
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow('Email required');
|
await expect(handler.handle({ operation })).rejects.toThrow('Email required');
|
||||||
operation = createPostFormOperation({ email: [ 'email', 'email2' ]});
|
operation = createPostJsonOperation({ email: [ 'email', 'email2' ]});
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow('Email required');
|
await expect(handler.handle({ operation })).rejects.toThrow('Email required');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import type {
|
||||||
|
InteractionResponseResult,
|
||||||
|
} from '../../../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
||||||
|
import {
|
||||||
|
InteractionHandler,
|
||||||
|
} from '../../../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
||||||
|
import { BasicRepresentation } from '../../../../../../src/ldp/representation/BasicRepresentation';
|
||||||
|
import { NotImplementedHttpError } from '../../../../../../src/util/errors/NotImplementedHttpError';
|
||||||
|
|
||||||
|
class SimpleInteractionHandler extends InteractionHandler {
|
||||||
|
public async handle(): Promise<InteractionResponseResult> {
|
||||||
|
return { type: 'response' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('An InteractionHandler', (): void => {
|
||||||
|
const handler = new SimpleInteractionHandler();
|
||||||
|
|
||||||
|
it('only supports JSON data.', async(): Promise<void> => {
|
||||||
|
let representation = new BasicRepresentation('{}', 'application/json');
|
||||||
|
await expect(handler.canHandle({ operation: { body: representation }} as any)).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
representation = new BasicRepresentation('', 'application/x-www-form-urlencoded');
|
||||||
|
await expect(handler.canHandle({ operation: { body: representation }} as any))
|
||||||
|
.rejects.toThrow(NotImplementedHttpError);
|
||||||
|
|
||||||
|
await expect(handler.canHandle({ operation: {}} as any)).rejects.toThrow(NotImplementedHttpError);
|
||||||
|
});
|
||||||
|
});
|
@ -3,7 +3,7 @@ import type {
|
|||||||
} from '../../../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
} from '../../../../../../src/identity/interaction/email-password/handler/InteractionHandler';
|
||||||
import { LoginHandler } from '../../../../../../src/identity/interaction/email-password/handler/LoginHandler';
|
import { LoginHandler } from '../../../../../../src/identity/interaction/email-password/handler/LoginHandler';
|
||||||
import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore';
|
import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore';
|
||||||
import { createPostFormOperation } from './Util';
|
import { createPostJsonOperation } from './Util';
|
||||||
|
|
||||||
describe('A LoginHandler', (): void => {
|
describe('A LoginHandler', (): void => {
|
||||||
const webId = 'http://alice.test.com/card#me';
|
const webId = 'http://alice.test.com/card#me';
|
||||||
@ -23,29 +23,29 @@ describe('A LoginHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('errors on invalid emails.', async(): Promise<void> => {
|
it('errors on invalid emails.', async(): Promise<void> => {
|
||||||
input.operation = createPostFormOperation({});
|
input.operation = createPostJsonOperation({});
|
||||||
let prom = handler.handle(input);
|
let prom = handler.handle(input);
|
||||||
await expect(prom).rejects.toThrow('Email required');
|
await expect(prom).rejects.toThrow('Email required');
|
||||||
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: {}}));
|
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: {}}));
|
||||||
input.operation = createPostFormOperation({ email: [ 'a', 'b' ]});
|
input.operation = createPostJsonOperation({ email: [ 'a', 'b' ]});
|
||||||
prom = handler.handle(input);
|
prom = handler.handle(input);
|
||||||
await expect(prom).rejects.toThrow('Email required');
|
await expect(prom).rejects.toThrow('Email required');
|
||||||
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { }}));
|
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { }}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors on invalid passwords.', async(): Promise<void> => {
|
it('errors on invalid passwords.', async(): Promise<void> => {
|
||||||
input.operation = createPostFormOperation({ email });
|
input.operation = createPostJsonOperation({ email });
|
||||||
let prom = handler.handle(input);
|
let prom = handler.handle(input);
|
||||||
await expect(prom).rejects.toThrow('Password required');
|
await expect(prom).rejects.toThrow('Password required');
|
||||||
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { email }}));
|
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { email }}));
|
||||||
input.operation = createPostFormOperation({ email, password: [ 'a', 'b' ]});
|
input.operation = createPostJsonOperation({ email, password: [ 'a', 'b' ]});
|
||||||
prom = handler.handle(input);
|
prom = handler.handle(input);
|
||||||
await expect(prom).rejects.toThrow('Password required');
|
await expect(prom).rejects.toThrow('Password required');
|
||||||
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { email }}));
|
await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { email }}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an IdpInteractionError if there is a problem.', async(): Promise<void> => {
|
it('throws an IdpInteractionError if there is a problem.', async(): Promise<void> => {
|
||||||
input.operation = createPostFormOperation({ email, password: 'password!' });
|
input.operation = createPostJsonOperation({ email, password: 'password!' });
|
||||||
(storageAdapter.authenticate as jest.Mock).mockRejectedValueOnce(new Error('auth failed!'));
|
(storageAdapter.authenticate as jest.Mock).mockRejectedValueOnce(new Error('auth failed!'));
|
||||||
const prom = handler.handle(input);
|
const prom = handler.handle(input);
|
||||||
await expect(prom).rejects.toThrow('auth failed!');
|
await expect(prom).rejects.toThrow('auth failed!');
|
||||||
@ -53,7 +53,7 @@ describe('A LoginHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an InteractionCompleteResult when done.', async(): Promise<void> => {
|
it('returns an InteractionCompleteResult when done.', async(): Promise<void> => {
|
||||||
input.operation = createPostFormOperation({ email, password: 'password!' });
|
input.operation = createPostJsonOperation({ email, password: 'password!' });
|
||||||
await expect(handler.handle(input)).resolves.toEqual({
|
await expect(handler.handle(input)).resolves.toEqual({
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
details: { webId, shouldRemember: false },
|
details: { webId, shouldRemember: false },
|
||||||
|
@ -10,7 +10,7 @@ import type { IdentifierGenerator } from '../../../../../../src/pods/generate/Id
|
|||||||
import type { PodManager } from '../../../../../../src/pods/PodManager';
|
import type { PodManager } from '../../../../../../src/pods/PodManager';
|
||||||
import type { PodSettings } from '../../../../../../src/pods/settings/PodSettings';
|
import type { PodSettings } from '../../../../../../src/pods/settings/PodSettings';
|
||||||
import { joinUrl } from '../../../../../../src/util/PathUtil';
|
import { joinUrl } from '../../../../../../src/util/PathUtil';
|
||||||
import { createPostFormOperation } from './Util';
|
import { createPostJsonOperation } from './Util';
|
||||||
|
|
||||||
describe('A RegistrationHandler', (): void => {
|
describe('A RegistrationHandler', (): void => {
|
||||||
// "Correct" values for easy object creation
|
// "Correct" values for easy object creation
|
||||||
@ -20,10 +20,9 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
const confirmPassword = password;
|
const confirmPassword = password;
|
||||||
const podName = 'alice';
|
const podName = 'alice';
|
||||||
const podBaseUrl = 'http://test.com/alice/';
|
const podBaseUrl = 'http://test.com/alice/';
|
||||||
// Strings instead of booleans because this is form data
|
const createWebId = true;
|
||||||
const createWebId = 'true';
|
const register = true;
|
||||||
const register = 'true';
|
const createPod = true;
|
||||||
const createPod = 'true';
|
|
||||||
|
|
||||||
let operation: Operation;
|
let operation: Operation;
|
||||||
|
|
||||||
@ -69,71 +68,71 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
|
|
||||||
describe('validating data', (): void => {
|
describe('validating data', (): void => {
|
||||||
it('rejects array inputs.', async(): Promise<void> => {
|
it('rejects array inputs.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ mydata: [ 'a', 'b' ]});
|
operation = createPostJsonOperation({ mydata: [ 'a', 'b' ]});
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Unexpected multiple values for mydata.');
|
.rejects.toThrow('Unexpected multiple values for mydata.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors on invalid emails.', async(): Promise<void> => {
|
it('errors on invalid emails.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email: undefined });
|
operation = createPostJsonOperation({ email: undefined });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a valid e-mail address.');
|
.rejects.toThrow('Please enter a valid e-mail address.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email: '' });
|
operation = createPostJsonOperation({ email: '' });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a valid e-mail address.');
|
.rejects.toThrow('Please enter a valid e-mail address.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email: 'invalidEmail' });
|
operation = createPostJsonOperation({ email: 'invalidEmail' });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a valid e-mail address.');
|
.rejects.toThrow('Please enter a valid e-mail address.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors when a required WebID is not valid.', async(): Promise<void> => {
|
it('errors when a required WebID is not valid.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, register, webId: undefined });
|
operation = createPostJsonOperation({ email, register, webId: undefined });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a valid WebID.');
|
.rejects.toThrow('Please enter a valid WebID.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email, register, webId: '' });
|
operation = createPostJsonOperation({ email, register, webId: '' });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a valid WebID.');
|
.rejects.toThrow('Please enter a valid WebID.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors on invalid passwords when registering.', async(): Promise<void> => {
|
it('errors on invalid passwords when registering.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, webId, password, confirmPassword: 'bad', register });
|
operation = createPostJsonOperation({ email, webId, password, confirmPassword: 'bad', register });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Your password and confirmation did not match.');
|
.rejects.toThrow('Your password and confirmation did not match.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors on invalid pod names when required.', async(): Promise<void> => {
|
it('errors on invalid pod names when required.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, webId, createPod, podName: undefined });
|
operation = createPostJsonOperation({ email, webId, createPod, podName: undefined });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please specify a Pod name.');
|
.rejects.toThrow('Please specify a Pod name.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email, webId, createPod, podName: ' ' });
|
operation = createPostJsonOperation({ email, webId, createPod, podName: ' ' });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please specify a Pod name.');
|
.rejects.toThrow('Please specify a Pod name.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email, webId, createWebId });
|
operation = createPostJsonOperation({ email, webId, createWebId });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please specify a Pod name.');
|
.rejects.toThrow('Please specify a Pod name.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors when trying to create a WebID without registering or creating a pod.', async(): Promise<void> => {
|
it('errors when trying to create a WebID without registering or creating a pod.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, podName, createWebId });
|
operation = createPostJsonOperation({ email, podName, createWebId });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a password.');
|
.rejects.toThrow('Please enter a password.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email, podName, createWebId, createPod });
|
operation = createPostJsonOperation({ email, podName, createWebId, createPod });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a password.');
|
.rejects.toThrow('Please enter a password.');
|
||||||
|
|
||||||
operation = createPostFormOperation({ email, podName, createWebId, createPod, register });
|
operation = createPostJsonOperation({ email, podName, createWebId, createPod, register });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please enter a password.');
|
.rejects.toThrow('Please enter a password.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors when no option is chosen.', async(): Promise<void> => {
|
it('errors when no option is chosen.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, webId });
|
operation = createPostJsonOperation({ email, webId });
|
||||||
await expect(handler.handle({ operation }))
|
await expect(handler.handle({ operation }))
|
||||||
.rejects.toThrow('Please register for a WebID or create a Pod.');
|
.rejects.toThrow('Please register for a WebID or create a Pod.');
|
||||||
});
|
});
|
||||||
@ -141,7 +140,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
|
|
||||||
describe('handling data', (): void => {
|
describe('handling data', (): void => {
|
||||||
it('can register a user.', async(): Promise<void> => {
|
it('can register a user.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ email, webId, password, confirmPassword, register });
|
operation = createPostJsonOperation({ email, webId, password, confirmPassword, register });
|
||||||
await expect(handler.handle({ operation })).resolves.toEqual({
|
await expect(handler.handle({ operation })).resolves.toEqual({
|
||||||
details: {
|
details: {
|
||||||
email,
|
email,
|
||||||
@ -168,7 +167,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
|
|
||||||
it('can create a pod.', async(): Promise<void> => {
|
it('can create a pod.', async(): Promise<void> => {
|
||||||
const params = { email, webId, podName, createPod };
|
const params = { email, webId, podName, createPod };
|
||||||
operation = createPostFormOperation(params);
|
operation = createPostJsonOperation(params);
|
||||||
await expect(handler.handle({ operation })).resolves.toEqual({
|
await expect(handler.handle({ operation })).resolves.toEqual({
|
||||||
details: {
|
details: {
|
||||||
email,
|
email,
|
||||||
@ -197,7 +196,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
it('adds an oidcIssuer to the data when doing both IDP registration and pod creation.', async(): Promise<void> => {
|
it('adds an oidcIssuer to the data when doing both IDP registration and pod creation.', async(): Promise<void> => {
|
||||||
const params = { email, webId, password, confirmPassword, podName, register, createPod };
|
const params = { email, webId, password, confirmPassword, podName, register, createPod };
|
||||||
podSettings.oidcIssuer = baseUrl;
|
podSettings.oidcIssuer = baseUrl;
|
||||||
operation = createPostFormOperation(params);
|
operation = createPostJsonOperation(params);
|
||||||
await expect(handler.handle({ operation })).resolves.toEqual({
|
await expect(handler.handle({ operation })).resolves.toEqual({
|
||||||
details: {
|
details: {
|
||||||
email,
|
email,
|
||||||
@ -228,7 +227,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
it('deletes the created account if pod generation fails.', async(): Promise<void> => {
|
it('deletes the created account if pod generation fails.', async(): Promise<void> => {
|
||||||
const params = { email, webId, password, confirmPassword, podName, register, createPod };
|
const params = { email, webId, password, confirmPassword, podName, register, createPod };
|
||||||
podSettings.oidcIssuer = baseUrl;
|
podSettings.oidcIssuer = baseUrl;
|
||||||
operation = createPostFormOperation(params);
|
operation = createPostJsonOperation(params);
|
||||||
(podManager.createPod as jest.Mock).mockRejectedValueOnce(new Error('pod error'));
|
(podManager.createPod as jest.Mock).mockRejectedValueOnce(new Error('pod error'));
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow('pod error');
|
await expect(handler.handle({ operation })).rejects.toThrow('pod error');
|
||||||
|
|
||||||
@ -252,7 +251,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
podSettings.webId = generatedWebID;
|
podSettings.webId = generatedWebID;
|
||||||
podSettings.oidcIssuer = baseUrl;
|
podSettings.oidcIssuer = baseUrl;
|
||||||
|
|
||||||
operation = createPostFormOperation(params);
|
operation = createPostJsonOperation(params);
|
||||||
await expect(handler.handle({ operation })).resolves.toEqual({
|
await expect(handler.handle({ operation })).resolves.toEqual({
|
||||||
details: {
|
details: {
|
||||||
email,
|
email,
|
||||||
@ -281,7 +280,7 @@ describe('A RegistrationHandler', (): void => {
|
|||||||
|
|
||||||
it('throws an IdpInteractionError with all data prefilled if something goes wrong.', async(): Promise<void> => {
|
it('throws an IdpInteractionError with all data prefilled if something goes wrong.', async(): Promise<void> => {
|
||||||
const params = { email, webId, podName, createPod };
|
const params = { email, webId, podName, createPod };
|
||||||
operation = createPostFormOperation(params);
|
operation = createPostJsonOperation(params);
|
||||||
(podManager.createPod as jest.Mock).mockRejectedValueOnce(new Error('pod error'));
|
(podManager.createPod as jest.Mock).mockRejectedValueOnce(new Error('pod error'));
|
||||||
const prom = handler.handle({ operation });
|
const prom = handler.handle({ operation });
|
||||||
await expect(prom).rejects.toThrow('pod error');
|
await expect(prom).rejects.toThrow('pod error');
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordHandler';
|
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordHandler';
|
||||||
import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore';
|
import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore';
|
||||||
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
||||||
import { createPostFormOperation } from './Util';
|
import { createPostJsonOperation } from './Util';
|
||||||
|
|
||||||
describe('A ResetPasswordHandler', (): void => {
|
describe('A ResetPasswordHandler', (): void => {
|
||||||
let operation: Operation;
|
let operation: Operation;
|
||||||
@ -25,27 +25,27 @@ describe('A ResetPasswordHandler', (): void => {
|
|||||||
|
|
||||||
it('errors for non-string recordIds.', async(): Promise<void> => {
|
it('errors for non-string recordIds.', async(): Promise<void> => {
|
||||||
const errorMessage = 'Invalid request. Open the link from your email again';
|
const errorMessage = 'Invalid request. Open the link from your email again';
|
||||||
operation = createPostFormOperation({});
|
operation = createPostJsonOperation({});
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||||
operation = createPostFormOperation({}, '');
|
operation = createPostJsonOperation({}, '');
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors for invalid passwords.', async(): Promise<void> => {
|
it('errors for invalid passwords.', async(): Promise<void> => {
|
||||||
const errorMessage = 'Your password and confirmation did not match.';
|
const errorMessage = 'Your password and confirmation did not match.';
|
||||||
operation = createPostFormOperation({ password: 'password!', confirmPassword: 'otherPassword!' }, url);
|
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'otherPassword!' }, url);
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors for invalid emails.', async(): Promise<void> => {
|
it('errors for invalid emails.', async(): Promise<void> => {
|
||||||
const errorMessage = 'This reset password link is no longer valid.';
|
const errorMessage = 'This reset password link is no longer valid.';
|
||||||
operation = createPostFormOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||||
(accountStore.getForgotPasswordRecord as jest.Mock).mockResolvedValueOnce(undefined);
|
(accountStore.getForgotPasswordRecord as jest.Mock).mockResolvedValueOnce(undefined);
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a message on success.', async(): Promise<void> => {
|
it('renders a message on success.', async(): Promise<void> => {
|
||||||
operation = createPostFormOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||||
await expect(handler.handle({ operation })).resolves.toEqual({ type: 'response' });
|
await expect(handler.handle({ operation })).resolves.toEqual({ type: 'response' });
|
||||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenCalledTimes(1);
|
expect(accountStore.getForgotPasswordRecord).toHaveBeenCalledTimes(1);
|
||||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenLastCalledWith(recordId);
|
expect(accountStore.getForgotPasswordRecord).toHaveBeenLastCalledWith(recordId);
|
||||||
@ -57,7 +57,7 @@ describe('A ResetPasswordHandler', (): void => {
|
|||||||
|
|
||||||
it('has a default error for non-native errors.', async(): Promise<void> => {
|
it('has a default error for non-native errors.', async(): Promise<void> => {
|
||||||
const errorMessage = 'Unknown error: not native';
|
const errorMessage = 'Unknown error: not native';
|
||||||
operation = createPostFormOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||||
(accountStore.getForgotPasswordRecord as jest.Mock).mockRejectedValueOnce('not native');
|
(accountStore.getForgotPasswordRecord as jest.Mock).mockRejectedValueOnce('not native');
|
||||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { stringify } from 'querystring';
|
|
||||||
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
import type { Operation } from '../../../../../../src/ldp/operations/Operation';
|
||||||
import { BasicRepresentation } from '../../../../../../src/ldp/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../../../../../src/ldp/representation/BasicRepresentation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a mock HttpRequest which is a stream of an object encoded as application/x-www-form-urlencoded
|
* Creates a mock HttpRequest which is a stream of an object encoded as application/json
|
||||||
* and a matching content-type header.
|
* and a matching content-type header.
|
||||||
* @param data - Object to encode.
|
* @param data - Object to encode.
|
||||||
* @param url - URL value of the request.
|
* @param url - URL value of the request.
|
||||||
*/
|
*/
|
||||||
export function createPostFormOperation(data: NodeJS.Dict<any>, url?: string): Operation {
|
export function createPostJsonOperation(data: NodeJS.Dict<any>, url?: string): Operation {
|
||||||
return {
|
return {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
preferences: {},
|
preferences: {},
|
||||||
target: { path: url ?? 'http://test.com/' },
|
target: { path: url ?? 'http://test.com/' },
|
||||||
body: new BasicRepresentation(stringify(data), 'application/x-www-form-urlencoded'),
|
body: new BasicRepresentation(JSON.stringify(data), 'application/json'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { stringify } from 'querystring';
|
|
||||||
import {
|
|
||||||
getFormDataRequestBody,
|
|
||||||
} from '../../../../../src/identity/interaction/util/FormDataUtil';
|
|
||||||
import type { Operation } from '../../../../../src/ldp/operations/Operation';
|
|
||||||
import { BasicRepresentation } from '../../../../../src/ldp/representation/BasicRepresentation';
|
|
||||||
import { UnsupportedMediaTypeHttpError } from '../../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
|
||||||
|
|
||||||
describe('FormDataUtil', (): void => {
|
|
||||||
describe('#getFormDataRequestBody', (): void => {
|
|
||||||
it('only supports form data.', async(): Promise<void> => {
|
|
||||||
await expect(getFormDataRequestBody({ headers: { 'content-type': 'text/turtle' }} as any))
|
|
||||||
.rejects.toThrow(UnsupportedMediaTypeHttpError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('converts the body to an object.', async(): Promise<void> => {
|
|
||||||
const data = { test: 'test!', moreTest: '!TEST!' };
|
|
||||||
const operation: Operation = {
|
|
||||||
method: 'GET',
|
|
||||||
preferences: {},
|
|
||||||
target: { path: '' },
|
|
||||||
body: new BasicRepresentation(stringify(data), 'application/x-www-form-urlencoded'),
|
|
||||||
};
|
|
||||||
await expect(getFormDataRequestBody(operation)).resolves.toEqual(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
22
test/unit/storage/conversion/FormToJsonConverter.test.ts
Normal file
22
test/unit/storage/conversion/FormToJsonConverter.test.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { stringify } from 'querystring';
|
||||||
|
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||||
|
import { FormToJsonConverter } from '../../../../src/storage/conversion/FormToJsonConverter';
|
||||||
|
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||||
|
|
||||||
|
describe('A FormToJsonConverter', (): void => {
|
||||||
|
const identifier = { path: 'http://test.com/foo' };
|
||||||
|
const converter = new FormToJsonConverter();
|
||||||
|
|
||||||
|
it('supports going from form data to json.', async(): Promise<void> => {
|
||||||
|
await expect(converter.getInputTypes()).resolves.toEqual({ 'application/x-www-form-urlencoded': 1 });
|
||||||
|
await expect(converter.getOutputTypes()).resolves.toEqual({ 'application/json': 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts form data to JSON.', async(): Promise<void> => {
|
||||||
|
const formData = stringify({ field: 'value' });
|
||||||
|
const representation = new BasicRepresentation(formData, 'application/x-www-form-urlencoded');
|
||||||
|
const result = await converter.handle({ identifier, representation, preferences: {}});
|
||||||
|
expect(result.metadata.contentType).toBe('application/json');
|
||||||
|
expect(JSON.parse(await readableToString(result.data))).toEqual({ field: 'value' });
|
||||||
|
});
|
||||||
|
});
|
@ -4,8 +4,10 @@ import { Quad, NamedNode, Literal, BlankNode, Store } from 'n3';
|
|||||||
import type { Logger } from '../../../src/logging/Logger';
|
import type { Logger } from '../../../src/logging/Logger';
|
||||||
import { getLoggerFor } from '../../../src/logging/LogUtil';
|
import { getLoggerFor } from '../../../src/logging/LogUtil';
|
||||||
import { isHttpRequest } from '../../../src/server/HttpRequest';
|
import { isHttpRequest } from '../../../src/server/HttpRequest';
|
||||||
import { guardedStreamFrom, pipeSafely, transformSafely,
|
import {
|
||||||
readableToString, readableToQuads } from '../../../src/util/StreamUtil';
|
guardedStreamFrom, pipeSafely, transformSafely,
|
||||||
|
readableToString, readableToQuads, readJsonStream,
|
||||||
|
} from '../../../src/util/StreamUtil';
|
||||||
|
|
||||||
jest.mock('../../../src/logging/LogUtil', (): any => {
|
jest.mock('../../../src/logging/LogUtil', (): any => {
|
||||||
const logger: Logger = { warn: jest.fn(), log: jest.fn() } as any;
|
const logger: Logger = { warn: jest.fn(), log: jest.fn() } as any;
|
||||||
@ -47,6 +49,13 @@ describe('StreamUtil', (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#readJsonStream', (): void => {
|
||||||
|
it('parses the stream as JSON.', async(): Promise<void> => {
|
||||||
|
const stream = Readable.from('{ "key": "value" }');
|
||||||
|
await expect(readJsonStream(stream)).resolves.toEqual({ key: 'value' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#pipeSafely', (): void => {
|
describe('#pipeSafely', (): void => {
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user