Skip to content

Conversation

@AndyTWF
Copy link
Contributor

@AndyTWF AndyTWF commented Dec 7, 2025

There are numerous tests that don't test anything useful (e.g. asserting that a stub works instead of testing the actual command). There are also plenty of files that don't properly test anything. This PR improves the tests.

FTF-156


Note

Overhauls unit tests to exercise real CLI behavior, adds a duration flag to bench subscriber, and streamlines CI E2E workflows with minor docs updates.

  • Benchmarks (CLI)
    • Add --duration (-d) flag to ably bench subscriber and implement timed shutdown via waitUntilInterruptedOrTimeout.
  • Tests
    • Replace superficial tests with end‑to‑end unit tests using @oclif/test, nock, and global mocks (__TEST_MOCKS__) across commands: accounts (login/logout), auth (revoke-token), channels (batch-publish/history/list/presence/subscribe), rooms (messages), spaces, help, status, and benchmarks.
  • CI/CD
    • Simplify GitHub Actions triggers (remove path filters, normalize branch lists) and prune artifact uploads/summary aggregation in e2e-web-cli-parallel.yml.
  • Docs
    • Update README to document new --duration flag for bench subscriber.

Written by Cursor Bugbot for commit a31bfc0. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features
    • Added a --duration (-d) flag to the benchmark subscribe command, allowing you to specify how long to subscribe for in seconds instead of running indefinitely until manual interruption.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 7, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
cli-web-cli Ready Ready Preview Comment Dec 11, 2025 0:07am

@coderabbitai
Copy link

coderabbitai bot commented Dec 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR addresses test audit concerns by refactoring unit tests from heavy class-based mocks and internal subclasses to lightweight runCommand-based integration testing with global mocks. Additionally, a duration flag is introduced to the bench subscriber command to enable timeout-based subscription limiting.

Changes

Cohort / File(s) Summary
Feature: Bench Subscriber Duration Control
src/commands/bench/subscriber.ts
Added duration flag (integer, -d) to limit subscription duration in seconds; updated waitForTermination method to use new waitUntilInterruptedOrTimeout utility with flag-driven timeout, replacing indefinite wait.
Test Refactor: Accounts & Auth Commands
test/unit/commands/accounts/login.test.ts, test/unit/commands/accounts/logout.test.ts, test/unit/commands/auth/revoke-token.test.ts
Replaced in-memory config/FS mocking with temporary config directories; introduced nock for HTTP endpoint mocking; shifted from internal-logic tests to CLI-driven end-to-end assertions on stdout/JSON output; simplified test scaffolding and removed custom command subclasses.
Test Refactor: Bench & Channel Commands
test/unit/commands/bench/benchmarking.test.ts, test/unit/commands/channels/batch-publish.test.ts, test/unit/commands/channels/history.test.ts, test/unit/commands/channels/list.test.ts
Replaced class-based test doubles and heavy mocking with global __TEST_MOCKS__ container; adopted runCommand for CLI invocation; removed direct Ably client stubs in favor of mocked request flows; refactored assertions to validate observable CLI behavior (help text, flags, stdout, JSON output) rather than internal state.
Test Refactor: Presence & Subscription Commands
test/unit/commands/channels/presence/enter.test.ts, test/unit/commands/channels/presence/subscribe.test.ts, test/unit/commands/channels/subscribe.test.ts
Removed TestableChannelsPresence* subclasses and deep Ably lifecycle mocking; introduced global mocks for channel/connection/presence structures; shifted to CLI-based testing with runCommand; added tests for help output, flag availability, and functional presence/subscription flows with simplified mocks.
Test Refactor: Utility & Root Commands
test/unit/commands/help.test.ts, test/unit/commands/spaces/spaces.test.ts, test/unit/commands/status.test.ts, test/unit/commands/support.test.ts
Eliminated custom TestableCommand subclasses and console/process.exit spying; adopted runCommand-based testing; integrated nock for HTTP mocking (status endpoint); reorganized tests into focused scenarios (help, arguments, errors, flags); replaced internal assertion patterns with stdout/stderr validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring close attention:

  • Global mock consistency: Verify that __TEST_MOCKS__ initialization and cleanup patterns are correctly applied across all test files and that mock structures (ablyRealtimeMock, ablyRestMock) properly simulate necessary Ably client behavior.
  • Test coverage parity: Ensure that the transition from internal-logic tests to end-to-end CLI tests maintains equivalent coverage; spot-check that previously validated error paths, edge cases, and JSON output scenarios are still explicitly tested.
  • HTTP mocking with nock: Review nock endpoint configurations (e.g., /v1/me, /v1/accounts/{id}/apps, /status/up.json) to confirm they match actual CLI request patterns and that cleanup/scope are correct to avoid test pollution.
  • Duration flag integration: Validate that waitUntilInterruptedOrTimeout behavior correctly ties to the new duration flag and that existing indefinite-wait tests remain valid where duration is not specified.

