Skip to content

Testing

Brand Protect has three layers of tests: the Python unit suite, the Rails suite, and a control-plane end-to-end smoke test.

  • 26 test files + a shared conftest.py (notably a stub_dnstwist fixture that replaces dnstwist.run so discovery tests are hermetic).

  • Run from services/:

    Terminal window
    .venv/bin/pytest -q
  • Baseline: 557 passed, 1 skipped, 23 warnings. The single skip is the PDF debug-artifact test, which is skipped when services/tmp/ isn’t writable (e.g. root-owned after a container run).

  • Known flaky: test_scoring.py::TestThreats::test_urlhaus_* hit the live URLhaus API (unmocked) and can fail on restricted networks. Pre-existing, unrelated to feature work.

Coverage includes orchestration (stage ordering, parallelism, resume, cancellation, failure isolation), domain generation, RDAP/WHOIS fallback, fingerprinting, identity matching, scoring and threats, AI settings, PDF report correctness, WebSocket/Redis messaging, and the audit MCP server.

  • Minitest with fixtures (fixtures :all) and parallelized execution.

  • SessionTestHelper#sign_in_as signs a user into controller tests.

  • The largest file is test/controllers/v1/internal_api (547 lines) — the Python→Rails surface.

  • Run:

    Terminal window
    cd rails_web && bin/rails test
  • Baseline: 117 runs / 390 assertions / 0 failures.

  • TEST_DATABASE_URL is pinned for tests; test.rb sets maintain_test_schema = false.

A control-plane smoke test — it does not run a real scan (no dnstwist, Playwright, or threat feeds). It verifies, against a live Rails + Python pair:

  1. Liveness: Rails /up and Python /health.
  2. A learned-weights round trip: PUT then GET /v1/scoring/weights — proves auth + persistence.
  3. Publishing scan status and findings on the brand_protect_cable:* channels.
  4. Polling GET /v1/scans/:id/audit_data until a finding and a final status appear.
  5. The analyst-feedback path (labels surface in /v1/scoring/feedback_samples).

Run it in CI via the e2e-scan job (see CI/CD).