mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Rename NotificationChannelInfo to NotificationChannel
This commit is contained in:
@@ -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<NotificationHandler>;
|
||||
let storage: jest.Mocked<NotificationChannelStorage>;
|
||||
let handler: BaseStateHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<NotificationGenerator>;
|
||||
let serializer: jest.Mocked<NotificationSerializer>;
|
||||
@@ -28,7 +28,7 @@ describe('A ComposedNotificationHandler', (): void => {
|
||||
let handler: ComposedNotificationHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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<Record<string, string>>;
|
||||
let channel: NotificationChannel<Record<string, string>>;
|
||||
let internalMap: Map<string, any>;
|
||||
let internalStorage: KeyValueStorage<string, any>;
|
||||
let locker: ReadWriteLocker;
|
||||
@@ -33,7 +35,7 @@ describe('A KeyValueChannelStorage', (): void => {
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
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<void> => {
|
||||
expect(storage.create(channel, features)).toEqual(info);
|
||||
it('creates channel based on a notification channel.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
await storage.add(info);
|
||||
await expect(storage.get(info.id)).resolves.toEqual(info);
|
||||
it('returns the matching channel.', async(): Promise<void> => {
|
||||
await storage.add(channel);
|
||||
await expect(storage.get(channel.id)).resolves.toEqual(channel);
|
||||
});
|
||||
|
||||
it('deletes expired info.', async(): Promise<void> => {
|
||||
info.endAt = 0;
|
||||
await storage.add(info);
|
||||
await expect(storage.get(info.id)).resolves.toBeUndefined();
|
||||
it('deletes expired channel.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
await storage.add(info);
|
||||
await expect(storage.getAll(identifier)).resolves.toEqual([ info.id ]);
|
||||
it('returns the identifiers of all the matching channels.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
await expect(storage.add(info)).resolves.toBeUndefined();
|
||||
it('adds the channel and adds its id to the topic collection.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
await storage.add(info);
|
||||
const newInfo = {
|
||||
...info,
|
||||
it('changes the channel.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
await storage.add(info);
|
||||
it('rejects update request targeting a non-channel value.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
const info2 = {
|
||||
...info,
|
||||
it('removes the channel and its reference.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<Logger> = 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<NotificationChannelStorage>;
|
||||
let emitter: ActivityEmitter;
|
||||
let notificationHandler: jest.Mocked<NotificationHandler>;
|
||||
@@ -29,7 +29,7 @@ describe('A ListeningActivityHandler', (): void => {
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('A NotificationSubscriber', (): void => {
|
||||
schema: NOTIFICATION_CHANNEL_SCHEMA,
|
||||
extractModes: jest.fn(async(subscription): Promise<AccessMap> =>
|
||||
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 = {
|
||||
|
||||
@@ -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<void> => {
|
||||
await expect(handler.canHandle({ info, topic })).resolves.toBeUndefined();
|
||||
it('requires the input channel to have the correct type.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<WebHookFeatures> = {
|
||||
const channel: NotificationChannel<WebHookFeatures> = {
|
||||
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<string> => '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`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<WebHookSubscription2021['schema']>;
|
||||
let json: InferType<WebHookSubscription2021['schema']>;
|
||||
const unsubscribeRoute = new AbsolutePathInteractionRoute('http://example.com/unsubscribe');
|
||||
let storage: jest.Mocked<NotificationChannelStorage<WebHookFeatures>>;
|
||||
let stateHandler: jest.Mocked<StateHandler>;
|
||||
let channelType: WebHookSubscription2021;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
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<WebHookFeatures> => ({
|
||||
create: jest.fn((features: WebHookFeatures): NotificationChannel<WebHookFeatures> => ({
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
const { response } = await channelType.subscribe(channel, credentials);
|
||||
it('stores the channel and returns a valid response when subscribing.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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
|
||||
|
||||
@@ -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<void> => {
|
||||
it('rejects if the id does not match any stored channel.', async(): Promise<void> => {
|
||||
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<void> => {
|
||||
it('deletes the corresponding channel.', async(): Promise<void> => {
|
||||
await expect(unsubscriber.handle({ operation, request, response }))
|
||||
.resolves.toEqual(new ResetResponseDescription());
|
||||
expect(storage.delete).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
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<WebSocket> = 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<void> => {
|
||||
const webSocket2: jest.Mocked<WebSocket> = 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);
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
@@ -68,18 +68,18 @@ describe('A WebSocket2021Storer', (): void => {
|
||||
webSocket2.close = jest.fn();
|
||||
const webSocketOther: jest.Mocked<WebSocket> = 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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<NotificationChannelStorage>;
|
||||
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<void> => {
|
||||
it('stores the channel and returns a valid response when subscribing.', async(): Promise<void> => {
|
||||
const { response } = await channelType.subscribe(channel);
|
||||
expect(response.metadata.contentType).toBe('application/ld+json');
|
||||
await expect(readJsonStream(response.data)).resolves.toEqual({
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
@@ -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',
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
@@ -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',
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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,
|
||||
|
||||
@@ -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<void> => {
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user