Suggested reviewers

  • m-hulbert

Poem

🐰 Tests once tangled, now untied,
Mocks refined with CLI pride,
Global threads and timeout grace,
Integration speeds the race—
Real behavior shines so bright,

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title is generic and vague. It uses non-descriptive phrasing like 'remove redundant tests, add additional' without conveying meaningful details about the changes. Replace with a more specific title that describes the main change, such as 'test: refactor test suites to use oclif/test and remove mocking scaffolding' or similar.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR directly addresses FTF-156 objectives by removing tests that validate only test scaffolding/stubs rather than real command logic, and replacing them with tests using runCommand and mocked HTTP/Ably interactions.
Out of Scope Changes check ✅ Passed All changes are focused on test refactoring—removing class-based mocks and stubs in favor of CLI-driven tests—which aligns with the FTF-156 test audit objectives. No production code was modified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-pointless-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (24)
test/unit/commands/support.test.ts (1)

44-61: Consider using a test helper for environment variable management.

The manual save/restore pattern for process.env.ABLY_CLI_NON_INTERACTIVE is correct but could be simplified using Vitest's vi.stubEnv() or a dedicated test helper to reduce boilerplate and improve maintainability across test files.

Example using Vitest's built-in environment mocking:

-    const originalEnv = process.env.ABLY_CLI_NON_INTERACTIVE;
-    process.env.ABLY_CLI_NON_INTERACTIVE = "true";
-
-    try {
-      const { stdout, stderr } = await runCommand(
-        ["support", "unknowncommand"],
-        import.meta.url,
-      );
-
-      // Should show error or warning about command not found
-      const output = stdout + (stderr || "");
-      expect(output).toMatch(/not found|not.*command|available/i);
-    } finally {
-      if (originalEnv === undefined) {
-        delete process.env.ABLY_CLI_NON_INTERACTIVE;
-      } else {
-        process.env.ABLY_CLI_NON_INTERACTIVE = originalEnv;
-      }
-    }
+    vi.stubEnv("ABLY_CLI_NON_INTERACTIVE", "true");
+    
+    const { stdout, stderr } = await runCommand(
+      ["support", "unknowncommand"],
+      import.meta.url,
+    );
+    
+    // Should show error or warning about command not found
+    const output = stdout + (stderr || "");
+    expect(output).toMatch(/not found|not.*command|available/i);
+    
+    vi.unstubAllEnvs();
test/unit/commands/accounts/logout.test.ts (4)

11-19: Consider more robust temp directory naming to avoid collisions.

Using Date.now() alone for directory naming could lead to collisions if tests run in parallel at the same millisecond. Consider using crypto.randomBytes() or a combination of timestamp and random string for guaranteed uniqueness.

