From 8f06b4b2de36debd0c4c78bf4309aec7cc186444 Mon Sep 17 00:00:00 2001 From: Amy Yan Date: Fri, 3 May 2024 14:07:53 +1000 Subject: [PATCH] feat: `notifications inbox remove` and `notifications outbox remove` commands --- src/notifications/inbox/CommandInbox.ts | 2 + src/notifications/inbox/CommandRemove.ts | 69 +++++++++++++++++++ src/notifications/outbox/CommandOutbox.ts | 2 + src/notifications/outbox/CommandRemove.ts | 69 +++++++++++++++++++ src/utils/parsers.ts | 4 ++ ...ar.test.ts => sendReadRemoveClear.test.ts} | 58 +++++++++++++++- ...ar.test.ts => sendReadRemoveClear.test.ts} | 58 +++++++++++++++- 7 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 src/notifications/inbox/CommandRemove.ts create mode 100644 src/notifications/outbox/CommandRemove.ts rename tests/notifications/inbox/{sendReadClear.test.ts => sendReadRemoveClear.test.ts} (84%) rename tests/notifications/outbox/{sendReadClear.test.ts => sendReadRemoveClear.test.ts} (78%) diff --git a/src/notifications/inbox/CommandInbox.ts b/src/notifications/inbox/CommandInbox.ts index cfa9a2f5..3783ebf4 100644 --- a/src/notifications/inbox/CommandInbox.ts +++ b/src/notifications/inbox/CommandInbox.ts @@ -1,5 +1,6 @@ import CommandClear from './CommandClear'; import CommandRead from './CommandRead'; +import CommandRemove from './CommandRemove'; import CommandPolykey from '../../CommandPolykey'; class CommandInbox extends CommandPolykey { @@ -9,6 +10,7 @@ class CommandInbox extends CommandPolykey { this.description('Notifications Inbox Operations'); this.addCommand(new CommandClear(...args)); this.addCommand(new CommandRead(...args)); + this.addCommand(new CommandRemove(...args)); } } diff --git a/src/notifications/inbox/CommandRemove.ts b/src/notifications/inbox/CommandRemove.ts new file mode 100644 index 00000000..c189088e --- /dev/null +++ b/src/notifications/inbox/CommandRemove.ts @@ -0,0 +1,69 @@ +import type PolykeyClient from 'polykey/dist/PolykeyClient'; +import * as notificationsUtils from 'polykey/dist/notifications/utils'; +import CommandPolykey from '../../CommandPolykey'; +import * as binUtils from '../../utils'; +import * as binOptions from '../../utils/options'; +import * as binProcessors from '../../utils/processors'; +import * as binParsers from '../../utils/parsers'; + +class CommandRemove extends CommandPolykey { + constructor(...args: ConstructorParameters) { + super(...args); + this.name('remove'); + this.description('Remove a Notification in the Inbox'); + this.argument( + '', + 'Id of the notification to remove', + binParsers.parseNotificationId, + ); + this.addOption(binOptions.nodeId); + this.addOption(binOptions.clientHost); + this.addOption(binOptions.clientPort); + this.action(async (notificationId, options) => { + const { default: PolykeyClient } = await import( + 'polykey/dist/PolykeyClient' + ); + const clientOptions = await binProcessors.processClientOptions( + options.nodePath, + options.nodeId, + options.clientHost, + options.clientPort, + this.fs, + this.logger.getChild(binProcessors.processClientOptions.name), + ); + const auth = await binProcessors.processAuthentication( + options.passwordFile, + this.fs, + ); + + let pkClient: PolykeyClient; + this.exitHandlers.handlers.push(async () => { + if (pkClient != null) await pkClient.stop(); + }); + try { + pkClient = await PolykeyClient.createPolykeyClient({ + nodeId: clientOptions.nodeId, + host: clientOptions.clientHost, + port: clientOptions.clientPort, + options: { + nodePath: options.nodePath, + }, + logger: this.logger.getChild(PolykeyClient.name), + }); + await binUtils.retryAuthentication( + (auth) => + pkClient.rpcClient.methods.notificationsInboxRemove({ + notificationIdEncoded: + notificationsUtils.encodeNotificationId(notificationId), + metadata: auth, + }), + auth, + ); + } finally { + if (pkClient! != null) await pkClient.stop(); + } + }); + } +} + +export default CommandRemove; diff --git a/src/notifications/outbox/CommandOutbox.ts b/src/notifications/outbox/CommandOutbox.ts index 40198d71..5c3fbe96 100644 --- a/src/notifications/outbox/CommandOutbox.ts +++ b/src/notifications/outbox/CommandOutbox.ts @@ -1,5 +1,6 @@ import CommandClear from './CommandClear'; import CommandRead from './CommandRead'; +import CommandRemove from './CommandRemove'; import CommandPolykey from '../../CommandPolykey'; class CommandOutbox extends CommandPolykey { @@ -9,6 +10,7 @@ class CommandOutbox extends CommandPolykey { this.description('Notifications Outbox Operations'); this.addCommand(new CommandClear(...args)); this.addCommand(new CommandRead(...args)); + this.addCommand(new CommandRemove(...args)); } } diff --git a/src/notifications/outbox/CommandRemove.ts b/src/notifications/outbox/CommandRemove.ts new file mode 100644 index 00000000..8861c748 --- /dev/null +++ b/src/notifications/outbox/CommandRemove.ts @@ -0,0 +1,69 @@ +import type PolykeyClient from 'polykey/dist/PolykeyClient'; +import * as notificationsUtils from 'polykey/dist/notifications/utils'; +import CommandPolykey from '../../CommandPolykey'; +import * as binUtils from '../../utils'; +import * as binOptions from '../../utils/options'; +import * as binProcessors from '../../utils/processors'; +import * as binParsers from '../../utils/parsers'; + +class CommandRemove extends CommandPolykey { + constructor(...args: ConstructorParameters) { + super(...args); + this.name('remove'); + this.description('Remove a Pending Notification to be Sent in the Outbox'); + this.argument( + '', + 'Id of the notification to remove', + binParsers.parseNotificationId, + ); + this.addOption(binOptions.nodeId); + this.addOption(binOptions.clientHost); + this.addOption(binOptions.clientPort); + this.action(async (notificationId, options) => { + const { default: PolykeyClient } = await import( + 'polykey/dist/PolykeyClient' + ); + const clientOptions = await binProcessors.processClientOptions( + options.nodePath, + options.nodeId, + options.clientHost, + options.clientPort, + this.fs, + this.logger.getChild(binProcessors.processClientOptions.name), + ); + const auth = await binProcessors.processAuthentication( + options.passwordFile, + this.fs, + ); + + let pkClient: PolykeyClient; + this.exitHandlers.handlers.push(async () => { + if (pkClient != null) await pkClient.stop(); + }); + try { + pkClient = await PolykeyClient.createPolykeyClient({ + nodeId: clientOptions.nodeId, + host: clientOptions.clientHost, + port: clientOptions.clientPort, + options: { + nodePath: options.nodePath, + }, + logger: this.logger.getChild(PolykeyClient.name), + }); + await binUtils.retryAuthentication( + (auth) => + pkClient.rpcClient.methods.notificationsOutboxRemove({ + notificationIdEncoded: + notificationsUtils.encodeNotificationId(notificationId), + metadata: auth, + }), + auth, + ); + } finally { + if (pkClient! != null) await pkClient.stop(); + } + }); + } +} + +export default CommandRemove; diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 307d0c45..1dc5b513 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -105,6 +105,9 @@ const parseNodeId: (data: string) => ids.NodeId = validateParserToArgParser( ids.parseNodeId, ); +const parseNotificationId: (data: string) => ids.NotificationId = + validateParserToArgParser(ids.parseNotificationId); + const parseGestaltId: (data: string) => ids.GestaltId = validateParserToArgParser(ids.parseGestaltId); @@ -153,6 +156,7 @@ export { parseInteger, parseNumber, parseNodeId, + parseNotificationId, parseGestaltId, parseGestaltIdentityId, parseGestaltAction, diff --git a/tests/notifications/inbox/sendReadClear.test.ts b/tests/notifications/inbox/sendReadRemoveClear.test.ts similarity index 84% rename from tests/notifications/inbox/sendReadClear.test.ts rename to tests/notifications/inbox/sendReadRemoveClear.test.ts index 08a03cf2..36b4e593 100644 --- a/tests/notifications/inbox/sendReadClear.test.ts +++ b/tests/notifications/inbox/sendReadRemoveClear.test.ts @@ -8,9 +8,11 @@ import * as nodesUtils from 'polykey/dist/nodes/utils'; import * as testUtils from '../../utils'; describe('send/read/claim', () => { - const logger = new Logger('inbox send/read/clear test', LogLevel.WARN, [ - new StreamHandler(), - ]); + const logger = new Logger( + 'inbox send/read/remove/clear test', + LogLevel.WARN, + [new StreamHandler()], + ); let dataDir: string; let senderId: NodeId; let senderHost: string; @@ -287,6 +289,56 @@ describe('send/read/claim', () => { sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); + // Get a notificationId to remove + ({ exitCode, stdout } = await testUtils.pkExec( + ['notifications', 'inbox', 'read', '--format', 'json'], + { + env: { + PK_NODE_PATH: receiverAgentDir, + PK_PASSWORD: receiverAgentPassword, + }, + cwd: receiverAgentDir, + }, + )); + expect(exitCode).toBe(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(3); + const deletedNotificationIdEncoded = + readNotificationMessages[0].notification.notificationIdEncoded; + // Remove inbox notificataions + await testUtils.pkExec( + ['notifications', 'inbox', 'remove', deletedNotificationIdEncoded], + { + env: { + PK_NODE_PATH: receiverAgentDir, + PK_PASSWORD: receiverAgentPassword, + }, + cwd: receiverAgentDir, + }, + ); + // Check that the notification no longer exists + ({ exitCode, stdout } = await testUtils.pkExec( + ['notifications', 'inbox', 'read', '--format', 'json'], + { + env: { + PK_NODE_PATH: receiverAgentDir, + PK_PASSWORD: receiverAgentPassword, + }, + cwd: receiverAgentDir, + }, + )); + expect(exitCode).toBe(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(2); + expect(readNotificationMessages).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + notification: { + notificationIdEncoded: deletedNotificationIdEncoded, + }, + }), + ]), + ); // Clear inbox notifications await testUtils.pkExec(['notifications', 'inbox', 'clear'], { env: { diff --git a/tests/notifications/outbox/sendReadClear.test.ts b/tests/notifications/outbox/sendReadRemoveClear.test.ts similarity index 78% rename from tests/notifications/outbox/sendReadClear.test.ts rename to tests/notifications/outbox/sendReadRemoveClear.test.ts index 1d3bc1c5..e0cc6d4c 100644 --- a/tests/notifications/outbox/sendReadClear.test.ts +++ b/tests/notifications/outbox/sendReadRemoveClear.test.ts @@ -8,9 +8,11 @@ import * as nodesUtils from 'polykey/dist/nodes/utils'; import * as testUtils from '../../utils'; describe('send/read/claim', () => { - const logger = new Logger('outbox send/read/clear test', LogLevel.WARN, [ - new StreamHandler(), - ]); + const logger = new Logger( + 'outbox send/read/remove/clear test', + LogLevel.WARN, + [new StreamHandler()], + ); let dataDir: string; let senderId: NodeId; let senderAgentStatus: StatusLive; @@ -183,6 +185,56 @@ describe('send/read/claim', () => { iss: nodesUtils.encodeNodeId(senderId), sub: receiverId, }); + // Get a notificationId to remove + ({ exitCode, stdout } = await testUtils.pkExec( + ['notifications', 'outbox', 'read', '--format', 'json'], + { + env: { + PK_NODE_PATH: senderAgentDir, + PK_PASSWORD: senderAgentPassword, + }, + cwd: senderAgentDir, + }, + )); + expect(exitCode).toBe(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(3); + const deletedNotificationIdEncoded = + readNotificationMessages[0].notification.notificationIdEncoded; + // Remove outbox notificataions + await testUtils.pkExec( + ['notifications', 'outbox', 'remove', deletedNotificationIdEncoded], + { + env: { + PK_NODE_PATH: senderAgentDir, + PK_PASSWORD: senderAgentPassword, + }, + cwd: senderAgentDir, + }, + ); + // Check that the notification no longer exists + ({ exitCode, stdout } = await testUtils.pkExec( + ['notifications', 'outbox', 'read', '--format', 'json'], + { + env: { + PK_NODE_PATH: senderAgentDir, + PK_PASSWORD: senderAgentPassword, + }, + cwd: senderAgentDir, + }, + )); + expect(exitCode).toBe(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(2); + expect(readNotificationMessages).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + notification: { + notificationIdEncoded: deletedNotificationIdEncoded, + }, + }), + ]), + ); // Clear outbox notifications await testUtils.pkExec(['notifications', 'outbox', 'clear'], { env: {