fix(cli): close 12 High bugs from #198 audit (PR 2/7)#208
Conversation
Per the systemic-audit issue #198, the WSDL parity gate landed in PR #205 marked 8 commands as xfail-strict pending their fixes. This PR applies the fixes one by one and drops every xfail / KNOWN_*_BUGS exemption, so the gate now passes outright (37 passed, 8 skipped). Patterns and fixes: * H1, H5, H8, H9, H10 — empty-payload no-op guard: - ``ads update``, ``adgroups update``, ``keywords update``: if the built payload has only ``Id``, raise UsageError listing the available update fields. - ``bids set``, ``keywordbids set``: require at least one bid value before constructing the request. * H2 — silent data loss on ``ads update``: - Per-type allow-list (TEXT_AD / TEXT_IMAGE_AD / MOBILE_APP_AD) rejects incompatible flags up front (e.g. ``--image-hash`` with ``--type TEXT_AD``) instead of silently dropping them. * H4 — ``adgroups add --region-ids`` is now ``required=True`` (WSDL ``AdGroupAddItem.RegionIds`` minOccurs=1). * H6 — ``campaigns add --type SMART_CAMPAIGN --counter-id`` is now required (WSDL ``SmartCampaignAddItem.CounterId`` minOccurs=1). * H7 — ``dynamicads add`` no longer requires ``--condition``; the WSDL field is minOccurs=0 and the CLI was over-constraining. * H11 — ``STRATEGY_TYPES`` in ``direct_cli/commands/strategies.py`` is rebuilt from ``StrategyAddItem``: dropped 5 names that were not in the WSDL (WbMaximumClicksPerBid, WbMaximumConversionRatePerBid, AverageCrrPerCampaign, MaxProfitPerFilter, MaxProfitPerCampaign), added 5 that were missing (AverageCpcPerCampaign, AverageCpcPerFilter, PayForConversionCrr, AverageCpaMultipleGoals, PayForConversionMultipleGoals). Membership sets (CPA / CRR / PFC / multi-goal) updated accordingly. * H12 — ``strategies update`` now validates ``--goal-id`` for the goal-id strategy family, mirroring ``strategies add``. * H13 — verified false-positive. WSDL ``ClientUpdateItem`` does NOT declare ``ClientId`` minOccurs=1; the login goes through the Client-Login header, and ``clients update`` already enforces "Provide at least one field to update". No code change here. The parity gate (``tests/test_wsdl_parity_gate.py``) drops every ``xfail`` marker and the ``KNOWN_REQUIRED_FIELD_BUGS`` exemption set, so any regression of these patterns will now fail CI on the spot. Three pre-existing tests updated to match the new validation surface: - ``test_adgroups_add_case_insensitive_default_type`` now passes ``--region-ids`` (now required). - ``test_campaigns_add_smart_requires_filter_average_cpc`` now passes ``--counter-id`` (now required, validated before ``--filter-average-cpc``). - ``test_dynamicads_add_requires_condition`` rewritten as ``test_dynamicads_add_without_condition_omits_conditions_field`` to reflect the new minOccurs=0 behaviour. Refs #198. Closes PR 2 of milestone 0.3.9. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production. |
|
Claude finished @axisrow's task in 6m 12s —— View job Claude PR Review — Critical-issues only
I cross-checked each of the 12 fixes against the WSDL cache in 🟡 Potential bug — H12:
|
| ID | Verdict | Notes |
|---|---|---|
| H1 | ✅ | ads update empty-subtype guard fires when len(ad_data) == 1 after type-specific block is built. Covers all paths. |
| H2 | ✅ | Per-type allow-list is built before the dry-run branch and uses is not None so explicit empties (--title "") still fall through to the empty-payload guard. |
| H4 | ✅ | Matches WSDL AdGroupAddItem.RegionIds minOccurs=1. |
| H5 | ✅ | adgroups update empty-payload guard. |
| H6 | ✅ | Matches WSDL SmartCampaignAddItem.CounterId minOccurs=1. Validation runs before the --filter-average-cpc check, which is a UX improvement — the test was updated accordingly. |
| H7 | ✅ | Matches WSDL WebpageAddItem.Conditions minOccurs=0. Correct removal of over-constraint. |
| H8 | ✅ | bids set requires --bid. |
| H9 | ✅ | keywordbids set requires at least one of --search-bid / --network-bid. |
| H10 | ✅ | keywords update empty-payload guard. |
| H11 | ✅ | I diffed STRATEGY_TYPES against every <element name="X" type="ns:Strategy*AddItem"/> in the WSDL — all 16 names match exactly, no extras, no omissions. |
| H13 | ✅ | Verified there is no ClientId minOccurs=1 in ClientUpdateItem; the existing "Provide at least one field" guard already covers the real WSDL contract. |
🔵 Minor (non-blocking)
-
direct_cli/commands/keywordbids.py:99-101— implicit string concatenation:raise click.UsageError( "keywordbids set requires at least one of " "--search-bid or --network-bid." )
Two adjacent string literals get joined to
"...of --search-bid..."which is correct output, just an unusual idiom. Not a bug; can be left as-is or flattened.
Summary
The only finding worth gating on is H12: the inline comment makes a WSDL claim that the cached schema disproves, and the validation may reject valid update payloads that the WSDL allows. Either rewrite the rationale to point at live-API behavior or drop the check. Everything else looks good — empty-payload guards close real silent-no-op bugs, the per-type allow-list eliminates the silent-data-loss class, and STRATEGY_TYPES is now exactly the WSDL set.
· Branch
There was a problem hiding this comment.
Pull request overview
This PR is the second milestone PR for the new WSDL ↔ CLI parity gate: it removes the xfail(strict=True) markers by adding/adjusting CLI-side validation so mutating commands (add/update/set) reject invalid “no-op” payloads and enforce WSDL-required inputs, bringing the parity gate fully green.
Changes:
- Removed all strict
xfailmarkers and theKNOWN_REQUIRED_FIELD_BUGSexemption from the WSDL parity gate tests. - Added empty-payload guards across multiple mutating commands (e.g.,
ads update,adgroups update,bids set,keywordbids set,keywords update) to prevent silent no-ops. - Tightened/adjusted validation to match WSDL semantics (e.g.,
adgroups add --region-idsrequired;campaigns add SMART_CAMPAIGN --counter-idrequired;dynamicads addno longer requires--condition).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_wsdl_parity_gate.py | Drops xfails/exemptions so the parity gate now fails on regressions. |
| tests/test_low_coverage_payloads.py | Updates dynamicads add test to reflect Conditions being optional and omitted when absent. |
| tests/test_dry_run.py | Adjusts existing dry-run tests for newly-required flags (--region-ids, --counter-id). |
| direct_cli/commands/ads.py | Adds per-type flag allow-list + rejects empty-subtype {Id} updates. |
| direct_cli/commands/adgroups.py | Makes --region-ids required for add; rejects empty update payloads. |
| direct_cli/commands/bids.py | Rejects bids set with no bid value. |
| direct_cli/commands/keywordbids.py | Rejects keywordbids set when both bid fields are missing. |
| direct_cli/commands/keywords.py | Rejects keywords update when no updatable fields are provided. |
| direct_cli/commands/dynamicads.py | Makes --condition optional and only emits Conditions when provided. |
| direct_cli/commands/campaigns.py | Enforces --counter-id for SMART_CAMPAIGN and always includes it in payload. |
| direct_cli/commands/strategies.py | Updates STRATEGY_TYPES to match WSDL and adds goal-id validation for updates. |
Comments suppressed due to low confidence (2)
direct_cli/commands/strategies.py:48
PayForConversionCrris included inPAY_FOR_CONVERSION_STRATEGY_TYPES, which makes_build_strategy_fieldstreat--average-cpaas a valid input and map it toCpa. The WSDLStrategyPayForConversionCrrAddItemhas required fieldsCrr+GoalIdand does not defineCpa, so this type should not be in the “pay-for-conversion uses Cpa” set; it likely belongs with the CRR family so--average-crrmaps toCrr.
PAY_FOR_CONVERSION_STRATEGY_TYPES = {
"PayForConversion",
"PayForConversionPerFilter",
"PayForConversionPerCampaign",
"PayForConversionCrr",
}
direct_cli/commands/strategies.py:358
PayForConversionMultipleGoalsis added toSTRATEGY_TYPESbut is not covered by theGOAL_ID_STRATEGY_TYPESvalidation. In the cached WSDL,StrategyPayForConversionMultipleGoalsAddItemrequiresGoalId(minOccurs=1), so without including this type in the goal-id family the CLI can’t enforce/persist--goal-idand will build an invalid payload for this subtype.
# GoalId is minOccurs=1 inside Strategy*Update for the
# goal-id family (issue #198 H12). Mirror the add command's
# validation here so updates do not silently drop GoalId.
if strategy_type in GOAL_ID_STRATEGY_TYPES and goal_id is None:
raise click.UsageError("Provide --goal-id for this strategy type")
| "AverageCpc", | ||
| "AverageCpcPerCampaign", | ||
| "AverageCpcPerFilter", |
Addresses PR #208 review: * claude[bot] H12 over-constraint: StrategyAverageCpaBase, StrategyPayForConversionBase, StrategyAverageCrrBase, StrategyPayForConversionCrrBase, StrategyAverageCpaPerCampaignBase all carry GoalId minOccurs=0, and every Strategy*UpdateItem is an empty extension of its *Base. Per strict WSDL parity the update command MUST accept payloads without --goal-id, so the issue #198 H12 validation block is removed (with a comment explaining why the add-time minOccurs=1 check still stands). * copilot #1: PayForConversionCrrAddItem schema is Crr + GoalId (no Cpa). Moved PayForConversionCrr out of PAY_FOR_CONVERSION_STRATEGY_TYPES into CRR_STRATEGY_TYPES so --average-crr now maps to Crr, not the WSDL-unknown Cpa. * copilot #2: StrategyPayForConversionMultipleGoalsAddItem.GoalId is minOccurs=1, so the type is added to GOAL_ID_STRATEGY_TYPES; the add command now rejects payloads without --goal-id for that subtype. AverageCpaMultipleGoals stays out — its AddItem has no GoalId field at all. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production. |
|
Claude finished @axisrow's task in 3m 33s —— View job Claude PR Review — Critical-issues only (re-review after 83f3636)
I re-reviewed after 83f3636 "fix(strategies): align with WSDL on update GoalId and Crr-family". That commit cleanly resolves my prior H12 concern and two of copilot's three suppressed findings ( However one suppressed copilot finding is still a live bug: 🔴 Critical —
|
| AddItem | Field name | minOccurs |
|---|---|---|
StrategyAverageCpcAddItem |
AverageCpc |
1 |
StrategyAverageCpcPerCampaignAddItem |
AverageCpc |
1 |
StrategyAverageCpcPerFilterAddItem |
FilterAverageCpc |
0 |
So a natural invocation like:
direct strategies add --name "Per-filter CPC" --type AverageCpcPerFilter --average-cpc 1000000
produces:
{ "AverageCpcPerFilter": { "AverageCpc": 1000000 } }…but the WSDL StrategyAverageCpcPerFilterAddItem has no AverageCpc field — only FilterAverageCpc, WeeklySpendLimit, CustomPeriodBudget, BidCeiling. The live API will reject this (or silently ignore the unknown field and create a strategy with no CPC set — either way, broken).
Parallel proof inside the same repo: direct_cli/commands/campaigns.py:324 correctly uses FilterAverageCpc for the SMART_CAMPAIGN → AVERAGE_CPC_PER_FILTER path, and tests/test_dry_run.py:984 asserts that field name explicitly. The strategies path is the inconsistent one.
The PAYLOAD_CASES set for strategies.add (tests/test_low_coverage_payloads.py:209-307) covers only AverageCpa, AverageCrr, PayForConversion — nothing exercises AverageCpcPerFilter (or AverageCpaMultipleGoals/PayForConversionMultipleGoals), which is why the gate doesn't catch this. Copilot's first suppressed comment flagged exactly this; the follow-up commit fixed the other two but missed this one.
Suggested fix — same shape as the existing PAY_FOR_CONVERSION / FILTER_CPA branches:
FILTER_CPC_STRATEGY_TYPES = {"AverageCpcPerFilter"}
if average_cpc is not None:
if strategy_type in FILTER_CPC_STRATEGY_TYPES:
fields["FilterAverageCpc"] = average_cpc
else:
fields["AverageCpc"] = average_cpc…and add a payload test analogous to test_strategies_add_pay_for_conversion_payload_uses_cpa_field so the parity gate locks the field name down.
🟢 Other 11 fixes — verified correct
| ID | Verdict | Notes |
|---|---|---|
| H1 | ✅ | ads update empty-subtype guard at ads.py:432 fires when len(ad_data) == 1. |
| H2 | ✅ | Per-type allow-list (ads.py:351-391) rejects incompatible flags before payload build. Uses is not None so explicit empties also reach the empty-payload guard. |
| H4 | ✅ | Matches WSDL AdGroupAddItem.RegionIds minOccurs=1. |
| H5 | ✅ | adgroups update empty-payload guard at adgroups.py:228-232. |
| H6 | ✅ | Matches WSDL SmartCampaignAddItem.CounterId minOccurs=1 (verified at tests/wsdl_cache/campaigns.xml). Validation runs before --filter-average-cpc check. |
| H7 | ✅ | Matches WSDL WebpageAddItem.Conditions minOccurs=0. Correct removal of over-constraint. |
| H8 | ✅ | bids set --bid now required. |
| H9 | ✅ | keywordbids set requires at least one of --search-bid / --network-bid. |
| H10 | ✅ | keywords update empty-payload guard at keywords.py:215-219. |
| H11 | ✅ | 16 names in STRATEGY_TYPES exactly match <element name="X" type="ns:Strategy*AddItem"/> in the WSDL. |
| H12 | ✅ | Fixed in 83f3636. Update no longer requires --goal-id (WSDL Strategy*UpdateItem extends Strategy*Base where GoalId is minOccurs=0). Add-side validation retained because *AddItem.GoalId is minOccurs=1. |
| H13 | ✅ | No ClientId minOccurs=1 in ClientUpdateItem; existing "Provide at least one field" guard already covers the real WSDL contract. |
🔵 Lower-severity observations (non-blocking)
-
AverageCpaMultipleGoalswith--average-cpa—StrategyAverageCpaMultipleGoalsAddItemhas noCpa/AverageCpafield at all. If a user passes--average-cpato--type AverageCpaMultipleGoals,_build_strategy_fieldsfalls through to theelsebranch and emitsAverageCpa, producing an invalid payload. Less likely than the H1 bug above (the option name doesn't suggest this type) but the same fix shape applies — either reject the flag for this subtype or omit it. -
keywordbids.py:99-101— implicit string concatenation across two literals. Not a bug; cosmetic.
Summary
One real bug remains: strategies add --type AverageCpcPerFilter produces a WSDL-invalid payload because the field-name dispatch in _build_strategy_fields doesn't account for FilterAverageCpc. Same pattern as the PayForConversion → Cpa and AverageCpaPerFilter → FilterAverageCpa branches that the function already handles correctly. The other 11 audit fixes are verified correct against the cached WSDL. PR is mergeable once that one field-name mapping is added (and ideally a payload test alongside it).
· Branch
…pcPerFilter Addresses PR #208 re-review: * claude[bot] critical: StrategyAverageCpcPerFilterAddItem has no AverageCpc field — its WSDL schema is FilterAverageCpc + WeeklySpendLimit + ... . The CLI used to emit {AverageCpc: N} unconditionally, producing a WSDL-invalid payload for `--type AverageCpcPerFilter`. Added a FILTER_CPC_STRATEGY_TYPES branch mirroring the existing FILTER_CPA_STRATEGY_TYPES handling so `--average-cpc` maps to `FilterAverageCpc` for that subtype only. * claude[bot] observation: AverageCpaMultipleGoals and PayForConversion- MultipleGoals AddItems have no Cpa/AverageCpa field at all. Now `_build_strategy_fields` raises `UsageError` if `--average-cpa` is passed with a multi-goal subtype, instead of silently emitting an unknown key. Tests: * test_strategies_add_average_cpc_per_filter_payload_uses_filter_field locks the new dispatch. * test_strategies_add_pay_for_conversion_crr_payload_uses_crr_field confirms the earlier commit's CRR-family classification still routes through `Crr`. * test_strategies_add_multi_goal_rejects_average_cpa exercises the multi-goal guard. * test_strategies_add_pay_for_conversion_multi_goal_requires_goal_id exercises the GOAL_ID_STRATEGY_TYPES extension from the prior commit. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production. |
|
Claude finished @axisrow's task in 6m 4s —— View job Claude PR Review — Critical-issues only (re-review after 2d890b7)
I re-reviewed after 2d890b7 "route --average-cpc to FilterAverageCpc for AverageCpcPerFilter". That commit closes the field-name bug I flagged previously, and also adds an explicit I found one remaining bug of the same class the PR exists to fix (silent no-op) — not in the audit's original 12, but caught by the same pattern. 🟠 Bug —
|
| Invocation | Current behavior | WSDL truth |
|---|---|---|
--type AverageCpa --average-cpc N |
emits AverageCpc field |
StrategyAverageCpaAddItem has no AverageCpc |
--type AverageCpc --average-cpa N |
emits AverageCpa field |
StrategyAverageCpcAddItem has no AverageCpa |
--type AverageCpa --average-crr N |
emits AverageCrr field |
StrategyAverageCpaAddItem has no AverageCrr |
--type AverageCpaMultipleGoals --goal-id N |
silently drops GoalId |
type has no GoalId — drop is "correct" but should be a hard reject per H2 pattern |
--type WbMaximumClicks --average-cpc N |
emits AverageCpc |
StrategyMaximumClicksAddItem has no AverageCpc |
The live API will either reject these (best case) or accept them and silently ignore the unknown field (worst case — user gets a strategy without the CPC/CPA they intended). The ads.py H2 fix solved the equivalent for ads update via a per-type allow-list table — strategies would benefit from the same pattern. Out of scope for this PR (issue #198 didn't enumerate these), but worth filing as a follow-up.
🟢 The 12 audit fixes — verified correct
| ID | Verdict | Notes |
|---|---|---|
| H1 | ✅ | ads update empty-subtype guard at ads.py:432. |
| H2 | ✅ | Per-type allow-list (ads.py:351-391) rejects incompatible flags before payload build. |
| H4 | ✅ | Matches WSDL AdGroupAddItem.RegionIds minOccurs=1. |
| H5 | ✅ | adgroups update empty-payload guard. |
| H6 | ✅ | Matches WSDL SmartCampaignAddItem.CounterId minOccurs=1; validation runs before --filter-average-cpc check. |
| H7 | ✅ | Matches WSDL WebpageAddItem.Conditions minOccurs=0. |
| H8 | ✅ | bids set --bid now required. |
| H9 | ✅ | keywordbids set requires at least one of --search-bid / --network-bid. |
| H10 | ✅ | keywords update empty-payload guard. |
| H11 | ✅ | 16 names in STRATEGY_TYPES exactly match <element name="X" type="ns:Strategy*AddItem"/> in the WSDL. |
| H12 | ✅ | Fixed in 83f3636. Update no longer requires --goal-id (WSDL Strategy*UpdateItem extends Strategy*Base where GoalId is minOccurs=0). |
| H13 | ✅ | No ClientId minOccurs=1 in ClientUpdateItem. |
Also verified the follow-up commits resolved both prior copilot/me findings: PayForConversionCrr is correctly in CRR_STRATEGY_TYPES (Crr+GoalId, no Cpa), PayForConversionMultipleGoals is correctly in GOAL_ID_STRATEGY_TYPES, and AverageCpcPerFilter correctly emits FilterAverageCpc with a payload test at tests/test_low_coverage_payloads.py:346-359.
Summary
The PR is one short guard away from done. Blocking: the strategies update empty-subtype no-op described above — it's exactly the bug class this PR was created to close, just not on the audit's hit list. Once that guard lands (plus a probe entry in the parity gate so it can't regress), this is mergeable. Everything in the listed 12 H-bugs checks out against the cached WSDL.
· Branch
PR #208 re-review found one more silent no-op of the same class this PR closes for ads/adgroups/bids/keywordbids/keywords: ``` direct strategies update --id 42 --type AverageCpa ``` …built `{Id: 42, AverageCpa: {}}` and the live API accepted it as a 200 OK no-op. A user mistyping a flag name got a successful response with no diff applied. Two guards now reject this: * if `--type X` is present but `_build_strategy_fields(...)` returned `{}`, the command errors with "strategies update requires at least one field for --type X."; * if after all flag merging `strategy_data` still contains only `Id` (no --name, no --counter-ids, no --priority-goal, no --attribution-model), the command errors with "strategies update requires at least one updatable field." Also locked the regression down in the WSDL parity gate: added a `strategies.update` probe to `EMPTY_PAYLOAD_PROBES` alongside the existing five so future refactors cannot silently re-open the no-op. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production. |
|
Claude finished @axisrow's task in 1m 52s —— View job Claude PR Review — Critical-issues only (re-review after 7945109)
I re-reviewed after 7945109 "fix(strategies): reject empty-subtype no-op on update". That commit cleanly closes the only blocker I flagged in my prior pass, and the fix matches the shape I'd suggested exactly. No remaining critical issues — PR is mergeable from a correctness standpoint. 🟢 Blocker from prior review — resolved
if strategy_type and not strategy_fields:
raise click.UsageError(
f"strategies update requires at least one field for "
f"--type {strategy_type}."
)…and if len(strategy_data) == 1:
raise click.UsageError(
"strategies update requires at least one updatable field."
)Both fire before payload construction. I traced the relevant cases:
Parity-gate lock-down is also in place — (
"strategies.update",
["strategies", "update", "--id", "1", "--type", "AverageCpa"],
"at least one",
),So the regression cannot silently re-open in a future refactor. 🟢 All 12 audit fixes — verified correct
🟡 Class-of-bug observation (non-blocking, broader than #198 H-list)
The live API will either reject these (best case) or silently ignore the unknown key (worst case — user gets a strategy without the CPC/CPA they intended). The SummaryNo blockers. The empty-subtype no-op I flagged last round is fixed and lock-down is in place. All 12 audit fixes verified against cached WSDL. The remaining |
Reword 0.3.10 entry from "fix per-type rejection" to "cover per-type rejection" to avoid implying a behavioral fix shipped in this release — the corrected rejection behavior was already shipped in 0.3.9 via the #198 audit follow-up PRs (#205/#208/#214/#202/#204). This PR only adds regression coverage. Addresses Copilot review comment on PR #226. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…226) * test: lock down subtype validation invariants from #210 repro matrix Add 9 new SILENT_LOSS_PROBES in tests/test_wsdl_parity_gate.py covering campaigns add, adgroups add, ads add, bidmodifiers add and strategies add incompatible flag/--type combinations from the #210 umbrella repro matrix. Add 3 non-regression tests in tests/test_dry_run.py for strategies update field aliases (AverageCpcPerFilter→FilterAverageCpc, PayForConversion→Cpa) and confirm AverageCpa update without --goal-id stays WSDL-valid. Refs: Closes #210. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * docs(changelog): clarify PR is test-only, not behavioral fix Reword 0.3.10 entry from "fix per-type rejection" to "cover per-type rejection" to avoid implying a behavioral fix shipped in this release — the corrected rejection behavior was already shipped in 0.3.9 via the #198 audit follow-up PRs (#205/#208/#214/#202/#204). This PR only adds regression coverage. Addresses Copilot review comment on PR #226. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Summary
PR 2 of milestone 0.3.9. The systemic-audit issue #198 found 13 High-severity bugs across mutating CLI commands; PR #205 added the WSDL ↔ CLI parity gate that marked 8 of them as
xfail-strictpending their fixes. This PR applies the fixes and drops everyxfailmarker, so the gate now passes outright.Counts after this PR:
tests/test_wsdl_parity_gate.py— 37 passed, 8 skipped, 0 xfailed, 0 failed.Fixes by audit ID
ads.pyads update.ads.py--image-hashon--type TEXT_AD).adgroups.py--region-idsis nowrequired=True.adgroups.pyadgroups update.campaigns.py--counter-idis now required for--type SMART_CAMPAIGN.dynamicads.py--conditionrequirement (WSDL Conditions minOccurs=0).bids.pybids set.keywordbids.pykeywordbids set.keywords.pykeywords update.strategies.pySTRATEGY_TYPESrebuilt fromStrategyAddItem: 5 bogus removed, 5 WSDL-only added. Membership sets updated.strategies.pystrategies updatevalidates--goal-idfor the goal-id family, mirroringadd.ClientUpdateItemhas noClientIdminOccurs=1; existingProvide at least one fieldguard already covers the real WSDL contract.Gate cleanup
@pytest.mark.xfail(strict=True)markers removed fromtests/test_wsdl_parity_gate.py.KNOWN_REQUIRED_FIELD_BUGSexemption set deleted.The gate now fails fast on any future regression of these patterns.
Pre-existing tests adjusted to new validation surface
test_adgroups_add_case_insensitive_default_type— passes--region-ids(now required).test_campaigns_add_smart_requires_filter_average_cpc— passes--counter-id(validated first now).test_dynamicads_add_requires_condition— rewritten astest_dynamicads_add_without_condition_omits_conditions_fieldto reflect minOccurs=0 behaviour.Test plan
pytest tests/test_wsdl_parity_gate.py→ 37 passed, 8 skipped, 0 xfailed.pytest -m 'not integration'→ 733 passed, 43 skipped.black+flake8clean on touched files.Refs #198. Part of milestone 0.3.9.
🤖 Generated with Claude Code