Skip to content

chore(xtest): replace cipherTexts caches with session-scoped fixture#442

Merged
dmihalcik-virtru merged 10 commits intomainfrom
DSPX-1745-refactor
Apr 30, 2026
Merged

chore(xtest): replace cipherTexts caches with session-scoped fixture#442
dmihalcik-virtru merged 10 commits intomainfrom
DSPX-1745-refactor

Conversation

@dmihalcik-virtru
Copy link
Copy Markdown
Member

@dmihalcik-virtru dmihalcik-virtru commented Apr 29, 2026

Refactor to remove per-test-module encrypted-file-cache with module scoped fixture.

Why?

Each test module maintained its own cipherTexts dict
and inlined the if-cache-hit-else-encrypt blocks at every call site.
They had to repeat the same pattern and be careful to construct a unique 'scenario name', which, if not unique, would result in two entries sharing a cache entry, a cause of several hard-to-debug errors and incorrectly passing tests in the past.

What?

Replaces the cache objects with a single auto-keyed factory fixture,
using pytest scoping to control the cache lifetime.
By using the pytest.request magic fixture for naming, we can be avoid incorrect collisions.
Also, this fixes a latent ordering bug where manifest-shape assertions in do_encrypt_with only fired on cache miss.

But also...

While I'm here, I also fixed several places where files were not getting unique names. While this won't cause any trouble with a single, sequential pass, this may cause difficulties when using x-dist or when debugging changes locally and running the same or similar tests multiple times with different options.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • Tests
    • Added a session-scoped, centralized encryption fixture that memoizes encrypted artifacts for reuse across the test session.
    • Updated multiple test suites to consume the shared encryption factory, removing per-test/module encryption caching and duplicated encryption logic.
    • Standardized test artifact naming to include encrypt/decrypt SDK identifiers and inlined some manifest assertions into tests for clearer validation.

@dmihalcik-virtru dmihalcik-virtru requested review from a team as code owners April 29, 2026 20:06
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@dmihalcik-virtru has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 40 minutes and 16 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f74d186-3078-4ee7-b5db-45e0315c037c

📥 Commits

Reviewing files that changed from the base of the PR and between dbd484b and dd4ff16.

📒 Files selected for processing (6)
  • xtest/fixtures/assertions.py
  • xtest/fixtures/encryption.py
  • xtest/test_abac.py
  • xtest/test_policytypes.py
  • xtest/test_pqc.py
  • xtest/test_tdfs.py
📝 Walkthrough

Walkthrough

Centralizes test encryption into a new session-scoped fixture module and registers it in pytest; increases scopes of pt_file and tmp_dir to session. Tests were refactored to use an encrypted_tdf factory instead of module-level caches or inline encryption helpers.

Changes

Cohort / File(s) Summary
Fixture Configuration
xtest/conftest.py
Registered fixtures.encryption in pytest_plugins; changed pt_file scope modulesession and tmp_dir scope packagesession.
Encryption Fixtures
xtest/fixtures/encryption.py
New module: session-scoped _encryption_cache and encrypted_tdf factory that memoizes encrypted .tdf generation keyed by (encrypt SDK id, container, target_mode, attr_values, az, mime_type).
Refactored Tests
xtest/test_abac.py, xtest/test_policytypes.py, xtest/test_pqc.py, xtest/test_tdfs.py
Removed module-level cipherTexts/do_encrypt_with; tests now accept encrypted_tdf: EncryptFactory and call it to obtain ciphertext paths; some manifest assertions were inlined where previously centralized.
Legacy test outputs
xtest/test_legacy.py
Updated decrypted output filenames to include decrypt_sdk to avoid collisions across SDK variants.

Sequence Diagram

sequenceDiagram
    participant Test as Test Function
    participant Factory as encrypted_tdf Factory
    participant Cache as Session Cache (_encryption_cache)
    participant TmpDir as tmp_dir
    participant EncryptSDK as Encryption SDK
    participant Disk as Filesystem

    Test->>Factory: encrypted_tdf(encrypt_sdk, attr_values, target_mode, ...)
    Factory->>Factory: Compute cache key (SDK id, container, mode, attrs, az, mime)
    Factory->>Cache: Check key
    alt Cache Hit
        Cache-->>Factory: cached Path
        Factory-->>Test: return cached .tdf Path
    else Cache Miss
        Factory->>TmpDir: build output filename (test name + short SHA1)
        Factory->>EncryptSDK: encrypt(..., output_path, assert_value=az)
        EncryptSDK->>Disk: write ciphertext .tdf
        EncryptSDK-->>Factory: encryption complete
        Factory->>Disk: verify file exists
        Factory->>Cache: store Path in cache[key]
        Factory-->>Test: return new .tdf Path
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • pflynn-virtru
  • elizabethhealy

