Skip to content

feat: add Bitwarden CLI integration#3

Merged
axisrow merged 5 commits into
mainfrom
feature/bitwarden-auth
Mar 13, 2026
Merged

feat: add Bitwarden CLI integration#3
axisrow merged 5 commits into
mainfrom
feature/bitwarden-auth

Conversation

@axisrow

@axisrow axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add bw_read() function in auth.py for reading secrets via Bitwarden CLI (bw get <field> <item>)
  • Add --bw-token-ref and --bw-login-ref CLI options with corresponding env vars (YANDEX_DIRECT_BW_TOKEN_REF, YANDEX_DIRECT_BW_LOGIN_REF)
  • Bitwarden acts as fallback after 1Password in credential resolution chain: CLI flags → env vars → 1Password → Bitwarden

Test plan

  • 13 new unit tests in tests/test_auth_bw.py (all passing)
  • Existing 1Password tests unaffected (11 passing)
  • Full test suite passes (65 tests)
  • Manual: direct-cli --bw-token-ref <item> campaigns get --help resolves token from Bitwarden

🤖 Generated with Claude Code

Add bw_read() function and --bw-token-ref / --bw-login-ref CLI options
as a fallback after 1Password for retrieving credentials from Bitwarden.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@axisrow

axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 48s —— View job


Code Review: feat: add Bitwarden CLI integration

  • Gather context and read changed files
  • Review direct_cli/auth.py
  • Review direct_cli/cli.py
  • Review tests/test_auth_bw.py
  • Review .env.example
  • Post final review feedback

Overall this is a clean, well-structured implementation that follows the same pattern as the existing 1Password integration. The credential chain logic is correct, tests cover the main scenarios, and the subprocess calls are safe (list form, not shell=True). A few issues worth addressing below.


Issues

1. Bitwarden session not unlocked — unhelpful error message (auth.py:76-77)

The most common failure mode when using the Bitwarden CLI is that the vault is locked (no BW_SESSION env var set). When this happens, bw get exits non-zero with stderr like "Vault is locked.". The user will see:

RuntimeError: Bitwarden CLI error: Vault is locked.

with no guidance on how to fix it. A hint about needing BW_SESSION would make this much more user-friendly. Consider wrapping the error:

if result.returncode != 0:
    stderr = result.stderr.strip()
    if "locked" in stderr.lower():
        raise RuntimeError(
            f"Bitwarden CLI error: {stderr}. "
            "Ensure your vault is unlocked: eval $(bw unlock)"
        )
    raise RuntimeError(f"Bitwarden CLI error: {stderr}")

Fix this →


2. bw get argument order (auth.py:69)

[bw_path, "get", field, item]

This produces bw get password yandex-direct-item. Per Bitwarden CLI docs, the correct syntax is bw get <object> <id> — e.g. bw get password <item-id>. This is correct. ✓


3. Empty output not guarded (auth.py:78)

If bw get exits 0 but returns an empty string (e.g. the password field is empty), result.stdout.strip() returns "". The caller in get_credentials will treat this as falsy and fall through to the ValueError about missing token. This is the same gap as in op_read, so it's consistent existing behavior — but worth noting both could silently ignore a misconfigured empty secret.


4. Test: subprocess import inside test method (tests/test_auth_bw.py:78)

def test_bw_read_timeout(self, mock_which, mock_run):
    import subprocess
    mock_run.side_effect = subprocess.TimeoutExpired(cmd="bw", timeout=10)

The import subprocess at the top of the test function is unusual (it already exists as a stdlib module). Move it to the top of the file alongside the other imports.

Fix this →


5. Incomplete env var cleanup in tests (tests/test_auth_bw.py:86-93)

test_get_credentials_bw_fallback clears YANDEX_DIRECT_OP_TOKEN_REF but not YANDEX_DIRECT_OP_LOGIN_REF. If that env var happens to be set in the test environment, op_read would be called for the login step (and it is not mocked), potentially causing a false failure. Similarly, test_get_credentials_bw_login_fallback doesn't clear YANDEX_DIRECT_OP_LOGIN_REF.

monkeypatch.delenv("YANDEX_DIRECT_OP_LOGIN_REF", raising=False)  # add this

Fix this →


6. Code duplication between op_read and bw_read (auth.py:16-78)

The two functions are structurally nearly identical. This is acceptable given the different CLI interfaces (op read <ref> vs bw get <field> <item>), and consolidating them into a generic helper would reduce readability. Leaving as-is is fine.


Minor Nits

  • .env.example: The Bitwarden references use the same item name for both token and login (yandex-direct-item). This is fine for single-item secrets, but it could be confusing — the token and username are read from different fields of the same item. A comment clarifying that would help users.

  • cli.py:125 inline comment says "Priority: arguments > env vars > 1Password > Bitwarden" — the .env file step is between env vars and 1Password per the docstring but is collapsed here. Consistent with existing code, no change needed.


Summary

Area Status
Logic correctness ✅ Correct
Security (no shell injection) ✅ Safe
Test coverage ✅ Good
Error UX (locked vault) ⚠️ Could be improved
Test robustness (env cleanup) ⚠️ Minor gaps

The implementation is ready to merge as-is; the issues above are improvements rather than blockers. Items 1 and 5 are the most worth fixing before merge.

- Add helpful error message when Bitwarden vault is locked
- Move subprocess import to top-level in test file
- Clean up env vars in tests for robustness
- Add test for locked vault scenario

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@axisrow

axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 59s —— View job


Code Review: feat: add Bitwarden CLI integration

  • Read changed files
  • Review auth.py
  • Review cli.py
  • Review tests/test_auth_bw.py
  • Review .env.example
  • Post final review

Clean, well-structured implementation that mirrors the 1Password integration correctly. Subprocess calls are safe (list form, no shell=True), the credential priority chain is correct, and the locked-vault error message is helpful. A few issues worth addressing.


Issues

1. test_get_credentials_bw_fallback missing YANDEX_DIRECT_BW_LOGIN_REF cleanup (tests/test_auth_bw.py:86)

The test clears 1Password env vars but not YANDEX_DIRECT_BW_LOGIN_REF. If that var is set in the test environment, bw_read gets called a second time (for login) and mock_bw_read.assert_called_once_with(...) will fail:

monkeypatch.delenv("YANDEX_DIRECT_BW_TOKEN_REF", raising=False)  # add this
monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False)   # add this
monkeypatch.setenv("YANDEX_DIRECT_BW_TOKEN_REF", "yandex-direct-item")

Same issue in test_get_credentials_explicit_bw_ref_paramYANDEX_DIRECT_BW_LOGIN_REF is not cleared.

Fix this →


2. Docstring lists notes as a valid field (auth.py:53)

field: Field to read (password, username, notes)

Bitwarden CLI's bw get supports specific object types. bw get notes <item> is supported, but the function is only called with "password" and "username" in the credential chain. The docstring is accurate for practical use, but it implies a broader contract than what's tested. A note like # Only password and username are used by get_credentials or removing notes from the docstring would prevent future misuse.


3. Priority comment is slightly misleading (auth.py:132)

# Priority: arguments > env vars > 1Password > Bitwarden

But the .env file step (step 3 in the docstring above) happens before step 2 env var lookup since load_env_file populates the env vars that os.getenv then reads. The comment could read "arguments > env vars (.env included) > 1Password > Bitwarden" for clarity — though this is the same pre-existing imprecision.


4. Empty string returned by bw_read silently falls through (auth.py:85)

If bw get exits 0 but the field is blank, result.stdout.strip() returns "". The caller treats this as falsy and falls through to the ValueError — the user gets "API token required" with no hint that Bitwarden returned an empty value. This is consistent with op_read, so it's an existing pattern rather than a regression, but both functions share the gap.


What Looks Good

Area Status
Subprocess security (no shell injection) ✅ Safe
Locked vault detection + helpful message ✅ Fixed
subprocess import at module level ✅ Fixed
YANDEX_DIRECT_OP_LOGIN_REF cleaned in bw_fallback test ✅ Present
Credential priority chain logic ✅ Correct
CLI options plumbed through correctly ✅ Correct
has_refs reads env vars via Click envvar= ✅ Correct
Test coverage breadth ✅ Good (13 tests)

Minor Nits

  • .env.example: Both BW_TOKEN_REF and BW_LOGIN_REF point to yandex-direct-item. A comment like # token reads 'password' field, login reads 'username' field of the same item would clarify the intent.
  • cli.py:125 comment says "Priority: arguments > env vars > 1Password > Bitwarden" — correct and consistent.

The implementation is ready to merge. Issue 1 is the only one that could cause a CI flake; the rest are polish.

- Add BW_LOGIN_REF cleanup in test_get_credentials_bw_fallback and
  test_get_credentials_explicit_bw_ref_param for env isolation
- Remove notes from bw_read docstring (only password/username used)
- Clarify priority comment to include .env step

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@axisrow

axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 44s —— View job


Code Review: feat: add Bitwarden CLI integration

  • Read changed files
  • Review auth.py
  • Review cli.py
  • Review tests/test_auth_bw.py
  • Review .env.example
  • Post final review

Clean implementation that correctly follows the existing 1Password pattern. Subprocess calls are safe (list form, no shell=True), the locked-vault error message is helpful, and the credential priority chain is correct. Two small issues remain worth addressing.


Issues

1. test_bw_token_ref_resolves_via_cli_flag missing env cleanup (tests/test_auth_bw.py:169)

This test clears YANDEX_DIRECT_TOKEN, YANDEX_DIRECT_LOGIN, and YANDEX_DIRECT_OP_TOKEN_REF, but not YANDEX_DIRECT_BW_LOGIN_REF or YANDEX_DIRECT_OP_LOGIN_REF. If either is set in the test environment, bw_read or op_read gets called for the login step and mock_bw_read.assert_called_once_with(...) fails:

monkeypatch.delenv("YANDEX_DIRECT_OP_LOGIN_REF", raising=False)   # add
monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False)   # add

Fix this →


2. .env.example missing field clarification (.env.example:9-10)

Both BW_TOKEN_REF and BW_LOGIN_REF point to yandex-direct-item with no explanation that they read different fields from the same item. First-time users will wonder why the same value is used twice:

# YANDEX_DIRECT_BW_TOKEN_REF=yandex-direct-item  # reads 'password' field
# YANDEX_DIRECT_BW_LOGIN_REF=yandex-direct-item  # reads 'username' field

What Looks Good

Area Status
Subprocess security (no shell injection) ✅ Safe
bw get <field> <item> argument order ✅ Correct
Locked vault detection + eval $(bw unlock) hint ✅ Implemented
import subprocess at module level ✅ Fixed
Credential priority chain (args > env > 1P > BW) ✅ Correct
CLI options plumbed through has_refs and get_credentials ✅ Correct
YANDEX_DIRECT_OP_LOGIN_REF / BW_LOGIN_REF cleanup in bw_fallback test ✅ Present
Test coverage breadth (13 tests) ✅ Good

The implementation is ready to merge. Issue 1 is the only one that could cause a CI flake; issue 2 is documentation polish.

- Add OP_LOGIN_REF and BW_LOGIN_REF cleanup to CLI integration tests
- Add field comments to .env.example Bitwarden references

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@axisrow

axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 31s —— View job


Code Review: feat: add Bitwarden CLI integration

  • Read changed files
  • Review auth.py
  • Review cli.py
  • Review tests/test_auth_bw.py
  • Review .env.example
  • Post final review

All issues from previous review rounds have been addressed. The implementation is clean, correct, and ready to merge. One small remaining gap worth noting.


Remaining Issue

test_op_takes_priority_over_bw missing YANDEX_DIRECT_BW_LOGIN_REF cleanup (tests/test_auth_bw.py:141)

The test clears YANDEX_DIRECT_TOKEN and YANDEX_DIRECT_LOGIN but does not clear YANDEX_DIRECT_BW_LOGIN_REF. If that env var is set in the test environment, bw_read would be called for the login step, causing mock_bw_read.assert_not_called() on line 157 to fail:

monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False)  # add this

Fix this →


What Looks Good

