Skip to content

test(api): speed up API test suite#11681

Merged
Davidm4r merged 6 commits into
masterfrom
dev/david/improve_timing_tests
Jun 24, 2026
Merged

test(api): speed up API test suite#11681
Davidm4r merged 6 commits into
masterfrom
dev/david/improve_timing_tests

Conversation

@Davidm4r

@Davidm4r Davidm4r commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Context

The API test suite had grown very slow. Profiling with --durations=50 showed two dominant cost centers:

  1. Password hashing in fixtures — every create_user() / check_password() ran PBKDF2 (~hundreds of ms each), which dominated fixture setup across the whole suite.
  2. Compliance catalog parsingget_bulk_compliance_frameworks_universal() and CheckMetadata.get_bulk() re-read and Pydantic-validate ~100 compliance JSONs (~20 MB) and ~1k check metadata files on every call. Tests parametrize over every provider and deliberately reset the API-level caches, so the same catalogs were re-parsed dozens of times (≈3s/call locally, ≈19s under coverage in CI). The worst offenders were TestGetComplianceFrameworks::test_listing_is_subset_of_bulk[*] (~19s × 18 providers) and TestComplianceOverviewViewSet::test_compliance_overview_attributes_resolves_provider_from_scan (~64s).

This PR speeds up the suite without touching SDK production behavior — all caching lives in the test harness (conftest.py) and test settings.

Description

Test/CI-only changes:

  • api/src/backend/config/django/testing.py: use MD5PasswordHasher in tests. Secure hashing is unnecessary for tests and PBKDF2 dominated fixture setup time.
  • api/src/backend/pytest.ini: add --reuse-db so the local loop doesn't recreate/migrate the test DB on every run.
  • api/src/backend/conftest.py: install a test-only memoization of the heavy SDK catalog loaders at conftest import time (before test modules are collected, so from ... import bindings resolve to the cached wrappers). This is the test-only equivalent of an lru_cache on the SDK functions:
    • Per-provider cache for get_bulk_compliance_frameworks_universal() and CheckMetadata.get_bulk() — avoids repeating the directory scan + filtering per provider.
    • Per-path cache for load_compliance_framework_universal() — since get_bulk_* parses every JSON and only then filters by provider, a per-provider cache still re-parses all ~100 files on the first load of each provider. Caching by file path means the first provider parses the files once and every other provider/test reuses the already-parsed ComplianceFramework objects (only the cheap listdir + filtering re-runs). _load_jsons_from_dir calls load_compliance_framework_universal as a module global, so patching the attribute is picked up without touching the SDK.
  • .github/workflows/api-tests.yml: add --durations=50 to surface the slowest tests in CI output.

Why it's safe: the catalog files are immutable during a run and all consumers treat the parsed objects as read-only (they only read .provider, .supports_provider(), .requirements, .checks to build new structures), so sharing the parsed objects across providers/tests cannot leak state.

Steps to review

  1. Run the previously-slow compliance module and check the durations:

    cd api && uv run pytest src/backend/api/tests/test_compliance.py -p no:cacheprovider --create-db --durations=15 -q
    

    Expected: test_listing_is_subset_of_bulk[*] drops from ~7-8s each (≈19s under coverage) to ~1.7s for the first provider and ~0.01s for every other one. Full module ≈60s → ≈8s.

  2. Confirm no regressions on the endpoint path that lazily loads compliance:

    cd api && uv run pytest src/backend/api/tests/test_views.py -k ComplianceOverview -p no:cacheprovider -q
    
  3. Confirm tests that mock these functions still behave correctly (the per-test unittest.mock.patch overrides the wrappers and restores them afterward):

    cd api && uv run pytest src/backend/api/tests/test_compliance.py -p no:cacheprovider --create-db -q
    

Performance evidence (test_compliance.py, local):

Test Before After
test_listing_is_subset_of_bulk[<first provider>] ~7-8s ~1.7s
test_listing_is_subset_of_bulk[<each other provider>] ~7-8s ~0.01s
Full test_compliance.py module ~60s ~8s

Checklist

Community Checklist
  • This feature/issue is listed in here or roadmap.prowler.com
  • Is it assigned to me, if not, request it via the issue/feature in here or Prowler Community Slack

SDK/CLI

  • Are there new checks included in this PR? No — SDK production code is unchanged; all caching is test-only.

UI

  • All issue/task requirements work as expected on the UI
  • If this PR adds or updates npm dependencies, include package-health evidence (maintenance, popularity, known vulnerabilities, license, release age) and explain why existing/native alternatives are insufficient.
  • Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
  • Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
  • Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
  • Ensure new entries are added to CHANGELOG.md, if applicable.

API

  • All issue/task requirements work as expected on the API
  • Endpoint response output (if applicable) — N/A, no endpoint behavior changes.
  • EXPLAIN ANALYZE output for new/modified queries or indexes (if applicable) — N/A.
  • Performance test results (if applicable) — see table above.
  • Any other relevant evidence of the implementation (if applicable)
  • Verify if API specs need to be regenerated. — N/A, no API surface change.
  • Check if version updates are required (e.g., specs, uv, etc.). — N/A.
  • Ensure new entries are added to CHANGELOG.md, if applicable. — N/A, test/CI-only change with no user-facing impact.

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Summary by CodeRabbit

  • Tests
    • Improved test performance by overriding password hashing for faster fixture-heavy authentication and by caching parsed compliance catalog data in-memory during test runs.
    • Added safeguards to prevent cached or monkeypatched compliance loading behavior from leaking into later tests.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 825f8229-439d-426a-802d-85e86640cdb7

📥 Commits

Reviewing files that changed from the base of the PR and between b6dd774 and 5462b7e.

📒 Files selected for processing (1)
  • api/src/backend/conftest.py

📝 Walkthrough

Walkthrough

The test settings file adds PASSWORD_HASHERS to use MD5PasswordHasher instead of PBKDF2. The conftest adds cached compliance catalog loader wrappers at import time and an autouse fixture that clears shared cache state after tests using monkeypatch.

Changes

Test Suite Performance Optimizations

Layer / File(s) Summary
MD5 password hasher for test Django settings
api/src/backend/config/django/testing.py
Adds PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] to replace the default PBKDF2 hasher during fixture-heavy test user creation and auth checks.
Compliance catalog session cache via monkeypatching
api/src/backend/conftest.py
Defines _install_compliance_catalog_test_cache() that wraps get_bulk_compliance_frameworks_universal, CheckMetadata.get_bulk, and load_compliance_framework_universal with per-provider and per-path in-memory caches, installs those wrappers at import time, and adds _compliance_cache_guard autouse fixture to clear shared compliance cache state after tests that use monkeypatch.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: speeding up the API test suite.
Description check ✅ Passed The PR follows the template with Context, Description, Steps to review, Checklist, and License, and includes the key performance changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/david/improve_timing_tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added github_actions Pull requests that update GitHub Actions code component/api labels Jun 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Changes detected in the following folders without a corresponding update to the CHANGELOG.md:

  • api

Please add an entry to the corresponding CHANGELOG.md file to maintain a clear history of changes.

@github-actions

Copy link
Copy Markdown
Contributor

Conflict Markers Resolved

All conflict markers have been successfully resolved in this pull request.

@github-actions github-actions Bot added the community Opened by the Community label Jun 24, 2026
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🔒 Container Security Scan

Image: prowler-api:deb8220
Last scan: 2026-06-24 12:48:16 UTC

✅ No Vulnerabilities Detected

The container image passed all security checks. No known CVEs were found.

📋 Resources:

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.12%. Comparing base (30d737c) to head (5462b7e).
⚠️ Report is 27 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #11681   +/-   ##
=======================================
  Coverage   94.12%   94.12%           
=======================================
  Files         247      247           
  Lines       36541    36581   +40     
=======================================
+ Hits        34393    34433   +40     
  Misses       2148     2148           
Flag Coverage Δ
api 94.12% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
prowler ∅ <ø> (∅)
api 94.12% <95.48%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot removed the github_actions Pull requests that update GitHub Actions code label Jun 24, 2026
@danibarranqueroo danibarranqueroo removed the community Opened by the Community label Jun 24, 2026
@Davidm4r Davidm4r marked this pull request as ready for review June 24, 2026 12:00
@Davidm4r Davidm4r requested a review from a team as a code owner June 24, 2026 12:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/backend/conftest.py`:
- Around line 116-129: The cached loader wrappers in conftest are using
session-wide state, which can leak mutated Compliance objects and bypass
per-test patches in the affected tests. Update the caching around
cached_bulk_frameworks, cached_get_bulk, and cached_load so it is either scoped
to the tests that opt in or can be explicitly reset between tests via a
fixture/hook. Make sure the cache is cleared before or after each test that
monkeypatches these loaders, and keep the original loader references intact so
patched test behavior is still honored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e4ba4fd7-78d8-4447-9b64-a0e9fe3b91f8

📥 Commits

Reviewing files that changed from the base of the PR and between dc228e8 and 158438c.

📒 Files selected for processing (3)
  • api/src/backend/config/django/testing.py
  • api/src/backend/conftest.py
  • api/src/backend/pytest.ini

Comment thread api/src/backend/conftest.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/backend/conftest.py`:
- Around line 163-167: The post-`monkeypatch` cleanup in `conftest.py` only
clears the backend compliance caches, but
`api.compliance.get_compliance_frameworks()` also memoizes results in
`AVAILABLE_COMPLIANCE_FRAMEWORKS`. Update the fixture cleanup path that runs
after `yield` to clear that API-level cache as well, alongside
`_COMPLIANCE_FRAMEWORK_CACHE`, `_COMPLIANCE_CHECKS_CACHE`, and
`_COMPLIANCE_PATH_CACHE`, so patched catalog data does not leak stale framework
IDs into later tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 44de642b-845f-4fbf-8c33-4b482d86bcc1

📥 Commits

Reviewing files that changed from the base of the PR and between 158438c and b6dd774.

📒 Files selected for processing (1)
  • api/src/backend/conftest.py

Comment thread api/src/backend/conftest.py
@Davidm4r Davidm4r added the no-changelog Skip including change in changelog/release notes label Jun 24, 2026
@Davidm4r Davidm4r changed the title test: Make tests faster test(api): speed up API test suite Jun 24, 2026
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@Davidm4r Davidm4r merged commit 917e5d0 into master Jun 24, 2026
42 of 43 checks passed
@Davidm4r Davidm4r deleted the dev/david/improve_timing_tests branch June 24, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/api no-changelog Skip including change in changelog/release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants