feat(connectors): HubSpot checks follow the design's rows, copy and states

Merged
EV
4d ago
+1190-204

Description

Backend PR for the connector checks redesign, stacked on #16199. HubSpot follows the design's rows, copy and states.

HubSpot

  • Rows:
    • The 9 checks are the design's 9 rows, with its names: "Sign in to HubSpot", "Read tickets", "Read companies", "Read deals", "Read contacts", "List linked records", "Sample notes", "List users" and "Read record viewers".
    • Dependencies follow the design's run order, with one change. "List linked records" and "Sample notes" run after the sign-in, not after the four read checks. A dependency that finds nothing to test stops the checks after it. Many portals have no tickets, so "No tickets in HubSpot" would stop both checks, but they can test a record of another type. The two checks sample the first type that lists a record, and they leave out a denied type: its read check reports it.
  • Copy:
    • Every failure gives the design's line and description.
    • A denial names the private app scope that HubSpot needs, e.g. "In HubSpot, add crm.objects.deals.read to the private app's scopes."
    • Passing checks give what they found: "Signed in to account {portal_id}", "Found {n} tickets" (the count from the Search API), "Listed contacts linked to tickets", "Read a note linked to a ticket", "{n} users found", "{n} users can view a sample ticket". Singular and plural forms are correct ("Found 1 company").
  • Honest passes. These checks passed while testing nothing:
    • A type with no records is now Skipped ("No tickets in HubSpot").
    • When no selected type has a record, the connector would index nothing. The first read check now fails ("No records in the selected types"). This blocks Create.
    • No record in scope is now Skipped for "List linked records", "Sample notes" and "Read record viewers" ("No records in scope").
    • A sample record without notes is now Incomplete ("No notes found to test").
    • No listed user is now Incomplete ("No users found to test").

Fixes from the design's review

  • Rate limits. After the rate limiter's 5 retries, a check raises RateLimitedValidationError, which stops the run. Before, the limit was an unexpected error.
  • Sign-in. It now reports a missing token ("Account has no API token"), an unreachable HubSpot ("Cannot reach HubSpot") and a timeout ("Sign-in timed out"). On a form run, an unreachable HubSpot or a timeout fails sign-in. A 5xx or an answer that is not JSON is "Unexpected response from HubSpot" (Incomplete).
  • Error Details. HubSpotApiError is a SourceApiError. The gateway adds the method, the URL and HubSpot's error body, for SDK and REST calls, so Error Details show them.
  • Connection errors. The gateway raises HubSpotConnectionError (with timed_out) for a connection failure or a timeout, from the SDK and from REST calls. The checks cannot import requests (the SDK import fence).
  • Permission sync validation. validate_hubspot_perm_sync accepts a portal with no records. Before, the viewers check passed silently there; now it raises NothingToCheckError, which would pause the connector.

Not in this PR:

  • validates_binding on "Sign in to HubSpot". The design says sign-in unlocks the form, but HubSpot's config has no credential-bound fields, and the framework requires a binding class for a binding check (test_binding_checks_require_only_bound_fields). GitHub and Google Drive also leave it out.
  • The design's HubSpot text for the 10-minute hang guard ("The check stopped after 10 minutes without an answer from HubSpot."). The runner writes this row text for every source. A per-source text needs a framework change.

How Has This Been Tested?

  • New unit tests (test_hubspot_check_row_text.py): the sign-in lines, the form-run policy (unreachable and timeout), the rate-limit stop, the dependency stops, Error Details, the empty-type and empty-scope states, the samples' Skipped and Incomplete states, and that "List linked records" runs past an empty type.
  • New gateway tests: SDK and REST errors carry the request and body; a connection failure says whether it timed out; a search keeps its match count.
  • Existing HubSpot check tests updated to the new states.
  • Connector, server and EE unit tests: 5519 passed, 25 skipped, 1 failed. The failed test is a web connector test (test_browser_lifecycle.py). It also fails without this change. ty and pre-commit are clean.
  • HubSpot has no daily live capability-check test, so there is nothing to update there.

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

Generated by Claude Code


Summary by cubic

Rewrites the HubSpot capability checks to follow the checks design: nine rows with the design's names, run order, copy, and outcome states. Checks now report what they tested and what they found instead of passing silently.

  • "List linked records" and "Sample notes" run after sign-in only, so an empty or denied type no longer stops them.
  • Read checks report Search API counts with singular and plural forms; a full users page reports a "100+" lower bound.
  • Empty types are Skipped, a record without notes or no listed user is Incomplete, and a portal with no records of any selected type fails the first read check to block Create.
  • Rate limits stop the run after retries with a clear message; sign-in reports missing tokens, unreachable HubSpot, and timeouts, failing a form run on the latter two.
  • HubSpot API errors now carry the request method, URL, and response body so Error Details show them; connection failures raise a dedicated HubSpotConnectionError.
  • Permission sync validation accepts a portal with no records instead of raising.
  • Row copy uses the shared CheckRowText model, and the check tests share a helpers module.

Summary by CodeRabbit

  • Improvements
    • HubSpot connection checks now distinguish common issues, including invalid credentials, missing permissions, rate limits, and connectivity problems.
    • Permission checks provide clearer results when selected data is empty or inaccessible, and can continue checking other readable record types.
    • User checks report the number of users found, while record-viewer checks report viewer counts when a sample is available.
    • HubSpot requests handle timeouts and connection failures more consistently.