Skip to content

Commit 0012e6e

Browse files
committed
Address CodeRabbit review feedback for push commands
- Guard deprecation warning in set-apns-p12 for JSON mode - Use formatWarning() and formatClientId() in push channels list - Validate --data flag parses to a JSON object in push devices save - Validate all JSON flags (--recipient, --payload, --data, --apns, --fcm, --web) parse to objects in push publish - Let E2E test setup failures propagate instead of silently skipping
1 parent 635c4e3 commit 0012e6e

9 files changed

Lines changed: 98 additions & 43 deletions

File tree

src/chat-base-command.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { ChatClient, Room, RoomStatus } from "@ably/chat";
33
import { AblyBaseCommand } from "./base-command.js";
44
import { productApiFlags } from "./flags.js";
55
import { BaseFlags } from "./types/cli.js";
6-
import chalk from "chalk";
76

87
import {
98
formatSuccess,

src/commands/apps/set-apns-p12.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
formatProgress,
99
formatResource,
1010
formatSuccess,
11+
formatWarning,
1112
} from "../../utils/output.js";
1213

1314
export default class AppsSetApnsP12Command extends ControlBaseCommand {
@@ -49,9 +50,13 @@ export default class AppsSetApnsP12Command extends ControlBaseCommand {
4950
async run(): Promise<void> {
5051
const { args, flags } = await this.parse(AppsSetApnsP12Command);
5152

52-
this.warn(
53-
'This command is deprecated. Use "ably push config set-apns" instead.',
54-
);
53+
if (!this.shouldOutputJson(flags)) {
54+
this.logToStderr(
55+
formatWarning(
56+
'This command is deprecated. Use "ably push config set-apns" instead.',
57+
),
58+
);
59+
}
5560

5661
// Display authentication information
5762
this.showAuthInfoIfNeeded(flags);

src/commands/push/channels/list.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { AblyBaseCommand } from "../../../base-command.js";
44
import { productApiFlags } from "../../../flags.js";
55
import { BaseFlags } from "../../../types/cli.js";
66
import {
7+
formatClientId,
78
formatCountLabel,
89
formatHeading,
910
formatLabel,
1011
formatLimitWarning,
1112
formatProgress,
1213
formatResource,
1314
formatSuccess,
15+
formatWarning,
1416
} from "../../../utils/output.js";
1517

1618
export default class PushChannelsList extends AblyBaseCommand {
@@ -71,7 +73,7 @@ export default class PushChannelsList extends AblyBaseCommand {
7173
}
7274

7375
if (subscriptions.length === 0) {
74-
this.log("No subscriptions found.");
76+
this.log(formatWarning("No subscriptions found."));
7577
return;
7678
}
7779

@@ -90,7 +92,9 @@ export default class PushChannelsList extends AblyBaseCommand {
9092
if (sub.deviceId)
9193
this.log(` ${formatLabel("Device ID")} ${sub.deviceId}`);
9294
if (sub.clientId)
93-
this.log(` ${formatLabel("Client ID")} ${sub.clientId}`);
95+
this.log(
96+
` ${formatLabel("Client ID")} ${formatClientId(sub.clientId)}`,
97+
);
9498
this.log("");
9599
}
96100

src/commands/push/devices/save.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ export default class PushDevicesSave extends AblyBaseCommand {
153153
}
154154
recipient = {
155155
transportType,
156-
deviceToken: flags["device-token"],
156+
...(transportType === "fcm"
157+
? { registrationToken: flags["device-token"] }
158+
: { deviceToken: flags["device-token"] }),
157159
};
158160
}
159161

@@ -225,14 +227,25 @@ export default class PushDevicesSave extends AblyBaseCommand {
225227
jsonString = fs.readFileSync(filePath, "utf8");
226228
}
227229

230+
let parsed: unknown;
228231
try {
229-
return JSON.parse(jsonString) as Record<string, unknown>;
232+
parsed = JSON.parse(jsonString);
230233
} catch {
231234
this.fail(
232235
"--data must be valid JSON or a path to a JSON file (prefixed with @)",
233236
flags as BaseFlags,
234237
"pushDeviceSave",
235238
);
236239
}
240+
241+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
242+
this.fail(
243+
"--data must be a JSON object",
244+
flags as BaseFlags,
245+
"pushDeviceSave",
246+
);
247+
}
248+
249+
return parsed as Record<string, unknown>;
237250
}
238251
}

src/commands/push/publish.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ export default class PushPublish extends AblyBaseCommand {
9393
recipient = { clientId: flags["client-id"] };
9494
} else {
9595
try {
96-
recipient = JSON.parse(flags.recipient!) as Record<string, unknown>;
96+
const parsed = JSON.parse(flags.recipient!);
97+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
98+
throw new Error("not an object");
99+
}
100+
recipient = parsed as Record<string, unknown>;
97101
} catch {
98102
this.fail(
99-
"--recipient must be valid JSON",
103+
"--recipient must be a valid JSON object",
100104
flags as BaseFlags,
101105
"pushPublish",
102106
);
@@ -107,10 +111,14 @@ export default class PushPublish extends AblyBaseCommand {
107111
let payload: Record<string, unknown>;
108112
if (flags.payload) {
109113
try {
110-
payload = JSON.parse(flags.payload) as Record<string, unknown>;
114+
const parsed = JSON.parse(flags.payload);
115+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
116+
throw new Error("not an object");
117+
}
118+
payload = parsed as Record<string, unknown>;
111119
} catch {
112120
this.fail(
113-
"--payload must be valid JSON",
121+
"--payload must be a valid JSON object",
114122
flags as BaseFlags,
115123
"pushPublish",
116124
);
@@ -133,46 +141,79 @@ export default class PushPublish extends AblyBaseCommand {
133141

134142
if (flags.data) {
135143
try {
136-
payload.data = JSON.parse(flags.data);
144+
const parsed = JSON.parse(flags.data);
145+
if (
146+
!parsed ||
147+
typeof parsed !== "object" ||
148+
Array.isArray(parsed)
149+
) {
150+
throw new Error("not an object");
151+
}
152+
payload.data = parsed;
137153
} catch {
138154
this.fail(
139-
"--data must be valid JSON",
155+
"--data must be a valid JSON object",
140156
flags as BaseFlags,
141157
"pushPublish",
142158
);
143159
}
144160
}
145161
}
146162

163+
// Validate that at least some payload content exists
164+
if (
165+
!payload!.notification &&
166+
!payload!.data &&
167+
Object.keys(payload!).length === 0
168+
) {
169+
this.fail(
170+
"No push payload provided. Use --payload, --title/--body, or --data to specify notification content",
171+
flags as BaseFlags,
172+
"pushPublish",
173+
);
174+
}
175+
147176
// Add platform-specific overrides
148177
if (flags.apns) {
149178
try {
150-
payload.apns = JSON.parse(flags.apns);
179+
const parsed = JSON.parse(flags.apns);
180+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
181+
throw new Error("not an object");
182+
}
183+
payload.apns = parsed;
151184
} catch {
152185
this.fail(
153-
"--apns must be valid JSON",
186+
"--apns must be a valid JSON object",
154187
flags as BaseFlags,
155188
"pushPublish",
156189
);
157190
}
158191
}
159192
if (flags.fcm) {
160193
try {
161-
payload.fcm = JSON.parse(flags.fcm);
194+
const parsed = JSON.parse(flags.fcm);
195+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
196+
throw new Error("not an object");
197+
}
198+
payload.fcm = parsed;
162199
} catch {
163200
this.fail(
164-
"--fcm must be valid JSON",
201+
"--fcm must be a valid JSON object",
165202
flags as BaseFlags,
166203
"pushPublish",
167204
);
168205
}
169206
}
170207
if (flags.web) {
171208
try {
172-
payload.web = JSON.parse(flags.web);
209+
const parsed = JSON.parse(flags.web);
210+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
211+
throw new Error("not an object");
212+
}
213+
payload.web = parsed;
173214
} catch {
174215
this.fail(
175-
"--web must be valid JSON",
216+
"--web must be a valid JSON object",
176217
flags as BaseFlags,
177218
"pushPublish",
178219
);

test/e2e/connections/connections.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ describe("Connections E2E Tests", () => {
152152
);
153153
});
154154

155-
describe("Live Connection Monitoring E2E", () => {
155+
describe.skipIf(!process.env.E2E_ABLY_API_KEY)("Live Connection Monitoring E2E", () => {
156156
it(
157157
"should monitor live connections with real client lifecycle",
158158
{ timeout: 180000 },
@@ -168,10 +168,7 @@ describe("Connections E2E Tests", () => {
168168
// Step 1: Start live connection log monitoring
169169
const monitorEnv = { ...process.env };
170170
delete monitorEnv.ABLY_CLI_TEST_MODE;
171-
const apiKey = process.env.E2E_ABLY_API_KEY;
172-
if (!apiKey) {
173-
throw new Error("E2E_ABLY_API_KEY environment variable is required");
174-
}
171+
const apiKey = process.env.E2E_ABLY_API_KEY!;
175172

176173
// Use connection-lifecycle command which uses the correct meta channel
177174
const connectionsMonitor = spawn(

test/e2e/push/push-config-e2e.test.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,19 @@ describe("Push Config E2E Tests", () => {
4141
logErrors: false,
4242
});
4343

44-
try {
45-
// Create a dedicated test app for push config tests
46-
const appName = `E2E Push Config Test ${Date.now()}`;
47-
const createResult = await runCommand(
48-
["apps", "create", "--name", appName, "--json"],
49-
{
50-
env: { ABLY_ACCESS_TOKEN: accessToken },
51-
},
52-
);
44+
// Create a dedicated test app for push config tests
45+
// Let setup failures propagate — only missing credentials should skip
46+
const appName = `E2E Push Config Test ${Date.now()}`;
47+
const createResult = await runCommand(
48+
["apps", "create", "--name", appName, "--json"],
49+
{
50+
env: { ABLY_ACCESS_TOKEN: accessToken },
51+
},
52+
);
5353

54-
const result = JSON.parse(createResult.stdout);
55-
testAppId = result.app.id;
56-
console.log(`Created test app for push config: ${testAppId}`);
57-
} catch (error) {
58-
console.error("Failed to create test app:", error);
59-
shouldSkip = true;
60-
}
54+
const result = JSON.parse(createResult.stdout);
55+
testAppId = result.app.id;
56+
console.log(`Created test app for push config: ${testAppId}`);
6157
});
6258

6359
afterAll(async () => {

test/fixtures/push/test-fcm-service-account.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "service_account",
33
"project_id": "test-project-id",
44
"private_key_id": "test-key-id",
5-
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nFAKE+KEY+FOR+TESTING\n-----END RSA PRIVATE KEY-----\n",
5+
"private_key": "fake-key-for-testing",
66
"client_email": "test@test-project-id.iam.gserviceaccount.com",
77
"client_id": "123456789",
88
"auth_uri": "https://accounts.google.com/o/oauth2/auth",

test/unit/commands/push/devices/save.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe("push:devices:save command", () => {
7171
[
7272
"push:devices:save",
7373
"--data",
74-
'{"id":"device-2","platform":"android","formFactor":"tablet","push":{"recipient":{"transportType":"fcm","deviceToken":"tok"}}}',
74+
'{"id":"device-2","platform":"android","formFactor":"tablet","push":{"recipient":{"transportType":"fcm","registrationToken":"tok"}}}',
7575
],
7676
import.meta.url,
7777
);

0 commit comments

Comments
 (0)