Poem

🐰 I hop and cache each ciphertext I make,

A session stash for every test's sake.
Filenames neat, no collisions in sight,
One short SHA keeps each output just right,
Hooray — this rabbit saved some test-day cake!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% 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 accurately describes the main refactoring: replacing module-level ciphertext caches with a session-scoped fixture across the test suite.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-1745-refactor

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
Review rate limit: 0/1 reviews remaining, refill in 40 minutes and 16 seconds.

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

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a session-scoped memoized encryption fixture, encrypted_tdf, and a corresponding cache to replace manual caching and the do_encrypt_with helper across the test suite. This change improves test efficiency by avoiding redundant encryption operations. Feedback indicates that several tests in test_pqc.py were missed during the migration, which will cause regressions. Additionally, because the new fixture does not perform the implicit manifest validation previously handled by the removed helper, explicit validation needs to be added back to format-specific tests.

Comment thread xtest/test_pqc.py
Comment thread xtest/test_tdfs.py
Comment thread xtest/test_tdfs.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xtest/test_pqc.py (1)

200-205: ⚠️ Potential issue | 🔴 Critical

Remove leftover cipherTexts cache references — they currently break lint and test execution.

Line 200 and Line 267 still use cipherTexts after the module cache was removed, which causes F821 and blocks the suite. Refactor these two tests to use encrypted_tdf like the rest of this PR; also fix the secpmlkem_5 filename prefix (secpmlkem_3 typo).

🛠️ Proposed fix
 def test_secpmlkem_3_roundtrip(
@@
     kas_url_km1: str,
     in_focus: set[tdfs.SDK],
+    encrypted_tdf: EncryptFactory,
 ):
@@
-    sample_name = f"secpmlkem_3-{encrypt_sdk}"
-    if sample_name in cipherTexts:
-        ct_file = cipherTexts[sample_name]
-    else:
-        ct_file = tmp_dir / f"{sample_name}.tdf"
-        cipherTexts[sample_name] = ct_file
-        encrypt_sdk.encrypt(
-            pt_file,
-            ct_file,
-            mime_type="text/plain",
-            container="ztdf",
-            attr_values=attr.value_fqns,
-            target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk),
-        )
+    ct_file = encrypted_tdf(
+        encrypt_sdk,
+        attr_values=attr.value_fqns,
+        target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk),
+    )
@@
 def test_secpmlkem_5_roundtrip(
@@
     kas_url_km1: str,
     in_focus: set[tdfs.SDK],
+    encrypted_tdf: EncryptFactory,
 ):
@@
-    sample_name = f"secpmlkem_3-{encrypt_sdk}"
-    if sample_name in cipherTexts:
-        ct_file = cipherTexts[sample_name]
-    else:
-        ct_file = tmp_dir / f"{sample_name}.tdf"
-        cipherTexts[sample_name] = ct_file
-        encrypt_sdk.encrypt(
-            pt_file,
-            ct_file,
-            mime_type="text/plain",
-            container="ztdf",
-            attr_values=attr.value_fqns,
-            target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk),
-        )
+    ct_file = encrypted_tdf(
+        encrypt_sdk,
+        attr_values=attr.value_fqns,
+        target_mode=tdfs.select_target_version(encrypt_sdk, decrypt_sdk),
+    )
@@
-    rt_file = tmp_dir / f"secpmlkem_3-{encrypt_sdk}-{decrypt_sdk}.untdf"
+    rt_file = tmp_dir / f"secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf"

Also applies to: 267-272

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtest/test_pqc.py` around lines 200 - 205, Tests reference a removed
module-level cache variable cipherTexts (used with sample_name and ct_file)
causing F821; replace those uses with the local encrypted_tdf variable like the
rest of the PR: remove any cipherTexts lookup/assignment and set ct_file (or the
path passed to encrypt_sdk.encrypt) to tmp_dir / encrypted_tdf (or the existing
encrypted_tdf Path) instead of using cipherTexts[sample_name]; do the same for
the other occurrence around the later block (previously lines 267-272). Also fix
the filename prefix typo by changing any "secpmlkem_3" used for the secpmlkem_5
test file to "secpmlkem_5" so the generated filename matches the intended test
vector.
🧹 Nitpick comments (1)
xtest/fixtures/encryption.py (1)

52-54: Harden cache hits by verifying the cached ciphertext still exists before reuse.

If a cached file is removed/cleaned mid-session, returning the stale Path can cause unrelated downstream failures. Re-encrypt when the cached path no longer exists.

♻️ Proposed fix
         cached = _encryption_cache.get(key)
-        if cached is not None:
+        if cached is not None and cached.is_file():
             return cached
+        if cached is not None and not cached.is_file():
+            _encryption_cache.pop(key, None)
Based on learnings: "When modifying test fixtures, be aware changes affect all tests using that fixture".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtest/fixtures/encryption.py` around lines 52 - 54, The cache lookup returns
a Path stored in _encryption_cache for key but doesn’t verify the file still
exists; change the branch that reads cached = _encryption_cache.get(key) to
check whether cached is not None and cached.exists() (or is_file()) before
returning it, and if the file is missing remove the stale entry from
_encryption_cache (del or pop) so the code falls through to re-encrypt and
produce a fresh Path; ensure you reference the same cache variable
_encryption_cache and the lookup key to locate where to add the existence check
and cache eviction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@xtest/fixtures/encryption.py`:
- Line 48: Update the shared encryption fixture to use the same default MIME
type as the SDK by changing the mime_type default from "text/plain" to
"application/octet-stream"; locate the fixture function in
xtest/fixtures/encryption.py and adjust the mime_type parameter default so
callers that omit mime_type keep consistent behavior with tdfs.SDK.encrypt.

---

Outside diff comments:
In `@xtest/test_pqc.py`:
- Around line 200-205: Tests reference a removed module-level cache variable
cipherTexts (used with sample_name and ct_file) causing F821; replace those uses
with the local encrypted_tdf variable like the rest of the PR: remove any
cipherTexts lookup/assignment and set ct_file (or the path passed to
encrypt_sdk.encrypt) to tmp_dir / encrypted_tdf (or the existing encrypted_tdf
Path) instead of using cipherTexts[sample_name]; do the same for the other
occurrence around the later block (previously lines 267-272). Also fix the
filename prefix typo by changing any "secpmlkem_3" used for the secpmlkem_5 test
file to "secpmlkem_5" so the generated filename matches the intended test
vector.

---

Nitpick comments:
In `@xtest/fixtures/encryption.py`:
- Around line 52-54: The cache lookup returns a Path stored in _encryption_cache
for key but doesn’t verify the file still exists; change the branch that reads
cached = _encryption_cache.get(key) to check whether cached is not None and
cached.exists() (or is_file()) before returning it, and if the file is missing
remove the stale entry from _encryption_cache (del or pop) so the code falls
through to re-encrypt and produce a fresh Path; ensure you reference the same
cache variable _encryption_cache and the lookup key to locate where to add the
existence check and cache eviction.
🪄 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: CHILL

Plan: Pro

Run ID: 4fc6fb55-7ed7-4c25-9dfa-6ebf4b9da18d

📥 Commits

Reviewing files that changed from the base of the PR and between ea2976e and 4eb23a1.

📒 Files selected for processing (6)
  • xtest/conftest.py
  • xtest/fixtures/encryption.py
  • xtest/test_abac.py
  • xtest/test_policytypes.py
  • xtest/test_pqc.py
  • xtest/test_tdfs.py

Comment thread xtest/fixtures/encryption.py
@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xtest/test_pqc.py (1)

289-291: ⚠️ Potential issue | 🟡 Minor

Fix the output filename in the 5-roundtrip test.

test_secpmlkem_5_roundtrip still writes to secpmlkem_3-...untdf, which can collide with the 3-roundtrip test and make failures harder to triage now that the temp directory is shared more broadly.

🛠️ Suggested fix
-    rt_file = tmp_dir / f"secpmlkem_3-{encrypt_sdk}-{decrypt_sdk}.untdf"
+    rt_file = tmp_dir / f"secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtest/test_pqc.py` around lines 289 - 291, The test
test_secpmlkem_5_roundtrip currently writes its recovered plaintext to a
filename hardcoded as "secpmlkem_3-...untdf", which collides with the
3-roundtrip test; update the rt_file construction in test_secpmlkem_5_roundtrip
(the variable rt_file) to use "secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf" so
each test writes to a unique output file before calling decrypt and asserting
with filecmp.cmp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@xtest/test_pqc.py`:
- Around line 289-291: The test test_secpmlkem_5_roundtrip currently writes its
recovered plaintext to a filename hardcoded as "secpmlkem_3-...untdf", which
collides with the 3-roundtrip test; update the rt_file construction in
test_secpmlkem_5_roundtrip (the variable rt_file) to use
"secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf" so each test writes to a unique
output file before calling decrypt and asserting with filecmp.cmp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f23b863-dc29-4fb2-872a-e2b5b0f4ddba

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb23a1 and 8b448f6.

📒 Files selected for processing (2)
  • xtest/test_pqc.py
  • xtest/test_tdfs.py

@github-actions
Copy link
Copy Markdown

dmihalcik-virtru and others added 4 commits April 30, 2026 12:31
Four test modules each maintained a module-level cipherTexts dict and
inline if-cache-hit-else-encrypt blocks at every call site (40+ blocks
plus do_encrypt_with). Replace with a single auto-keyed factory fixture
so pytest's own scoping owns the cache lifetime and callers don't pick
scenario strings. Also fixes a latent ordering bug where manifest-shape
assertions in do_encrypt_with only fired on cache miss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two tests added in 5958b3f (test_secpmlkem_3_roundtrip and
test_secpmlkem_5_roundtrip) still used the removed cipherTexts dict.
Move them onto the encrypted_tdf fixture introduced in 81fcefa.

Side note: the second test had a copy-paste sample_name of
"secpmlkem_3-..." which would have made it cache-collide with the
first under the old keying. Auto-keying by attr_values fixes that
incidentally — the two tests use different attribute fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xtest/test_pqc.py (1)

289-291: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the secpmlkem_5 artifact name.

This path still uses secpmlkem_3, so the 5-roundtrip case overwrites the 3-roundtrip output in the shared tmp_dir. Use the matching prefix here.

🛠️ Proposed fix
-    rt_file = tmp_dir / f"secpmlkem_3-{encrypt_sdk}-{decrypt_sdk}.untdf"
+    rt_file = tmp_dir / f"secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtest/test_pqc.py` around lines 289 - 291, The artifact name for the
5-roundtrip case is wrong: the rt_file string uses "secpmlkem_3-…" and
overwrites the 3-roundtrip output; update the rt_file construction in
xtest/test_pqc.py (the line assigning rt_file in the 5-roundtrip block) to use
the matching "secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf" prefix so
decrypt_sdk.decrypt writes to a distinct file in tmp_dir.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@xtest/test_pqc.py`:
- Around line 289-291: The artifact name for the 5-roundtrip case is wrong: the
rt_file string uses "secpmlkem_3-…" and overwrites the 3-roundtrip output;
update the rt_file construction in xtest/test_pqc.py (the line assigning rt_file
in the 5-roundtrip block) to use the matching
"secpmlkem_5-{encrypt_sdk}-{decrypt_sdk}.untdf" prefix so decrypt_sdk.decrypt
writes to a distinct file in tmp_dir.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2eeb03e9-3328-43bb-92bd-ad69bcbd7ace

📥 Commits

Reviewing files that changed from the base of the PR and between 8b448f6 and b299d16.

📒 Files selected for processing (6)
  • xtest/conftest.py
  • xtest/fixtures/encryption.py
  • xtest/test_abac.py
  • xtest/test_policytypes.py
  • xtest/test_pqc.py
  • xtest/test_tdfs.py

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

…fstrings

Avoids hand-crafted unique names by constructing rt_file as
ct_file.with_name(f"{ct_file.stem}-{decrypt_sdk}.untdf"), which is
automatically unique across all encrypt params (already encoded in
ct_file) and the decrypt sdk/version. Also removes the now-redundant
short_names variable and tmp_dir fixture parameter from test_policytypes,
and fixes a copy-paste bug where test_secpmlkem_5_roundtrip was writing
to a secpmlkem_3-named file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

X-Test Failure Report

✅ java-v0.14.0
✅ java-main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

X-Test Failure Report

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

✅ java-main

- Convert encrypted_tdf factory closure to EncryptFactory class with
  rt_file() method that includes the current test label, preventing
  rt_file path collisions between tests that share a cached ciphertext
- Replace all ct_file.with_name()/b_file.with_name() rt_file patterns
  with encrypted_tdf.rt_file() across all test files; standardize
  test_policytypes.py from .returned to .untdf extension
- Bump assertion fixtures from package to session scope to match the
  session-scoped tmp_dir they write into (latent path collision fix)
- Fix stale module docstring in fixtures/encryption.py
- Remove unused pt_file parameter from test_manifest_validity and
  test_manifest_validity_with_assertions
- Delete dead commented-out audit_logs blocks in test_tdf_with_unbound_policy
  and test_tdf_with_altered_policy_binding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

X-Test Failure Report

✅ java-v0.14.0

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

@sonarqubecloud
Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@github-actions
Copy link
Copy Markdown

@dmihalcik-virtru dmihalcik-virtru merged commit c241f94 into main Apr 30, 2026
14 of 15 checks passed
@dmihalcik-virtru dmihalcik-virtru deleted the DSPX-1745-refactor branch April 30, 2026 21:18
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.

2 participants