Skip to content

Test suite audit: ~280 low-value tests, 8 false-passing negative controls, test-only production seams #790

Description

@padak

Summary

A read-only audit of the unit test suite (227 files, ~6,650 tests, ~163k LOC; tests/test_e2e*.py excluded) against main @ 6d764b64, using the openclaw test-audit methodology: junk patterns, a value bar, a retention bar, and per-candidate evidence (what regression the test can detect, which stronger test already owns the contract, recommended action, confidence).

Nothing has been deleted yet. This issue records the findings and proposes a batched cleanup.

Overall the suite is healthy. Merge-request, notification and sync tests have almost no junk: nearly every test pins a named incident or review finding.

Headline numbers

  • About 280 tests (~4%) can be deleted or folded into parametrized cases, removing about 4,000 test LOC.
  • About 60-80 LOC of dead production code is kept alive only by tests.
  • Several negative controls and security checks pass for the wrong reason. They need a rewrite, not deletion; this is the most important finding.
Lane Scope Candidates Approx. LOC
1 test_cli.py, test_services.py, test_client.py (legacy monoliths) ~67 tests ~1,890
2 sync, lineage, transformation, flow, schedule ~60 tests ~1,050
3 storage, workspace, data-app, stream, sharing, branch, merge-request, notification ~26 delete + ~40 to parametrize ~620
4 auth, token, HTTP, config store, permissions, server, SDK, billing ~21 delete + 5 rewrites + 7 merge groups ~420
5 config, component, semantic layer, variables, kai, agent, search ~49 tests ~590
6 tooling/CI tests + cross-cutting sweep + test-only production seams ~45 tests + 60-80 prod LOC

Per-lane reports with exact test ids, evidence and the remaining owner test for each candidate follow as comments below.

Findings by category

1. Tests that pass even when the guarded behavior is broken (rewrite)

  • test_file_locking.py::TestTryFlock::test_flock_skipped_when_no_fcntl: if the if not _HAS_FCNTL: return guard were removed, fcntl.flock(42, 0) would raise on the bad fd and contextlib.suppress(OSError) would swallow it, so the test still passes. (Verified manually.)
  • test_errors.py::test_no_duplicate_values can never fail. A duplicate Enum value becomes an alias, and iterating the enum skips aliases. Replace it with @enum.unique on ErrorCode. (Verified manually.)
  • test_server_permissions.py::...::test_app_without_an_engine_fails_closed accepts any non-200 status, so a crash (500) also passes.
  • test_permissions_cli.py::...::test_set_rejected_without_confirmation asserts the exit code its own mock raises and never checks that nothing was persisted.
  • test_billing_cli.py::...::test_per_project_errors_surface_as_warnings_exit_0 effectively asserts that the letter "b" is in the output.
  • test_sentinel_is_set_even_when_body_raises never checks the sentinel.
  • test_e2e_lineage_deep.py::test_12 asserts only deep >= shallow, which passes even if --depth is ignored.
  • test_multiple_types_deduped (semantic layer) passes no duplicate input, so it doesn't test deduplication.

2. Duplicates of a stronger owner test (delete)

  • test_client.py re-tests the retry, timeout, error-truncation, 404-mapping and User-Agent behavior owned by test_http_base.py. The client is a pass-through to BaseHttpClient. One assert (around line 1515) is always true.
  • test_services.py: TestResolveProjects duplicates test_base_service.py. TestJobListDeterministicOrder has been stale since feat(ui,serve): cross-project All Jobs and All Tokens views #675: its jobs have no startTime, so it only exercises the tie-break.
  • test_cli.py:
    • TestExitCodes: four tests are byte-identical to per-command tests.
    • TestResolveManageToken duplicates the class of the same name in test_helpers.py.
    • TestOrgSetupBasic duplicates TestOrgSetup, and TestSharingEdges duplicates TestSharingEdgesIntegration.
    • Two byte-identical duplicates: the lineage help test and the exit-code-5 test.
  • test_lineage_service.py re-runs the max-workers and resolve_projects tests from test_base_service.py, and has two same-input scenario duplicates.
  • Service-level tests that replay a CLI test on the same inputs, where the CLI test already runs the real service: config rename, row, oauth-url and change-description tests, the sharing link-stage tests, and the table-definition tests.
  • Copy-paste duplicates: Storage client URL tests (truncate, swap, clone); the merge-request client field helper; test_backend_surfaced_from_bucket in the describe service; the permission-engine policy tests (test_vojta_use_case, test_read_only_mode); billing GET/POST and payload tests.

3. Tests that only check what their own mock returned

  • 9 test_sync_cli.py::*_json_output tests. Every sync command's --json path passes the service result through unchanged, so they collapse into one parametrized pass-through test (~360 LOC).
  • TestLargeResponse in test_client.py.
  • Assorted CLI tests in flow/schedule and billing.

4. --help text greps with no contract

About 20 tests across test_cli.py::TestHelp, test_kai_cli.py, test_sync_cli.py and test_config_rename_cli.py.

5. Names that promise more than the test checks

  • "concurrent" tests that run sequentially, in test_file_locking.py and test_job_idempotency_store.py. Real multi-thread and multi-process siblings already exist.
  • --no-color tests that can't fail, because CliRunner is not a TTY.
  • test_feature_gate_checked_before_get_credits asserts no ordering.
  • test_default_client_factory_is_used_when_none.

6. Test-only production seams (delete with their tests)

  • auto_update._should_skip: no production caller. Its 11 TestShouldSkip tests therefore never exercise the real gates. Retarget them, then delete the alias.
  • default_*_client_factory wrappers in the snapshot, org and token services.
  • get_nested_value, clear_orphans and build_and_cache.

7. Other

  • The error-code guard self-tests in test_integration.py are marked integration, so CI deselects them (-m "not integration"). CI does run scripts/check_error_codes.py itself, so the gate works but its self-tests don't.
  • Two AWS-token tests never run anywhere: KBA_TEST_TOKEN_AWS is never set.
  • The __all__ public-API test checks "contains at least"; it should assert the exact set (semver contract).
  • test_update_config_row_state_returns_full_detail documents behavior that contradicts the config: no CLI path to write configuration state (PUT .../state unused); config update --set 'state...' silently no-ops #593 finding verified against the real API: the endpoint returns the bare row.

Deliberately retained (look like junk, guard real contracts)

Proposed plan

One coherent PR per batch. Each batch runs the focused owner tests plus make check, and reports test LOC and production LOC separately.

  1. Batch 1 (correctness first):
    • Rewrite the false-passing negative controls from category 1.
    • Add @enum.unique to ErrorCode.
    • Fix the test_integration.py CI routing.
    • Delete the high-confidence duplicates in lane 1: test_client.py vs test_http_base.py, plus the byte-identical duplicates in test_cli.py (~55 tests).
  2. Batch 2: sync and lineage (lane 2). Parametrize the sync --json pass-through tests and remove the base_service replays.
  3. Batch 3: storage, workspace, data-app and merge-request (lane 3). Delete the copy-paste duplicates and parametrize the near-duplicates.
  4. Batch 4: auth, permissions, billing and server (lane 4), plus the __all__ exact-set tightening.
  5. Batch 5: config, component and semantic layer (lane 5). Fix test_multiple_types_deduped and tighten the oauth-url CLI error test before deleting its service twin.
  6. Batch 6: delete the test-only production seams (_should_skip, the dead factories and helpers) together with their tests.

Rules for every batch:

  • No candidate is deleted without re-reading it. These findings come from an AI-assisted audit. Two were verified by hand (marked above), the rest were not. Each deletion must name the remaining owner test in the PR description.
  • Medium-confidence items are optional. Uncertain candidates are not converted into cleanup just to increase the count.
  • No replacement tests that restate the implementation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions