Repository navigation
test: centralize scrubber fixtures + add regression meta-test #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,6 +60,10 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| use super::*; | ||||||||||||||||||||||||||||||||||||||
| use crate::config::SecretKind; | ||||||||||||||||||||||||||||||||||||||
| use crate::pipeline::Role; | ||||||||||||||||||||||||||||||||||||||
| use crate::test_fixtures::{ | ||||||||||||||||||||||||||||||||||||||
| FAKE_ABSOLUTE_PATH, FAKE_ANTHROPIC_API_KEY, FAKE_AWS_ACCESS_KEY, FAKE_EMAIL, | ||||||||||||||||||||||||||||||||||||||
| FAKE_GITHUB_TOKEN, FAKE_PRIVATE_IP, | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
| use chrono::TimeZone; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| fn make_msg(content: &str) -> NormalizedMessage { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -90,7 +94,7 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_aws_access_key() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("key=AKIAIOSFODNN7EXAMPLE leaked"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("key={} leaked", FAKE_AWS_ACCESS_KEY)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.was_redacted()); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::AwsAccessKey)); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -101,7 +105,7 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_anthropic_api_key() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("auth: sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789ABCD"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("auth: {}", FAKE_ANTHROPIC_API_KEY)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::AnthropicApiKey)); | ||||||||||||||||||||||||||||||||||||||
| assert!(msg.content.contains("[REDACTED:anthropic_key]")); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -110,24 +114,24 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_github_token() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("token: ghp_1234567890abcdefghijklmnopqrstuvwxyzAB"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("token: {}", FAKE_GITHUB_TOKEN)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::GitHubToken)); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_email_address() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("ping davidmirelesll@outlook.com later"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("ping {} later", FAKE_EMAIL)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::EmailAddress)); | ||||||||||||||||||||||||||||||||||||||
| assert!(!msg.content.contains("@outlook.com")); | ||||||||||||||||||||||||||||||||||||||
| assert!(!msg.content.contains("@b.com")); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
122
to
129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Assert removal of the shared fixture, not a stale suffix. The input now uses Proposed fix- assert!(!msg.content.contains("`@b.com`"));
+ assert!(!msg.content.contains(FAKE_EMAIL));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_private_ipv4() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("server at 192.168.1.42 was down"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("server at {} was down", FAKE_PRIVATE_IP)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::PrivateIpv4)); | ||||||||||||||||||||||||||||||||||||||
| assert!(msg.content.contains("[REDACTED:private_ip]")); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -136,7 +140,7 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_absolute_user_path() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("see /home/lou/secret.txt for details"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("see {} for details", FAKE_ABSOLUTE_PATH)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report | ||||||||||||||||||||||||||||||||||||||
| .redacted_kinds | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -156,7 +160,7 @@ mod tests { | |||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||
| fn redacts_multiple_kinds_in_one_message() { | ||||||||||||||||||||||||||||||||||||||
| let cfg = PipelineConfig::with_safe_defaults(); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg("key AKIAIOSFODNN7EXAMPLE email a@b.com"); | ||||||||||||||||||||||||||||||||||||||
| let mut msg = make_msg(&format!("key {} email {}", FAKE_AWS_ACCESS_KEY, FAKE_EMAIL)); | ||||||||||||||||||||||||||||||||||||||
| let report = scrub(&mut msg, &cfg); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::AwsAccessKey)); | ||||||||||||||||||||||||||||||||||||||
| assert!(report.redacted_kinds.contains(&SecretKind::EmailAddress)); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| //! Shared test fixtures for the scrubber test suite. | ||
| //! | ||
| //! These constants exist so the scrubber has realistic-looking inputs to | ||
| //! redact in tests, without copy-pasting the same placeholder strings | ||
| //! across `src/lib.rs`, `src/pipeline/scrubber.rs`, and the integration | ||
| //! tests under `tests/`. | ||
| //! | ||
| //! ## Why these strings are not real secrets | ||
| //! | ||
| //! - `FAKE_AWS_ACCESS_KEY` is `AKIAIOSFODNN7EXAMPLE` — Amazon publishes | ||
| //! this literal in their AWS docs as the "example" key. Not real. | ||
| //! - `FAKE_ANTHROPIC_API_KEY` is `sk-ant-api03-` followed by contiguous | ||
| //! alphabet+digits (`abcdefghijklmnopqrstuvwxyz0123456789ABCD`). | ||
| //! Real Anthropic keys have ~93 random base62 chars after the prefix | ||
| //! — never contiguous alphabet. | ||
| //! - `FAKE_GITHUB_TOKEN` is `ghp_` followed by contiguous alphabet+digits | ||
| //! (`1234567890abcdefghijklmnopqrstuvwxyzAB`). Real PATs are random. | ||
| //! - `FAKE_EMAIL` / `FAKE_PRIVATE_IP` / `FAKE_ABSOLUTE_PATH` are | ||
| //! obviously not real PII. | ||
| //! | ||
| //! ## Scanner caveats | ||
| //! | ||
| //! Upstream secret scanners (GitHub Secret Scanning, KeyScout) pattern | ||
| //! match on the prefix shape and may still flag these. If a scanner | ||
| //! opens an issue against this repo referencing these literals, the | ||
| //! response is: "constructed test fixture, see `src/test_fixtures.rs`". | ||
| //! See `CHANGELOG.md` v0.6.2 for the consolidation that reduced the | ||
| //! duplication that triggered the original KeyScout report. | ||
| //! | ||
| //! ## Visibility | ||
| //! | ||
| //! Marked `#[doc(hidden)]` in `lib.rs` so the module does not appear | ||
| //! in rustdoc, but kept `pub` so integration tests under `tests/` | ||
| //! can import the constants. This is the same pattern used by | ||
| //! `tokio`, `serde`, and other published crates with shared test | ||
| //! fixtures. | ||
|
|
||
| #![allow(dead_code)] // Different tests use different subsets | ||
|
|
||
| pub const FAKE_AWS_ACCESS_KEY: &str = "AKIAIOSFODNN7EXAMPLE"; | ||
| pub const FAKE_ANTHROPIC_API_KEY: &str = "sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789ABCD"; | ||
| pub const FAKE_GITHUB_TOKEN: &str = "ghp_1234567890abcdefghijklmnopqrstuvwxyzAB"; | ||
| pub const FAKE_EMAIL: &str = "a@b.com"; | ||
| pub const FAKE_PRIVATE_IP: &str = "192.168.1.42"; | ||
| pub const FAKE_ABSOLUTE_PATH: &str = "/home/lou/secret.txt"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| //! Meta-test: verifies the scrubber recognizes every fixture in | ||
| //! `test_fixtures` as a redactable pattern. | ||
| //! | ||
| //! This is a regression guard against two failure modes: | ||
| //! | ||
| //! 1. **Fixture added but regex not updated to match.** If a future | ||
| //! contributor adds a new `FAKE_*` constant to `src/test_fixtures.rs` | ||
| //! but forgets to add a corresponding pattern to | ||
| //! `PipelineConfig::with_safe_defaults`, this test catches it. | ||
| //! | ||
| //! 2. **Regex updated but fixture strings no longer match.** If the | ||
| //! scrubber's regex patterns get tightened (e.g., length minimum | ||
| //! increased), the fixture strings may no longer match, and this | ||
| //! test catches it before the individual `redacts_*` unit tests | ||
| //! silently start failing. | ||
|
Comment on lines
+1
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Make the meta-test fixture table authoritative. The tests iterate manually maintained arrays, so adding a new Also applies to: 49-56, 84-91 🤖 Prompt for AI Agents |
||
| //! | ||
| //! See `src/test_fixtures.rs` module docs for the rationale on why | ||
| //! these strings are not real secrets. | ||
|
|
||
| use chrono::TimeZone; | ||
| use loust_llm_mempipe::config::PipelineConfig; | ||
| use loust_llm_mempipe::pipeline::scrubber::scrub; | ||
| use loust_llm_mempipe::pipeline::{NormalizedMessage, Role}; | ||
| use loust_llm_mempipe::test_fixtures::{ | ||
| FAKE_ABSOLUTE_PATH, FAKE_ANTHROPIC_API_KEY, FAKE_AWS_ACCESS_KEY, FAKE_EMAIL, FAKE_GITHUB_TOKEN, | ||
| FAKE_PRIVATE_IP, | ||
| }; | ||
|
|
||
| fn msg_with(content: &str) -> NormalizedMessage { | ||
| NormalizedMessage { | ||
| id: "meta-1".into(), | ||
| source: "meta-test".into(), | ||
| role: Role::User, | ||
| content: content.into(), | ||
| original_length: 0, | ||
| created_at: chrono::Utc.timestamp_opt(1_700_000_000, 0).unwrap(), | ||
| thread_id: "t-meta".into(), | ||
| thread_title: None, | ||
| project_hint: None, | ||
| content_hash: 0, | ||
| hits: 1, | ||
| signal_score: 0.0, | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn every_fixture_is_redacted_by_the_scrubber() { | ||
| let cfg = PipelineConfig::with_safe_defaults(); | ||
| let fixtures = [ | ||
| ("FAKE_AWS_ACCESS_KEY", FAKE_AWS_ACCESS_KEY), | ||
| ("FAKE_ANTHROPIC_API_KEY", FAKE_ANTHROPIC_API_KEY), | ||
| ("FAKE_GITHUB_TOKEN", FAKE_GITHUB_TOKEN), | ||
| ("FAKE_EMAIL", FAKE_EMAIL), | ||
| ("FAKE_PRIVATE_IP", FAKE_PRIVATE_IP), | ||
| ("FAKE_ABSOLUTE_PATH", FAKE_ABSOLUTE_PATH), | ||
| ]; | ||
|
|
||
| for (name, fixture) in fixtures { | ||
| let mut msg = msg_with(fixture); | ||
| let report = scrub(&mut msg, &cfg); | ||
| assert!( | ||
| report.was_redacted(), | ||
| "fixture {name} = {fixture:?} was not redacted by any pattern \ | ||
| — did you forget to add a matching SecretKind to with_safe_defaults()?", | ||
| ); | ||
| // Sanity: the literal fixture string should be gone from the | ||
| // post-scrub content (or at least not equal to the original). | ||
| assert_ne!( | ||
| msg.content, fixture, | ||
| "fixture {name} = {fixture:?} survived redaction (literal still equals content)" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn fixture_strings_match_their_declared_regex_pattern() { | ||
| // Independent check at the regex layer, complementing the end-to-end | ||
| // scrubber test above. Catches a pattern that exists but doesn't match | ||
| // the fixture (e.g., if someone tightens the regex without updating | ||
| // the fixture string). | ||
| use loust_llm_mempipe::config::SecretKind; | ||
| let cfg = PipelineConfig::with_safe_defaults(); | ||
|
|
||
| let pairs = [ | ||
| (SecretKind::AwsAccessKey, FAKE_AWS_ACCESS_KEY), | ||
| (SecretKind::AnthropicApiKey, FAKE_ANTHROPIC_API_KEY), | ||
| (SecretKind::GitHubToken, FAKE_GITHUB_TOKEN), | ||
| (SecretKind::EmailAddress, FAKE_EMAIL), | ||
| (SecretKind::PrivateIpv4, FAKE_PRIVATE_IP), | ||
| (SecretKind::AbsoluteUserPath, FAKE_ABSOLUTE_PATH), | ||
| ]; | ||
|
|
||
| for (kind, fixture) in pairs { | ||
| let pattern = cfg | ||
| .secret_patterns | ||
| .iter() | ||
| .find(|(k, _)| k == &kind) | ||
| .unwrap_or_else(|| panic!("no pattern registered for SecretKind::{kind:?}")); | ||
| assert!( | ||
| pattern.1.is_match(fixture), | ||
| "regex for {kind:?} does not match its declared fixture {fixture:?} \ | ||
| — the fixture string drifted from what the pattern accepts", | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: LOUST-PRO/LLMmempipe
Length of output: 13236
🏁 Script executed:
Repository: LOUST-PRO/LLMmempipe
Length of output: 150
Avoid exposing the test fixture module to the public crate API.
#[doc(hidden)]suppresses rustdoc, butpub mod test_fixturesstill allows downstream crates to import the module and its#\[doc(hidden)\] pub constfixtures. Keep it test-only, e.g. behind a#[cfg(test)]or test-only Cargo feature; if it must remain public, update the CHANGELOG note to acknowledge the public API change.📍 Affects 2 files
src/lib.rs#L11-L14(this comment)CHANGELOG.md#L21-L24🤖 Prompt for AI Agents