+import { randomBytes } from "node:crypto";
+
 describe("accounts:logout command", () => {
   let testConfigDir: string;
   let originalConfigDir: string;

   beforeEach(() => {
     // Create a temporary config directory for testing
-    testConfigDir = resolve(tmpdir(), `ably-cli-test-logout-${Date.now()}`);
+    testConfigDir = resolve(tmpdir(), `ably-cli-test-logout-${Date.now()}-${randomBytes(4).toString("hex")}`);
     mkdirSync(testConfigDir, { recursive: true, mode: 0o700 });

65-75: Verify exitCode for error scenarios.

Error cases should also verify that the command exits with a non-zero exit code, not just JSON error output. The runCommand helper returns exitCode which should be checked.

     it("should output error in JSON format when no account is selected", async () => {
-      const { stdout } = await runCommand(
+      const { stdout, exitCode } = await runCommand(
         ["accounts:logout", "--json"],
         import.meta.url,
       );

       const result = JSON.parse(stdout);
       expect(result).toHaveProperty("success", false);
       expect(result).toHaveProperty("error");
       expect(result.error).toContain("No account");
+      expect(exitCode).not.toBe(0);
     });

93-115: Consider verifying actual config file changes and exit codes.

While JSON output validation is useful, these tests would be more thorough if they also:

  1. Verified the command exits with code 0 for success
  2. Checked that the account was actually removed from the config file (side effect verification)

This would ensure the command is working end-to-end, not just producing correct JSON output.

Example enhancement for one test:

+import { readFileSync } from "node:fs";
+
     it("should successfully logout with --force and --json flags", async () => {
-      const { stdout } = await runCommand(
+      const { stdout, exitCode } = await runCommand(
         ["accounts:logout", "--force", "--json"],
         import.meta.url,
       );

       const result = JSON.parse(stdout);
       expect(result).toHaveProperty("success", true);
       expect(result).toHaveProperty("account");
       expect(result.account).toHaveProperty("alias", "testaccount");
       expect(result).toHaveProperty("remainingAccounts");
+      expect(exitCode).toBe(0);
+      
+      // Verify account was actually removed from config
+      const configContent = readFileSync(resolve(testConfigDir, "config"), "utf-8");
+      expect(configContent).not.toContain("[accounts.testaccount]");
     });

179-189: Verify exitCode for error scenario.

Similar to the earlier error test, this should also verify that the command exits with a non-zero exit code.

     it("should output error in JSON format when account alias does not exist", async () => {
-      const { stdout } = await runCommand(
+      const { stdout, exitCode } = await runCommand(
         ["accounts:logout", "nonexistent", "--json"],
         import.meta.url,
       );

       const result = JSON.parse(stdout);
       expect(result).toHaveProperty("success", false);
       expect(result).toHaveProperty("error");
       expect(result.error).toContain("not found");
+      expect(exitCode).not.toBe(0);
     });
test/unit/commands/status.test.ts (1)

74-88: Consider adding assertion for browser-opening behavior.

The test comment on line 85 mentions browser opening is simulated, but the only assertion is that the status URL appears in stdout. Consider verifying that the --open flag triggers the expected browser-opening code path, or document why this level of coverage is sufficient.

test/unit/commands/channels/presence/subscribe.test.ts (1)

112-143: Timing-based test coordination may be fragile.

The 50ms setTimeout on line 125 for waiting on subscription setup is a timing-based approach that could cause flaky tests under load. Consider using a more deterministic approach such as polling for presenceCallback to be defined, or restructuring the mock to immediately invoke the callback.

-      // Wait for subscription setup
-      await new Promise((resolve) => setTimeout(resolve, 50));
+      // Wait for subscription setup with polling
+      await vi.waitFor(() => expect(presenceCallback).not.toBeNull(), { timeout: 500 });
test/unit/commands/channels/presence/enter.test.ts (1)

192-227: Same timing concern as subscribe tests.

Line 199 uses a 50ms setTimeout for async coordination, which has the same potential for flakiness as noted in the subscribe tests.

test/unit/commands/channels/list.test.ts (2)

58-60: Consider adding vi.restoreAllMocks() in afterEach for consistency.

Other test files in this PR (e.g., benchmarking.test.ts) call vi.restoreAllMocks() in afterEach. Adding it here ensures mock state doesn't leak between tests.

 afterEach(() => {
   delete globalThis.__TEST_MOCKS__;
+  vi.restoreAllMocks();
 });

223-241: Flags section duplicates help tests.

These tests only verify flags appear in help output, which overlaps with the help tests. The actual flag behavior is already tested in the "channel listing" section (lines 140-161). Consider removing this section to reduce redundancy, or consolidating with the help tests.

test/unit/commands/auth/revoke-token.test.ts (2)

26-60: Consider cleaning up the temporary config directory in afterEach.

The test creates a temporary directory at testConfigDir but doesn't clean it up. While the OS will eventually clean tmpdir(), explicitly removing it prevents accumulation during repeated test runs.

+import { rmSync } from "node:fs";
 // ...
 afterEach(() => {
   nock.cleanAll();
   delete globalThis.__TEST_MOCKS__;
   process.env.ABLY_CLI_CONFIG_DIR = originalConfigDir;
+  try {
+    rmSync(testConfigDir, { recursive: true, force: true });
+  } catch {
+    // Ignore cleanup errors
+  }
 });

300-329: Flags section duplicates help tests.

Similar to the channels:list tests, these only verify flags appear in help output, which is already covered in the help section. Consider consolidating.

test/unit/commands/bench/benchmarking.test.ts (1)

265-275: Duration flag tested only in help output.

While the help test verifies the --duration flag is documented, there's no functional test that actually exercises the duration-limited subscription behavior. Consider adding a test that passes --duration and verifies the command terminates after the specified time.

Example test:

it("should terminate after specified duration", async () => {
  const startTime = Date.now();
  const { error } = await runCommand(
    [
      "bench:subscriber",
      "test-channel",
      "--api-key",
      "app.key:secret",
      "--duration",
      "1",
      "--json",
    ],
    import.meta.url,
  );

  const elapsed = Date.now() - startTime;
  expect(error).toBeUndefined();
  // Should complete around 1 second (with some tolerance)
  expect(elapsed).toBeGreaterThanOrEqual(900);
  expect(elapsed).toBeLessThan(3000);
});
test/unit/commands/channels/history.test.ts (1)

87-216: Well-structured history retrieval tests.

The test coverage is comprehensive, including successful retrieval, message details, empty history, JSON output, various flags (--limit, --direction, --start, --end), and error handling. The approach of verifying mock calls with expected parameters ensures flags are properly passed through.

Consider adding a test for JSON output with empty history to ensure the JSON structure is correct even when no messages are found:

it("should output empty JSON array when no messages found", async () => {
  mockHistory.mockResolvedValue({ items: [] });
  
  const { stdout } = await runCommand(
    [
      "channels:history",
      "test-channel",
      "--api-key",
      "app.key:secret",
      "--json",
    ],
    import.meta.url,
  );
  
  const result = JSON.parse(stdout);
  expect(result).toHaveProperty("messages");
  expect(result.messages).toHaveLength(0);
});
test/unit/commands/spaces/spaces.test.ts (6)

173-180: Remove redundant help flag verification.

This test only verifies that --profile appears in the help text, which doesn't validate any actual command behavior. The functional test at lines 153-171 already confirms that the --profile flag works correctly by testing the actual command execution with profile data.

Given the PR's objective to remove tests that don't test useful behavior, this test should be removed.

-  it("should accept --profile flag", async () => {
-    const { stdout } = await runCommand(
-      ["spaces:members:enter", "--help"],
-      import.meta.url,
-    );
-
-    expect(stdout).toContain("--profile");
-  });

183-194: Consider adding functional test for spaces members subscribe.

The mockMembersSubscribe mock is initialized in beforeEach but never verified in any test. This describe block only contains a help text test and lacks coverage of the actual subscribe functionality.

Do you want me to generate a functional test that verifies the subscribe command actually calls the mock?


208-215: Remove redundant help flag verification and consider functional test.

This test only verifies that --location appears in the help text without testing any actual command behavior. Similar to the --profile test, this should be removed per the PR's objective.

Additionally, mockLocationsSet is initialized but never verified in any test, indicating missing functional coverage.

-  it("should accept --location flag", async () => {
-    const { stdout } = await runCommand(
-      ["spaces:locations:set", "--help"],
-      import.meta.url,
-    );
-
-    expect(stdout).toContain("--location");
-  });

231-238: Remove redundant help flag verification and consider functional test.

This test only checks help text for the --data flag without validating command behavior. Consistent with the PR's objective to remove non-functional tests, this should be removed.

The mockLocksAcquire mock is also unused, suggesting missing functional test coverage.

-  it("should accept --data flag", async () => {
-    const { stdout } = await runCommand(
-      ["spaces:locks:acquire", "--help"],
-      import.meta.url,
-    );
-
-    expect(stdout).toContain("--data");
-  });

253-261: Remove redundant help flag verification and consider functional test.

This test only verifies position flags appear in help text using regex matching, without testing actual cursor set behavior. This aligns with tests that should be removed per the PR objective.

The mockCursorsSet mock is initialized but never used in assertions, indicating missing functional coverage.

-  it("should accept position flags", async () => {
-    const { stdout } = await runCommand(
-      ["spaces:cursors:set", "--help"],
-      import.meta.url,
-    );
-
-    expect(stdout).toMatch(/--x|--data/);
-    expect(stdout).toMatch(/--y|--data/);
-  });

13-34: Consider removing or utilizing unused mocks.

The following mocks are initialized in beforeEach but never verified in any test assertions:

  • mockMembersUnsubscribe
  • mockSpaceLeave
  • mockLocationsGetAll
  • mockLocksGetAll
  • mockCursorsGetAll

This creates unnecessary test setup overhead. Either add functional tests that verify these mocks are called, or remove them from the setup to simplify the test harness.

test/unit/commands/channels/batch-publish.test.ts (4)

1-33: Global Ably mock wiring looks solid; double‑check runCommand load options & centralize __TEST_MOCKS__ typing

  • The beforeEach/afterEach pattern with globalThis.__TEST_MOCKS__ cleanly injects a mocked Ably REST client and removes it after each test, which aligns well with the PR goal of exercising real command behavior via a shared mock instead of per‑test subclasses. 👍
  • Two small follow‑ups to consider:
    • The runCommand function from @oclif/test has the signature runCommand(args: string | string[], loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions), where loadOpts is an options object, not a plain string.(jsdocs.io) Here you pass import.meta.url directly as the second argument in all call sites. It would be safer (and more type‑correct) either to:
      • Drop the second argument entirely if you don’t need custom load options, e.g. await runCommand(["channels:batch-publish", "--help"]), or
      • Wrap it in the appropriate options object if you do, e.g. await runCommand([...], { dir: import.meta.url }) (or whatever Interfaces.LoadOptions expects in this codebase).
    • The declare global for __TEST_MOCKS__ is inline in this file. If other tests also augment globalThis.__TEST_MOCKS__, consider moving the declaration into a single shared .d.ts (or helper) so the shape is defined once and can evolve without divergence.

35-55: Help/flags coverage is good; minor duplication you could consolidate later

The help and flags suites do a nice job asserting real CLI help text and that all key flags are exposed via --help, which is exactly the kind of behavior‑level testing FTF‑156 is aiming for.

If you ever want to trim a bit of duplication, you could capture stdout from a single --help invocation in each describe and assert multiple expectations against it (rather than re‑running runCommand for each flag name), but that’s purely an optimization—what you have is already clear and correct.

Also applies to: 341-386


94-339: End‑to‑end batch‑publish behavior, JSON mode, and error paths are very well covered

This describe("batch publish functionality") block does a thorough job of exercising the command as a black box:

  • Verifies the Ably REST call shape via mockRequest (method, path, version argument, and body fields for channels, data, name, and encoding).
  • Confirms both --channels and --channels-json inputs, plus the --spec path, all drive the same underlying request structure.
  • Tests both human‑readable output and --json mode, including success and error shapes, by parsing the JSON that follows the initial “Sending batch publish request” log line.
  • Covers both full failures (rejected request) and partial success responses, ensuring the user‑facing messaging (“partially successful”, JSON success: false, and error text) is correct.

The JSON extraction via findIndex((line) => line.trim() === "{") is a bit opinionated about pretty‑printing, but that’s acceptable given the current output format; if the formatter ever changes, you can always relax this to look for the first line starting with { instead.


31-33: Optional: consider centralizing a vi cleanup in shared test setup

In this file the only persistent thing torn down in afterEach is globalThis.__TEST_MOCKS__, and the vi mocks are local and recreated per test, so nothing is strictly wrong.

If, however, your broader suite uses vi to stub globals or shared modules, you might want to add a vi.restoreAllMocks() / vi.clearAllMocks() in a shared Vitest setup file (rather than here) to guarantee cross‑test isolation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ee6a8ad and 8d096b6.

📒 Files selected for processing (15)
  • src/commands/bench/subscriber.ts (3 hunks)
  • test/unit/commands/accounts/login.test.ts (1 hunks)
  • test/unit/commands/accounts/logout.test.ts (1 hunks)
  • test/unit/commands/auth/revoke-token.test.ts (1 hunks)
  • test/unit/commands/bench/benchmarking.test.ts (1 hunks)
  • test/unit/commands/channels/batch-publish.test.ts (1 hunks)
  • test/unit/commands/channels/history.test.ts (1 hunks)
  • test/unit/commands/channels/list.test.ts (2 hunks)
  • test/unit/commands/channels/presence/enter.test.ts (1 hunks)
  • test/unit/commands/channels/presence/subscribe.test.ts (1 hunks)
  • test/unit/commands/channels/subscribe.test.ts (1 hunks)
  • test/unit/commands/help.test.ts (1 hunks)
  • test/unit/commands/spaces/spaces.test.ts (1 hunks)
  • test/unit/commands/status.test.ts (1 hunks)
  • test/unit/commands/support.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/AI-Assistance.mdc)

**/*.test.ts: When testing, mock the Ably SDK properly by stubbing the constructor (e.g., sinon.stub(Ably, 'Rest').returns(mockClient)).
Always ensure resources are properly closed/cleaned up in tests (e.g., call await client.close() and sinon.restore() in afterEach).

Files:

  • test/unit/commands/help.test.ts
  • test/unit/commands/status.test.ts
  • test/unit/commands/channels/list.test.ts
  • test/unit/commands/channels/presence/subscribe.test.ts
  • test/unit/commands/channels/history.test.ts
  • test/unit/commands/support.test.ts
  • test/unit/commands/spaces/spaces.test.ts
  • test/unit/commands/channels/batch-publish.test.ts
  • test/unit/commands/channels/presence/enter.test.ts
  • test/unit/commands/accounts/logout.test.ts
  • test/unit/commands/auth/revoke-token.test.ts
  • test/unit/commands/accounts/login.test.ts
  • test/unit/commands/bench/benchmarking.test.ts
  • test/unit/commands/channels/subscribe.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/Development.mdc)

**/*.{ts,tsx}: Use TypeScript and follow best practice naming conventions
ESLint is used to ensure all code adheres best practices; ensure you check that all code passes the linter before concluding your work.

Files:

  • test/unit/commands/help.test.ts
  • src/commands/bench/subscriber.ts
  • test/unit/commands/status.test.ts
  • test/unit/commands/channels/list.test.ts
  • test/unit/commands/channels/presence/subscribe.test.ts
  • test/unit/commands/channels/history.test.ts
  • test/unit/commands/support.test.ts
  • test/unit/commands/spaces/spaces.test.ts
  • test/unit/commands/channels/batch-publish.test.ts
  • test/unit/commands/channels/presence/enter.test.ts
  • test/unit/commands/accounts/logout.test.ts
  • test/unit/commands/auth/revoke-token.test.ts
  • test/unit/commands/accounts/login.test.ts
  • test/unit/commands/bench/benchmarking.test.ts
  • test/unit/commands/channels/subscribe.test.ts
{test/**/*.{ts,js},**/*.{test,spec}.{ts,js}}

📄 CodeRabbit inference engine (.cursor/rules/Development.mdc)

When new features are added or changes made, tests must be updated or added, and it is your responsibility to ensure the tests pass before deeming your work complete.

Files:

  • test/unit/commands/help.test.ts
  • test/unit/commands/status.test.ts
  • test/unit/commands/channels/list.test.ts
  • test/unit/commands/channels/presence/subscribe.test.ts
  • test/unit/commands/channels/history.test.ts
  • test/unit/commands/support.test.ts
  • test/unit/commands/spaces/spaces.test.ts
  • test/unit/commands/channels/batch-publish.test.ts
  • test/unit/commands/channels/presence/enter.test.ts
  • test/unit/commands/accounts/logout.test.ts
  • test/unit/commands/auth/revoke-token.test.ts
  • test/unit/commands/accounts/login.test.ts
  • test/unit/commands/bench/benchmarking.test.ts
  • test/unit/commands/channels/subscribe.test.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/AI-Assistance.mdc)

src/commands/**/*.ts: Commands should follow the oclif structure: export default class MyCommand extends Command { ... }
Command should support auth via the standard auth helper (import { getAuth } from '../../helpers/auth') and use it in the run method.
Use try/catch for error handling in commands, and provide user-friendly error messages (e.g., handle error.code === 40100 for authentication failures).
Do not use direct HTTP requests (e.g., fetch) for data plane APIs; use the Ably SDK instead.
Do not use hardcoded endpoint URLs; use configuration values (e.g., flags.controlHost, config.controlHost) when constructing URLs.
Do not use non-standard command structures (e.g., export default class { async execute(args) { ... } }); always use the oclif Command class.

Files:

  • src/commands/bench/subscriber.ts
src/commands/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/Development.mdc)

Follow oclif framework best practices as described in the oclif documentation.

Files:

  • src/commands/bench/subscriber.ts
🧬 Code graph analysis (13)
test/unit/commands/help.test.ts (3)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
src/interactive-base-command.ts (1)
  • error (23-52)
src/commands/bench/subscriber.ts (1)
src/utils/long-running.ts (1)
  • waitUntilInterruptedOrTimeout (13-79)
test/unit/commands/status.test.ts (2)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/list.test.ts (3)
test/root-hooks.ts (2)
  • beforeEach (8-11)
  • afterEach (13-61)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/history.test.ts (3)
test/root-hooks.ts (2)
  • beforeEach (8-11)
  • afterEach (13-61)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/support.test.ts (2)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (2)
  • stdout (125-127)
  • stderr (129-131)
test/unit/commands/spaces/spaces.test.ts (3)
test/root-hooks.ts (2)
  • beforeEach (8-11)
  • afterEach (13-61)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/batch-publish.test.ts (3)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/publish.test.ts (1)
  • error (54-61)
test/unit/commands/accounts/logout.test.ts (3)
test/root-hooks.ts (2)
  • beforeEach (8-11)
  • afterEach (13-61)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/auth/revoke-token.test.ts (3)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (2)
  • stdout (125-127)
  • stderr (129-131)
src/interactive-base-command.ts (1)
  • error (23-52)
test/unit/commands/accounts/login.test.ts (3)
test/root-hooks.ts (2)
  • beforeEach (8-11)
  • afterEach (13-61)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/bench/benchmarking.test.ts (2)
test/helpers/command-helpers.ts (1)
  • runCommand (67-83)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/subscribe.test.ts (3)
test/helpers/cli-runner.ts (1)
  • stdout (125-127)
test/unit/commands/channels/publish.test.ts (1)
  • error (54-61)
test/unit/commands/channels/occupancy/get.test.ts (1)
  • error (55-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: ui-tests
  • GitHub Check: session-tests
  • GitHub Check: auth-tests
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test
  • GitHub Check: e2e-cli
🔇 Additional comments (38)
test/unit/commands/support.test.ts (3)

4-20: LGTM! Topic listing tests exercise real CLI behavior.

These tests appropriately invoke the actual CLI command and validate output, which aligns with the PR's objective to replace mock-heavy tests with real behavioral tests.


22-88: LGTM! Help and routing tests provide good coverage.

The help flag tests (both --help and -h) and subcommand routing tests effectively validate CLI behavior by invoking actual commands and checking output. The regex patterns used for matching help content are appropriately flexible for integration testing.


2-2: No action needed on this import.

The runCommand import from @oclif/test is consistent across all unit and integration tests in the codebase. The custom helper in test/helpers/command-helpers.ts is used exclusively by e2e tests, which have different requirements. The import in this file is correct for its test category.

test/unit/commands/accounts/logout.test.ts (2)

35-55: LGTM!

Help tests appropriately verify that documentation is present and includes expected flags and examples.


139-161: LGTM!

The tests properly verify multi-account logout scenarios, ensuring the correct account is removed and remaining accounts are preserved in the output.

test/unit/commands/status.test.ts (3)

1-12: Well-structured test setup with proper HTTP mock cleanup.

The test file correctly uses vitest, @oclif/test's runCommand, and nock for HTTP mocking. The beforeEach/afterEach hooks properly clean up nock interceptors to prevent test pollution.


14-26: Good test coverage for operational status scenario.

The test verifies real command behavior by mocking the HTTP endpoint and asserting on actual stdout output. This aligns well with the PR objective of testing actual command logic rather than just stubs.


42-72: Comprehensive error handling tests.

Good coverage of error scenarios including invalid response payloads, network errors, and HTTP 500 errors. The assertions verify that errors are properly propagated from the command.

test/unit/commands/help.test.ts (2)

1-19: Clean test structure for help command.

The test file appropriately uses runCommand from @oclif/test without unnecessary mocking since help commands don't require network calls. The regex-based version check on line 17 is flexible enough to handle different output formats.


60-79: Good error handling tests for unknown commands.

The tests properly verify that the CLI returns meaningful errors when users request help for non-existent commands. This covers an important edge case.

test/unit/commands/channels/presence/subscribe.test.ts (3)

4-9: Global type declaration for test mocks.

The declare global block provides type safety for the __TEST_MOCKS__ pattern used across presence tests. This is a reasonable approach for sharing mock state between the test and the code under test.


16-55: Comprehensive mock setup for Ably Realtime client.

The mock structure covers the essential Ably Realtime API surface: channels, presence, connection, and auth. The presenceCallback capture pattern allows tests to simulate incoming presence events.


57-59: Proper cleanup of global test mocks.

The afterEach hook correctly cleans up the global __TEST_MOCKS__ to prevent test pollution, which aligns with the coding guidelines for resource cleanup.

test/unit/commands/channels/presence/enter.test.ts (3)

17-62: Well-structured mock setup for presence enter command.

The mock configuration properly covers the presence API surface including enter, get, leave, and subscribe methods. The mock responses (e.g., mockPresenceGet returning presence members) enable meaningful assertions about command behavior.


119-138: Good test for data payload parsing.

This test properly verifies that the --data flag's JSON string is parsed and passed to presence.enter as an object. The assertion on line 134-137 confirms the actual behavior rather than just that a stub was called.


174-190: Good error handling test for invalid JSON.

The test verifies that the command properly reports an error when invalid JSON is provided via the --data flag. The regex assertion on line 189 is appropriately flexible.

test/unit/commands/accounts/login.test.ts (4)

14-30: Excellent test isolation with temporary config directory.

The setup creates an isolated config directory per test with secure permissions (0o700), preventing tests from affecting each other or the developer's actual config. The minimal config file on lines 26-29 provides just enough state for the command to run.


32-46: Thorough cleanup in afterEach.

The cleanup properly:

  1. Cleans nock interceptors
  2. Restores the original ABLY_CLI_CONFIG_DIR (handling both set and unset cases)
  3. Removes the temporary directory

This prevents test pollution and filesystem leaks.


126-157: Good test for auto-selected app behavior.

The test verifies an important UX feature where a single app is auto-selected. Note the .twice() on line 133 which correctly accounts for the /me endpoint being called twice during the login flow.


188-236: Comprehensive error handling tests.

Good coverage of error scenarios including authentication failure (401), network errors, and server errors (500). The tests verify that errors are properly captured and formatted in JSON output.

test/unit/commands/channels/list.test.ts (3)

62-82: LGTM!

Help tests properly verify CLI interface documentation is accessible and contains expected content.


84-161: LGTM!

Channel listing tests provide good coverage of success paths, empty responses, API errors, and flag handling. The mock verification approach properly validates the command's interaction with the Ably REST client.


164-221: LGTM!

JSON output tests thoroughly validate the response structure and error handling in JSON mode. Good coverage of the --json flag behavior.

test/unit/commands/auth/revoke-token.test.ts (4)

68-99: LGTM!

Help tests properly verify the command interface documentation, including required arguments and available flags.


101-112: LGTM!

Argument validation test correctly verifies the required token argument.


114-251: LGTM!

Token revocation tests provide excellent coverage using nock for HTTP mocking. The tests cover success cases, fallback behavior, JSON output, and various error scenarios including the special token_not_found handling.


276-297: LGTM!

Good security test verifying that the API key secret is masked in debug output. This prevents accidental credential exposure.

src/commands/bench/subscriber.ts (2)

34-38: LGTM!

The new --duration flag is well-implemented with a clear description and appropriate short flag -d. This enables timeout-based subscription limiting for benchmark tests.


802-812: LGTM!

The waitForTermination method correctly delegates to waitUntilInterruptedOrTimeout and logs the exit reason. The type assertion is safe since oclif parses --duration as an integer flag.

test/unit/commands/bench/benchmarking.test.ts (4)

4-10: LGTM!

Global test mocks declaration properly defines both ablyRealtimeMock and ablyRestMock to support testing both realtime and REST-based benchmark commands.


27-64: LGTM!

Comprehensive mock setup properly simulates the Ably SDK interface including channels, connection state, presence, and auth. The once callback for 'connected' event enables proper testing of connection-dependent flows.


158-238: LGTM!

Publishing functionality tests properly verify command behavior by checking mock interactions. The use of vi.waitFor for async assertions is appropriate for timing-sensitive tests.


289-342: LGTM!

Subscription functionality tests properly verify that the subscriber enters presence with the correct role and subscribes to channel messages and presence events. This ensures proper coordination with publisher commands.

test/unit/commands/channels/history.test.ts (2)

14-49: Verify that the global mock approach aligns with Ably SDK testing guidelines.

The coding guidelines specify: "When testing, mock the Ably SDK properly by stubbing the constructor (e.g., sinon.stub(Ably, 'Rest').returns(mockClient))."

This test uses a global __TEST_MOCKS__ container instead of constructor stubbing. While this may be intentional as part of the migration to lightweight integration tests, please confirm this approach properly exercises the actual Ably SDK initialization code paths.

Based on coding guidelines for **/*.test.ts.


1-2: > Likely an incorrect or invalid review comment.

test/unit/commands/channels/subscribe.test.ts (2)

11-266: Well-structured test refactoring.

This refactoring successfully addresses the PR objectives by:

  • Replacing class-based mocks with streamlined runCommand-based integration testing
  • Verifying real command behavior through stdout assertions and mock interaction checks
  • Testing actual channel configuration (lines 220-265) rather than just stubs
  • Properly organizing tests into logical groups (help, validation, functionality, flags)

The flag configuration tests are particularly strong, as they verify the command correctly passes options to the Ably SDK.


134-160: Test has implicit timeout via environment variable—no hanging concern.

The subscribe command terminates properly in this test because the test environment (vitest.config.ts) sets ABLY_CLI_DEFAULT_DURATION="0.25" (250ms), which waitUntilInterruptedOrTimeout() uses as a fallback timeout when no explicit duration flag is provided. The test is designed correctly and does not require additional timeout handling.

Likely an incorrect or invalid review comment.

test/unit/commands/channels/batch-publish.test.ts (1)

57-92: Argument‑validation tests now exercise real CLI failure modes

The two argument‑validation specs correctly drive the channels:batch-publish command via runCommand and assert on the resulting error.message, so they cover actual error paths (missing channels vs missing message) instead of just test scaffolding. That’s a solid improvement in terms of FTF‑156’s goal to ensure tests validate real behavior.

Copy link
Member

@denissellu denissellu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a massive PR, but i think i got the gist o it, just a quick question, maybe i missed it, should we add docs for the duration flag?

@AndyTWF
Copy link
Contributor Author

AndyTWF commented Dec 9, 2025

@denissellu done in ce13983

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on January 4

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on January 4

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.


Bug: Signal handler prevents client cleanup causing resource leak

The cleanup() function was modified to remove client.close() with a comment that cleanup is handled by the base class finally() method. However, the signal handlers still call process.exit(0) immediately after cleanup(), which terminates the process synchronously and prevents the finally() method from ever executing. This results in the Ably Realtime connection not being properly closed when the user presses Ctrl+C, causing a resource leak. The command should either use waitUntilInterruptedOrTimeout() like other commands, or restore the client.close() call in the cleanup function.

src/commands/logs/push/subscribe.ts#L185-L200

// Handle process termination
process.on("SIGINT", () => {
if (!this.shouldOutputJson(flags)) {
this.log("\nSubscription ended");
}
cleanup();
process.exit(0); // Reinstated: Explicit exit on signal
});
process.on("SIGTERM", () => {
cleanup();
process.exit(0); // Reinstated: Explicit exit on signal
});

Fix in Cursor Fix in Web


AndyTWF and others added 2 commits December 11, 2025 11:36
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AndyTWF and others added 16 commits December 11, 2025 12:06
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand, nock HTTP mocking,
and temporary config directory for more realistic testing of actual
command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Includes
proper cleanup with vi.restoreAllMocks().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tern

Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Includes
functional test for presence event display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… pattern

Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Uses vi.waitFor
for deterministic async testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Includes
functional tests for --location, --data, --x and --y flags.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Includes
functional cipher test and proper cleanup with vi.clearAllMocks().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and nock HTTP mocking
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and nock HTTP mocking
for more realistic testing of actual command behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Testable* wrapper pattern with runCommand and global mocks
for more realistic testing of actual command behavior. Uses vi.waitFor
for deterministic message count verification. Also adds test mode
duration override to bench/subscriber.ts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
We have a redundant job to "trigger" e2e tests - it only console prints, so skip that.

We also only run them on certain changesets right now which means we cant make them a required check. Remove this
constraint to always run them.
The job just takes the uploaded results and prints them, offerring no additional value. Lets remove it.
Copy link
Member

@jamiehenson jamiehenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the previous DRY-up PR, not a comment on the quality of the offering, but there's no way I can develop a sufficient mental model for this with regards to test effectiveness, given the scope and my previous lack of familiarity of the cli code itself.

Therefore, abstracting my analysis to AI summaries/reviews, green CI and general vibes, this works.

@AndyTWF AndyTWF merged commit e2753ad into main Dec 11, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants