Skip to content

fix: testops_multi xdist run_id handoff + mask API token in config debug log#497

Closed
gibiw wants to merge 4 commits into
mainfrom
fix/testops-multi-xdist
Closed

fix: testops_multi xdist run_id handoff + mask API token in config debug log#497
gibiw wants to merge 4 commits into
mainfrom
fix/testops-multi-xdist

Conversation

@gibiw

@gibiw gibiw commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Bundles two related reliability/security fixes for testops_multi mode. Both ship in qase-python-commons 5.1.2.


Fix 1 — testops_multi run_id handoff under pytest-xdist

Summary

  • Fixes QaseTestOpsMulti.set_run_id() missing 1 required positional argument: 'run_id' crash and the No run_id for project ..., skipping send cascade when testops_multi mode runs under pytest-xdist (-n N).
  • Workers now reconstruct the full {project_code: run_id} map written by the xdist controller and seed it through a new QaseTestOpsMulti.set_run_ids() bulk setter.
  • Backwards compatible: single-project mode keeps its scalar lock file, QaseTestOpsMulti.set_run_id(project_code, run_id) API is untouched.

Root cause

  1. QasePytestPlugin.pytest_sessionstart wrote the run id via str(self.run_id). In multi mode self.run_id is a dict, so the lock file held "{'DW': 1553}" — not round-trippable.
  2. QasePytestPlugin.load_run_from_lock then called self.reporter.set_run_id(self.run_id) with one argument, but QaseTestOpsMulti.set_run_id requires (project_code, run_id).
  3. QaseCoreReporter.set_run_id swallowed the TypeError silently and routed the worker through the fallback reporter — so results that the user thought were going to TestOps were quietly written to local JSON files instead.

Fix

  • QaseCoreReporter.set_run_id(run_id) accepts both scalar and dict, dispatching dict input to set_run_ids when the inner reporter exposes it.
  • QaseTestOpsMulti.set_run_ids(run_ids: Dict[str, Union[str, int]]) seeds project_runs for every known project in one shot, warning on unknown codes.
  • QasePytestPlugin serialises the run id via json.dumps in the controller and parses it back with json.loads in workers; if a legacy non-JSON file is found, it falls back to the previous string handling.

Fix 2 — Mask API token in debug config dump

Summary

  • QaseCoreReporter.__init__ logged the full config via str(self.config), which serialised testops.api.token in clear text. Any debug log shared with support or pasted into an issue leaked the customer's API key.
  • New qase.commons.util.token_masker module mirrors the JS contract from qase-javascript-commons/src/utils/token-masker.ts.

Behaviour

  • Tokens of 8+ chars are shown as abc****wxyz (first 3 + last 4 visible).
  • Tokens of 7 chars or fewer are fully replaced with *.
  • Falls back to plain str(config) if the serialised form cannot be parsed as JSON, so a bug in the masker can never silence debug logs.

Live verification — examples/multiproject/pytest with pytest -n 2

Smoke-tested against real Qase TestOps API in projects DEVX and DEMO. Side-by-side log comparison:

Marker main (baseline) fix/testops-multi-xdist
set_run_id() missing 1 required positional argument errors in worker log 4 0
Failed to set run id log lines 4 0
Full API token leaked in debug config dump 3 0
API token rendered as <64 char plain token> 90e****ce5a
Local fallback JSON files (silent fallback) 4 in build/qase-report/results/ none — build/ not created
Adding result debug lines 24 (mixed across reporter + fallback) 12 (clean routing to API)
Controller-created runs reachable on TestOps DEVX #891, DEMO #710 — partial results only DEVX #895, DEMO #714 — all 12 results

Test plan

  • qase-python-commons unit suite: 247 passed, including 3 new set_run_ids cases, 3 new QaseCoreReporter.set_run_id dispatch cases, and 10 new token_masker cases.
  • qase-pytest unit suite: 115 passed, including 5 new lock-file round-trip cases (controller-write / worker-read for both dict and scalar shapes, plus legacy-format tolerance).
  • qase-pytest integration suite: 10 passed.
  • End-to-end smoke against real Qase API with pytest -n 2 in examples/multiproject/pytest — confirmed all 12 worker results land in TestOps and token is masked in debug log.

Release

  • Bumped qase-python-commons 5.1.1 → 5.1.2.
  • Bumped qase-pytest 8.3.0 → 8.3.1.

gibiw added 4 commits June 3, 2026 17:23
In testops_multi mode the controller produces a {project_code: run_id}
dict, but the xdist lock file was written via str(dict) and read back as
a single positional argument. Workers crashed in QaseTestOpsMulti.set_run_id()
with "missing 1 required positional argument: 'run_id'", project_runs stayed
empty, and every result was skipped with "No run_id for project ...".

Serialise the run id via json.dumps in the controller, parse it back in
load_run_from_lock, and add QaseTestOpsMulti.set_run_ids() to seed every
project at once. QaseCoreReporter.set_run_id() now dispatches dict input
to set_run_ids so the single-project path is unchanged.
QaseCoreReporter.__init__ logged the full config via str(self.config),
which serialised testops.api.token in clear text. Any debug log shared
with support or pasted into an issue leaked the customer's API key.

Add qase.commons.util.token_masker with mask_token() and
sanitize_config_for_log() mirroring the JS token-masker contract
(qase-javascript-commons/src/utils/token-masker.ts). Route the debug
config dump through sanitize_config_for_log so testops.api.token is
shown as "abc****wxyz" and tokens of 7 chars or fewer are fully hidden.
@gibiw gibiw changed the title fix: testops_multi run_id handoff for pytest-xdist workers fix: testops_multi xdist run_id handoff + mask API token in config debug log Jun 3, 2026
@gibiw

gibiw commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Splitting per request into two PRs:

Closing this combined PR — superseded by the two split PRs.

@gibiw gibiw closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant