Uh oh!
There was an error while loading. Please reload this page.
test(integration): dynamic-stack CMA SDK sanity suite + AM 2.0 coverage + asset Content-Type fix (v1.10.1) - #168
Conversation
Add a self-contained pytest integration suite under tests/integration that creates a fresh stack per run, exercises every SDK resource method (positive, negative, and edge cases) against the live CMA API, and tears the stack down. - framework/: dynamic stack setup/teardown, request+cURL capture, response/error validators, tracked assertions, and a custom dashboard HTML report - api/: 30 resource files with full method coverage - data/: complex content-type schemas (modular blocks, groups, references, JSON RTE) and entry payloads - strict, bug-catching assertions; genuine SDK/environment issues tracked via xfail - timestamped HTML report + cURL log written to repo root (gitignored) Also: update AGENTS.md to document the sanity suite + env vars, add pytest/pytest-order to requirements, gitignore secrets/reports/docs.
Cover the asset scanning feature (DAM/AM 2.0) against both the normal org and the AM 2.0 (DAM-enabled) org: - normal-org scan tests (test_06_asset): upload returns _asset_scan_status 'pending'; field absent unless include_asset_scan_status=true; clean file scans 'clean'; EICAR test file scans 'quarantined'; listing includes status - test_31_am_assets: AM-org assets get 'am'-prefixed UIDs, full CRUD round-trip, the same scan lifecycle, publish() with the api_version: 3.2 header (publish- only; 404 on fetch) - framework: am_stack fixture (stack in AM_ORG_UID; whole suite skips when unset), runtime-generated EICAR fixture (base64-encoded so the signature is not committed raw), wait_for_scan() polling helper, and api_version header reset for per-test isolation Note: the correct query param is include_asset_scan_status (the response field is _asset_scan_status); verified live.
Asset.update() forced Content-Type: multipart/form-data while sending a JSON body, and Asset.replace() set a bare multipart/form-data header (no boundary) while passing files=. Both made the CMA API reject the request with 422 'Please send a valid multipart/form-data payload', and both leaked the wrong Content-Type onto subsequent requests on the shared client. - update(): send the JSON body as application/json (matches the JS SDK and the live API, which returns 200) - replace(): let the HTTP layer build the multipart body with a proper boundary Bump version to 1.10.1 and update the asset update unit test accordingly.
- taxonomy/terms/global-field delete: drop Content-Type on the body-less DELETE (the SDK merges application/json, which the API rejects 400/500; the JS SDK omits it) — these now pass instead of xfail - bulk update_workflow: provide a real target stage uid + notify field - branch delete: poll-retry the transient 'branch not valid' (905) while the branch finishes async provisioning - asset update/replace: flip from xfail to passing (fixed in the SDK, v1.10.1) - capture/setup: retry transient network errors (ReadTimeout/ConnectionError) 2x with backoff and bump the client timeout to 120s so dev11 blips don't fail the run
🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
There was a problem hiding this comment.
Pull request overview
Adds a new live “sanity” integration test suite under tests/integration/ that provisions a fresh stack per run, captures all CMA traffic into an HTML/cURL report, and exercises broad SDK resource coverage. It also includes an SDK fix for asset Content-Type handling (replace() boundary + update() JSON), bumps the package version to 1.10.1, and updates the changelog/agent docs.
Changes:
- Introduces a full dynamic-stack integration framework (setup/teardown, request capture, HTML reporting, shared store/fixtures).
- Adds ordered, per-resource live API coverage tests (including AM 2.0 asset scanning/publish behavior).
- Fixes asset request header behavior for
replace()andupdate(), and updates versioning/docs.
Reviewed changes
Copilot reviewed 46 out of 51 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/assets/test_assets_unit.py | Updates asset unit assertions (incl. new scan-status/api_version coverage). |
| tests/integration/pytest.ini | Integration-only pytest config (test discovery, markers, opts). |
| tests/integration/conftest.py | Session fixtures, dynamic setup/teardown, per-test header isolation, report hooks. |
| tests/integration/framework/init.py | Framework package marker. |
| tests/integration/framework/context.py | Shared run context + cross-file UID store. |
| tests/integration/framework/helpers.py | Generators, retry/wait helpers, response validators, tracked assertions. |
| tests/integration/framework/capture.py | Monkeypatches requests.request to capture traffic + generate cURL + retry transients. |
| tests/integration/framework/report.py | Renders a self-contained HTML dashboard + plain cURL log. |
| tests/integration/framework/setup.py | Login + dynamic stack lifecycle + management token + optional Personalize project. |
| tests/integration/data/init.py | Data package marker. |
| tests/integration/data/content_types.py | Content-type schema payload factories for integration coverage. |
| tests/integration/data/entries.py | Entry payload factories for integration coverage. |
| tests/integration/data/assets/extension.html | HTML asset used for extension upload tests. |
| tests/integration/api/test_01_user.py | Live tests for user endpoints and safe auth ops. |
| tests/integration/api/test_02_organization.py | Live tests for organization endpoints and safe ownership negatives. |
| tests/integration/api/test_03_stack.py | Live tests for stack endpoints (settings/users/sharing). |
| tests/integration/api/test_04_locale.py | Live tests for locale CRUD + fallback operations. |
| tests/integration/api/test_05_environment.py | Live tests for environment CRUD. |
| tests/integration/api/test_06_asset.py | Live tests for asset CRUD, folders, versions, publish, and scan status. |
| tests/integration/api/test_07_taxonomy.py | Live tests for taxonomy CRUD. |
| tests/integration/api/test_08_terms.py | Live tests for taxonomy terms CRUD + hierarchy/search. |
| tests/integration/api/test_09_extension.py | Live tests for extensions CRUD + upload. |
| tests/integration/api/test_10_webhook.py | Live tests for webhook CRUD + executions/logs/retry. |
| tests/integration/api/test_11_global_field.py | Live tests for global field CRUD + export. |
| tests/integration/api/test_12_content_type.py | Live tests for content type CRUD + complex schema round-trip. |
| tests/integration/api/test_13_label.py | Live tests for label CRUD. |
| tests/integration/api/test_14_entry.py | Live tests for entry CRUD, complex content, atomic ops, localization, publish. |
| tests/integration/api/test_15_variant_group.py | Live tests for variant group CRUD (Personalize-gated). |
| tests/integration/api/test_16_variants.py | Live tests for variants CRUD (Personalize-gated). |
| tests/integration/api/test_17_entry_variants.py | Live tests for entry variants endpoints (Personalize-gated). |
| tests/integration/api/test_18_branch.py | Live tests for branch CRUD with force delete + retries. |
| tests/integration/api/test_19_alias.py | Live tests for branch alias assign/find/fetch/delete. |
| tests/integration/api/test_20_role.py | Live tests for role CRUD. |
| tests/integration/api/test_21_workflow.py | Live tests for workflow CRUD, stages, publish rules, tasks. |
| tests/integration/api/test_22_delivery_token.py | Live tests for delivery token CRUD. |
| tests/integration/api/test_23_management_token.py | Live tests for management token CRUD. |
| tests/integration/api/test_24_release.py | Live tests for release CRUD + clone. |
| tests/integration/api/test_25_release_item.py | Live tests for release items operations. |
| tests/integration/api/test_26_bulk_operation.py | Live tests for bulk publish/unpublish/delete/workflow update. |
| tests/integration/api/test_27_publish_queue.py | Live tests for publish queue find/fetch/cancel. |
| tests/integration/api/test_28_metadata.py | Live tests for metadata endpoints (extension-backed). |
| tests/integration/api/test_29_auditlog.py | Live tests for audit log find/fetch. |
| tests/integration/api/test_30_oauth.py | Live tests for OAuth handler surface (authorize URL, token accessors). |
| tests/integration/api/test_31_am_assets.py | Live tests for AM 2.0 assets (UID prefix, scan, publish-only api_version header). |
| requirements.txt | Adds integration-suite deps (pytest, pytest-order). |
| contentstack_management/assets/assets.py | Fixes asset replace() multipart boundary handling and update() Content-Type logic. |
| contentstack_management/init.py | Bumps SDK version to 1.10.1. |
| CHANGELOG.md | Adds v1.10.1 entry describing asset header fixes. |
| AGENTS.md | Documents new integration suite usage and env vars. |
| .gitignore | Ignores integration outputs/env files and timestamped reports. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- assets.py replace(): use per-request header copy (no Content-Type) instead of mutating shared client headers dict; wrap file open in context manager to prevent FD leak - assets.py update(): use per-request header copy with explicit Content-Type: application/json, robust against prior upload/replace calls - capture.py retry: close old FD before reopening on transient retry; use tuple(val[2:]) to handle both 2-tuple and 3-tuple file specs without crashing on missing index - setup.py: remove references to non-existent .env.example in docstring and runtime error message - test_assets_unit.py: fix scan-status unit tests to use correct query param name (include_asset_scan_status) and lowercase string value (true), matching the integration suite and CMA API wire format
🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
_api_client._call_request merges self.headers into every request via headers.update(self.headers), which overwrites any per-request copy before the request is sent. The only effective way to suppress Content-Type for multipart uploads is to temporarily pop it from the shared dict and restore it in a finally block. Also retains the with-open() fix so the file handle is always closed after the request.
c0ca20c🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
…roval publish_request_approval calls GET /workflow which returns 401 when the entry has no active workflow state. wf_entry is class-scoped so TestWorkflowPublishRules gets a fresh entry that was never assigned to a stage (TestWorkflowStagesAndTasks sets a different instance). Fix: call set_workflow_stage inside the test to activate the entry's workflow state before querying. Removes the xfail marker.
b2eb8d1🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
…e reason GET /workflow (publish approval state) and POST /workflow (set stage) are separate ACL checks. The test account has set-stage permission but not get-approval permission on DEV11, causing 401 regardless of workflow state. The set_workflow_stage setup step is kept — it is still correct hygiene. Unblock: grant Workflow > Publish Approval to Owner role in the test org.
🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
🔒 Security Scan Results
⏱️ SLA Breach Summary
ℹ️ Vulnerabilities Without Available Fixes (Informational Only)The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:
✅ BUILD PASSED - All security checks passed |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Adds a comprehensive, dynamic-stack live integration ("sanity") test suite for the CMA Python SDK under
tests/integration/, replacing the ~2-year-old API tests. Each run creates a fresh stack, exercises every SDK resource method (positive / negative / edge cases) against the live CMA API, then tears the stack down. Modeled on the JS CMA SDK sanity suite, adapted to pytest.What's included
Framework (
tests/integration/framework/)setup.py— login → create stack → management token → (optional) Personalize project → flag-gated teardowncapture.py— monkeypatchesrequeststo record every call (method/url/headers/body/cURL) + transient-error retriesreport.py— self-contained dashboard HTML report (summary, coverage-by-resource, per-test request/response/cURL); honest status (passed-with-warnings, xfail)helpers.py— generators, response/error validators,tracked_assert, scan-status pollingconftest.py— session fixtures (ctx,stack,store, AM stack, EICAR file),pytest-ordersequencing, per-test header isolation, report hooksCoverage (
tests/integration/api/, 31 files) — user, org, stack, locale, environment, asset, taxonomy, terms, extension, webhook, global field, content type, label, entry, variant group, variants, entry variants, branch, alias, role, workflow, delivery/management tokens, release, release items, bulk ops, publish queue, metadata, audit log, oauth, AM 2.0 assets. Complex content-type schemas (modular blocks, groups, references, JSON RTE) round-tripped through entries.AM 2.0 / asset scanning — covers
include_asset_scan_status=truesurfacing_asset_scan_status(pending → clean | quarantined),am-prefixed UIDs in DAM-enabled orgs, and the publish-onlyapi_version: 3.2header. The asset-scan suite skips gracefully when the DAM-enabled org isn't configured.SDK bug fix (v1.10.1) — included here, see commit
fix(assets): …Asset.update()sent a JSON body withContent-Type: multipart/form-data→ API 422. Fixed toapplication/json.Asset.replace()set a baremultipart/form-data(no boundary) → API 422. Fixed to let the HTTP layer set the boundary.1.10.0 → 1.10.1+ CHANGELOG + unit-test assertion updated. Backward-compatible (the methods were previously always failing).Notes for reviewers
pytest-orderenforces dependency order; cross-file UIDs pass via a sharedstore.tests/unit+tests/mockare untouched (offline coverage retained); legacytests/apiis superseded by this suite (kept for now).tests/unit/assets/), so the asset-scan unit coverage lands with this PR.