Skip to content

Commit fa74360

Browse files
committed
Adds more happy path E2E tests, now covering most commands
1 parent d61dc90 commit fa74360

34 files changed

+3737
-47
lines changed

src/base-command.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,15 @@ export abstract class AblyBaseCommand extends InteractiveBaseCommand {
751751
return { apiKey, appId };
752752
}
753753

754+
// Fall back to ABLY_API_KEY environment variable (for CI/scripting)
755+
const envApiKey = process.env.ABLY_API_KEY;
756+
if (envApiKey) {
757+
const envAppId = envApiKey.split(".")[0] || "";
758+
if (envAppId) {
759+
return { apiKey: envApiKey, appId: envAppId };
760+
}
761+
}
762+
754763
// Get access token for control API
755764
const accessToken =
756765
process.env.ABLY_ACCESS_TOKEN || this.configManager.getAccessToken();

src/commands/logs/subscribe.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,6 @@ export default class LogsSubscribe extends AblyBaseCommand {
6262
includeUserFriendlyMessages: true,
6363
});
6464

65-
// Get the logs channel
66-
const appConfig = await this.ensureAppAndKey(flags);
67-
if (!appConfig) {
68-
this.fail(
69-
"Unable to determine app configuration",
70-
flags,
71-
"logSubscribe",
72-
);
73-
}
7465
const logsChannelName = `[meta]log`;
7566

7667
// Configure channel options for rewind if specified

src/commands/rooms/typing/keystroke.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,24 @@ export default class TypingKeystroke extends ChatBaseCommand {
160160
}, KEYSTROKE_INTERVAL);
161161
}
162162

163-
this.logCliEvent(
164-
flags,
165-
"typing",
166-
"listening",
167-
"Maintaining typing status...",
168-
);
163+
// If auto-type is enabled, keep the command running to maintain typing state
164+
if (flags["auto-type"]) {
165+
this.logCliEvent(
166+
flags,
167+
"typing",
168+
"listening",
169+
"Maintaining typing status...",
170+
);
169171

170-
// Wait until the user interrupts, duration elapses, or the room fails
171-
await Promise.race([
172-
this.waitAndTrackCleanup(flags, "typing", flags.duration),
173-
failurePromise,
174-
]);
172+
// Wait until the user interrupts, duration elapses, or the room fails
173+
await Promise.race([
174+
this.waitAndTrackCleanup(flags, "typing", flags.duration),
175+
failurePromise,
176+
]);
177+
} else {
178+
// Suppress unhandled rejection if room fails during cleanup
179+
failurePromise.catch(() => {});
180+
}
175181
} catch (error) {
176182
this.fail(error, flags, "roomTypingKeystroke", { room: args.room });
177183
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {
2+
describe,
3+
it,
4+
beforeEach,
5+
afterEach,
6+
beforeAll,
7+
afterAll,
8+
expect,
9+
} from "vitest";
10+
import {
11+
E2E_ACCESS_TOKEN,
12+
SHOULD_SKIP_CONTROL_E2E,
13+
forceExit,
14+
cleanupTrackedResources,
15+
setupTestFailureHandler,
16+
resetTestTracking,
17+
} from "../../helpers/e2e-test-helper.js";
18+
import { runCommand } from "../../helpers/command-helpers.js";
19+
20+
describe.skipIf(SHOULD_SKIP_CONTROL_E2E)("Accounts E2E Tests", () => {
21+
beforeAll(() => {
22+
process.on("SIGINT", forceExit);
23+
});
24+
25+
afterAll(() => {
26+
process.removeListener("SIGINT", forceExit);
27+
});
28+
29+
beforeEach(() => {
30+
resetTestTracking();
31+
});
32+
33+
afterEach(async () => {
34+
await cleanupTrackedResources();
35+
});
36+
37+
it(
38+
"should list locally configured accounts",
39+
{ timeout: 15000 },
40+
async () => {
41+
setupTestFailureHandler("should list locally configured accounts");
42+
43+
// accounts list reads from local config, not the API directly.
44+
// In E2E environment, there may or may not be configured accounts.
45+
// We just verify the command runs without crashing.
46+
const listResult = await runCommand(["accounts", "list"], {
47+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
48+
});
49+
50+
// The command may exit 0 (accounts found) or non-zero (no accounts configured).
51+
// Either way, it should produce output and not crash.
52+
const combinedOutput = listResult.stdout + listResult.stderr;
53+
expect(combinedOutput.length).toBeGreaterThan(0);
54+
},
55+
);
56+
57+
it("should show help for accounts current", { timeout: 10000 }, async () => {
58+
setupTestFailureHandler("should show help for accounts current");
59+
60+
const helpResult = await runCommand(["accounts", "current", "--help"], {
61+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
62+
});
63+
64+
expect(helpResult.exitCode).toBe(0);
65+
const output = helpResult.stdout + helpResult.stderr;
66+
expect(output).toContain("USAGE");
67+
});
68+
});
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import {
2+
describe,
3+
it,
4+
beforeEach,
5+
afterEach,
6+
beforeAll,
7+
afterAll,
8+
expect,
9+
} from "vitest";
10+
import {
11+
E2E_ACCESS_TOKEN,
12+
SHOULD_SKIP_CONTROL_E2E,
13+
forceExit,
14+
cleanupTrackedResources,
15+
setupTestFailureHandler,
16+
resetTestTracking,
17+
} from "../../helpers/e2e-test-helper.js";
18+
import { runCommand } from "../../helpers/command-helpers.js";
19+
import { parseNdjsonLines } from "../../helpers/ndjson.js";
20+
21+
describe.skipIf(SHOULD_SKIP_CONTROL_E2E)("Auth Keys E2E Tests", () => {
22+
let testAppId: string;
23+
24+
beforeAll(async () => {
25+
process.on("SIGINT", forceExit);
26+
27+
// Create a test app for key operations
28+
const createResult = await runCommand(
29+
["apps", "create", "--name", `e2e-keys-test-${Date.now()}`, "--json"],
30+
{
31+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
32+
},
33+
);
34+
35+
if (createResult.exitCode !== 0) {
36+
throw new Error(`Failed to create test app: ${createResult.stderr}`);
37+
}
38+
const result = parseNdjsonLines(createResult.stdout).find(
39+
(r) => r.type === "result",
40+
) as Record<string, unknown>;
41+
const app = result.app as Record<string, unknown>;
42+
testAppId = (app.id ?? app.appId) as string;
43+
if (!testAppId) {
44+
throw new Error(`No app ID found in result: ${JSON.stringify(result)}`);
45+
}
46+
});
47+
48+
afterAll(async () => {
49+
if (testAppId) {
50+
try {
51+
await runCommand(["apps", "delete", testAppId, "--force"], {
52+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
53+
});
54+
} catch {
55+
// Ignore cleanup errors — the app may already be deleted
56+
}
57+
}
58+
process.removeListener("SIGINT", forceExit);
59+
});
60+
61+
beforeEach(() => {
62+
resetTestTracking();
63+
});
64+
65+
afterEach(async () => {
66+
await cleanupTrackedResources();
67+
});
68+
69+
it("should list API keys for an app", { timeout: 15000 }, async () => {
70+
setupTestFailureHandler("should list API keys for an app");
71+
72+
const listResult = await runCommand(
73+
["auth", "keys", "list", "--app", testAppId, "--json"],
74+
{
75+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
76+
},
77+
);
78+
79+
expect(listResult.exitCode).toBe(0);
80+
const records = parseNdjsonLines(listResult.stdout);
81+
const result = records.find((r) => r.type === "result");
82+
expect(result).toBeDefined();
83+
expect(result).toHaveProperty("success", true);
84+
expect(Array.isArray(result!.keys)).toBe(true);
85+
// Every app has at least one default key
86+
expect((result!.keys as unknown[]).length).toBeGreaterThan(0);
87+
});
88+
89+
it("should create a new API key", { timeout: 15000 }, async () => {
90+
setupTestFailureHandler("should create a new API key");
91+
92+
const keyName = `e2e-test-key-${Date.now()}`;
93+
const createResult = await runCommand(
94+
[
95+
"auth",
96+
"keys",
97+
"create",
98+
"--app",
99+
testAppId,
100+
"--name",
101+
keyName,
102+
"--json",
103+
],
104+
{
105+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
106+
},
107+
);
108+
109+
expect(createResult.exitCode).toBe(0);
110+
const result = parseNdjsonLines(createResult.stdout).find(
111+
(r) => r.type === "result",
112+
) as Record<string, unknown>;
113+
expect(result).toBeDefined();
114+
expect(result).toHaveProperty("success", true);
115+
const key = result.key as Record<string, unknown>;
116+
expect(key).toHaveProperty("name", keyName);
117+
expect(key).toHaveProperty("key");
118+
expect(key).toHaveProperty("keyName");
119+
});
120+
121+
it("should get details for a specific key", { timeout: 20000 }, async () => {
122+
setupTestFailureHandler("should get details for a specific key");
123+
124+
// First create a key to get
125+
const keyName = `e2e-get-key-${Date.now()}`;
126+
const createResult = await runCommand(
127+
[
128+
"auth",
129+
"keys",
130+
"create",
131+
"--app",
132+
testAppId,
133+
"--name",
134+
keyName,
135+
"--json",
136+
],
137+
{
138+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
139+
},
140+
);
141+
142+
const createRecord = parseNdjsonLines(createResult.stdout).find(
143+
(r) => r.type === "result",
144+
) as Record<string, unknown>;
145+
const createdKey = createRecord.key as Record<string, unknown>;
146+
const keyFullName = createdKey.keyName as string;
147+
148+
// Now get that key by its name
149+
const getResult = await runCommand(
150+
["auth", "keys", "get", keyFullName, "--app", testAppId, "--json"],
151+
{
152+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
153+
},
154+
);
155+
156+
expect(getResult.exitCode).toBe(0);
157+
const getRecord = parseNdjsonLines(getResult.stdout).find(
158+
(r) => r.type === "result",
159+
) as Record<string, unknown>;
160+
expect(getRecord).toBeDefined();
161+
expect(getRecord).toHaveProperty("success", true);
162+
const fetchedKey = getRecord.key as Record<string, unknown>;
163+
expect(fetchedKey).toHaveProperty("name", keyName);
164+
expect(fetchedKey).toHaveProperty("keyName", keyFullName);
165+
});
166+
167+
it("should update a key name", { timeout: 20000 }, async () => {
168+
setupTestFailureHandler("should update a key name");
169+
170+
// First create a key to update
171+
const originalName = `e2e-update-key-${Date.now()}`;
172+
const createResult = await runCommand(
173+
[
174+
"auth",
175+
"keys",
176+
"create",
177+
"--app",
178+
testAppId,
179+
"--name",
180+
originalName,
181+
"--json",
182+
],
183+
{
184+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
185+
},
186+
);
187+
188+
const createRecord = parseNdjsonLines(createResult.stdout).find(
189+
(r) => r.type === "result",
190+
) as Record<string, unknown>;
191+
const createdKey = createRecord.key as Record<string, unknown>;
192+
const keyFullName = createdKey.keyName as string;
193+
194+
// Update the key name
195+
const updatedName = `updated-key-${Date.now()}`;
196+
const updateResult = await runCommand(
197+
[
198+
"auth",
199+
"keys",
200+
"update",
201+
keyFullName,
202+
"--app",
203+
testAppId,
204+
"--name",
205+
updatedName,
206+
"--json",
207+
],
208+
{
209+
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
210+
},
211+
);
212+
213+
expect(updateResult.exitCode).toBe(0);
214+
const updateRecord = parseNdjsonLines(updateResult.stdout).find(
215+
(r) => r.type === "result",
216+
) as Record<string, unknown>;
217+
expect(updateRecord).toBeDefined();
218+
expect(updateRecord).toHaveProperty("success", true);
219+
const updatedKey = updateRecord.key as Record<string, unknown>;
220+
const nameChange = updatedKey.name as Record<string, unknown>;
221+
expect(nameChange).toHaveProperty("before", originalName);
222+
expect(nameChange).toHaveProperty("after", updatedName);
223+
});
224+
});

0 commit comments

Comments
 (0)