mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Use URLs for channel identifiers
In the future these can potentially be used to dereference them
This commit is contained in:
parent
67d1ff4ac0
commit
cbbb10afa1
@ -7,12 +7,6 @@
|
|||||||
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
|
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
|
||||||
"relativePath": "/WebHookSubscription2021/"
|
"relativePath": "/WebHookSubscription2021/"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"@id": "urn:solid-server:default:WebHookUnsubscribeRoute",
|
|
||||||
"@type": "RelativePathInteractionRoute",
|
|
||||||
"base": { "@id": "urn:solid-server:default:WebHookRoute" },
|
|
||||||
"relativePath": "/unsubscribe/"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:WebHookWebIdRoute",
|
"@id": "urn:solid-server:default:WebHookWebIdRoute",
|
||||||
"@type": "RelativePathInteractionRoute",
|
"@type": "RelativePathInteractionRoute",
|
||||||
@ -27,7 +21,7 @@
|
|||||||
"@type": "OperationRouterHandler",
|
"@type": "OperationRouterHandler",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"allowedMethods": [ "DELETE" ],
|
"allowedMethods": [ "DELETE" ],
|
||||||
"allowedPathNames": [ "/WebHookSubscription2021/unsubscribe/" ],
|
"allowedPathNames": [ "/WebHookSubscription2021/" ],
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "WebHookUnsubscriber",
|
"@type": "WebHookUnsubscriber",
|
||||||
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
|
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
|
||||||
@ -39,7 +33,7 @@
|
|||||||
"@id": "urn:solid-server:default:WebHookWebId",
|
"@id": "urn:solid-server:default:WebHookWebId",
|
||||||
"@type": "OperationRouterHandler",
|
"@type": "OperationRouterHandler",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"allowedPathNames": [ "/WebHookSubscription2021/webId" ],
|
"allowedPathNames": [ "/WebHookSubscription2021/webId$" ],
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "WebHookWebId",
|
"@type": "WebHookWebId",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
"@type": "WebHookSubscription2021",
|
"@type": "WebHookSubscription2021",
|
||||||
"route": { "@id": "urn:solid-server:default:WebHookRoute" },
|
"route": { "@id": "urn:solid-server:default:WebHookRoute" },
|
||||||
"webIdRoute": { "@id": "urn:solid-server:default:WebHookWebIdRoute" },
|
"webIdRoute": { "@id": "urn:solid-server:default:WebHookWebIdRoute" },
|
||||||
"unsubscribeRoute": { "@id": "urn:solid-server:default:WebHookUnsubscribeRoute" },
|
|
||||||
"stateHandler": {
|
"stateHandler": {
|
||||||
"@type": "BaseStateHandler",
|
"@type": "BaseStateHandler",
|
||||||
"handler": { "@id": "urn:solid-server:default:WebHookNotificationHandler" },
|
"handler": { "@id": "urn:solid-server:default:WebHookNotificationHandler" },
|
||||||
|
@ -14,6 +14,7 @@ import type { InteractionRoute } from '../../identity/interaction/routing/Intera
|
|||||||
import { ContextDocumentLoader } from '../../storage/conversion/ConversionUtil';
|
import { ContextDocumentLoader } from '../../storage/conversion/ConversionUtil';
|
||||||
import { UnprocessableEntityHttpError } from '../../util/errors/UnprocessableEntityHttpError';
|
import { UnprocessableEntityHttpError } from '../../util/errors/UnprocessableEntityHttpError';
|
||||||
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
|
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
|
||||||
|
import { joinUrl } from '../../util/PathUtil';
|
||||||
import { readableToQuads } from '../../util/StreamUtil';
|
import { readableToQuads } from '../../util/StreamUtil';
|
||||||
import { msToDuration } from '../../util/StringUtil';
|
import { msToDuration } from '../../util/StringUtil';
|
||||||
import { NOTIFY, RDF, XSD } from '../../util/Vocabularies';
|
import { NOTIFY, RDF, XSD } from '../../util/Vocabularies';
|
||||||
@ -201,7 +202,9 @@ export abstract class BaseChannelType implements NotificationChannelType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a set of quads to a {@link NotificationChannel}.
|
* Converts a set of quads to a {@link NotificationChannel}.
|
||||||
* Assumes the data is valid, so this should be called after {@link validateSubscription}
|
* Assumes the data is valid, so this should be called after {@link validateSubscription}.
|
||||||
|
*
|
||||||
|
* The generated identifier will be a URL made by combining the base URL of the channel type with a unique identifier.
|
||||||
*
|
*
|
||||||
* The values of the default features will be added to the resulting channel,
|
* The values of the default features will be added to the resulting channel,
|
||||||
* subclasses with additional features that need to be added are responsible for parsing those quads.
|
* subclasses with additional features that need to be added are responsible for parsing those quads.
|
||||||
@ -216,7 +219,7 @@ export abstract class BaseChannelType implements NotificationChannelType {
|
|||||||
const type = data.getObjects(subject, RDF.terms.type, null)[0] as NamedNode;
|
const type = data.getObjects(subject, RDF.terms.type, null)[0] as NamedNode;
|
||||||
|
|
||||||
const channel: NotificationChannel = {
|
const channel: NotificationChannel = {
|
||||||
id: `${v4()}:${topic.value}`,
|
id: joinUrl(this.path, v4()),
|
||||||
type: type.value,
|
type: type.value,
|
||||||
topic: topic.value,
|
topic: topic.value,
|
||||||
};
|
};
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { joinUrl } from '../../../util/PathUtil';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a specific unsubscribe URL for a WebHookSubscription2021
|
|
||||||
* by combining the default unsubscribe URL with the given identifier.
|
|
||||||
* @param url - The default unsubscribe URL.
|
|
||||||
* @param id - The identifier.
|
|
||||||
*/
|
|
||||||
export function generateWebHookUnsubscribeUrl(url: string, id: string): string {
|
|
||||||
return joinUrl(url, encodeURIComponent(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a WebHookSubscription2021 unsubscribe URL to extract the identifier.
|
|
||||||
* @param url - The unsubscribe URL that is being called.
|
|
||||||
*/
|
|
||||||
export function parseWebHookUnsubscribeUrl(url: string): string {
|
|
||||||
// Split always returns an array of at least length 1 so result can not be undefined
|
|
||||||
return decodeURIComponent(url.split(/\//u).pop()!);
|
|
||||||
}
|
|
@ -9,7 +9,6 @@ import { BaseChannelType, DEFAULT_NOTIFICATION_FEATURES } from '../BaseChannelTy
|
|||||||
import type { NotificationChannel } from '../NotificationChannel';
|
import type { NotificationChannel } from '../NotificationChannel';
|
||||||
import type { SubscriptionService } from '../NotificationChannelType';
|
import type { SubscriptionService } from '../NotificationChannelType';
|
||||||
import type { StateHandler } from '../StateHandler';
|
import type { StateHandler } from '../StateHandler';
|
||||||
import { generateWebHookUnsubscribeUrl } from './WebHook2021Util';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link NotificationChannel} containing the necessary fields for a WebHookSubscription2021 channel.
|
* A {@link NotificationChannel} containing the necessary fields for a WebHookSubscription2021 channel.
|
||||||
@ -57,19 +56,17 @@ export function isWebHook2021Channel(channel: NotificationChannel): channel is W
|
|||||||
export class WebHookSubscription2021 extends BaseChannelType {
|
export class WebHookSubscription2021 extends BaseChannelType {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly unsubscribePath: string;
|
|
||||||
private readonly stateHandler: StateHandler;
|
private readonly stateHandler: StateHandler;
|
||||||
private readonly webId: string;
|
private readonly webId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param route - The route corresponding to the URL of the subscription service of this channel type.
|
* @param route - The route corresponding to the URL of the subscription service of this channel type.
|
||||||
* @param webIdRoute - The route to the WebID that needs to be used when generating DPoP tokens for notifications.
|
* @param webIdRoute - The route to the WebID that needs to be used when generating DPoP tokens for notifications.
|
||||||
* @param unsubscribeRoute - The route where the request needs to be sent to unsubscribe.
|
|
||||||
* @param stateHandler - The {@link StateHandler} that will be called after a successful subscription.
|
* @param stateHandler - The {@link StateHandler} that will be called after a successful subscription.
|
||||||
* @param features - The features that need to be enabled for this channel type.
|
* @param features - The features that need to be enabled for this channel type.
|
||||||
*/
|
*/
|
||||||
public constructor(route: InteractionRoute, webIdRoute: InteractionRoute, unsubscribeRoute: InteractionRoute,
|
public constructor(route: InteractionRoute, webIdRoute: InteractionRoute, stateHandler: StateHandler,
|
||||||
stateHandler: StateHandler, features: string[] = DEFAULT_NOTIFICATION_FEATURES) {
|
features: string[] = DEFAULT_NOTIFICATION_FEATURES) {
|
||||||
super(NOTIFY.terms.WebHookSubscription2021,
|
super(NOTIFY.terms.WebHookSubscription2021,
|
||||||
route,
|
route,
|
||||||
[ ...features, NOTIFY.webhookAuth ],
|
[ ...features, NOTIFY.webhookAuth ],
|
||||||
@ -80,7 +77,6 @@ export class WebHookSubscription2021 extends BaseChannelType {
|
|||||||
// which would make this more annoying so we are lenient here.
|
// which would make this more annoying so we are lenient here.
|
||||||
// Could change in the future once this field is updated and part of the context.
|
// Could change in the future once this field is updated and part of the context.
|
||||||
[{ path: NOTIFY.target, minCount: 1, maxCount: 1 }]);
|
[{ path: NOTIFY.target, minCount: 1, maxCount: 1 }]);
|
||||||
this.unsubscribePath = unsubscribeRoute.getPath();
|
|
||||||
this.stateHandler = stateHandler;
|
this.stateHandler = stateHandler;
|
||||||
this.webId = webIdRoute.getPath();
|
this.webId = webIdRoute.getPath();
|
||||||
}
|
}
|
||||||
@ -114,7 +110,7 @@ export class WebHookSubscription2021 extends BaseChannelType {
|
|||||||
webId,
|
webId,
|
||||||
target: target.value,
|
target: target.value,
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
unsubscribe_endpoint: generateWebHookUnsubscribeUrl(this.unsubscribePath, channel.id),
|
unsubscribe_endpoint: channel.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { NotFoundHttpError } from '../../../util/errors/NotFoundHttpError';
|
|||||||
import type { OperationHttpHandlerInput } from '../../OperationHttpHandler';
|
import type { OperationHttpHandlerInput } from '../../OperationHttpHandler';
|
||||||
import { OperationHttpHandler } from '../../OperationHttpHandler';
|
import { OperationHttpHandler } from '../../OperationHttpHandler';
|
||||||
import type { NotificationChannelStorage } from '../NotificationChannelStorage';
|
import type { NotificationChannelStorage } from '../NotificationChannelStorage';
|
||||||
import { parseWebHookUnsubscribeUrl } from './WebHook2021Util';
|
|
||||||
import { isWebHook2021Channel } from './WebHookSubscription2021';
|
import { isWebHook2021Channel } from './WebHookSubscription2021';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +26,7 @@ export class WebHookUnsubscriber extends OperationHttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handle({ operation, request }: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
public async handle({ operation, request }: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
||||||
const id = parseWebHookUnsubscribeUrl(operation.target.path);
|
const id = operation.target.path;
|
||||||
const channel = await this.storage.get(id);
|
const channel = await this.storage.get(id);
|
||||||
if (!channel || !isWebHook2021Channel(channel)) {
|
if (!channel || !isWebHook2021Channel(channel)) {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
|
@ -5,6 +5,9 @@ import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|||||||
import type { NamedNode } from 'n3';
|
import type { NamedNode } from 'n3';
|
||||||
import { DataFactory, Parser, Store } from 'n3';
|
import { DataFactory, Parser, Store } from 'n3';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
|
import type {
|
||||||
|
WebHookSubscription2021Channel,
|
||||||
|
} from '../../src/server/notifications/WebHookSubscription2021/WebHookSubscription2021';
|
||||||
import { matchesAuthorizationScheme } from '../../src/util/HeaderUtil';
|
import { matchesAuthorizationScheme } from '../../src/util/HeaderUtil';
|
||||||
import { joinUrl, trimTrailingSlashes } from '../../src/util/PathUtil';
|
import { joinUrl, trimTrailingSlashes } from '../../src/util/PathUtil';
|
||||||
import { readJsonStream } from '../../src/util/StreamUtil';
|
import { readJsonStream } from '../../src/util/StreamUtil';
|
||||||
@ -181,4 +184,15 @@ describe.each(stores)('A server supporting WebHookSubscription2021 using %s', (n
|
|||||||
// Close the connection so the server can shut down
|
// Close the connection so the server can shut down
|
||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows a user to unsubscribe.', async(): Promise<void> => {
|
||||||
|
const json = await subscribe(notificationType, webId, subscriptionUrl, topic, { [NOTIFY.target]: target });
|
||||||
|
const unsubscribeUrl = (json as WebHookSubscription2021Channel).unsubscribe_endpoint;
|
||||||
|
let response = await fetch(unsubscribeUrl, { method: 'DELETE' });
|
||||||
|
expect(response.status).toBe(403);
|
||||||
|
response = await fetch(unsubscribeUrl, { method: 'DELETE', headers: { authorization: `WebID ${webId}` }});
|
||||||
|
expect(response.status).toBe(205);
|
||||||
|
response = await fetch(joinUrl(subscriptionUrl, 'abc'), { method: 'DELETE' });
|
||||||
|
expect(response.status).toBe(404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ import { BasicRepresentation } from '../../src/http/representation/BasicRepresen
|
|||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
||||||
import { joinUrl } from '../../src/util/PathUtil';
|
import { joinUrl } from '../../src/util/PathUtil';
|
||||||
import { NOTIFY } from '../../src/util/Vocabularies';
|
import { NOTIFY, RDF } from '../../src/util/Vocabularies';
|
||||||
import { expectNotification, subscribe } from '../util/NotificationUtil';
|
import { expectNotification, subscribe } from '../util/NotificationUtil';
|
||||||
import { getPort } from '../util/Util';
|
import { getPort } from '../util/Util';
|
||||||
import {
|
import {
|
||||||
@ -223,4 +223,29 @@ describe.each(stores)('A server supporting WebSocketSubscription2021 using %s',
|
|||||||
const message = (await messagePromise).toString();
|
const message = (await messagePromise).toString();
|
||||||
expect(message).toBe('Notification channel has expired');
|
expect(message).toBe('Notification channel has expired');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can use other RDF formats and content negotiation when creating a channel.', async(): Promise<void> => {
|
||||||
|
const turtleChannel = `
|
||||||
|
_:id <${RDF.type}> <${notificationType}> ;
|
||||||
|
<http://www.w3.org/ns/solid/notifications#topic> <${topic}>.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await fetch(subscriptionUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
authorization: `WebID ${webId}`,
|
||||||
|
'content-type': 'text/turtle',
|
||||||
|
accept: 'text/turtle',
|
||||||
|
},
|
||||||
|
body: turtleChannel,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get('content-type')).toBe('text/turtle');
|
||||||
|
|
||||||
|
const parser = new Parser({ baseIRI: subscriptionUrl });
|
||||||
|
const quads = new Store(parser.parse(await response.text()));
|
||||||
|
|
||||||
|
expect(quads.getObjects(null, RDF.terms.type, null)).toEqual([ NOTIFY.terms.WebSocketSubscription2021 ]);
|
||||||
|
expect(quads.getObjects(null, NOTIFY.terms.topic, null)).toEqual([ namedNode(topic) ]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ class DummyChannelType extends BaseChannelType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('A BaseChannelType', (): void => {
|
describe('A BaseChannelType', (): void => {
|
||||||
const id = '4c9b88c1-7502-4107-bb79-2a3a590c7aa3:https://storage.example/resource';
|
const id = 'http://example.com/DummyType/4c9b88c1-7502-4107-bb79-2a3a590c7aa3';
|
||||||
const credentials: Credentials = {};
|
const credentials: Credentials = {};
|
||||||
const channelType = new DummyChannelType();
|
const channelType = new DummyChannelType();
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import {
|
|
||||||
generateWebHookUnsubscribeUrl, parseWebHookUnsubscribeUrl,
|
|
||||||
} from '../../../../../src/server/notifications/WebHookSubscription2021/WebHook2021Util';
|
|
||||||
|
|
||||||
describe('WebHook2021Util', (): void => {
|
|
||||||
describe('#generateWebHookUnsubscribeUrl', (): void => {
|
|
||||||
it('generates the URL with the identifier.', async(): Promise<void> => {
|
|
||||||
expect(generateWebHookUnsubscribeUrl('http://example.com/unsubscribe', '123$456'))
|
|
||||||
.toBe('http://example.com/unsubscribe/123%24456');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#parseWebHookUnsubscribeUrl', (): void => {
|
|
||||||
it('returns the parsed identifier from the URL.', async(): Promise<void> => {
|
|
||||||
expect(parseWebHookUnsubscribeUrl('http://example.com/unsubscribe/123%24456')).toBe('123$456');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -18,7 +18,6 @@ import {
|
|||||||
isWebHook2021Channel,
|
isWebHook2021Channel,
|
||||||
WebHookSubscription2021,
|
WebHookSubscription2021,
|
||||||
} from '../../../../../src/server/notifications/WebHookSubscription2021/WebHookSubscription2021';
|
} from '../../../../../src/server/notifications/WebHookSubscription2021/WebHookSubscription2021';
|
||||||
import { joinUrl } from '../../../../../src/util/PathUtil';
|
|
||||||
import { NOTIFY, RDF } from '../../../../../src/util/Vocabularies';
|
import { NOTIFY, RDF } from '../../../../../src/util/Vocabularies';
|
||||||
import quad = DataFactory.quad;
|
import quad = DataFactory.quad;
|
||||||
import blankNode = DataFactory.blankNode;
|
import blankNode = DataFactory.blankNode;
|
||||||
@ -41,7 +40,6 @@ describe('A WebHookSubscription2021', (): void => {
|
|||||||
let channel: WebHookSubscription2021Channel;
|
let channel: WebHookSubscription2021Channel;
|
||||||
const route = new AbsolutePathInteractionRoute('http://example.com/webhooks/');
|
const route = new AbsolutePathInteractionRoute('http://example.com/webhooks/');
|
||||||
const webIdRoute = new RelativePathInteractionRoute(route, '/webid');
|
const webIdRoute = new RelativePathInteractionRoute(route, '/webid');
|
||||||
const unsubscribeRoute = new RelativePathInteractionRoute(route, '/unsubscribe');
|
|
||||||
let stateHandler: jest.Mocked<StateHandler>;
|
let stateHandler: jest.Mocked<StateHandler>;
|
||||||
let channelType: WebHookSubscription2021;
|
let channelType: WebHookSubscription2021;
|
||||||
|
|
||||||
@ -51,7 +49,7 @@ describe('A WebHookSubscription2021', (): void => {
|
|||||||
data.addQuad(quad(subject, NOTIFY.terms.topic, namedNode(topic)));
|
data.addQuad(quad(subject, NOTIFY.terms.topic, namedNode(topic)));
|
||||||
data.addQuad(quad(subject, NOTIFY.terms.target, namedNode(target)));
|
data.addQuad(quad(subject, NOTIFY.terms.target, namedNode(target)));
|
||||||
|
|
||||||
const id = '4c9b88c1-7502-4107-bb79-2a3a590c7aa3:https://storage.example/resource';
|
const id = 'http://example.com/webhooks/4c9b88c1-7502-4107-bb79-2a3a590c7aa3';
|
||||||
channel = {
|
channel = {
|
||||||
id,
|
id,
|
||||||
type: NOTIFY.WebHookSubscription2021,
|
type: NOTIFY.WebHookSubscription2021,
|
||||||
@ -59,14 +57,14 @@ describe('A WebHookSubscription2021', (): void => {
|
|||||||
target,
|
target,
|
||||||
webId: 'http://example.org/alice',
|
webId: 'http://example.org/alice',
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
unsubscribe_endpoint: joinUrl(unsubscribeRoute.getPath(), encodeURIComponent(id)),
|
unsubscribe_endpoint: id,
|
||||||
};
|
};
|
||||||
|
|
||||||
stateHandler = {
|
stateHandler = {
|
||||||
handleSafe: jest.fn(),
|
handleSafe: jest.fn(),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
channelType = new WebHookSubscription2021(route, webIdRoute, unsubscribeRoute, stateHandler);
|
channelType = new WebHookSubscription2021(route, webIdRoute, stateHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes a utility function to verify if a channel is a webhook channel.', async(): Promise<void> => {
|
it('exposes a utility function to verify if a channel is a webhook channel.', async(): Promise<void> => {
|
||||||
|
@ -25,7 +25,7 @@ describe('A WebHookUnsubscriber', (): void => {
|
|||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
operation = {
|
operation = {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
target: { path: 'http://example.com/.notifications/webhooks/unsubscribe/134' },
|
target: { path: 'http://example.com/.notifications/webhooks/134' },
|
||||||
preferences: {},
|
preferences: {},
|
||||||
body: new BasicRepresentation(),
|
body: new BasicRepresentation(),
|
||||||
};
|
};
|
||||||
@ -58,6 +58,6 @@ describe('A WebHookUnsubscriber', (): void => {
|
|||||||
await expect(unsubscriber.handle({ operation, request, response }))
|
await expect(unsubscriber.handle({ operation, request, response }))
|
||||||
.resolves.toEqual(new ResetResponseDescription());
|
.resolves.toEqual(new ResetResponseDescription());
|
||||||
expect(storage.delete).toHaveBeenCalledTimes(1);
|
expect(storage.delete).toHaveBeenCalledTimes(1);
|
||||||
expect(storage.delete).toHaveBeenLastCalledWith('134');
|
expect(storage.delete).toHaveBeenLastCalledWith('http://example.com/.notifications/webhooks/134');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -34,7 +34,7 @@ describe('A WebSocketSubscription2021', (): void => {
|
|||||||
data.addQuad(quad(subject, RDF.terms.type, NOTIFY.terms.WebSocketSubscription2021));
|
data.addQuad(quad(subject, RDF.terms.type, NOTIFY.terms.WebSocketSubscription2021));
|
||||||
data.addQuad(quad(subject, NOTIFY.terms.topic, namedNode(topic)));
|
data.addQuad(quad(subject, NOTIFY.terms.topic, namedNode(topic)));
|
||||||
|
|
||||||
const id = '4c9b88c1-7502-4107-bb79-2a3a590c7aa3:https://storage.example/resource';
|
const id = 'http://example.com/foo/4c9b88c1-7502-4107-bb79-2a3a590c7aa3';
|
||||||
channel = {
|
channel = {
|
||||||
id,
|
id,
|
||||||
type: NOTIFY.WebSocketSubscription2021,
|
type: NOTIFY.WebSocketSubscription2021,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user