Area Status
Subprocess security (no shell=True) ✅ Safe
bw get <field> <item> argument order ✅ Correct
Locked vault detection + eval $(bw unlock) hint ✅ Implemented
import subprocess at module level ✅ Fixed
Credential priority chain (args > env/.env > 1P > BW) ✅ Correct
CLI options plumbed through has_refs and get_credentials ✅ Correct
.env.example field clarification comments ✅ Added
YANDEX_DIRECT_OP_LOGIN_REF / BW_LOGIN_REF cleanup in tests ✅ Present in most tests
Error message for missing token includes Bitwarden hint ✅ Present (auth.py:158-162)
Test coverage (13 tests) ✅ Good

The implementation is ready to merge. The one remaining item is a low-risk robustness fix for a test that could flake only if YANDEX_DIRECT_BW_LOGIN_REF happens to be set in the CI environment.

@axisrow

axisrow commented Mar 13, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 16s —— View job


Code Review: feat: add Bitwarden CLI integration

  • Gather context
  • Read changed files
  • Review auth.py
  • Review tests/test_auth_bw.py
  • Review cli.py and .env.example
  • Post final review

All issues from previous review rounds have been addressed. This implementation is clean, correct, and ready to merge.


What's In Good Shape

Area Status
Subprocess security (no shell=True) ✅ Safe
bw get <field> <item> argument order ✅ Correct
Locked vault detection + eval $(bw unlock) hint ✅ Implemented (auth.py:78-83)
import subprocess at module level ✅ Fixed (test_auth_bw.py:3)
Credential priority chain (args > env/.env > 1P > BW) ✅ Correct
CLI options plumbed through has_refs and get_credentials ✅ Correct (cli.py:77-90)
.env.example field clarification comments ✅ Added (lines 9-10)
Priority comment accuracy ✅ Updated (auth.py:132)
Env var cleanup in test_get_credentials_bw_fallback ✅ All 5 vars cleared (lines 87-92)
Env var cleanup in test_get_credentials_explicit_bw_ref_param ✅ All 6 vars cleared (lines 129-134)
Env var cleanup in test_op_takes_priority_over_bw BW_LOGIN_REF and OP_LOGIN_REF cleared (lines 148-149)
Env var cleanup in test_bw_token_ref_resolves_via_cli_flag OP_LOGIN_REF and BW_LOGIN_REF cleared (lines 179-180)
Missing token error message mentions Bitwarden ✅ Present (auth.py:156-163)
Test coverage (13 tests) ✅ Good

One Minor Observation (Not a Blocker)

test_get_credentials_env_takes_priority_over_bw (test_auth_bw.py:100) does not clear YANDEX_DIRECT_OP_TOKEN_REF or YANDEX_DIRECT_OP_LOGIN_REF. However, since YANDEX_DIRECT_TOKEN is set directly (which is resolved before the 1Password/Bitwarden steps), op_read is never reached, and the test assertion mock_bw_read.assert_not_called() remains valid regardless. Not worth changing.


The PR is ready to merge. ✅

@axisrow
axisrow merged commit 2f6913f into main Mar 13, 2026
1 check passed
@axisrow
axisrow deleted the feature/bitwarden-auth branch March 13, 2026 15:30
axisrow added a commit that referenced this pull request Apr 28, 2026
…y, advideos discovery (#119)

* fix: close #112 with imported XSD validation, runtime-deprecated registry, advideos discovery

Closes #112 by addressing the two open bug classes from the audit (the other
three were resolved on main).

