diff --git a/src/server/notifications/BaseStateHandler.ts b/src/server/notifications/BaseStateHandler.ts index 2ae17a39c..04232bac7 100644 --- a/src/server/notifications/BaseStateHandler.ts +++ b/src/server/notifications/BaseStateHandler.ts @@ -1,14 +1,15 @@ import { getLoggerFor } from '../../logging/LogUtil'; import { createErrorMessage } from '../../util/errors/ErrorUtil'; -import type { NotificationChannelInfo, NotificationChannelStorage } from './NotificationChannelStorage'; +import type { NotificationChannel } from './NotificationChannel'; +import type { NotificationChannelStorage } from './NotificationChannelStorage'; import type { NotificationHandler } from './NotificationHandler'; import { StateHandler } from './StateHandler'; /** * Handles the `state` feature by calling a {@link NotificationHandler} - * in case the {@link NotificationChannelInfo} has a `state` value. + * in case the {@link NotificationChannel} has a `state` value. * - * Deletes the `state` parameter from the info afterwards. + * Deletes the `state` parameter from the channel afterwards. */ export class BaseStateHandler extends StateHandler { protected readonly logger = getLoggerFor(this); @@ -22,14 +23,14 @@ export class BaseStateHandler extends StateHandler { this.storage = storage; } - public async handle({ info }: { info: NotificationChannelInfo }): Promise { - if (info.state) { - const topic = { path: info.topic }; + public async handle({ channel }: { channel: NotificationChannel }): Promise { + if (channel.state) { + const topic = { path: channel.topic }; try { - await this.handler.handleSafe({ info, topic }); + await this.handler.handleSafe({ channel, topic }); // Remove the state once the relevant notification has been sent - delete info.state; - await this.storage.update(info); + delete channel.state; + await this.storage.update(channel); } catch (error: unknown) { this.logger.error(`Problem emitting state notification: ${createErrorMessage(error)}`); } diff --git a/src/server/notifications/ComposedNotificationHandler.ts b/src/server/notifications/ComposedNotificationHandler.ts index ecdd4e8c5..e3b410e35 100644 --- a/src/server/notifications/ComposedNotificationHandler.ts +++ b/src/server/notifications/ComposedNotificationHandler.ts @@ -14,7 +14,7 @@ export interface ComposedNotificationHandlerArgs { * Generates, serializes and emits a {@link Notification} using a {@link NotificationGenerator}, * {@link NotificationSerializer} and {@link NotificationEmitter}. * - * Will not emit an event in case it has the same state as the notification channel info. + * Will not emit an event when it has the same state as the notification channel. */ export class ComposedNotificationHandler extends NotificationHandler { private readonly generator: NotificationGenerator; @@ -35,13 +35,13 @@ export class ComposedNotificationHandler extends NotificationHandler { public async handle(input: NotificationHandlerInput): Promise { const notification = await this.generator.handle(input); - const { state } = input.info; + const { state } = input.channel; // In case the state matches there is no need to send the notification if (typeof state === 'string' && state === notification.state) { return; } - const representation = await this.serializer.handleSafe({ info: input.info, notification }); - await this.emitter.handleSafe({ info: input.info, representation }); + const representation = await this.serializer.handleSafe({ channel: input.channel, notification }); + await this.emitter.handleSafe({ channel: input.channel, representation }); } } diff --git a/src/server/notifications/KeyValueChannelStorage.ts b/src/server/notifications/KeyValueChannelStorage.ts index 4dfa718c3..ece5dcbe6 100644 --- a/src/server/notifications/KeyValueChannelStorage.ts +++ b/src/server/notifications/KeyValueChannelStorage.ts @@ -4,13 +4,13 @@ import { getLoggerFor } from '../../logging/LogUtil'; import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage'; import { InternalServerError } from '../../util/errors/InternalServerError'; import type { ReadWriteLocker } from '../../util/locking/ReadWriteLocker'; -import type { NotificationChannel } from './NotificationChannel'; -import type { NotificationChannelInfo, NotificationChannelStorage } from './NotificationChannelStorage'; +import type { NotificationChannel, NotificationChannelJson } from './NotificationChannel'; +import type { NotificationChannelStorage } from './NotificationChannelStorage'; -type StorageValue = string | string[] | NotificationChannelInfo; +type StorageValue = string | string[] | NotificationChannel; /** - * Stores all the {@link NotificationChannelInfo} in a {@link KeyValueStorage}. + * Stores all the {@link NotificationChannel} in a {@link KeyValueStorage}. * * Uses a {@link ReadWriteLocker} to prevent internal race conditions. */ @@ -25,7 +25,7 @@ export class KeyValueChannelStorage> implement this.locker = locker; } - public create(channel: NotificationChannel, features: T): NotificationChannelInfo { + public create(channel: NotificationChannelJson, features: T): NotificationChannel { return { id: `${channel.type}:${v4()}:${channel.topic}`, topic: channel.topic, @@ -40,92 +40,92 @@ export class KeyValueChannelStorage> implement }; } - public async get(id: string): Promise | undefined> { - const info = await this.storage.get(id); - if (info && this.isChannelInfo(info)) { - if (typeof info.endAt === 'number' && info.endAt < Date.now()) { + public async get(id: string): Promise | undefined> { + const channel = await this.storage.get(id); + if (channel && this.isChannel(channel)) { + if (typeof channel.endAt === 'number' && channel.endAt < Date.now()) { this.logger.info(`Notification channel ${id} has expired.`); await this.locker.withWriteLock(this.getLockKey(id), async(): Promise => { - await this.deleteInfo(info); + await this.deleteChannel(channel); }); return; } - return info; + return channel; } } public async getAll(topic: ResourceIdentifier): Promise { - const infos = await this.storage.get(topic.path); - if (Array.isArray(infos)) { - return infos; + const channels = await this.storage.get(topic.path); + if (Array.isArray(channels)) { + return channels; } return []; } - public async add(info: NotificationChannelInfo): Promise { - const target = { path: info.topic }; + public async add(channel: NotificationChannel): Promise { + const target = { path: channel.topic }; return this.locker.withWriteLock(this.getLockKey(target), async(): Promise => { - const infos = await this.getAll(target); - await this.storage.set(info.id, info); - infos.push(info.id); - await this.storage.set(info.topic, infos); + const channels = await this.getAll(target); + await this.storage.set(channel.id, channel); + channels.push(channel.id); + await this.storage.set(channel.topic, channels); }); } - public async update(info: NotificationChannelInfo): Promise { - return this.locker.withWriteLock(this.getLockKey(info.id), async(): Promise => { - const oldInfo = await this.storage.get(info.id); + public async update(channel: NotificationChannel): Promise { + return this.locker.withWriteLock(this.getLockKey(channel.id), async(): Promise => { + const oldChannel = await this.storage.get(channel.id); - if (oldInfo) { - if (!this.isChannelInfo(oldInfo)) { - throw new InternalServerError(`Trying to update ${info.id} which is not a NotificationChannelInfo.`); + if (oldChannel) { + if (!this.isChannel(oldChannel)) { + throw new InternalServerError(`Trying to update ${channel.id} which is not a NotificationChannel.`); } - if (info.topic !== oldInfo.topic) { - throw new InternalServerError(`Trying to change the topic of a notification channel ${info.id}`); + if (channel.topic !== oldChannel.topic) { + throw new InternalServerError(`Trying to change the topic of a notification channel ${channel.id}`); } } - await this.storage.set(info.id, info); + await this.storage.set(channel.id, channel); }); } public async delete(id: string): Promise { return this.locker.withWriteLock(this.getLockKey(id), async(): Promise => { - const info = await this.get(id); - if (!info) { + const channel = await this.get(id); + if (!channel) { return; } - await this.deleteInfo(info); + await this.deleteChannel(channel); }); } /** - * Utility function for deleting a specific {@link NotificationChannelInfo} object. - * Does not create a lock on the info ID so should be wrapped in such a lock. + * Utility function for deleting a specific {@link NotificationChannel} object. + * Does not create a lock on the channel ID so should be wrapped in such a lock. */ - private async deleteInfo(info: NotificationChannelInfo): Promise { - await this.locker.withWriteLock(this.getLockKey(info.topic), async(): Promise => { - const infos = await this.getAll({ path: info.topic }); - const idx = infos.indexOf(info.id); + private async deleteChannel(channel: NotificationChannel): Promise { + await this.locker.withWriteLock(this.getLockKey(channel.topic), async(): Promise => { + const channels = await this.getAll({ path: channel.topic }); + const idx = channels.indexOf(channel.id); // If idx < 0 we have an inconsistency if (idx < 0) { - this.logger.error(`Channel info ${info.id} was not found in the list of info targeting ${info.topic}.`); + this.logger.error(`Channel ${channel.id} was not found in the list of channels targeting ${channel.topic}.`); this.logger.error('This should not happen and indicates a data consistency issue.'); } else { - infos.splice(idx, 1); - if (infos.length > 0) { - await this.storage.set(info.topic, infos); + channels.splice(idx, 1); + if (channels.length > 0) { + await this.storage.set(channel.topic, channels); } else { - await this.storage.delete(info.topic); + await this.storage.delete(channel.topic); } } - await this.storage.delete(info.id); + await this.storage.delete(channel.id); }); } - private isChannelInfo(value: StorageValue): value is NotificationChannelInfo { - return Boolean((value as NotificationChannelInfo).id); + private isChannel(value: StorageValue): value is NotificationChannel { + return Boolean((value as NotificationChannel).id); } private getLockKey(identifier: ResourceIdentifier | string): ResourceIdentifier { diff --git a/src/server/notifications/ListeningActivityHandler.ts b/src/server/notifications/ListeningActivityHandler.ts index e1032aaf7..43518f2e2 100644 --- a/src/server/notifications/ListeningActivityHandler.ts +++ b/src/server/notifications/ListeningActivityHandler.ts @@ -39,25 +39,25 @@ export class ListeningActivityHandler extends StaticHandler { const channelIds = await this.storage.getAll(topic); for (const id of channelIds) { - const info = await this.storage.get(id); - if (!info) { + const channel = await this.storage.get(id); + if (!channel) { // Notification channel has expired continue; } // Don't emit if the previous notification was too recent according to the requested rate - if (info.rate && info.rate > Date.now() - info.lastEmit) { + if (channel.rate && channel.rate > Date.now() - channel.lastEmit) { continue; } // Don't emit if we have not yet reached the requested starting time - if (info.startAt && info.startAt > Date.now()) { + if (channel.startAt && channel.startAt > Date.now()) { continue; } // No need to wait on this to resolve before going to the next channel. // Prevent failed notification from blocking other notifications. - this.handler.handleSafe({ info, activity, topic }).catch((error): void => { + this.handler.handleSafe({ channel, activity, topic }).catch((error): void => { this.logger.error(`Error trying to handle notification for ${id}: ${createErrorMessage(error)}`); }); } diff --git a/src/server/notifications/NotificationChannel.ts b/src/server/notifications/NotificationChannel.ts index 5a3901b75..9082dbc85 100644 --- a/src/server/notifications/NotificationChannel.ts +++ b/src/server/notifications/NotificationChannel.ts @@ -27,4 +27,21 @@ export const NOTIFICATION_CHANNEL_SCHEMA = object({ toSeconds(parse(original)) * 1000).optional(), accept: string().optional(), }); -export type NotificationChannel = InferType; +export type NotificationChannelJson = InferType; + +/** + * The info provided for a notification channel during a subscription. + * `features` can contain custom values relevant for a specific channel type. + */ +export type NotificationChannel> = { + id: string; + topic: string; + type: string; + startAt?: number; + endAt?: number; + accept?: string; + rate?: number; + state?: string; + lastEmit: number; + features: T; +}; diff --git a/src/server/notifications/NotificationChannelStorage.ts b/src/server/notifications/NotificationChannelStorage.ts index d411110b2..b38b2c13c 100644 --- a/src/server/notifications/NotificationChannelStorage.ts +++ b/src/server/notifications/NotificationChannelStorage.ts @@ -1,22 +1,5 @@ import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier'; -import type { NotificationChannel } from './NotificationChannel'; - -/** - * The info provided for a notification channel during a subscription. - * `features` can contain custom values relevant for a specific channel type. - */ -export type NotificationChannelInfo> = { - id: string; - topic: string; - type: string; - startAt?: number; - endAt?: number; - accept?: string; - rate?: number; - state?: string; - lastEmit: number; - features: T; -}; +import type { NotificationChannel, NotificationChannelJson } from './NotificationChannel'; /** * Stores all the information necessary to keep track of notification channels. @@ -26,19 +9,19 @@ export type NotificationChannelInfo> = { */ export interface NotificationChannelStorage = Record> { /** - * Creates info corresponding to the given channel and features. - * This does not store the generated info in the storage. - * @param channel - Notification channel to generate info of. - * @param features - Features to add to the info + * Creates channel corresponding to the given channel and features. + * This does not store the generated channel in the storage. + * @param channel - Notification channel to generate channel of. + * @param features - Features to add to the channel */ - create: (channel: NotificationChannel, features: T) => NotificationChannelInfo; + create: (channel: NotificationChannelJson, features: T) => NotificationChannel; /** - * Returns the info for the requested notification channel. + * Returns the channel for the requested notification channel. * `undefined` if no match was found or if the notification channel expired. * @param id - The identifier of the notification channel. */ - get: (id: string) => Promise | undefined>; + get: (id: string) => Promise | undefined>; /** * Returns the identifiers of all notification channel entries that have the given identifier as their topic. @@ -48,17 +31,17 @@ export interface NotificationChannelStorage = getAll: (topic: ResourceIdentifier) => Promise; /** - * Adds the given info to the storage. - * @param info - Info to add. + * Adds the given channel to the storage. + * @param channel - Channel to add. */ - add: (info: NotificationChannelInfo) => Promise; + add: (channel: NotificationChannel) => Promise; /** - * Updates the given notification channel info. + * Updates the given notification channel. * The `id` and the `topic` can not be updated. - * @param info - The info to update. + * @param channel - The channel to update. */ - update: (info: NotificationChannelInfo) => Promise; + update: (channel: NotificationChannel) => Promise; /** * Deletes the given notification channel from the storage. diff --git a/src/server/notifications/NotificationChannelType.ts b/src/server/notifications/NotificationChannelType.ts index 203aac4d7..df78993c1 100644 --- a/src/server/notifications/NotificationChannelType.ts +++ b/src/server/notifications/NotificationChannelType.ts @@ -2,12 +2,11 @@ import type { InferType } from 'yup'; import type { Credentials } from '../../authentication/Credentials'; import type { AccessMap } from '../../authorization/permissions/Permissions'; import type { Representation } from '../../http/representation/Representation'; -import type { NOTIFICATION_CHANNEL_SCHEMA } from './NotificationChannel'; -import type { NotificationChannelInfo } from './NotificationChannelStorage'; +import type { NOTIFICATION_CHANNEL_SCHEMA, NotificationChannel } from './NotificationChannel'; export interface NotificationChannelResponse = Record> { response: Representation; - info: NotificationChannelInfo; + channel: NotificationChannel; } /** @@ -32,13 +31,13 @@ export interface NotificationChannelType< * * @returns The required modes. */ - extractModes: (channel: InferType) => Promise; + extractModes: (json: InferType) => Promise; /** * Registers the given notification channel. * @param channel - The notification channel to register. * @param credentials - The credentials of the client trying to subscribe. * - * @returns A {@link Representation} to return as a response and the generated {@link NotificationChannelInfo}. + * @returns A {@link Representation} to return as a response and the generated {@link NotificationChannel}. */ - subscribe: (channel: InferType, credentials: Credentials) => Promise>; + subscribe: (json: InferType, credentials: Credentials) => Promise>; } diff --git a/src/server/notifications/NotificationEmitter.ts b/src/server/notifications/NotificationEmitter.ts index f6612dedb..a9ba7cbda 100644 --- a/src/server/notifications/NotificationEmitter.ts +++ b/src/server/notifications/NotificationEmitter.ts @@ -1,14 +1,14 @@ import type { Representation } from '../../http/representation/Representation'; import { AsyncHandler } from '../../util/handlers/AsyncHandler'; -import type { NotificationChannelInfo } from './NotificationChannelStorage'; +import type { NotificationChannel } from './NotificationChannel'; export interface NotificationEmitterInput> { representation: Representation; - info: NotificationChannelInfo; + channel: NotificationChannel; } /** - * Emits a serialized Notification to the channel defined by the info. + * Emits a serialized Notification to the channel defined by the channel. */ export abstract class NotificationEmitter> extends AsyncHandler> {} diff --git a/src/server/notifications/NotificationHandler.ts b/src/server/notifications/NotificationHandler.ts index f73667cad..e432aa5bc 100644 --- a/src/server/notifications/NotificationHandler.ts +++ b/src/server/notifications/NotificationHandler.ts @@ -1,15 +1,15 @@ import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier'; import { AsyncHandler } from '../../util/handlers/AsyncHandler'; import type { AS, VocabularyTerm } from '../../util/Vocabularies'; -import type { NotificationChannelInfo } from './NotificationChannelStorage'; +import type { NotificationChannel } from './NotificationChannel'; export interface NotificationHandlerInput { topic: ResourceIdentifier; - info: NotificationChannelInfo; + channel: NotificationChannel; activity?: VocabularyTerm; } /** - * Makes sure an activity gets emitted to the relevant channel based on the given info. + * Makes sure an activity gets emitted to the relevant channel. */ export abstract class NotificationHandler extends AsyncHandler {} diff --git a/src/server/notifications/NotificationSubscriber.ts b/src/server/notifications/NotificationSubscriber.ts index 7435049fd..45cd707aa 100644 --- a/src/server/notifications/NotificationSubscriber.ts +++ b/src/server/notifications/NotificationSubscriber.ts @@ -13,7 +13,7 @@ import { readableToString } from '../../util/StreamUtil'; import type { HttpRequest } from '../HttpRequest'; import type { OperationHttpHandlerInput } from '../OperationHttpHandler'; import { OperationHttpHandler } from '../OperationHttpHandler'; -import type { NotificationChannel } from './NotificationChannel'; +import type { NotificationChannelJson } from './NotificationChannel'; import type { NotificationChannelType } from './NotificationChannelType'; export interface NotificationSubscriberArgs { @@ -34,8 +34,8 @@ export interface NotificationSubscriberArgs { */ authorizer: Authorizer; /** - * Overrides the expiration feature of channels by making sure they always expire after the `maxDuration` value. - * In case the expiration of the channel is shorter than `maxDuration` the original value will be kept. + * Overrides the expiration feature of channels, by making sure they always expire after the `maxDuration` value. + * If the expiration of the channel is shorter than `maxDuration`, the original value will be kept. * Value is set in minutes. 0 is infinite. */ maxDuration?: number; @@ -70,7 +70,7 @@ export class NotificationSubscriber extends OperationHttpHandler { throw new UnsupportedMediaTypeHttpError('Subscribe bodies need to be application/ld+json.'); } - let channel: NotificationChannel; + let channel: NotificationChannelJson; try { const json = JSON.parse(await readableToString(operation.body.data)); channel = await this.channelType.schema.validate(json); @@ -93,7 +93,7 @@ export class NotificationSubscriber extends OperationHttpHandler { return new OkResponseDescription(response.metadata, response.data); } - private async authorize(request: HttpRequest, channel: NotificationChannel): Promise { + private async authorize(request: HttpRequest, channel: NotificationChannelJson): Promise { const credentials = await this.credentialsExtractor.handleSafe(request); this.logger.debug(`Extracted credentials: ${JSON.stringify(credentials)}`); diff --git a/src/server/notifications/StateHandler.ts b/src/server/notifications/StateHandler.ts index 7c4400672..ca391ba45 100644 --- a/src/server/notifications/StateHandler.ts +++ b/src/server/notifications/StateHandler.ts @@ -1,5 +1,5 @@ import { AsyncHandler } from '../../util/handlers/AsyncHandler'; -import type { NotificationChannelInfo } from './NotificationChannelStorage'; +import type { NotificationChannel } from './NotificationChannel'; /** * Handles the `state` feature of notifications. @@ -8,4 +8,4 @@ import type { NotificationChannelInfo } from './NotificationChannelStorage'; * * Implementations of this class should handle all channels and filter out those that need a `state` notification. */ -export abstract class StateHandler extends AsyncHandler<{ info: NotificationChannelInfo }> {} +export abstract class StateHandler extends AsyncHandler<{ channel: NotificationChannel }> {} diff --git a/src/server/notifications/TypedNotificationHandler.ts b/src/server/notifications/TypedNotificationHandler.ts index 5be8c93ce..4eb9516bc 100644 --- a/src/server/notifications/TypedNotificationHandler.ts +++ b/src/server/notifications/TypedNotificationHandler.ts @@ -16,7 +16,7 @@ export class TypedNotificationHandler extends NotificationHandler { } public async canHandle(input: NotificationHandlerInput): Promise { - if (input.info.type !== this.type) { + if (input.channel.type !== this.type) { throw new NotImplementedHttpError(`Only ${this.type} notification channels are supported.`); } await this.source.canHandle(input); diff --git a/src/server/notifications/WebHookSubscription2021/WebHookEmitter.ts b/src/server/notifications/WebHookSubscription2021/WebHookEmitter.ts index 93b8f4c80..e451ca820 100644 --- a/src/server/notifications/WebHookSubscription2021/WebHookEmitter.ts +++ b/src/server/notifications/WebHookSubscription2021/WebHookEmitter.ts @@ -34,8 +34,8 @@ export class WebHookEmitter extends NotificationEmitter { this.expiration = expiration * 60 * 1000; } - public async handle({ info, representation }: NotificationEmitterInput): Promise { - this.logger.debug(`Emitting WebHook notification with target ${info.features.target}`); + public async handle({ channel, representation }: NotificationEmitterInput): Promise { + this.logger.debug(`Emitting WebHook notification with target ${channel.features.target}`); const privateKey = await this.jwkGenerator.getPrivateKey(); const publicKey = await this.jwkGenerator.getPublicKey(); @@ -66,14 +66,14 @@ export class WebHookEmitter extends NotificationEmitter { // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop#section-4.2 const dpopProof = await new SignJWT({ - htu: info.features.target, + htu: channel.features.target, htm: 'POST', }).setProtectedHeader({ alg: privateKey.alg, jwk: publicKey, typ: 'dpop+jwt' }) .setIssuedAt(time) .setJti(v4()) .sign(privateKeyObject); - const response = await fetch(info.features.target, { + const response = await fetch(channel.features.target, { method: 'POST', headers: { 'content-type': representation.metadata.contentType!, @@ -83,7 +83,7 @@ export class WebHookEmitter extends NotificationEmitter { body: await readableToString(representation.data), }); if (response.status >= 400) { - this.logger.error(`There was an issue emitting a WebHook notification with target ${info.features.target}: ${ + this.logger.error(`There was an issue emitting a WebHook notification with target ${channel.features.target}: ${ await response.text()}`); } } diff --git a/src/server/notifications/WebHookSubscription2021/WebHookSubscription2021.ts b/src/server/notifications/WebHookSubscription2021/WebHookSubscription2021.ts index 7abd78618..e72906c1c 100644 --- a/src/server/notifications/WebHookSubscription2021/WebHookSubscription2021.ts +++ b/src/server/notifications/WebHookSubscription2021/WebHookSubscription2021.ts @@ -52,11 +52,11 @@ export class WebHookSubscription2021 implements NotificationChannelType): Promise { - return new IdentifierSetMultiMap([[{ path: channel.topic }, AccessMode.read ]]); + public async extractModes(json: InferType): Promise { + return new IdentifierSetMultiMap([[{ path: json.topic }, AccessMode.read ]]); } - public async subscribe(channel: InferType, credentials: Credentials): + public async subscribe(json: InferType, credentials: Credentials): Promise> { const webId = credentials.agent?.webId; @@ -66,15 +66,15 @@ export class WebHookSubscription2021 implements NotificationChannelType => this.stateHandler.handleSafe({ info })) + .then((): Promise => this.stateHandler.handleSafe({ channel })) .catch((error): void => { this.logger.error(`Error emitting state notification: ${createErrorMessage(error)}`); }); - return { response, info }; + return { response, channel }; } } diff --git a/src/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.ts b/src/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.ts index 17ed8a74a..55df95425 100644 --- a/src/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.ts +++ b/src/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.ts @@ -28,13 +28,13 @@ export class WebHookUnsubscriber extends OperationHttpHandler { public async handle({ operation, request }: OperationHttpHandlerInput): Promise { const id = parseWebHookUnsubscribeUrl(operation.target.path); - const info = await this.storage.get(id); - if (!info) { + const channel = await this.storage.get(id); + if (!channel) { throw new NotFoundHttpError(); } const credentials = await this.credentialsExtractor.handleSafe(request); - if (info.features.webId !== credentials.agent?.webId) { + if (channel.features.webId !== credentials.agent?.webId) { throw new ForbiddenHttpError(); } diff --git a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.ts b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.ts index 7c32702bd..ae2bf629f 100644 --- a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.ts +++ b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.ts @@ -21,9 +21,9 @@ export class WebSocket2021Emitter extends NotificationEmitter { this.socketMap = socketMap; } - public async handle({ info, representation }: NotificationEmitterInput): Promise { + public async handle({ channel, representation }: NotificationEmitterInput): Promise { // Called as a NotificationEmitter: emit the notification - const webSockets = this.socketMap.get(info.id); + const webSockets = this.socketMap.get(channel.id); if (webSockets) { const data = await readableToString(representation.data); for (const webSocket of webSockets) { diff --git a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Handler.ts b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Handler.ts index 02721f85c..9e22553f7 100644 --- a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Handler.ts +++ b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Handler.ts @@ -1,9 +1,9 @@ import type { WebSocket } from 'ws'; import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; -import type { NotificationChannelInfo } from '../NotificationChannelStorage'; +import type { NotificationChannel } from '../NotificationChannel'; export interface WebSocket2021HandlerInput { - info: NotificationChannelInfo; + channel: NotificationChannel; webSocket: WebSocket; } diff --git a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.ts b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.ts index ff8b59eb6..a6ad8a1ab 100644 --- a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.ts +++ b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.ts @@ -38,16 +38,16 @@ export class WebSocket2021Listener extends WebSocketServerConfigurator { return webSocket.close(); } - const info = await this.storage.get(id); + const channel = await this.storage.get(id); - if (!info) { - // Info not being there implies it has expired + if (!channel) { + // Channel not being there implies it has expired webSocket.send(`Notification channel has expired`); return webSocket.close(); } - this.logger.info(`Accepted WebSocket connection listening to changes on ${info.topic}`); + this.logger.info(`Accepted WebSocket connection listening to changes on ${channel.topic}`); - await this.handler.handleSafe({ info, webSocket }); + await this.handler.handleSafe({ channel, webSocket }); } } diff --git a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.ts b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.ts index b85f82f1d..ee2d9551b 100644 --- a/src/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.ts +++ b/src/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.ts @@ -34,10 +34,10 @@ export class WebSocket2021Storer extends WebSocket2021Handler { timer.unref(); } - public async handle({ webSocket, info }: WebSocket2021HandlerInput): Promise { - this.socketMap.add(info.id, webSocket); - webSocket.on('error', (): boolean => this.socketMap.deleteEntry(info.id, webSocket)); - webSocket.on('close', (): boolean => this.socketMap.deleteEntry(info.id, webSocket)); + public async handle({ webSocket, channel }: WebSocket2021HandlerInput): Promise { + this.socketMap.add(channel.id, webSocket); + webSocket.on('error', (): boolean => this.socketMap.deleteEntry(channel.id, webSocket)); + webSocket.on('close', (): boolean => this.socketMap.deleteEntry(channel.id, webSocket)); } /** diff --git a/src/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.ts b/src/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.ts index 196114ab6..ba6e61bbf 100644 --- a/src/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.ts +++ b/src/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.ts @@ -7,7 +7,7 @@ import { getLoggerFor } from '../../../logging/LogUtil'; import { APPLICATION_LD_JSON } from '../../../util/ContentTypes'; import { IdentifierSetMultiMap } from '../../../util/map/IdentifierMap'; import { CONTEXT_NOTIFICATION } from '../Notification'; -import type { NotificationChannel } from '../NotificationChannel'; +import type { NotificationChannelJson } from '../NotificationChannel'; import { NOTIFICATION_CHANNEL_SCHEMA } from '../NotificationChannel'; import type { NotificationChannelStorage } from '../NotificationChannelStorage'; import type { NotificationChannelResponse, NotificationChannelType } from '../NotificationChannelType'; @@ -38,21 +38,21 @@ export class WebSocketSubscription2021 implements NotificationChannelType { - return new IdentifierSetMultiMap([[{ path: channel.topic }, AccessMode.read ]]); + public async extractModes(json: NotificationChannelJson): Promise { + return new IdentifierSetMultiMap([[{ path: json.topic }, AccessMode.read ]]); } - public async subscribe(channel: NotificationChannel): Promise { - const info = this.storage.create(channel, {}); - await this.storage.add(info); + public async subscribe(json: NotificationChannelJson): Promise { + const channel = this.storage.create(json, {}); + await this.storage.add(channel); const jsonld = { '@context': [ CONTEXT_NOTIFICATION ], type: this.type, - source: generateWebSocketUrl(this.path, info.id), + source: generateWebSocketUrl(this.path, channel.id), }; const response = new BasicRepresentation(JSON.stringify(jsonld), APPLICATION_LD_JSON); - return { response, info }; + return { response, channel }; } } diff --git a/src/server/notifications/serialize/ConvertingNotificationSerializer.ts b/src/server/notifications/serialize/ConvertingNotificationSerializer.ts index 56e30ab3e..8ff6ceb20 100644 --- a/src/server/notifications/serialize/ConvertingNotificationSerializer.ts +++ b/src/server/notifications/serialize/ConvertingNotificationSerializer.ts @@ -25,7 +25,7 @@ export class ConvertingNotificationSerializer extends NotificationSerializer { public async handle(input: NotificationSerializerInput): Promise { const representation = await this.source.handle(input); - const type = input.info.accept; + const type = input.channel.accept; if (!type) { return representation; diff --git a/src/server/notifications/serialize/NotificationSerializer.ts b/src/server/notifications/serialize/NotificationSerializer.ts index 21fd2f1a8..bdec92bff 100644 --- a/src/server/notifications/serialize/NotificationSerializer.ts +++ b/src/server/notifications/serialize/NotificationSerializer.ts @@ -1,17 +1,17 @@ import type { Representation } from '../../../http/representation/Representation'; import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; import type { Notification } from '../Notification'; -import type { NotificationChannelInfo } from '../NotificationChannelStorage'; +import type { NotificationChannel } from '../NotificationChannel'; export interface NotificationSerializerInput { notification: Notification; - info: NotificationChannelInfo; + channel: NotificationChannel; } /** * Converts a {@link Notification} into a {@link Representation} that can be transmitted. * - * The reason this is a separate class in between a generator and emitter, - * is so a specific notification channel type can add extra metadata to the Representation if needed. + * This is a separate class between a generator and emitter, + * so that a specific notification channel type can add extra metadata to the Representation if needed. */ export abstract class NotificationSerializer extends AsyncHandler { } diff --git a/test/unit/server/notifications/BaseStateHandler.test.ts b/test/unit/server/notifications/BaseStateHandler.test.ts index 57977699a..a49594ebe 100644 --- a/test/unit/server/notifications/BaseStateHandler.test.ts +++ b/test/unit/server/notifications/BaseStateHandler.test.ts @@ -1,20 +1,20 @@ import { BaseStateHandler } from '../../../../src/server/notifications/BaseStateHandler'; +import type { NotificationChannel } from '../../../../src/server/notifications/NotificationChannel'; import type { - NotificationChannelInfo, NotificationChannelStorage, } from '../../../../src/server/notifications/NotificationChannelStorage'; import type { NotificationHandler } from '../../../../src/server/notifications/NotificationHandler'; describe('A BaseStateHandler', (): void => { - let info: NotificationChannelInfo; + let channel: NotificationChannel; let notificationHandler: jest.Mocked; let storage: jest.Mocked; let handler: BaseStateHandler; beforeEach(async(): Promise => { - info = { + channel = { id: 'id', - topic: 'http://example.com/foo', + topic: 'http://exa mple.com/foo', type: 'type', features: {}, lastEmit: 0, @@ -33,21 +33,21 @@ describe('A BaseStateHandler', (): void => { }); it('calls the handler if there is a trigger.', async(): Promise => { - await expect(handler.handleSafe({ info })).resolves.toBeUndefined(); + await expect(handler.handleSafe({ channel })).resolves.toBeUndefined(); expect(notificationHandler.handleSafe).toHaveBeenCalledTimes(1); // Note that jest stores a reference to the input object so we can't see that the state value was still there - expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ topic: { path: info.topic }, info }); - expect(info.state).toBeUndefined(); + expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ topic: { path: channel.topic }, channel }); + expect(channel.state).toBeUndefined(); expect(storage.update).toHaveBeenCalledTimes(1); - expect(storage.update).toHaveBeenLastCalledWith(info); + expect(storage.update).toHaveBeenLastCalledWith(channel); }); it('does not delete the state parameter if something goes wrong.', async(): Promise => { notificationHandler.handleSafe.mockRejectedValue(new Error('bad input')); - await expect(handler.handleSafe({ info })).resolves.toBeUndefined(); + await expect(handler.handleSafe({ channel })).resolves.toBeUndefined(); expect(notificationHandler.handleSafe).toHaveBeenCalledTimes(1); - expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ topic: { path: info.topic }, info }); - expect(info.state).toBe('123'); + expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ topic: { path: channel.topic }, channel }); + expect(channel.state).toBe('123'); expect(storage.update).toHaveBeenCalledTimes(0); }); }); diff --git a/test/unit/server/notifications/ComposedNotificationHandler.test.ts b/test/unit/server/notifications/ComposedNotificationHandler.test.ts index 3bf5a56ea..5084c0ab5 100644 --- a/test/unit/server/notifications/ComposedNotificationHandler.test.ts +++ b/test/unit/server/notifications/ComposedNotificationHandler.test.ts @@ -3,7 +3,7 @@ import type { ResourceIdentifier } from '../../../../src/http/representation/Res import { ComposedNotificationHandler } from '../../../../src/server/notifications/ComposedNotificationHandler'; import type { NotificationGenerator } from '../../../../src/server/notifications/generate/NotificationGenerator'; import type { Notification } from '../../../../src/server/notifications/Notification'; -import type { NotificationChannelInfo } from '../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../src/server/notifications/NotificationChannel'; import type { NotificationEmitter } from '../../../../src/server/notifications/NotificationEmitter'; import type { NotificationSerializer } from '../../../../src/server/notifications/serialize/NotificationSerializer'; @@ -20,7 +20,7 @@ describe('A ComposedNotificationHandler', (): void => { published: '123', state: '123', }; - let info: NotificationChannelInfo; + let channel: NotificationChannel; const representation = new BasicRepresentation(); let generator: jest.Mocked; let serializer: jest.Mocked; @@ -28,7 +28,7 @@ describe('A ComposedNotificationHandler', (): void => { let handler: ComposedNotificationHandler; beforeEach(async(): Promise => { - info = { + channel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -53,26 +53,26 @@ describe('A ComposedNotificationHandler', (): void => { }); it('can only handle input supported by the generator.', async(): Promise => { - await expect(handler.canHandle({ info, topic })).resolves.toBeUndefined(); + await expect(handler.canHandle({ channel, topic })).resolves.toBeUndefined(); generator.canHandle.mockRejectedValue(new Error('bad input')); - await expect(handler.canHandle({ info, topic })).rejects.toThrow('bad input'); + await expect(handler.canHandle({ channel, topic })).rejects.toThrow('bad input'); }); it('calls the three wrapped classes in order.', async(): Promise => { - await expect(handler.handle({ info, topic })).resolves.toBeUndefined(); + await expect(handler.handle({ channel, topic })).resolves.toBeUndefined(); expect(generator.handle).toHaveBeenCalledTimes(1); - expect(generator.handle).toHaveBeenLastCalledWith({ info, topic }); + expect(generator.handle).toHaveBeenLastCalledWith({ channel, topic }); expect(serializer.handleSafe).toHaveBeenCalledTimes(1); - expect(serializer.handleSafe).toHaveBeenLastCalledWith({ info, notification }); + expect(serializer.handleSafe).toHaveBeenLastCalledWith({ channel, notification }); expect(emitter.handleSafe).toHaveBeenCalledTimes(1); - expect(emitter.handleSafe).toHaveBeenLastCalledWith({ info, representation }); + expect(emitter.handleSafe).toHaveBeenLastCalledWith({ channel, representation }); }); - it('does not emit the notification if its state matches the info state.', async(): Promise => { - info.state = notification.state; - await expect(handler.handle({ info, topic })).resolves.toBeUndefined(); + it('does not emit the notification if its state matches the channel state.', async(): Promise => { + channel.state = notification.state; + await expect(handler.handle({ channel, topic })).resolves.toBeUndefined(); expect(generator.handle).toHaveBeenCalledTimes(1); - expect(generator.handle).toHaveBeenLastCalledWith({ info, topic }); + expect(generator.handle).toHaveBeenLastCalledWith({ channel, topic }); expect(serializer.handleSafe).toHaveBeenCalledTimes(0); expect(emitter.handleSafe).toHaveBeenCalledTimes(0); }); diff --git a/test/unit/server/notifications/KeyValueChannelStorage.test.ts b/test/unit/server/notifications/KeyValueChannelStorage.test.ts index 67baca1b9..5fe51de62 100644 --- a/test/unit/server/notifications/KeyValueChannelStorage.test.ts +++ b/test/unit/server/notifications/KeyValueChannelStorage.test.ts @@ -3,8 +3,10 @@ import type { ResourceIdentifier } from '../../../../src/http/representation/Res import type { Logger } from '../../../../src/logging/Logger'; import { getLoggerFor } from '../../../../src/logging/LogUtil'; import { KeyValueChannelStorage } from '../../../../src/server/notifications/KeyValueChannelStorage'; -import type { NotificationChannel } from '../../../../src/server/notifications/NotificationChannel'; -import type { NotificationChannelInfo } from '../../../../src/server/notifications/NotificationChannelStorage'; +import type { + NotificationChannel, + NotificationChannelJson, +} from '../../../../src/server/notifications/NotificationChannel'; import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage'; import type { ReadWriteLocker } from '../../../../src/util/locking/ReadWriteLocker'; import resetAllMocks = jest.resetAllMocks; @@ -19,13 +21,13 @@ describe('A KeyValueChannelStorage', (): void => { const logger = getLoggerFor('mock'); const topic = 'http://example.com/foo'; const identifier = { path: topic }; - const channel = { + const json = { '@context': [ 'https://www.w3.org/ns/solid/notification/v1' ], type: 'WebSocketSubscription2021', topic, - } as NotificationChannel; + } as NotificationChannelJson; const features = { aa: 'bb' }; - let info: NotificationChannelInfo>; + let channel: NotificationChannel>; let internalMap: Map; let internalStorage: KeyValueStorage; let locker: ReadWriteLocker; @@ -33,7 +35,7 @@ describe('A KeyValueChannelStorage', (): void => { beforeEach(async(): Promise => { resetAllMocks(); - info = { + channel = { id: `WebSocketSubscription2021:${v4()}:http://example.com/foo`, topic, type: 'WebSocketSubscription2021', @@ -54,8 +56,8 @@ describe('A KeyValueChannelStorage', (): void => { }); describe('#create', (): void => { - it('creates info based on a notification channel.', async(): Promise => { - expect(storage.create(channel, features)).toEqual(info); + it('creates channel based on a notification channel.', async(): Promise => { + expect(storage.create(json, features)).toEqual(channel); }); }); @@ -64,15 +66,15 @@ describe('A KeyValueChannelStorage', (): void => { await expect(storage.get('notexists')).resolves.toBeUndefined(); }); - it('returns the matching info.', async(): Promise => { - await storage.add(info); - await expect(storage.get(info.id)).resolves.toEqual(info); + it('returns the matching channel.', async(): Promise => { + await storage.add(channel); + await expect(storage.get(channel.id)).resolves.toEqual(channel); }); - it('deletes expired info.', async(): Promise => { - info.endAt = 0; - await storage.add(info); - await expect(storage.get(info.id)).resolves.toBeUndefined(); + it('deletes expired channel.', async(): Promise => { + channel.endAt = 0; + await storage.add(channel); + await expect(storage.get(channel.id)).resolves.toBeUndefined(); expect(internalMap.size).toBe(0); }); }); @@ -82,93 +84,93 @@ describe('A KeyValueChannelStorage', (): void => { await expect(storage.getAll(identifier)).resolves.toEqual([]); }); - it('returns the identifiers of all the matching infos.', async(): Promise => { - await storage.add(info); - await expect(storage.getAll(identifier)).resolves.toEqual([ info.id ]); + it('returns the identifiers of all the matching channels.', async(): Promise => { + await storage.add(channel); + await expect(storage.getAll(identifier)).resolves.toEqual([ channel.id ]); }); }); describe('#add', (): void => { - it('adds the info and adds its id to the topic collection.', async(): Promise => { - await expect(storage.add(info)).resolves.toBeUndefined(); + it('adds the channel and adds its id to the topic collection.', async(): Promise => { + await expect(storage.add(channel)).resolves.toBeUndefined(); expect(internalMap.size).toBe(2); expect([ ...internalMap.values() ]).toEqual(expect.arrayContaining([ - [ info.id ], - info, + [ channel.id ], + channel, ])); }); }); describe('#update', (): void => { - it('changes the info.', async(): Promise => { - await storage.add(info); - const newInfo = { - ...info, + it('changes the channel.', async(): Promise => { + await storage.add(channel); + const newChannel = { + ...channel, state: '123456', }; - await expect(storage.update(newInfo)).resolves.toBeUndefined(); + await expect(storage.update(newChannel)).resolves.toBeUndefined(); expect([ ...internalMap.values() ]).toEqual(expect.arrayContaining([ - [ info.id ], - newInfo, + [ channel.id ], + newChannel, ])); }); it('rejects update requests that change the topic.', async(): Promise => { - await storage.add(info); - const newInfo = { - ...info, + await storage.add(channel); + const newChannel = { + ...channel, topic: 'http://example.com/other', }; - await expect(storage.update(newInfo)).rejects - .toThrow(`Trying to change the topic of a notification channel ${info.id}`); + await expect(storage.update(newChannel)).rejects + .toThrow(`Trying to change the topic of a notification channel ${channel.id}`); }); - it('rejects update request targeting a non-info value.', async(): Promise => { - await storage.add(info); + it('rejects update request targeting a non-channel value.', async(): Promise => { + await storage.add(channel); // Looking for the key so this test doesn't depend on the internal keys used const id = [ ...internalMap.entries() ].find((entry): boolean => Array.isArray(entry[1]))![0]; - const newInfo = { - ...info, + const newChannel = { + ...channel, id, }; - await expect(storage.update(newInfo)).rejects - .toThrow(`Trying to update ${id} which is not a NotificationChannelInfo.`); + await expect(storage.update(newChannel)).rejects + .toThrow(`Trying to update ${id} which is not a NotificationChannel.`); }); }); describe('#delete', (): void => { - it('removes the info and its reference.', async(): Promise => { - const info2 = { - ...info, + it('removes the channel and its reference.', async(): Promise => { + const channel2 = { + ...channel, id: 'differentId', }; - await storage.add(info); - await storage.add(info2); + await storage.add(channel); + await storage.add(channel2); expect(internalMap.size).toBe(3); - await expect(storage.delete(info.id)).resolves.toBeUndefined(); + await expect(storage.delete(channel.id)).resolves.toBeUndefined(); expect(internalMap.size).toBe(2); expect([ ...internalMap.values() ]).toEqual(expect.arrayContaining([ - [ info2.id ], - info2, + [ channel2.id ], + channel2, ])); }); it('removes the references for an identifier if the array is empty.', async(): Promise => { - await storage.add(info); - await expect(storage.delete(info.id)).resolves.toBeUndefined(); + await storage.add(channel); + await expect(storage.delete(channel.id)).resolves.toBeUndefined(); expect(internalMap.size).toBe(0); }); it('does nothing if the target does not exist.', async(): Promise => { - await expect(storage.delete(info.id)).resolves.toBeUndefined(); + await expect(storage.delete(channel.id)).resolves.toBeUndefined(); }); it('logs an error if the target can not be found in the list of references.', async(): Promise => { - await storage.add(info); + await storage.add(channel); // Looking for the key so this test doesn't depend on the internal keys used const id = [ ...internalMap.entries() ].find((entry): boolean => Array.isArray(entry[1]))![0]; internalMap.set(id, []); - await expect(storage.delete(info.id)).resolves.toBeUndefined(); + await expect(storage.delete(channel.id)).resolves.toBeUndefined(); expect(logger.error).toHaveBeenCalledTimes(2); }); }); diff --git a/test/unit/server/notifications/ListeningActivityHandler.test.ts b/test/unit/server/notifications/ListeningActivityHandler.test.ts index 3c43d82e6..0b71368d4 100644 --- a/test/unit/server/notifications/ListeningActivityHandler.test.ts +++ b/test/unit/server/notifications/ListeningActivityHandler.test.ts @@ -4,8 +4,8 @@ import type { Logger } from '../../../../src/logging/Logger'; import { getLoggerFor } from '../../../../src/logging/LogUtil'; import type { ActivityEmitter } from '../../../../src/server/notifications/ActivityEmitter'; import { ListeningActivityHandler } from '../../../../src/server/notifications/ListeningActivityHandler'; +import type { NotificationChannel } from '../../../../src/server/notifications/NotificationChannel'; import type { - NotificationChannelInfo, NotificationChannelStorage, } from '../../../../src/server/notifications/NotificationChannelStorage'; import type { NotificationHandler } from '../../../../src/server/notifications/NotificationHandler'; @@ -21,7 +21,7 @@ describe('A ListeningActivityHandler', (): void => { const logger: jest.Mocked = getLoggerFor('mock') as any; const topic: ResourceIdentifier = { path: 'http://example.com/foo' }; const activity = AS.terms.Update; - let info: NotificationChannelInfo; + let channel: NotificationChannel; let storage: jest.Mocked; let emitter: ActivityEmitter; let notificationHandler: jest.Mocked; @@ -29,7 +29,7 @@ describe('A ListeningActivityHandler', (): void => { beforeEach(async(): Promise => { jest.clearAllMocks(); - info = { + channel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -38,8 +38,8 @@ describe('A ListeningActivityHandler', (): void => { }; storage = { - getAll: jest.fn().mockResolvedValue([ info.id ]), - get: jest.fn().mockResolvedValue(info), + getAll: jest.fn().mockResolvedValue([ channel.id ]), + get: jest.fn().mockResolvedValue(channel), } as any; emitter = new EventEmitter() as any; @@ -58,13 +58,13 @@ describe('A ListeningActivityHandler', (): void => { await flushPromises(); expect(notificationHandler.handleSafe).toHaveBeenCalledTimes(1); - expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ info, activity, topic }); + expect(notificationHandler.handleSafe).toHaveBeenLastCalledWith({ channel, activity, topic }); expect(logger.error).toHaveBeenCalledTimes(0); }); it('does not emit an event on channels if their rate does not yet allow it.', async(): Promise => { - info.rate = 100000; - info.lastEmit = Date.now(); + channel.rate = 100000; + channel.lastEmit = Date.now(); emitter.emit('changed', topic, activity); @@ -75,7 +75,7 @@ describe('A ListeningActivityHandler', (): void => { }); it('does not emit an event on channels if their start time has not been reached.', async(): Promise => { - info.startAt = Date.now() + 100000; + channel.startAt = Date.now() + 100000; emitter.emit('changed', topic, activity); @@ -86,7 +86,7 @@ describe('A ListeningActivityHandler', (): void => { }); it('does not stop if one channel causes an error.', async(): Promise => { - storage.getAll.mockResolvedValue([ info.id, info.id ]); + storage.getAll.mockResolvedValue([ channel.id, channel.id ]); notificationHandler.handleSafe.mockRejectedValueOnce(new Error('bad input')); emitter.emit('changed', topic, activity); @@ -95,7 +95,7 @@ describe('A ListeningActivityHandler', (): void => { expect(notificationHandler.handleSafe).toHaveBeenCalledTimes(2); expect(logger.error).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenLastCalledWith(`Error trying to handle notification for ${info.id}: bad input`); + expect(logger.error).toHaveBeenLastCalledWith(`Error trying to handle notification for ${channel.id}: bad input`); }); it('logs an error if something goes wrong handling the event.', async(): Promise => { diff --git a/test/unit/server/notifications/NotificationSubscriber.test.ts b/test/unit/server/notifications/NotificationSubscriber.test.ts index ecae57b3d..2df45f250 100644 --- a/test/unit/server/notifications/NotificationSubscriber.test.ts +++ b/test/unit/server/notifications/NotificationSubscriber.test.ts @@ -47,7 +47,7 @@ describe('A NotificationSubscriber', (): void => { schema: NOTIFICATION_CHANNEL_SCHEMA, extractModes: jest.fn(async(subscription): Promise => new IdentifierSetMultiMap([[{ path: subscription.topic }, AccessMode.read ]]) as AccessMap), - subscribe: jest.fn().mockResolvedValue({ response: new BasicRepresentation(), info: {}}), + subscribe: jest.fn().mockResolvedValue({ response: new BasicRepresentation(), channel: {}}), }; credentialsExtractor = { diff --git a/test/unit/server/notifications/TypedNotificationHandler.test.ts b/test/unit/server/notifications/TypedNotificationHandler.test.ts index aa345c7ee..2c31b6d82 100644 --- a/test/unit/server/notifications/TypedNotificationHandler.test.ts +++ b/test/unit/server/notifications/TypedNotificationHandler.test.ts @@ -1,12 +1,12 @@ import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier'; -import type { NotificationChannelInfo } from '../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../src/server/notifications/NotificationChannel'; import type { NotificationHandler } from '../../../../src/server/notifications/NotificationHandler'; import { TypedNotificationHandler } from '../../../../src/server/notifications/TypedNotificationHandler'; import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError'; describe('A TypedNotificationHandler', (): void => { const topic: ResourceIdentifier = { path: 'http://example.com/foo' }; - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: topic.path, type: 'NotificationChannelType', @@ -23,27 +23,27 @@ describe('A TypedNotificationHandler', (): void => { handleSafe: jest.fn(), }; - handler = new TypedNotificationHandler(info.type, source); + handler = new TypedNotificationHandler(channel.type, source); }); - it('requires the input info to have the correct type.', async(): Promise => { - await expect(handler.canHandle({ info, topic })).resolves.toBeUndefined(); + it('requires the input channel to have the correct type.', async(): Promise => { + await expect(handler.canHandle({ channel, topic })).resolves.toBeUndefined(); - const wrongInfo = { - ...info, + const wrongChannel = { + ...channel, type: 'somethingElse', }; - await expect(handler.canHandle({ info: wrongInfo, topic })).rejects.toThrow(NotImplementedHttpError); + await expect(handler.canHandle({ channel: wrongChannel, topic })).rejects.toThrow(NotImplementedHttpError); }); it('rejects input the source handler can not handle.', async(): Promise => { source.canHandle.mockRejectedValue(new Error('bad input')); - await expect(handler.canHandle({ info, topic })).rejects.toThrow('bad input'); + await expect(handler.canHandle({ channel, topic })).rejects.toThrow('bad input'); }); it('calls the source handle function.', async(): Promise => { - await expect(handler.handle({ info, topic })).resolves.toBeUndefined(); + await expect(handler.handle({ channel, topic })).resolves.toBeUndefined(); expect(source.handle).toHaveBeenCalledTimes(1); - expect(source.handle).toHaveBeenLastCalledWith({ info, topic }); + expect(source.handle).toHaveBeenLastCalledWith({ channel, topic }); }); }); diff --git a/test/unit/server/notifications/WebHookSubscription2021/WebHookEmitter.test.ts b/test/unit/server/notifications/WebHookSubscription2021/WebHookEmitter.test.ts index 07cb23225..29806da91 100644 --- a/test/unit/server/notifications/WebHookSubscription2021/WebHookEmitter.test.ts +++ b/test/unit/server/notifications/WebHookSubscription2021/WebHookEmitter.test.ts @@ -9,7 +9,7 @@ import { import type { Logger } from '../../../../../src/logging/Logger'; import { getLoggerFor } from '../../../../../src/logging/LogUtil'; import type { Notification } from '../../../../../src/server/notifications/Notification'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import { WebHookEmitter } from '../../../../../src/server/notifications/WebHookSubscription2021/WebHookEmitter'; import type { WebHookFeatures, @@ -40,7 +40,7 @@ describe('A WebHookEmitter', (): void => { published: '123', }; let representation: Representation; - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -79,7 +79,7 @@ describe('A WebHookEmitter', (): void => { const now = Date.now(); jest.useFakeTimers(); jest.setSystemTime(now); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(fetchMock).toHaveBeenCalledTimes(1); const call = fetchMock.mock.calls[0]; @@ -95,13 +95,13 @@ describe('A WebHookEmitter', (): void => { // Check all the DPoP token fields const decodedDpopToken = await jwtVerify(encodedDpopToken, publicObject, { issuer: trimTrailingSlashes(baseUrl) }); expect(decodedDpopToken.payload).toMatchObject({ - webid: info.features.webId, - azp: info.features.webId, - sub: info.features.webId, + webid: channel.features.webId, + azp: channel.features.webId, + sub: channel.features.webId, cnf: { jkt: await calculateJwkThumbprint(publicJwk, 'sha256') }, iat: now, exp: now + (20 * 60 * 1000), - aud: [ info.features.webId, 'solid' ], + aud: [ channel.features.webId, 'solid' ], jti: expect.stringContaining('-'), }); expect(decodedDpopToken.protectedHeader).toMatchObject({ @@ -111,7 +111,7 @@ describe('A WebHookEmitter', (): void => { // CHeck the DPoP proof const decodedDpopProof = await jwtVerify(dpop, publicObject); expect(decodedDpopProof.payload).toMatchObject({ - htu: info.features.target, + htu: channel.features.target, htm: 'POST', iat: now, jti: expect.stringContaining('-'), @@ -129,11 +129,11 @@ describe('A WebHookEmitter', (): void => { const logger = getLoggerFor('mock'); fetchMock.mockResolvedValue({ status: 400, text: async(): Promise => 'invalid request' }); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenLastCalledWith( - `There was an issue emitting a WebHook notification with target ${info.features.target}: invalid request`, + `There was an issue emitting a WebHook notification with target ${channel.features.target}: invalid request`, ); }); }); diff --git a/test/unit/server/notifications/WebHookSubscription2021/WebHookSubscription2021.test.ts b/test/unit/server/notifications/WebHookSubscription2021/WebHookSubscription2021.test.ts index 10012d97a..0de6a75c7 100644 --- a/test/unit/server/notifications/WebHookSubscription2021/WebHookSubscription2021.test.ts +++ b/test/unit/server/notifications/WebHookSubscription2021/WebHookSubscription2021.test.ts @@ -6,8 +6,8 @@ import { } from '../../../../../src/identity/interaction/routing/AbsolutePathInteractionRoute'; import type { Logger } from '../../../../../src/logging/Logger'; import { getLoggerFor } from '../../../../../src/logging/LogUtil'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import type { - NotificationChannelInfo, NotificationChannelStorage, } from '../../../../../src/server/notifications/NotificationChannelStorage'; import type { StateHandler } from '../../../../../src/server/notifications/StateHandler'; @@ -31,14 +31,14 @@ jest.mock('../../../../../src/logging/LogUtil', (): any => { describe('A WebHookSubscription2021', (): void => { const credentials: Credentials = { agent: { webId: 'http://example.org/alice' }}; const target = 'http://example.org/somewhere-else'; - let channel: InferType; + let json: InferType; const unsubscribeRoute = new AbsolutePathInteractionRoute('http://example.com/unsubscribe'); let storage: jest.Mocked>; let stateHandler: jest.Mocked; let channelType: WebHookSubscription2021; beforeEach(async(): Promise => { - channel = { + json = { '@context': [ 'https://www.w3.org/ns/solid/notification/v1' ], type: 'WebHookSubscription2021', topic: 'https://storage.example/resource', @@ -51,7 +51,7 @@ describe('A WebHookSubscription2021', (): void => { }; storage = { - create: jest.fn((features: WebHookFeatures): NotificationChannelInfo => ({ + create: jest.fn((features: WebHookFeatures): NotificationChannel => ({ id: '123', topic: 'http://example.com/foo', type: 'WebHookSubscription2021', @@ -73,19 +73,19 @@ describe('A WebHookSubscription2021', (): void => { }); it('correctly parses notification channel bodies.', async(): Promise => { - await expect(channelType.schema.isValid(channel)).resolves.toBe(true); + await expect(channelType.schema.isValid(json)).resolves.toBe(true); - channel.type = 'something else'; - await expect(channelType.schema.isValid(channel)).resolves.toBe(false); + json.type = 'something else'; + await expect(channelType.schema.isValid(json)).resolves.toBe(false); }); it('requires Read permissions on the topic.', async(): Promise => { - await expect(channelType.extractModes(channel)).resolves - .toEqual(new IdentifierSetMultiMap([[{ path: channel.topic }, AccessMode.read ]])); + await expect(channelType.extractModes(json)).resolves + .toEqual(new IdentifierSetMultiMap([[{ path: json.topic }, AccessMode.read ]])); }); - it('stores the info and returns a valid response when subscribing.', async(): Promise => { - const { response } = await channelType.subscribe(channel, credentials); + it('stores the channel and returns a valid response when subscribing.', async(): Promise => { + const { response } = await channelType.subscribe(json, credentials); expect(response.metadata.contentType).toBe('application/ld+json'); await expect(readJsonStream(response.data)).resolves.toEqual({ '@context': [ 'https://www.w3.org/ns/solid/notification/v1' ], @@ -97,26 +97,26 @@ describe('A WebHookSubscription2021', (): void => { }); it('errors if the credentials do not contain a WebID.', async(): Promise => { - await expect(channelType.subscribe(channel, {})).rejects + await expect(channelType.subscribe(json, {})).rejects .toThrow('A WebHookSubscription2021 subscription request needs to be authenticated with a WebID.'); }); it('calls the state handler once the response has been read.', async(): Promise => { - const { response, info } = await channelType.subscribe(channel, credentials); + const { response, channel } = await channelType.subscribe(json, credentials); expect(stateHandler.handleSafe).toHaveBeenCalledTimes(0); // Read out data to end stream correctly await readableToString(response.data); expect(stateHandler.handleSafe).toHaveBeenCalledTimes(1); - expect(stateHandler.handleSafe).toHaveBeenLastCalledWith({ info }); + expect(stateHandler.handleSafe).toHaveBeenLastCalledWith({ channel }); }); it('logs an error if something went wrong emitting the state notification.', async(): Promise => { const logger = getLoggerFor('mock'); stateHandler.handleSafe.mockRejectedValue(new Error('notification error')); - const { response } = await channelType.subscribe(channel, credentials); + const { response } = await channelType.subscribe(json, credentials); expect(logger.error).toHaveBeenCalledTimes(0); // Read out data to end stream correctly diff --git a/test/unit/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.test.ts b/test/unit/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.test.ts index 5ef6cc344..220b4dc2e 100644 --- a/test/unit/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.test.ts +++ b/test/unit/server/notifications/WebHookSubscription2021/WebHookUnsubscriber.test.ts @@ -43,7 +43,7 @@ describe('A WebHookUnsubscriber', (): void => { unsubscriber = new WebHookUnsubscriber(credentialsExtractor, storage); }); - it('rejects if the id does not match any stored info.', async(): Promise => { + it('rejects if the id does not match any stored channel.', async(): Promise => { storage.get.mockResolvedValue(undefined); await expect(unsubscriber.handle({ operation, request, response })).rejects.toThrow(NotFoundHttpError); expect(storage.delete).toHaveBeenCalledTimes(0); @@ -55,7 +55,7 @@ describe('A WebHookUnsubscriber', (): void => { expect(storage.delete).toHaveBeenCalledTimes(0); }); - it('deletes the corresponding info.', async(): Promise => { + it('deletes the corresponding channel.', async(): Promise => { await expect(unsubscriber.handle({ operation, request, response })) .resolves.toEqual(new ResetResponseDescription()); expect(storage.delete).toHaveBeenCalledTimes(1); diff --git a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.test.ts b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.test.ts index aea7ec47d..2828e3af2 100644 --- a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.test.ts +++ b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter.test.ts @@ -1,9 +1,7 @@ import { EventEmitter } from 'events'; import type { WebSocket } from 'ws'; import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation'; -import type { - NotificationChannelInfo, -} from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import { WebSocket2021Emitter, } from '../../../../../src/server/notifications/WebSocketSubscription2021/WebSocket2021Emitter'; @@ -11,7 +9,7 @@ import type { SetMultiMap } from '../../../../../src/util/map/SetMultiMap'; import { WrappedSetMultiMap } from '../../../../../src/util/map/WrappedSetMultiMap'; describe('A WebSocket2021Emitter', (): void => { - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -34,17 +32,17 @@ describe('A WebSocket2021Emitter', (): void => { }); it('emits notifications to the stored WebSockets.', async(): Promise => { - socketMap.add(info.id, webSocket); + socketMap.add(channel.id, webSocket); const representation = new BasicRepresentation('notification', 'text/plain'); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(webSocket.send).toHaveBeenCalledTimes(1); expect(webSocket.send).toHaveBeenLastCalledWith('notification'); }); it('destroys the representation if there is no matching WebSocket.', async(): Promise => { const representation = new BasicRepresentation('notification', 'text/plain'); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(webSocket.send).toHaveBeenCalledTimes(0); expect(representation.data.destroyed).toBe(true); }); @@ -53,11 +51,11 @@ describe('A WebSocket2021Emitter', (): void => { const webSocket2: jest.Mocked = new EventEmitter() as any; webSocket2.send = jest.fn(); - socketMap.add(info.id, webSocket); - socketMap.add(info.id, webSocket2); + socketMap.add(channel.id, webSocket); + socketMap.add(channel.id, webSocket2); const representation = new BasicRepresentation('notification', 'text/plain'); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(webSocket.send).toHaveBeenCalledTimes(1); expect(webSocket.send).toHaveBeenLastCalledWith('notification'); expect(webSocket2.send).toHaveBeenCalledTimes(1); @@ -67,16 +65,16 @@ describe('A WebSocket2021Emitter', (): void => { it('only sends to the matching WebSockets.', async(): Promise => { const webSocket2: jest.Mocked = new EventEmitter() as any; webSocket2.send = jest.fn(); - const info2: NotificationChannelInfo = { - ...info, + const channel2: NotificationChannel = { + ...channel, id: 'other', }; - socketMap.add(info.id, webSocket); - socketMap.add(info2.id, webSocket2); + socketMap.add(channel.id, webSocket); + socketMap.add(channel2.id, webSocket2); const representation = new BasicRepresentation('notification', 'text/plain'); - await expect(emitter.handle({ info, representation })).resolves.toBeUndefined(); + await expect(emitter.handle({ channel, representation })).resolves.toBeUndefined(); expect(webSocket.send).toHaveBeenCalledTimes(1); expect(webSocket.send).toHaveBeenLastCalledWith('notification'); expect(webSocket2.send).toHaveBeenCalledTimes(0); diff --git a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.test.ts b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.test.ts index b00f574dd..49d0f5770 100644 --- a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.test.ts +++ b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Listener.test.ts @@ -5,8 +5,8 @@ import { AbsolutePathInteractionRoute, } from '../../../../../src/identity/interaction/routing/AbsolutePathInteractionRoute'; import type { HttpRequest } from '../../../../../src/server/HttpRequest'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import type { - NotificationChannelInfo, NotificationChannelStorage, } from '../../../../../src/server/notifications/NotificationChannelStorage'; import type { @@ -27,7 +27,7 @@ jest.mock('ws', (): any => ({ })); describe('A WebSocket2021Listener', (): void => { - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -52,7 +52,7 @@ describe('A WebSocket2021Listener', (): void => { upgradeRequest = { url: `/foo?auth=${auth}` } as any; storage = { - get: jest.fn().mockResolvedValue(info), + get: jest.fn().mockResolvedValue(channel), } as any; handler = { @@ -119,6 +119,6 @@ describe('A WebSocket2021Listener', (): void => { expect(webSocket.send).toHaveBeenCalledTimes(0); expect(webSocket.close).toHaveBeenCalledTimes(0); expect(handler.handleSafe).toHaveBeenCalledTimes(1); - expect(handler.handleSafe).toHaveBeenLastCalledWith({ webSocket, info }); + expect(handler.handleSafe).toHaveBeenLastCalledWith({ webSocket, channel }); }); }); diff --git a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.test.ts b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.test.ts index 859eb7dd9..18a4b3002 100644 --- a/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.test.ts +++ b/test/unit/server/notifications/WebSocketSubscription2021/WebSocket2021Storer.test.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; import type { WebSocket } from 'ws'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import type { - NotificationChannelInfo, NotificationChannelStorage, } from '../../../../../src/server/notifications/NotificationChannelStorage'; @@ -13,7 +13,7 @@ import { WrappedSetMultiMap } from '../../../../../src/util/map/WrappedSetMultiM import { flushPromises } from '../../../../util/Util'; describe('A WebSocket2021Storer', (): void => { - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -39,23 +39,23 @@ describe('A WebSocket2021Storer', (): void => { }); it('stores WebSockets.', async(): Promise => { - await expect(storer.handle({ info, webSocket })).resolves.toBeUndefined(); + await expect(storer.handle({ channel, webSocket })).resolves.toBeUndefined(); expect([ ...socketMap.keys() ]).toHaveLength(1); - expect(socketMap.has(info.id)).toBe(true); + expect(socketMap.has(channel.id)).toBe(true); }); it('removes closed WebSockets.', async(): Promise => { - await expect(storer.handle({ info, webSocket })).resolves.toBeUndefined(); - expect(socketMap.has(info.id)).toBe(true); + await expect(storer.handle({ channel, webSocket })).resolves.toBeUndefined(); + expect(socketMap.has(channel.id)).toBe(true); webSocket.emit('close'); - expect(socketMap.has(info.id)).toBe(false); + expect(socketMap.has(channel.id)).toBe(false); }); it('removes erroring WebSockets.', async(): Promise => { - await expect(storer.handle({ info, webSocket })).resolves.toBeUndefined(); - expect(socketMap.has(info.id)).toBe(true); + await expect(storer.handle({ channel, webSocket })).resolves.toBeUndefined(); + expect(socketMap.has(channel.id)).toBe(true); webSocket.emit('error'); - expect(socketMap.has(info.id)).toBe(false); + expect(socketMap.has(channel.id)).toBe(false); }); it('removes expired WebSockets.', async(): Promise => { @@ -68,18 +68,18 @@ describe('A WebSocket2021Storer', (): void => { webSocket2.close = jest.fn(); const webSocketOther: jest.Mocked = new EventEmitter() as any; webSocketOther.close = jest.fn(); - const infoOther: NotificationChannelInfo = { - ...info, + const channelOther: NotificationChannel = { + ...channel, id: 'other', }; - await expect(storer.handle({ info, webSocket })).resolves.toBeUndefined(); - await expect(storer.handle({ info, webSocket: webSocket2 })).resolves.toBeUndefined(); - await expect(storer.handle({ info: infoOther, webSocket: webSocketOther })).resolves.toBeUndefined(); + await expect(storer.handle({ channel, webSocket })).resolves.toBeUndefined(); + await expect(storer.handle({ channel, webSocket: webSocket2 })).resolves.toBeUndefined(); + await expect(storer.handle({ channel: channelOther, webSocket: webSocketOther })).resolves.toBeUndefined(); - // `info` expired, `infoOther` did not + // `channel` expired, `channelOther` did not storage.get.mockImplementation((id): any => { - if (id === infoOther.id) { - return infoOther; + if (id === channelOther.id) { + return channelOther; } }); diff --git a/test/unit/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.test.ts b/test/unit/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.test.ts index ff86a6b33..ab8349bfc 100644 --- a/test/unit/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.test.ts +++ b/test/unit/server/notifications/WebSocketSubscription2021/WebSocketSubscription2021.test.ts @@ -2,7 +2,7 @@ import { AccessMode } from '../../../../../src/authorization/permissions/Permiss import { AbsolutePathInteractionRoute, } from '../../../../../src/identity/interaction/routing/AbsolutePathInteractionRoute'; -import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; +import type { NotificationChannelJson } from '../../../../../src/server/notifications/NotificationChannel'; import type { NotificationChannelStorage } from '../../../../../src/server/notifications/NotificationChannelStorage'; import { WebSocketSubscription2021, @@ -11,7 +11,7 @@ import { IdentifierSetMultiMap } from '../../../../../src/util/map/IdentifierMap import { readJsonStream } from '../../../../../src/util/StreamUtil'; describe('A WebSocketSubscription2021', (): void => { - let channel: NotificationChannel; + let channel: NotificationChannelJson; let storage: jest.Mocked; const route = new AbsolutePathInteractionRoute('http://example.com/foo'); let channelType: WebSocketSubscription2021; @@ -58,7 +58,7 @@ describe('A WebSocketSubscription2021', (): void => { .toEqual(new IdentifierSetMultiMap([[{ path: channel.topic }, AccessMode.read ]])); }); - it('stores the info and returns a valid response when subscribing.', async(): Promise => { + it('stores the channel and returns a valid response when subscribing.', async(): Promise => { const { response } = await channelType.subscribe(channel); expect(response.metadata.contentType).toBe('application/ld+json'); await expect(readJsonStream(response.data)).resolves.toEqual({ diff --git a/test/unit/server/notifications/generate/ActivityNotificationGenerator.test.ts b/test/unit/server/notifications/generate/ActivityNotificationGenerator.test.ts index 5b8d844b0..c3f0ed59f 100644 --- a/test/unit/server/notifications/generate/ActivityNotificationGenerator.test.ts +++ b/test/unit/server/notifications/generate/ActivityNotificationGenerator.test.ts @@ -4,13 +4,13 @@ import type { ResourceIdentifier } from '../../../../../src/http/representation/ import { ActivityNotificationGenerator, } from '../../../../../src/server/notifications/generate/ActivityNotificationGenerator'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import type { ResourceStore } from '../../../../../src/storage/ResourceStore'; import { AS, DC, LDP, RDF } from '../../../../../src/util/Vocabularies'; describe('An ActivityNotificationGenerator', (): void => { const topic: ResourceIdentifier = { path: 'http://example.com/foo' }; - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: topic.path, type: 'type', @@ -35,8 +35,8 @@ describe('An ActivityNotificationGenerator', (): void => { }); it('only handles defined activities.', async(): Promise => { - await expect(generator.canHandle({ topic, info })).rejects.toThrow('Only defined activities are supported.'); - await expect(generator.canHandle({ topic, info, activity })).resolves.toBeUndefined(); + await expect(generator.canHandle({ topic, channel })).rejects.toThrow('Only defined activities are supported.'); + await expect(generator.canHandle({ topic, channel, activity })).resolves.toBeUndefined(); }); it('generates a notification.', async(): Promise => { @@ -45,7 +45,7 @@ describe('An ActivityNotificationGenerator', (): void => { jest.useFakeTimers(); jest.setSystemTime(ms); - await expect(generator.handle({ topic, info, activity })).resolves.toEqual({ + await expect(generator.handle({ topic, channel, activity })).resolves.toEqual({ '@context': [ 'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/solid/notification/v1', diff --git a/test/unit/server/notifications/generate/DeleteNotificationGenerator.test.ts b/test/unit/server/notifications/generate/DeleteNotificationGenerator.test.ts index 1ac8db3cf..449d2fc5c 100644 --- a/test/unit/server/notifications/generate/DeleteNotificationGenerator.test.ts +++ b/test/unit/server/notifications/generate/DeleteNotificationGenerator.test.ts @@ -2,12 +2,12 @@ import type { ResourceIdentifier } from '../../../../../src/http/representation/ import { DeleteNotificationGenerator, } from '../../../../../src/server/notifications/generate/DeleteNotificationGenerator'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import { AS } from '../../../../../src/util/Vocabularies'; describe('A DeleteNotificationGenerator', (): void => { const topic: ResourceIdentifier = { path: 'http://example.com/foo' }; - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: topic.path, type: 'type', @@ -18,10 +18,11 @@ describe('A DeleteNotificationGenerator', (): void => { const generator = new DeleteNotificationGenerator(); it('can only handle input with the Delete activity.', async(): Promise => { - await expect(generator.canHandle({ topic, info })).rejects.toThrow('Only Delete activity updates are supported.'); - await expect(generator.canHandle({ topic, info, activity: AS.terms.Update })) + await expect(generator.canHandle({ topic, channel })).rejects + .toThrow('Only Delete activity updates are supported.'); + await expect(generator.canHandle({ topic, channel, activity: AS.terms.Update })) .rejects.toThrow('Only Delete activity updates are supported.'); - await expect(generator.canHandle({ topic, info, activity })).resolves.toBeUndefined(); + await expect(generator.canHandle({ topic, channel, activity })).resolves.toBeUndefined(); }); it('generates a Delete notification.', async(): Promise => { @@ -30,7 +31,7 @@ describe('A DeleteNotificationGenerator', (): void => { jest.useFakeTimers(); jest.setSystemTime(ms); - await expect(generator.handle({ topic, info, activity })).resolves.toEqual({ + await expect(generator.handle({ topic, channel, activity })).resolves.toEqual({ '@context': [ 'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/solid/notification/v1', diff --git a/test/unit/server/notifications/generate/StateNotificationGenerator.test.ts b/test/unit/server/notifications/generate/StateNotificationGenerator.test.ts index 48f2d80fc..2f268cac6 100644 --- a/test/unit/server/notifications/generate/StateNotificationGenerator.test.ts +++ b/test/unit/server/notifications/generate/StateNotificationGenerator.test.ts @@ -4,13 +4,13 @@ import { StateNotificationGenerator, } from '../../../../../src/server/notifications/generate/StateNotificationGenerator'; import type { Notification } from '../../../../../src/server/notifications/Notification'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import type { ResourceSet } from '../../../../../src/storage/ResourceSet'; import { AS } from '../../../../../src/util/Vocabularies'; describe('A StateNotificationGenerator', (): void => { const topic: ResourceIdentifier = { path: 'http://example.com/foo' }; - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: topic.path, type: 'type', @@ -44,25 +44,25 @@ describe('A StateNotificationGenerator', (): void => { }); it('returns the source notification if there is an activity.', async(): Promise => { - await expect(generator.handle({ topic, info, activity: AS.terms.Update })).resolves.toBe(notification); + await expect(generator.handle({ topic, channel, activity: AS.terms.Update })).resolves.toBe(notification); expect(source.handleSafe).toHaveBeenCalledTimes(1); - expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, info, activity: AS.terms.Update }); + expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, channel, activity: AS.terms.Update }); expect(resourceSet.hasResource).toHaveBeenCalledTimes(0); }); it('calls the source with an Update notification if the topic exists.', async(): Promise => { resourceSet.hasResource.mockResolvedValue(true); - await expect(generator.handle({ topic, info })).resolves.toBe(notification); + await expect(generator.handle({ topic, channel })).resolves.toBe(notification); expect(source.handleSafe).toHaveBeenCalledTimes(1); - expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, info, activity: AS.terms.Update }); + expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, channel, activity: AS.terms.Update }); expect(resourceSet.hasResource).toHaveBeenCalledTimes(1); }); it('calls the source with a Delete notification if the topic does not exist.', async(): Promise => { resourceSet.hasResource.mockResolvedValue(false); - await expect(generator.handle({ topic, info })).resolves.toBe(notification); + await expect(generator.handle({ topic, channel })).resolves.toBe(notification); expect(source.handleSafe).toHaveBeenCalledTimes(1); - expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, info, activity: AS.terms.Delete }); + expect(source.handleSafe).toHaveBeenLastCalledWith({ topic, channel, activity: AS.terms.Delete }); expect(resourceSet.hasResource).toHaveBeenCalledTimes(1); }); }); diff --git a/test/unit/server/notifications/serialize/ConvertingNotificationSerializer.test.ts b/test/unit/server/notifications/serialize/ConvertingNotificationSerializer.test.ts index 7d56ed7a7..2c61b4a05 100644 --- a/test/unit/server/notifications/serialize/ConvertingNotificationSerializer.test.ts +++ b/test/unit/server/notifications/serialize/ConvertingNotificationSerializer.test.ts @@ -1,7 +1,7 @@ import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation'; import type { Representation } from '../../../../../src/http/representation/Representation'; import type { Notification } from '../../../../../src/server/notifications/Notification'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import { ConvertingNotificationSerializer, } from '../../../../../src/server/notifications/serialize/ConvertingNotificationSerializer'; @@ -9,7 +9,7 @@ import type { NotificationSerializer } from '../../../../../src/server/notificat import type { RepresentationConverter } from '../../../../../src/storage/conversion/RepresentationConverter'; describe('A ConvertingNotificationSerializer', (): void => { - let info: NotificationChannelInfo; + let channel: NotificationChannel; const notification: Notification = { '@context': [ 'https://www.w3.org/ns/activitystreams', @@ -26,7 +26,7 @@ describe('A ConvertingNotificationSerializer', (): void => { let serializer: ConvertingNotificationSerializer; beforeEach(async(): Promise => { - info = { + channel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -49,19 +49,19 @@ describe('A ConvertingNotificationSerializer', (): void => { }); it('can handle input its source can handle.', async(): Promise => { - await expect(serializer.canHandle({ info, notification })).resolves.toBeUndefined(); + await expect(serializer.canHandle({ channel, notification })).resolves.toBeUndefined(); source.canHandle.mockRejectedValue(new Error('bad input')); - await expect(serializer.canHandle({ info, notification })).rejects.toThrow('bad input'); + await expect(serializer.canHandle({ channel, notification })).rejects.toThrow('bad input'); }); it('returns the source result if there is no accept value.', async(): Promise => { - await expect(serializer.handle({ info, notification })).resolves.toBe(representation); + await expect(serializer.handle({ channel, notification })).resolves.toBe(representation); expect(converter.handleSafe).toHaveBeenCalledTimes(0); }); it('converts the source result if there is an accept value.', async(): Promise => { - info.accept = 'text/turtle'; - await expect(serializer.handle({ info, notification })).resolves.toBe(representation); + channel.accept = 'text/turtle'; + await expect(serializer.handle({ channel, notification })).resolves.toBe(representation); expect(converter.handleSafe).toHaveBeenCalledTimes(1); expect(converter.handleSafe).toHaveBeenLastCalledWith({ representation, diff --git a/test/unit/server/notifications/serialize/JsonLdNotificationSerializer.test.ts b/test/unit/server/notifications/serialize/JsonLdNotificationSerializer.test.ts index 5784ce522..91253e486 100644 --- a/test/unit/server/notifications/serialize/JsonLdNotificationSerializer.test.ts +++ b/test/unit/server/notifications/serialize/JsonLdNotificationSerializer.test.ts @@ -1,12 +1,12 @@ import type { Notification } from '../../../../../src/server/notifications/Notification'; -import type { NotificationChannelInfo } from '../../../../../src/server/notifications/NotificationChannelStorage'; +import type { NotificationChannel } from '../../../../../src/server/notifications/NotificationChannel'; import { JsonLdNotificationSerializer, } from '../../../../../src/server/notifications/serialize/JsonLdNotificationSerializer'; import { readableToString } from '../../../../../src/util/StreamUtil'; describe('A JsonLdNotificationSerializer', (): void => { - const info: NotificationChannelInfo = { + const channel: NotificationChannel = { id: 'id', topic: 'http://example.com/foo', type: 'type', @@ -27,7 +27,7 @@ describe('A JsonLdNotificationSerializer', (): void => { const serializer = new JsonLdNotificationSerializer(); it('converts notifications into JSON-LD.', async(): Promise => { - const representation = await serializer.handle({ notification, info }); + const representation = await serializer.handle({ notification, channel }); expect(representation.metadata.contentType).toBe('application/ld+json'); expect(JSON.parse(await readableToString(representation.data))).toEqual(notification); }); diff --git a/test/util/NotificationUtil.ts b/test/util/NotificationUtil.ts index d8c761094..5bbce325c 100644 --- a/test/util/NotificationUtil.ts +++ b/test/util/NotificationUtil.ts @@ -4,7 +4,7 @@ import { fetch } from 'cross-fetch'; * Subscribes to a notification channel. * @param type - The type of the notification channel. E.g. "WebSocketSubscription2021". * @param webId - The WebID to spoof in the authorization header. This assumes the config uses the debug auth import. - * @param subscriptionUrl - The subscription resource URL where the request needs to be sent to. + * @param subscriptionUrl - The subscription URL where the request needs to be sent to. * @param topic - The topic to subscribe to. * @param features - Any extra fields that need to be added to the subscription body. */