From d3575be52cec1d8d1cb02352d7b8c6bcd2d983d5 Mon Sep 17 00:00:00 2001 From: Marat Alekperov Date: Tue, 3 Mar 2026 22:41:46 +0100 Subject: [PATCH] Add extras in the message data if provided for channel push notifications. --- src/commands/channels/publish.ts | 16 ++++- test/unit/commands/channels/publish.test.ts | 78 +++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/commands/channels/publish.ts b/src/commands/channels/publish.ts index bde8e7ca..39078039 100644 --- a/src/commands/channels/publish.ts +++ b/src/commands/channels/publish.ts @@ -31,6 +31,7 @@ export default class ChannelsPublish extends AblyBaseCommand { '$ ably channels publish --transport realtime my-channel "Using realtime transport"', '$ ably channels publish my-channel "Hello World" --json', '$ ably channels publish my-channel "Hello World" --pretty-json', + '$ ably channels publish my-channel \'{"data":"Push notification","extras":{"push":{"notification":{"title":"Hello","body":"World"}}}}\'', ]; static override flags = { @@ -206,11 +207,22 @@ export default class ChannelsPublish extends AblyBaseCommand { delete messageData.name; } + // Add extras if provided in the message data (before processing data) + if ( + messageData.extras && + typeof messageData.extras === "object" && + Object.keys(messageData.extras).length > 0 + ) { + message.extras = messageData.extras; + // Remove extras from messageData to avoid duplication in data + delete messageData.extras; + } + // If data is explicitly provided in the message, use it if ("data" in messageData) { message.data = messageData.data; - } else { - // Otherwise use the entire messageData as the data + } else if (Object.keys(messageData).length > 0) { + // Otherwise use the entire messageData object (not empty) as the data message.data = messageData; } diff --git a/test/unit/commands/channels/publish.test.ts b/test/unit/commands/channels/publish.test.ts index 173d2069..d307bcd5 100644 --- a/test/unit/commands/channels/publish.test.ts +++ b/test/unit/commands/channels/publish.test.ts @@ -399,4 +399,82 @@ describe("ChannelsPublish", function () { expect(stdout).toMatch(/error/i); }); }); + + describe("should publish a message with data and extras", function () { + it("should include extras.push when provided in message data", async function () { + const restMock = getMockAblyRest(); + const channel = restMock.channels._getChannel("test-channel"); + + await runCommand( + [ + "channels:publish", + "test-channel", + '{"data":"hello","extras":{"push":{"notification":{"title":"Test","body":"Push notification"}}}}', + "--transport", + "rest", + ], + import.meta.url, + ); + + expect(channel.publish).toHaveBeenCalledOnce(); + const publishArgs = channel.publish.mock.calls[0][0]; + expect(publishArgs).toHaveProperty("data", "hello"); + expect(publishArgs).toHaveProperty("extras"); + expect(publishArgs.extras).toHaveProperty("push"); + expect(publishArgs.extras.push).toEqual({ + notification: { title: "Test", body: "Push notification" }, + }); + }); + + it("should publish a message when only extras is provided without data", async function () { + const restMock = getMockAblyRest(); + const channel = restMock.channels._getChannel("test-channel"); + + await runCommand( + [ + "channels:publish", + "test-channel", + '{"extras":{"push":{"notification":{"title":"Extras only","body":"No data field"}}}}', + "--transport", + "rest", + ], + import.meta.url, + ); + + expect(channel.publish).toHaveBeenCalledOnce(); + const publishArgs = channel.publish.mock.calls[0][0]; + expect(publishArgs).toHaveProperty("extras"); + expect(publishArgs.extras).toHaveProperty("push"); + expect(publishArgs.extras.push).toEqual({ + notification: { title: "Extras only", body: "No data field" }, + }); + expect(publishArgs).not.toHaveProperty("data"); + }); + + it("should preserve name when extras is provided without data", async function () { + const restMock = getMockAblyRest(); + const channel = restMock.channels._getChannel("test-channel"); + + await runCommand( + [ + "channels:publish", + "test-channel", + '{"name":"eventName","extras":{"push":{"notification":{"title":"With name","body":"No data field"}}}}', + "--transport", + "rest", + ], + import.meta.url, + ); + + expect(channel.publish).toHaveBeenCalledOnce(); + const publishArgs = channel.publish.mock.calls[0][0]; + expect(publishArgs).toHaveProperty("name", "eventName"); + expect(publishArgs).toHaveProperty("extras"); + expect(publishArgs.extras).toHaveProperty("push"); + expect(publishArgs.extras.push).toEqual({ + notification: { title: "With name", body: "No data field" }, + }); + expect(publishArgs).not.toHaveProperty("data"); + }); + }); });