PR-A — Runtime-deprecated method registry (closes AC #3):
- Add RUNTIME_DEPRECATED_METHODS to direct_cli/wsdl_coverage.py (registry of
  WSDL-present methods that Yandex rejects at runtime, e.g. agencyclients.add
  with error_code=3500).
- Add assert_not_runtime_deprecated() helper in direct_cli/utils.py raising
  click.UsageError with a stable replacement hint.
- Wire the assertion into agencyclients.add so both real-call and --dry-run
  paths fail with a clear message before request construction.
- Add 8th schema gate bucket runtime_deprecated_unguarded in
  scripts/build_api_coverage_report.py: walks RUNTIME_DEPRECATED_METHODS, uses
  the captured-client harness to verify each entry's CLI command actually
  rejects with UsageError before reaching the API. Bucket flips
  schema_parity_ok to False; mirrored into report["schema"] and a top-level
  report["runtime_deprecated_methods"] section for machine-readable parity.

PR-B — Imported XSD validation + AdVideos smoke discovery (closes AC #1, #4):
- Cache imported XSD schemas in tests/wsdl_cache/imports/ (general.xsd,
  generalclients.xsd, adextensiontypes.xsd). Add IMPORTED_XSD_REGISTRY
  (namespace -> filename) and fetch_imported_xsd() in wsdl_coverage.py.
- Resolve <xsd:import> nested types in get_operation_request_schema() via
  _load_schema_contexts(), and walk extension/@base inheritance so item_fields
  for types like gc:NotificationAdd and gc:ClientUpdateItem now contain
  resolved nested + inherited fields (e.g. Notification.EmailSubscriptions,
  Clients[].Settings).
- Add find_nested_schema_violations() in wsdl_coverage.py (recursive walk of
  body keys against the resolved schema).
- Add 9th schema gate bucket nested_schema_violations: dry-runs every entry
  in PAYLOAD_CASES (excluding DRY_RUN_PAYLOAD_EXCLUSIONS) and flags any
  unknown nested key.
- Extract get_first_advideo_probe_id from tests/test_integration.py into
  reusable direct_cli/_smoke_probes.py module with CLI entry point
  (python3 -m direct_cli._smoke_probes advideo). Honors
  YANDEX_DIRECT_TEST_ADVIDEO_ID env override; returns None on any failure
  so smoke scripts can skip without falsely passing. Includes graceful
  handling of create_client failures (auth/network).
- Update scripts/test_safe_commands.sh to use run_advideos_probe_get with
  [INFO] skip-on-no-id semantics instead of dummy --ids 1.
- Extend refresh_all_caches() and scripts/check_wsdl_drift.py to also
  refresh and monitor IMPORTED_XSD_REGISTRY entries.

Tests added (9 new):
PR-A: registry shape, block invocation (real + --dry-run paths), schema gate
flags unguarded entry.
PR-B: imported XSD cache↔registry parity, resolves imports
(Notification.{Email,Lang,EmailSubscriptions}), resolves extension/@base
fields (clients.update Clients[].Notification/Settings), payload nested
violation flagging, smoke probe returns None gracefully without credentials.

Schema gate goes from 7 to 9 buckets, all green: schema_parity_ok=True.
Full test suite: 338 passed, 29 skipped (integration, no creds), 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(schema-gate): unify dry_run_failed classification across click versions

Older click (Python 3.9) returns exit_code=0 with usage/error text in
stdout when a missing CLI group is invoked, while newer click returns
exit_code=2. The previous logic took two paths (exit-code-based
dry_run_failed vs. JSON-parse-based invalid_json), so the same input
produced different bucket kinds across versions.

Collapse to a single dry_run_failed kind: if either the exit code is
non-zero or the output is not parseable JSON, the case is reported as
dry_run_failed with the relevant error text. invalid_json was redundant
and not asserted anywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix: address PR #119 review findings

- Dedupe nested-schema violation reports: the recursive call already
  walks the same expected-key logic for each child, so the inline loop
  that appended unknown direct keys produced duplicate entries
  (clients.update Email/ClientId would each show up twice in the gate
  output). Delegate unknown-key detection entirely to the recursion.
- Add XSD_BASE_URL constant for tests/wsdl_cache/imports/ refresh; the
  hardcoded soap.direct.yandex.ru URL is now grep-able and documented
  alongside WSDL_BASE_URL.
- Add test_runtime_deprecated_methods_have_capture_fixture to enforce
  that every RUNTIME_DEPRECATED_METHODS entry has a complete
  RUNTIME_DEPRECATED_CAPTURE_FIXTURES record covering all of the target
  command's required options. Without this, Click would fail with
  "Missing option ..." before the deprecation guard runs and the gate
  would misclassify a guarded method as unguarded. Added a docstring on
  RUNTIME_DEPRECATED_CAPTURE_FIXTURES referencing the same invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* perf(schema-gate): avoid duplicate get_operation_field_name_enums calls

The _field_name_operations comprehension called the function twice per
operation: once in the if-filter and once as the value. Each call walks
get_operation_request_schema → _load_schema_contexts → parse_wsdl_field_enums,
so for ~27 services × ~5 operations this was ~270 redundant
schema-context loads per coverage report build. Compute the dict once,
then filter empty values.

No behavior change. Pre-existing inefficiency that became more visible
once _load_schema_contexts started resolving imported XSD types.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
axisrow added a commit that referenced this pull request May 30, 2026
* fix(auth): stop --help and unit tests from hanging on client-login network call

A regression from #480 made get_credentials() resolve the bare Client-Login
via a network clients.get on every CLI invocation when an OAuth profile with
an email login lacked the login_migration_checked flag — including plain
`<group> --help`. That call had no timeout (neither the vendored client nor
tapi2 set one), so a slow link or a Yandex SmartCaptcha gateway could hang the
CLI indefinitely. Under CliRunner this also hung the unit suite (it stalled on
test_registered_mapped_groups_show_docs_url, which fires ~30 `--help` calls).

Three independent guards:
- auth.py: cap the best-effort resolver with LOGIN_RESOLVE_TIMEOUT_SECONDS=8
  and disable retries, so it can never wait forever.
- cli.py: skip the resolver entirely on --help/--version/-h passes
  (allow_login_resolve=False) — no command runs, so no login is needed.
- tests/conftest.py: autouse fixture neutralizes the network resolver for the
  whole unit suite; the module-level test-credential probe no longer triggers it.

Adds regression tests asserting a --help pass makes zero resolver calls and
that allow_login_resolve=False suppresses the migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix: resolve 13 confirmed bugs from the #483 bug hunt

Verified each finding against the live API, the WSDL cache, and the official
Yandex Direct docs before fixing. Bugs 10 and 14 were false positives
(no UsageError source inside the try — style only) and are intentionally skipped.

Functional fixes:
- bids/keywordbids get: reject empty SelectionCriteria with a UsageError
  before the request (live API error 4001) (#1, #2).
- bids get: re-raise UsageError so the new guard surfaces with exit code 2 (#8).
- bids set-auto: require exactly one of CampaignId/AdGroupId/KeywordId via
  add_single_id_selector (docs: the three are mutually exclusive) (#4).
- vendored to_columns: pad short report rows instead of IndexError (#3).
- vendored error handler: error_data.get("error_detail") to avoid KeyError (#11).
- reports build_report_request: reject empty FieldNames (live API error 8000) (#12).

Swallowed-validation fixes (add `except click.UsageError/ClickException: raise`
before the bare `except Exception`): balance (#6), strategies get (#7),
campaigns get (#9), ads get (#13), negativekeywordsharedsets update (#5).

Type annotation: parse_priority_goals_spec items -> List[Dict[str, Any]] (#15).

build_api_coverage_report: supply --campaign-ids for bids/keywordbids get
wire-capture so the empty-criteria guard does not break schema parity.

Closes #483

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix: broaden #483 coverage and add CHANGELOG (cherry-picked from #484)

Incorporates the genuinely-better parts of the parallel PR #484 into this
branch, which already carries the regression tests, the #15 annotation fix,
and the localized messages:

- ads: re-raise click.UsageError in all lifecycle handlers (update, delete,
  archive, unarchive, suspend, resume, moderate) in addition to get — so the
  whole resource matches bug #13's "get + 8 lifecycle" scope. Defensive: these
  handlers have no pre-API UsageError source today, but the guard keeps them
  consistent and future-proof.
- retargeting get/delete, advideos get: same defensive re-raise for uniformity
  (issue #483 items 10/14; not live defects, but harmless consistency).
- bids/keywordbids get: the empty-criteria message now also lists
  --serving-statuses, which likewise populates SelectionCriteria (kept the
  t() localization wrapper).
- CHANGELOG.md: document the bug-hunt fixes (#483) and the --help network-hang
  follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: axisrow <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
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