diff --git a/README.md b/README.md index d5949d47..0f6f00b9 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ $ ably-interactive * [`ably logs push subscribe`](#ably-logs-push-subscribe) * [`ably logs subscribe`](#ably-logs-subscribe) * [`ably push`](#ably-push) -* [`ably push batch-publish [PAYLOAD]`](#ably-push-batch-publish) +* [`ably push batch-publish [PAYLOAD]`](#ably-push-batch-publish-payload) * [`ably push channels`](#ably-push-channels) * [`ably push channels list`](#ably-push-channels-list) * [`ably push channels list-channels`](#ably-push-channels-list-channels) diff --git a/src/commands/accounts/current.ts b/src/commands/accounts/current.ts index fc359954..361c6861 100644 --- a/src/commands/accounts/current.ts +++ b/src/commands/accounts/current.ts @@ -122,9 +122,9 @@ export default class AccountsCurrent extends ControlBaseCommand { { account: { cached: true, - name: currentAccount.accountName || "Unknown", - id: currentAccount.accountId || "Unknown ID", - user: { email: currentAccount.userEmail || null }, + name: currentAccount.accountName, + id: currentAccount.accountId, + user: { email: currentAccount.userEmail }, warning: "Unable to verify account information. Your access token may have expired.", }, @@ -142,11 +142,9 @@ export default class AccountsCurrent extends ControlBaseCommand { ); // Show cached information - if (currentAccount.accountName || currentAccount.accountId) { - this.log( - `${formatLabel("Account (cached)")} ${chalk.cyan.bold(currentAccount.accountName || "Unknown")} ${chalk.gray(`(${currentAccount.accountId || "Unknown ID"})`)}`, - ); - } + this.log( + `${formatLabel("Account (cached)")} ${chalk.cyan.bold(currentAccount.accountName)} ${chalk.gray(`(${currentAccount.accountId})`)}`, + ); if (currentAccount.userEmail) { this.log( diff --git a/src/commands/accounts/list.ts b/src/commands/accounts/list.ts index 20835f03..f167e975 100644 --- a/src/commands/accounts/list.ts +++ b/src/commands/accounts/list.ts @@ -47,10 +47,10 @@ export default class AccountsList extends ControlBaseCommand { account.currentAppId, } : undefined, - id: account.accountId || "Unknown", + id: account.accountId, isCurrent: alias === currentAlias, - name: account.accountName || "Unknown", - user: account.userEmail || "Unknown", + name: account.accountName, + user: account.userEmail, })), currentAccount: currentAlias, }, @@ -72,9 +72,9 @@ export default class AccountsList extends ControlBaseCommand { (isCurrent ? chalk.green(" (current)") : ""), ); this.log( - ` ${formatLabel("Name")} ${account.accountName || "Unknown"} (${account.accountId || "Unknown"})`, + ` ${formatLabel("Name")} ${account.accountName} (${account.accountId})`, ); - this.log(` ${formatLabel("User")} ${account.userEmail || "Unknown"}`); + this.log(` ${formatLabel("User")} ${account.userEmail}`); // Count number of apps configured for this account const appCount = account.apps ? Object.keys(account.apps).length : 0; diff --git a/src/commands/accounts/switch.ts b/src/commands/accounts/switch.ts index 404264b1..e79f2c49 100644 --- a/src/commands/accounts/switch.ts +++ b/src/commands/accounts/switch.ts @@ -63,8 +63,8 @@ export default class AccountsSwitch extends ControlBaseCommand { { availableAccounts: accounts.map(({ account, alias }) => ({ alias, - id: account.accountId || "Unknown", - name: account.accountName || "Unknown", + id: account.accountId, + name: account.accountName, })), }, ); @@ -83,7 +83,7 @@ export default class AccountsSwitch extends ControlBaseCommand { private async switchToAccount( alias: string, accounts: Array<{ - account: { accountId?: string; accountName?: string }; + account: { accountId: string; accountName: string }; alias: string; }>, flags: Record, @@ -99,8 +99,8 @@ export default class AccountsSwitch extends ControlBaseCommand { { availableAccounts: accounts.map(({ account, alias }) => ({ alias, - id: account.accountId || "Unknown", - name: account.accountName || "Unknown", + id: account.accountId, + name: account.accountName, })), }, ); diff --git a/src/commands/apps/current.ts b/src/commands/apps/current.ts index 274e68e2..4743967d 100644 --- a/src/commands/apps/current.ts +++ b/src/commands/apps/current.ts @@ -83,7 +83,7 @@ export default class AppsCurrent extends ControlBaseCommand { ); } else { this.log( - `${formatLabel("Account")} ${chalk.cyan.bold(currentAccount.accountName || currentAccountAlias)} ${chalk.gray(`(${currentAccount.accountId || "Unknown ID"})`)}`, + `${formatLabel("Account")} ${chalk.cyan.bold(currentAccount.accountName)} ${chalk.gray(`(${currentAccount.accountId})`)}`, ); this.log( `${formatLabel("App")} ${chalk.green.bold(appName)} ${chalk.gray(`(${currentAppId})`)}`, @@ -161,7 +161,9 @@ export default class AppsCurrent extends ControlBaseCommand { flags, ); } else { - // Get account info if possible + // Web CLI mode: account info comes from the Control API, not local config. + // The API call may fail (e.g. token lacks account-level scope), so we + // fall back to generic defaults — accountId stays empty, hiding the "(id)" suffix. let accountName = "Web CLI Account"; let accountId = ""; @@ -170,7 +172,7 @@ export default class AppsCurrent extends ControlBaseCommand { accountName = account.name; accountId = account.id; } catch { - // If we can't get account details, just use default values + // Control API unavailable — use defaults above } this.log( diff --git a/src/commands/apps/rules/list.ts b/src/commands/apps/rules/list.ts index e74020d1..e17612cc 100644 --- a/src/commands/apps/rules/list.ts +++ b/src/commands/apps/rules/list.ts @@ -69,6 +69,7 @@ export default class RulesListCommand extends ControlBaseCommand { (rule: Namespace): ChannelRuleOutput => ({ authenticated: rule.authenticated || false, batchingEnabled: rule.batchingEnabled || false, + // Control API may omit optional fields; emit null for consistent JSON schema batchingInterval: rule.batchingInterval ?? null, conflationEnabled: rule.conflationEnabled || false, conflationInterval: rule.conflationInterval ?? null, diff --git a/src/commands/auth/issue-ably-token.ts b/src/commands/auth/issue-ably-token.ts index 5c6dd939..bf790e7b 100644 --- a/src/commands/auth/issue-ably-token.ts +++ b/src/commands/auth/issue-ably-token.ts @@ -4,6 +4,11 @@ import { randomUUID } from "node:crypto"; import { AblyBaseCommand } from "../../base-command.js"; import { productApiFlags } from "../../flags.js"; +import { + formatClientId, + formatLabel, + formatSuccess, +} from "../../utils/output.js"; export default class IssueAblyTokenCommand extends AblyBaseCommand { static description = "Create an Ably Token with capabilities"; @@ -118,22 +123,32 @@ export default class IssueAblyTokenCommand extends AblyBaseCommand { value: tokenDetails.token, issuedAt: new Date(tokenDetails.issued).toISOString(), expiresAt: new Date(tokenDetails.expires).toISOString(), - clientId: tokenDetails.clientId || null, + ...(tokenDetails.clientId + ? { clientId: tokenDetails.clientId } + : {}), capability: tokenDetails.capability, }, }, flags, ); } else { - this.log("Generated Ably Token:"); - this.log(`Token: ${tokenDetails.token}`); - this.log(`Type: Ably`); - this.log(`Issued: ${new Date(tokenDetails.issued).toISOString()}`); - this.log(`Expires: ${new Date(tokenDetails.expires).toISOString()}`); - this.log(`TTL: ${flags.ttl} seconds`); - this.log(`Client ID: ${tokenDetails.clientId || "None"}`); + this.log(formatSuccess("Ably token generated.")); + this.log(`${formatLabel("Token")} ${tokenDetails.token}`); + this.log(`${formatLabel("Type")} Ably`); this.log( - `Capability: ${this.formatJsonOutput({ capability: tokenDetails.capability }, flags)}`, + `${formatLabel("Issued")} ${new Date(tokenDetails.issued).toISOString()}`, + ); + this.log( + `${formatLabel("Expires")} ${new Date(tokenDetails.expires).toISOString()}`, + ); + this.log(`${formatLabel("TTL")} ${flags.ttl} seconds`); + if (tokenDetails.clientId) { + this.log( + `${formatLabel("Client ID")} ${formatClientId(tokenDetails.clientId)}`, + ); + } + this.log( + `${formatLabel("Capability")} ${this.formatJsonOutput({ capability: tokenDetails.capability }, flags)}`, ); } } catch (error) { diff --git a/src/commands/auth/issue-jwt-token.ts b/src/commands/auth/issue-jwt-token.ts index 1295431f..5ab5bb66 100644 --- a/src/commands/auth/issue-jwt-token.ts +++ b/src/commands/auth/issue-jwt-token.ts @@ -4,6 +4,11 @@ import { randomUUID } from "node:crypto"; import { AblyBaseCommand } from "../../base-command.js"; import { productApiFlags } from "../../flags.js"; +import { + formatClientId, + formatLabel, + formatSuccess, +} from "../../utils/output.js"; interface JwtPayload { exp: number; @@ -133,7 +138,7 @@ export default class IssueJwtTokenCommand extends AblyBaseCommand { token: { appId, capability: capabilities, - clientId, + ...(clientId ? { clientId } : {}), expires: new Date(jwtPayload.exp * 1000).toISOString(), issued: new Date(jwtPayload.iat * 1000).toISOString(), keyId, @@ -145,16 +150,24 @@ export default class IssueJwtTokenCommand extends AblyBaseCommand { flags, ); } else { - this.log("Generated Ably JWT Token:"); - this.log(`Token: ${token}`); - this.log(`Type: JWT`); - this.log(`Issued: ${new Date(jwtPayload.iat * 1000).toISOString()}`); - this.log(`Expires: ${new Date(jwtPayload.exp * 1000).toISOString()}`); - this.log(`TTL: ${flags.ttl} seconds`); - this.log(`App ID: ${appId}`); - this.log(`Key ID: ${keyId}`); - this.log(`Client ID: ${clientId || "None"}`); - this.log(`Capability: ${this.formatJsonOutput(capabilities, flags)}`); + this.log(formatSuccess("Ably JWT token generated.")); + this.log(`${formatLabel("Token")} ${token}`); + this.log(`${formatLabel("Type")} JWT`); + this.log( + `${formatLabel("Issued")} ${new Date(jwtPayload.iat * 1000).toISOString()}`, + ); + this.log( + `${formatLabel("Expires")} ${new Date(jwtPayload.exp * 1000).toISOString()}`, + ); + this.log(`${formatLabel("TTL")} ${flags.ttl} seconds`); + this.log(`${formatLabel("App ID")} ${appId}`); + this.log(`${formatLabel("Key ID")} ${keyId}`); + if (clientId) { + this.log(`${formatLabel("Client ID")} ${formatClientId(clientId)}`); + } + this.log( + `${formatLabel("Capability")} ${this.formatJsonOutput(capabilities, flags)}`, + ); } } catch (error) { this.fail(error, flags, "issueJwtToken"); diff --git a/src/commands/auth/keys/current.ts b/src/commands/auth/keys/current.ts index 4c2f7957..66d1fe74 100644 --- a/src/commands/auth/keys/current.ts +++ b/src/commands/auth/keys/current.ts @@ -71,7 +71,7 @@ export default class KeysCurrentCommand extends ControlBaseCommand { const currentAccountAlias = this.configManager.getCurrentAccountAlias(); this.log( - `${formatLabel("Account")} ${chalk.cyan.bold(currentAccount?.accountName || currentAccountAlias)} ${chalk.gray(`(${currentAccount?.accountId || "Unknown ID"})`)}`, + `${formatLabel("Account")} ${chalk.cyan.bold(currentAccount?.accountName || currentAccountAlias)}${currentAccount ? chalk.gray(` (${currentAccount.accountId})`) : ""}`, ); this.log( `${formatLabel("App")} ${chalk.green.bold(appName)} ${chalk.gray(`(${appId})`)}`, @@ -118,7 +118,9 @@ export default class KeysCurrentCommand extends ControlBaseCommand { flags, ); } else { - // Get account info if possible + // Web CLI mode: account info comes from the Control API, not local config. + // The API call may fail (e.g. token lacks account-level scope), so we + // fall back to generic defaults — accountId stays empty, hiding the "(id)" suffix. let accountName = "Web CLI Account"; let accountId = ""; @@ -128,7 +130,7 @@ export default class KeysCurrentCommand extends ControlBaseCommand { accountName = account.name; accountId = account.id; } catch { - // If we can't get account details, just use default values + // Control API unavailable — use defaults above } this.log( diff --git a/src/commands/channels/presence/enter.ts b/src/commands/channels/presence/enter.ts index f61cd72c..b928d3dc 100644 --- a/src/commands/channels/presence/enter.ts +++ b/src/commands/channels/presence/enter.ts @@ -124,7 +124,7 @@ export default class ChannelsPresenceEnter extends AblyBaseCommand { ? `${formatIndex(this.sequenceCounter)}` : ""; this.log( - `${formatTimestamp(timestamp)}${sequencePrefix} ${formatResource(`Channel: ${channelName}`)} | Action: ${formatEventType(String(presenceMessage.action))} | Client: ${formatClientId(presenceMessage.clientId || "N/A")}`, + `${formatTimestamp(timestamp)}${sequencePrefix} ${formatResource(`Channel: ${channelName}`)} | Action: ${formatEventType(String(presenceMessage.action))} | Client: ${formatClientId(presenceMessage.clientId)}`, ); if ( diff --git a/src/commands/channels/presence/get.ts b/src/commands/channels/presence/get.ts index 96a998f5..3ba5dd54 100644 --- a/src/commands/channels/presence/get.ts +++ b/src/commands/channels/presence/get.ts @@ -102,6 +102,7 @@ export default class ChannelsPresenceGet extends AblyBaseCommand { clientId: member.clientId, connectionId: member.connectionId, action: member.action, + // SDK presence data may be undefined; emit null for consistent JSON schema data: (member.data as unknown) ?? null, timestamp: formatMessageTimestamp(member.timestamp), id: member.id, diff --git a/src/commands/push/channels/list.ts b/src/commands/push/channels/list.ts index 861e755c..0174fd6f 100644 --- a/src/commands/push/channels/list.ts +++ b/src/commands/push/channels/list.ts @@ -108,7 +108,7 @@ export default class PushChannelsList extends AblyBaseCommand { for (const sub of subscriptions) { const type = sub.deviceId ? "device" : "client"; - const id = sub.deviceId || sub.clientId || "unknown"; + const id = sub.deviceId || sub.clientId; this.log(formatHeading(`${type}: ${id}`)); this.log(` ${formatLabel("Channel")} ${formatResource(sub.channel)}`); if (sub.deviceId) diff --git a/src/commands/rooms/messages/history.ts b/src/commands/rooms/messages/history.ts index a2bf19df..9589e4d2 100644 --- a/src/commands/rooms/messages/history.ts +++ b/src/commands/rooms/messages/history.ts @@ -188,7 +188,7 @@ export default class MessagesHistory extends ChatBaseCommand { const messagesInOrder = [...items]; for (const [i, message] of messagesInOrder.entries()) { const timestamp = formatMessageTimestamp(message.timestamp); - const author = message.clientId || "Unknown"; + const author = message.clientId; this.log(`${formatIndex(i + 1)} ${formatTimestamp(timestamp)}`); this.log( diff --git a/src/commands/rooms/messages/reactions/subscribe.ts b/src/commands/rooms/messages/reactions/subscribe.ts index 4583fd17..2d0a696d 100644 --- a/src/commands/rooms/messages/reactions/subscribe.ts +++ b/src/commands/rooms/messages/reactions/subscribe.ts @@ -152,7 +152,7 @@ export default class MessagesReactionsSubscribe extends ChatBaseCommand { this.logJsonEvent({ reaction: eventData }, flags); } else { this.log( - `${formatTimestamp(timestamp)} ${chalk.green("⚡")} ${formatClientId(event.reaction.clientId || "Unknown")} [${event.reaction.type}] ${event.type}: ${formatEventType(event.reaction.name || "unknown")} to message ${formatResource(event.reaction.messageSerial)}`, + `${formatTimestamp(timestamp)} ${chalk.green("⚡")} ${formatClientId(event.reaction.clientId)} [${event.reaction.type}] ${event.type}: ${formatEventType(event.reaction.name || "unknown")} to message ${formatResource(event.reaction.messageSerial)}`, ); } }, diff --git a/src/commands/rooms/messages/subscribe.ts b/src/commands/rooms/messages/subscribe.ts index 6c2f27ab..75bd78d4 100644 --- a/src/commands/rooms/messages/subscribe.ts +++ b/src/commands/rooms/messages/subscribe.ts @@ -127,7 +127,7 @@ export default class MessagesSubscribe extends ChatBaseCommand { } else { // Format message with timestamp, author and content const timestamp = formatMessageTimestamp(message.timestamp); - const author = message.clientId || "Unknown"; + const author = message.clientId; // Prefix with room name when multiple rooms const roomPrefix = diff --git a/src/commands/rooms/presence/enter.ts b/src/commands/rooms/presence/enter.ts index c67ee444..16c66091 100644 --- a/src/commands/rooms/presence/enter.ts +++ b/src/commands/rooms/presence/enter.ts @@ -131,7 +131,7 @@ export default class RoomsPresenceEnter extends ChatBaseCommand { ? `${formatIndex(this.sequenceCounter)}` : ""; this.log( - `${formatTimestamp(timestamp)}${sequencePrefix} ${formatResource(`Room: ${this.roomName!}`)} | Action: ${formatEventType(event.type)} | Client: ${formatClientId(member.clientId || "N/A")}`, + `${formatTimestamp(timestamp)}${sequencePrefix} ${formatResource(`Room: ${this.roomName!}`)} | Action: ${formatEventType(event.type)} | Client: ${formatClientId(member.clientId)}`, ); if (member.data !== null && member.data !== undefined) { diff --git a/src/commands/rooms/reactions/subscribe.ts b/src/commands/rooms/reactions/subscribe.ts index ad753deb..eb56e1da 100644 --- a/src/commands/rooms/reactions/subscribe.ts +++ b/src/commands/rooms/reactions/subscribe.ts @@ -136,7 +136,7 @@ export default class RoomsReactionsSubscribe extends ChatBaseCommand { ); } else { this.log( - `${formatTimestamp(timestamp)} ${chalk.green("⚡")} ${formatClientId(reaction.clientId || "Unknown")} reacted with ${chalk.yellow(reaction.name || "unknown")}`, + `${formatTimestamp(timestamp)} ${chalk.green("⚡")} ${formatClientId(reaction.clientId)} reacted with ${chalk.yellow(reaction.name || "unknown")}`, ); // Show any additional metadata in the reaction diff --git a/src/commands/spaces/members/subscribe.ts b/src/commands/spaces/members/subscribe.ts index b6cf0bf4..07736512 100644 --- a/src/commands/spaces/members/subscribe.ts +++ b/src/commands/spaces/members/subscribe.ts @@ -73,8 +73,8 @@ export default class SpacesMembersSubscribe extends SpacesBaseCommand { // Determine the action from the member's lastEvent const action = member.lastEvent.name; - const clientId = member.clientId || "Unknown"; - const connectionId = member.connectionId || "Unknown"; + const clientId = member.clientId; + const connectionId = member.connectionId; // Create a unique key for this client+connection combination const clientKey = `${clientId}:${connectionId}`; diff --git a/src/commands/spaces/subscribe.ts b/src/commands/spaces/subscribe.ts index f169021e..dcbce363 100644 --- a/src/commands/spaces/subscribe.ts +++ b/src/commands/spaces/subscribe.ts @@ -70,8 +70,8 @@ export default class SpacesSubscribe extends SpacesBaseCommand { const now = Date.now(); const action = member.lastEvent.name; - const clientId = member.clientId || "Unknown"; - const connectionId = member.connectionId || "Unknown"; + const clientId = member.clientId; + const connectionId = member.connectionId; // Dedup within 500ms window const clientKey = `${clientId}:${connectionId}`; diff --git a/src/services/config-manager.ts b/src/services/config-manager.ts index f8560f8b..c1efd88c 100644 --- a/src/services/config-manager.ts +++ b/src/services/config-manager.ts @@ -14,15 +14,15 @@ export interface AppConfig { export interface AccountConfig { accessToken: string; - accountId?: string; - accountName?: string; + accountId: string; + accountName: string; apps?: { [appId: string]: AppConfig; }; currentAppId?: string; endpoint?: string; tokenId?: string; - userEmail?: string; + userEmail: string; } export interface AblyConfig { @@ -48,12 +48,12 @@ export interface ConfigManager { listAccounts(): { account: AccountConfig; alias: string }[]; storeAccount( accessToken: string, - alias?: string, - accountInfo?: { - accountId?: string; - accountName?: string; + alias: string, + accountInfo: { + accountId: string; + accountName: string; tokenId?: string; - userEmail?: string; + userEmail: string; }, ): void; switchAccount(alias: string): boolean; @@ -347,23 +347,26 @@ export class TomlConfigManager implements ConfigManager { this.saveConfig(); } - // Store account information with an optional alias + // Store account information public storeAccount( accessToken: string, alias: string = "default", - accountInfo?: { - accountId?: string; - accountName?: string; + accountInfo: { + accountId: string; + accountName: string; tokenId?: string; - userEmail?: string; + userEmail: string; }, ): void { - // Create or update the account entry + const existing = this.config.accounts[alias]; this.config.accounts[alias] = { accessToken, - ...accountInfo, - apps: this.config.accounts[alias]?.apps || {}, - currentAppId: this.config.accounts[alias]?.currentAppId, + accountId: accountInfo.accountId, + accountName: accountInfo.accountName, + userEmail: accountInfo.userEmail, + tokenId: accountInfo.tokenId, + apps: existing?.apps || {}, + currentAppId: existing?.currentAppId, }; // Set as current account if it's the first one or no current account is set diff --git a/src/services/interactive-helper.ts b/src/services/interactive-helper.ts index ebc798f7..daf153ee 100644 --- a/src/services/interactive-helper.ts +++ b/src/services/interactive-helper.ts @@ -58,11 +58,8 @@ export class InteractiveHelper { { choices: accounts.map((account) => { const isCurrent = account.alias === currentAlias; - const accountInfo = - account.account.accountName || - account.account.accountId || - "Unknown"; - const userInfo = account.account.userEmail || "Unknown"; + const accountInfo = account.account.accountName; + const userInfo = account.account.userEmail; return { name: `${isCurrent ? "* " : " "}${account.alias} (${accountInfo}, ${userInfo})`, value: account, diff --git a/src/utils/spaces-output.ts b/src/utils/spaces-output.ts index d4a41c2b..127e0a97 100644 --- a/src/utils/spaces-output.ts +++ b/src/utils/spaces-output.ts @@ -41,6 +41,10 @@ export interface LocationEntry { } // --- JSON formatters (SDK type → display interface) --- +// These use `?? null` to ensure optional SDK fields always appear in JSON output +// (consistent schema for consumers). The Spaces SDK uses `null` as its "not set" +// sentinel for profileData/location; for cursors and locks, undefined → null +// keeps the field present rather than omitting it from JSON.stringify output. export function formatMemberOutput(member: SpaceMember): MemberOutput { return { diff --git a/test/e2e/auth/basic-auth.test.ts b/test/e2e/auth/basic-auth.test.ts index e6f66444..be89cf61 100644 --- a/test/e2e/auth/basic-auth.test.ts +++ b/test/e2e/auth/basic-auth.test.ts @@ -226,6 +226,7 @@ describe("Authentication E2E", () => { configManager.storeAccount("token", "platform-test", { accountId: "platform_test", accountName: "Platform Test", + userEmail: "platform@test.com", }); expect(fs.existsSync(configPath)).toBe(true); @@ -242,6 +243,7 @@ describe("Authentication E2E", () => { configManager.storeAccount("token", "lineending-test", { accountId: "lineending_test", accountName: "Line Ending Test", + userEmail: "lineending@test.com", }); // Read config file and verify it's readable regardless of line endings @@ -270,6 +272,7 @@ describe("Authentication E2E", () => { configManager.storeAccount("isolated-token", "isolated-account", { accountId: "isolated_account", accountName: "Isolated Account", + userEmail: "isolated@test.com", }); // Verify it's in our temp directory, not the user's home diff --git a/test/helpers/control-api-test-helpers.ts b/test/helpers/control-api-test-helpers.ts index bcaef713..6883884a 100644 --- a/test/helpers/control-api-test-helpers.ts +++ b/test/helpers/control-api-test-helpers.ts @@ -19,7 +19,7 @@ export function nockControl(): nock.Scope { export function getControlApiContext() { const mock = getMockConfigManager(); return { - accountId: mock.getCurrentAccount()!.accountId!, + accountId: mock.getCurrentAccount()!.accountId, appId: mock.getCurrentAppId()!, mock, }; diff --git a/test/helpers/mock-config-manager.ts b/test/helpers/mock-config-manager.ts index 2f70a031..a7a4aee9 100644 --- a/test/helpers/mock-config-manager.ts +++ b/test/helpers/mock-config-manager.ts @@ -374,18 +374,26 @@ export class MockConfigManager implements ConfigManager { public storeAccount( accessToken: string, alias: string = "default", - accountInfo?: { - accountId?: string; - accountName?: string; + accountInfo: { + accountId: string; + accountName: string; tokenId?: string; - userEmail?: string; + userEmail: string; + } = { + accountId: "test-account-id", + accountName: "Test Account", + userEmail: "test@example.com", }, ): void { + const existing = this.config.accounts[alias]; this.config.accounts[alias] = { accessToken, - ...accountInfo, - apps: this.config.accounts[alias]?.apps || {}, - currentAppId: this.config.accounts[alias]?.currentAppId, + accountId: accountInfo.accountId, + accountName: accountInfo.accountName, + userEmail: accountInfo.userEmail, + tokenId: accountInfo.tokenId, + apps: existing?.apps || {}, + currentAppId: existing?.currentAppId, }; if (!this.config.current || !this.config.current.account) { diff --git a/test/unit/commands/apps/create.test.ts b/test/unit/commands/apps/create.test.ts index 06d48ba7..3bb72fd7 100644 --- a/test/unit/commands/apps/create.test.ts +++ b/test/unit/commands/apps/create.test.ts @@ -29,9 +29,9 @@ describe("apps:create command", () => { describe("functionality", () => { it("should create an app successfully", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint to get account ID nockControl() @@ -72,9 +72,9 @@ describe("apps:create command", () => { it("should create an app with TLS only flag", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint nockControl() @@ -112,9 +112,9 @@ describe("apps:create command", () => { it("should output JSON format when --json flag is used", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const mockApp = { id: newAppId, @@ -153,9 +153,9 @@ describe("apps:create command", () => { it("should use ABLY_ACCESS_TOKEN environment variable when provided", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const customToken = "custom_access_token"; process.env.ABLY_ACCESS_TOKEN = customToken; @@ -200,9 +200,9 @@ describe("apps:create command", () => { it("should automatically switch to the newly created app", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint nockControl() @@ -265,9 +265,9 @@ describe("apps:create command", () => { } // 500: need /v1/me pre-mock, then fail on the actual endpoint const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; nockControl() .get("/v1/me") .reply(200, { @@ -282,9 +282,9 @@ describe("apps:create command", () => { it("should handle 403 forbidden error", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint nockControl() @@ -310,9 +310,9 @@ describe("apps:create command", () => { it("should handle 404 not found error", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint nockControl() @@ -345,9 +345,9 @@ describe("apps:create command", () => { it("should handle validation errors from API", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; // Mock the /me endpoint nockControl() diff --git a/test/unit/commands/apps/current.test.ts b/test/unit/commands/apps/current.test.ts index 947b31e3..93b9216b 100644 --- a/test/unit/commands/apps/current.test.ts +++ b/test/unit/commands/apps/current.test.ts @@ -21,7 +21,7 @@ describe("apps:current command", () => { it("should display account information", async () => { const accountName = - getMockConfigManager().getCurrentAccount()!.accountName!; + getMockConfigManager().getCurrentAccount()!.accountName; const { stdout } = await runCommand(["apps:current"], import.meta.url); expect(stdout).toContain(`Account: ${accountName}`); diff --git a/test/unit/commands/apps/delete.test.ts b/test/unit/commands/apps/delete.test.ts index 91e40e3a..2b19bd2b 100644 --- a/test/unit/commands/apps/delete.test.ts +++ b/test/unit/commands/apps/delete.test.ts @@ -28,9 +28,9 @@ describe("apps:delete command", () => { describe("functionality", () => { it("should delete app successfully with --force flag", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Mock the /me endpoint for getApp (listApps) @@ -69,9 +69,9 @@ describe("apps:delete command", () => { it("should output JSON format when --json flag is used", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; const mockApp = { @@ -114,9 +114,9 @@ describe("apps:delete command", () => { it("should use ABLY_ACCESS_TOKEN environment variable when provided", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; const customToken = "custom_access_token"; @@ -209,9 +209,9 @@ describe("apps:delete command", () => { } // 500: need full pre-mock chain, then fail on DELETE const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; nockControl() .get("/v1/me") @@ -240,9 +240,9 @@ describe("apps:delete command", () => { it("should handle app not found error", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Mock the /me endpoint @@ -267,9 +267,9 @@ describe("apps:delete command", () => { it("should handle errors in JSON format when --json flag is used", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Mock the /me endpoint @@ -315,9 +315,9 @@ describe("apps:delete command", () => { it("should handle 403 forbidden error", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Mock the /me endpoint @@ -359,9 +359,9 @@ describe("apps:delete command", () => { it("should handle 409 conflict error when app has dependencies", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Mock the /me endpoint @@ -406,9 +406,9 @@ describe("apps:delete command", () => { describe("current app handling", () => { it("should use current app when no app ID provided", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; - const accountName = mock.getCurrentAccount()!.accountName!; - const userEmail = mock.getCurrentAccount()!.userEmail!; + const accountId = mock.getCurrentAccount()!.accountId; + const accountName = mock.getCurrentAccount()!.accountName; + const userEmail = mock.getCurrentAccount()!.userEmail; const appId = mock.getCurrentAppId()!; // Set environment variable for current app diff --git a/test/unit/commands/apps/update.test.ts b/test/unit/commands/apps/update.test.ts index 99796ab5..daf1bf4d 100644 --- a/test/unit/commands/apps/update.test.ts +++ b/test/unit/commands/apps/update.test.ts @@ -29,7 +29,7 @@ describe("apps:update command", () => { describe("functionality", () => { it("should update an app name successfully", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; const updatedName = "UpdatedAppName"; @@ -60,7 +60,7 @@ describe("apps:update command", () => { it("should update TLS only flag successfully", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; // Mock the app update endpoint @@ -89,7 +89,7 @@ describe("apps:update command", () => { it("should update both name and TLS only successfully", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; const updatedName = "UpdatedAppName"; @@ -121,7 +121,7 @@ describe("apps:update command", () => { it("should output JSON format when --json flag is used", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; const updatedName = "UpdatedAppName"; @@ -154,7 +154,7 @@ describe("apps:update command", () => { it("should use ABLY_ACCESS_TOKEN environment variable when provided", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; const customToken = "custom_access_token"; const updatedName = "UpdatedAppName"; @@ -324,7 +324,7 @@ describe("apps:update command", () => { describe("output formatting", () => { it("should display APNS sandbox cert status when available", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; // Mock the app update endpoint with APNS cert info @@ -349,7 +349,7 @@ describe("apps:update command", () => { it("should include APNS info in JSON output when available", async () => { const mock = getMockConfigManager(); - const accountId = mock.getCurrentAccount()!.accountId!; + const accountId = mock.getCurrentAccount()!.accountId; const appId = mock.getCurrentAppId()!; // Mock the app update endpoint with APNS cert info diff --git a/test/unit/commands/auth/issue-ably-token.test.ts b/test/unit/commands/auth/issue-ably-token.test.ts index 1fd43b2e..ea06f457 100644 --- a/test/unit/commands/auth/issue-ably-token.test.ts +++ b/test/unit/commands/auth/issue-ably-token.test.ts @@ -39,7 +39,7 @@ describe("auth:issue-ably-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably Token"); + expect(stdout).toContain("Ably token generated."); expect(stdout).toContain(`Token: ${mockTokenDetails.token}`); expect(stdout).toContain("Type: Ably"); expect(restMock.auth.createTokenRequest).toHaveBeenCalled(); @@ -65,7 +65,7 @@ describe("auth:issue-ably-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably Token"); + expect(stdout).toContain("Ably token generated."); expect(restMock.auth.createTokenRequest).toHaveBeenCalled(); const tokenParams = restMock.auth.createTokenRequest.mock.calls[0][0]; expect(tokenParams.capability).toHaveProperty("chat:*"); @@ -89,7 +89,7 @@ describe("auth:issue-ably-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably Token"); + expect(stdout).toContain("Ably token generated."); expect(stdout).toContain("TTL: 7200 seconds"); expect(restMock.auth.createTokenRequest).toHaveBeenCalled(); const tokenParams = restMock.auth.createTokenRequest.mock.calls[0][0]; @@ -115,7 +115,7 @@ describe("auth:issue-ably-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably Token"); + expect(stdout).toContain("Ably token generated."); expect(stdout).toContain(`Client ID: ${customClientId}`); expect(restMock.auth.createTokenRequest).toHaveBeenCalled(); const tokenParams = restMock.auth.createTokenRequest.mock.calls[0][0]; @@ -140,8 +140,8 @@ describe("auth:issue-ably-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably Token"); - expect(stdout).toContain("Client ID: None"); + expect(stdout).toContain("Ably token generated."); + expect(stdout).not.toContain("Client ID:"); expect(restMock.auth.createTokenRequest).toHaveBeenCalled(); const tokenParams = restMock.auth.createTokenRequest.mock.calls[0][0]; expect(tokenParams.clientId).toBeUndefined(); @@ -168,7 +168,7 @@ describe("auth:issue-ably-token command", () => { // Should only output the token string expect(stdout.trim()).toBe(mockTokenString); - expect(stdout).not.toContain("Generated Ably Token"); + expect(stdout).not.toContain("Ably token generated."); }); it("should output JSON format when --json flag is used", async () => { @@ -234,7 +234,7 @@ describe("auth:issue-ably-token command", () => { ); // When no app is configured, command should not produce token output - expect(stdout).not.toContain("Generated Ably Token"); + expect(stdout).not.toContain("Ably token generated."); }); }); diff --git a/test/unit/commands/auth/issue-jwt-token.test.ts b/test/unit/commands/auth/issue-jwt-token.test.ts index a5835e78..b79aa189 100644 --- a/test/unit/commands/auth/issue-jwt-token.test.ts +++ b/test/unit/commands/auth/issue-jwt-token.test.ts @@ -19,7 +19,7 @@ describe("auth:issue-jwt-token command", () => { import.meta.url, ); - expect(stdout).toContain("Generated Ably JWT Token"); + expect(stdout).toContain("Ably JWT token generated."); expect(stdout).toContain("Token:"); expect(stdout).toContain("Type: JWT"); expect(stdout).toContain(`App ID: ${appId}`); @@ -134,7 +134,7 @@ describe("auth:issue-jwt-token command", () => { ); // Should only output the token string (no "Generated" message) - expect(stdout).not.toContain("Generated Ably JWT Token"); + expect(stdout).not.toContain("Ably JWT token generated."); expect(stdout.trim().split(".")).toHaveLength(3); // JWT has 3 parts }); @@ -219,7 +219,7 @@ describe("auth:issue-jwt-token command", () => { ); // When no app is configured, command should not produce token output - expect(stdout).not.toContain("Generated Ably JWT Token"); + expect(stdout).not.toContain("Ably JWT token generated."); }); }); @@ -237,13 +237,22 @@ describe("auth:issue-jwt-token command", () => { expect(stdout).toContain("TTL: 1800 seconds"); }); - it("should display client ID as None when not specified with none", async () => { + it("should display client ID when specified", async () => { + const { stdout } = await runCommand( + ["auth:issue-jwt-token", "--client-id", "my-client"], + import.meta.url, + ); + + expect(stdout).toContain("Client ID: my-client"); + }); + + it("should omit client ID line when not specified with none", async () => { const { stdout } = await runCommand( ["auth:issue-jwt-token", "--client-id", "none"], import.meta.url, ); - expect(stdout).toContain("Client ID: None"); + expect(stdout).not.toContain("Client ID:"); }); }); }); diff --git a/test/unit/commands/auth/keys/current.test.ts b/test/unit/commands/auth/keys/current.test.ts index 5d9928cd..b22d0d06 100644 --- a/test/unit/commands/auth/keys/current.test.ts +++ b/test/unit/commands/auth/keys/current.test.ts @@ -32,7 +32,7 @@ describe("auth:keys:current command", () => { it("should display account and app information", async () => { const mockConfig = getMockConfigManager(); - const accountName = mockConfig.getCurrentAccount()!.accountName!; + const accountName = mockConfig.getCurrentAccount()!.accountName; const appId = mockConfig.getCurrentAppId()!; const appName = mockConfig.getAppName(appId)!; const { stdout } = await runCommand( diff --git a/test/unit/commands/channels/inspect.test.ts b/test/unit/commands/channels/inspect.test.ts index bc3edcba..f7b97395 100644 --- a/test/unit/commands/channels/inspect.test.ts +++ b/test/unit/commands/channels/inspect.test.ts @@ -27,7 +27,7 @@ describe("channels:inspect command", () => { it("should open browser with correct dashboard URL", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const appId = mockConfig.getCurrentAppId()!; const { stdout } = await runCommand( @@ -44,7 +44,7 @@ describe("channels:inspect command", () => { it("should URL-encode special characters in channel name", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const appId = mockConfig.getCurrentAppId()!; const { stdout } = await runCommand( @@ -88,7 +88,7 @@ describe("channels:inspect command", () => { it("should use --app flag over current app", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const { stdout } = await runCommand( ["channels:inspect", "my-channel", "--app", "custom-app-id"], @@ -102,7 +102,7 @@ describe("channels:inspect command", () => { it("should use --app flag when no current app is set", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; mockConfig.setCurrentAppIdForAccount(undefined); const { stdout } = await runCommand( @@ -117,7 +117,7 @@ describe("channels:inspect command", () => { it("should use --dashboard-host flag to override base URL", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const appId = mockConfig.getCurrentAppId()!; const { stdout } = await runCommand( @@ -138,7 +138,7 @@ describe("channels:inspect command", () => { it("should prepend https:// when --dashboard-host has no scheme", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const appId = mockConfig.getCurrentAppId()!; const { stdout } = await runCommand( @@ -164,7 +164,7 @@ describe("channels:inspect command", () => { it("should display URL without opening browser", async () => { const mockConfig = getMockConfigManager(); - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const appId = mockConfig.getCurrentAppId()!; const { stdout } = await runCommand( diff --git a/test/unit/commands/integrations/delete.test.ts b/test/unit/commands/integrations/delete.test.ts index b03929cd..9d4658ad 100644 --- a/test/unit/commands/integrations/delete.test.ts +++ b/test/unit/commands/integrations/delete.test.ts @@ -227,7 +227,7 @@ describe("integrations:delete command", () => { it("should accept --app flag", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const mockIntegration = { id: mockRuleId, appId, diff --git a/test/unit/commands/integrations/get.test.ts b/test/unit/commands/integrations/get.test.ts index d41613e3..44a42510 100644 --- a/test/unit/commands/integrations/get.test.ts +++ b/test/unit/commands/integrations/get.test.ts @@ -262,7 +262,7 @@ describe("integrations:get command", () => { it("should accept --app flag", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const mockIntegration = { id: mockRuleId, appId, diff --git a/test/unit/commands/integrations/update.test.ts b/test/unit/commands/integrations/update.test.ts index 81f84242..b94b895e 100644 --- a/test/unit/commands/integrations/update.test.ts +++ b/test/unit/commands/integrations/update.test.ts @@ -328,7 +328,7 @@ describe("integrations:update command", () => { it("should accept --app flag", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const mockIntegration = { id: mockRuleId, appId, diff --git a/test/unit/commands/queues/create.test.ts b/test/unit/commands/queues/create.test.ts index 3814b2bf..a730c496 100644 --- a/test/unit/commands/queues/create.test.ts +++ b/test/unit/commands/queues/create.test.ts @@ -32,7 +32,7 @@ describe("queues:create command", () => { it("should create a queue successfully with default settings", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -72,7 +72,7 @@ describe("queues:create command", () => { it("should create a queue with custom settings", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -119,7 +119,7 @@ describe("queues:create command", () => { it("should output JSON format when --json flag is used", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -148,7 +148,7 @@ describe("queues:create command", () => { }); it("should use custom app ID when provided", async () => { - const accountId = getMockConfigManager().getCurrentAccount()!.accountId!; + const accountId = getMockConfigManager().getCurrentAccount()!.accountId; const customAppId = "custom-app-id"; nockControl() @@ -180,7 +180,7 @@ describe("queues:create command", () => { it("should use ABLY_ACCESS_TOKEN environment variable when provided", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; const customToken = "custom_access_token"; process.env.ABLY_ACCESS_TOKEN = customToken; @@ -224,7 +224,7 @@ describe("queues:create command", () => { setupNock: (scenario) => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; // Pre-mock /v1/me (needed for app resolution) nockControl() .get("/v1/me") @@ -243,7 +243,7 @@ describe("queues:create command", () => { it("should handle 403 forbidden error", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -269,7 +269,7 @@ describe("queues:create command", () => { it("should handle 404 app not found error", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -316,7 +316,7 @@ describe("queues:create command", () => { it("should handle validation errors from API", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -343,7 +343,7 @@ describe("queues:create command", () => { it("should handle 429 rate limit error", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -372,7 +372,7 @@ describe("queues:create command", () => { it("should accept minimum valid parameter values", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") @@ -415,7 +415,7 @@ describe("queues:create command", () => { it("should accept max parameter values and different regions", async () => { const mockConfig = getMockConfigManager(); const appId = mockConfig.getCurrentAppId()!; - const accountId = mockConfig.getCurrentAccount()!.accountId!; + const accountId = mockConfig.getCurrentAccount()!.accountId; nockControl() .get("/v1/me") diff --git a/test/unit/commands/queues/delete.test.ts b/test/unit/commands/queues/delete.test.ts index b6f60f82..ae0f3800 100644 --- a/test/unit/commands/queues/delete.test.ts +++ b/test/unit/commands/queues/delete.test.ts @@ -55,7 +55,7 @@ describe("queues:delete command", () => { }); it("should delete a queue with custom app ID", async () => { - const accountId = getMockConfigManager().getCurrentAccount()!.accountId!; + const accountId = getMockConfigManager().getCurrentAccount()!.accountId; const customAppId = "custom-app-id"; const mockQueueId = `${customAppId}:us-east-1-a:${mockQueueName}`; diff --git a/test/unit/commands/queues/list.test.ts b/test/unit/commands/queues/list.test.ts index a18ef818..919e3c52 100644 --- a/test/unit/commands/queues/list.test.ts +++ b/test/unit/commands/queues/list.test.ts @@ -154,7 +154,7 @@ describe("queues:list command", () => { }); it("should use custom app ID when provided", async () => { - const accountId = getMockConfigManager().getCurrentAccount()!.accountId!; + const accountId = getMockConfigManager().getCurrentAccount()!.accountId; const customAppId = "custom-app-id"; const mockAppResponse = mockApp({ id: customAppId, accountId }); diff --git a/test/unit/services/config-manager.test.ts b/test/unit/services/config-manager.test.ts index 759ae1a9..e6d32035 100644 --- a/test/unit/services/config-manager.test.ts +++ b/test/unit/services/config-manager.test.ts @@ -260,6 +260,7 @@ accessToken = "testaccesstoken" configManager.storeAccount("newaccesstoken", "newaccount", { accountId: "newaccountid", accountName: "New Account", + userEmail: "new@test.com", }); expect(writeFileStub).toHaveBeenCalledOnce(); @@ -281,7 +282,11 @@ accessToken = "testaccesstoken" const writeFileStub = vi.spyOn(fs, "writeFileSync"); const manager = new TomlConfigManager(); - manager.storeAccount("firstaccesstoken", "firstaccount"); + manager.storeAccount("firstaccesstoken", "firstaccount", { + accountId: "acc-1", + accountName: "First", + userEmail: "first@test.com", + }); expect(writeFileStub).toHaveBeenCalledOnce(); expect(manager.getCurrentAccountAlias()).toBe("firstaccount"); @@ -310,7 +315,11 @@ accessToken = "testaccesstoken" it("should store an API key for an app with a specific account", () => { const writeFileStub = fs.writeFileSync as ReturnType; // First create a new account - configManager.storeAccount("anotheraccesstoken", "anotheraccount"); + configManager.storeAccount("anotheraccesstoken", "anotheraccount", { + accountId: "acc-2", + accountName: "Another", + userEmail: "another@test.com", + }); configManager.storeAppKey( "anotherappid", @@ -379,7 +388,11 @@ accessToken = "testaccesstoken" it("should switch to another account and return true", () => { const writeFileStub = fs.writeFileSync as ReturnType; // First create another account - configManager.storeAccount("anotheraccesstoken", "anotheraccount"); + configManager.storeAccount("anotheraccesstoken", "anotheraccount", { + accountId: "acc-2", + accountName: "Another", + userEmail: "another@test.com", + }); expect(configManager.switchAccount("anotheraccount")).toBe(true); // writeFileSync called for storeAccount and switchAccount