refactor: modularize codebase and add 404 unit tests (#13)
All checks were successful
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
All checks were successful
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
This commit was merged in pull request #13.
This commit is contained in:
@@ -256,3 +256,159 @@ fn walkdir(path: &Path) -> Result<Vec<walkdir::DirEntry>, CoreError> {
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// --- compile_regex tests ---
|
||||
|
||||
#[test]
|
||||
fn compile_regex_valid_pattern() {
|
||||
let re = compile_regex(r"\bfoo\b");
|
||||
assert!(re.is_match("hello foo bar"));
|
||||
assert!(!re.is_match("foobar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_regex_invalid_pattern_returns_fallback() {
|
||||
// An invalid regex should return the fallback "^$" that only matches empty strings
|
||||
let re = compile_regex(r"[invalid");
|
||||
assert!(re.is_match(""));
|
||||
assert!(!re.is_match("anything"));
|
||||
}
|
||||
|
||||
// --- GDPR pattern tests ---
|
||||
|
||||
#[test]
|
||||
fn gdpr_pii_logging_matches() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
let pattern = &scanner.patterns[0]; // gdpr-pii-logging
|
||||
// Regex: (log|print|console\.|logger\.|tracing::)\s*[\.(].*\b(pii_keyword)\b
|
||||
assert!(pattern.pattern.is_match("console.log(email)"));
|
||||
assert!(pattern.pattern.is_match("console.log(user.ssn)"));
|
||||
assert!(pattern.pattern.is_match("print(phone_number)"));
|
||||
assert!(pattern.pattern.is_match("tracing::(ip_addr)"));
|
||||
assert!(pattern.pattern.is_match("log.debug(credit_card)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gdpr_pii_logging_no_false_positive() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
let pattern = &scanner.patterns[0];
|
||||
// Regular logging without PII fields should not match
|
||||
assert!(!pattern
|
||||
.pattern
|
||||
.is_match("logger.info(\"request completed\")"));
|
||||
assert!(!pattern.pattern.is_match("let email = user.email;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gdpr_no_consent_matches() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
let pattern = &scanner.patterns[1]; // gdpr-no-consent
|
||||
assert!(pattern.pattern.is_match("collect personal data"));
|
||||
assert!(pattern.pattern.is_match("store user_data in db"));
|
||||
assert!(pattern.pattern.is_match("save pii to disk"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gdpr_user_model_matches() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
let pattern = &scanner.patterns[2]; // gdpr-no-delete-endpoint
|
||||
assert!(pattern.pattern.is_match("struct User {"));
|
||||
assert!(pattern.pattern.is_match("class User(Model):"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gdpr_hardcoded_retention_matches() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
let pattern = &scanner.patterns[3]; // gdpr-hardcoded-retention
|
||||
assert!(pattern.pattern.is_match("retention = 30"));
|
||||
assert!(pattern.pattern.is_match("ttl: 3600"));
|
||||
assert!(pattern.pattern.is_match("expire = 86400"));
|
||||
}
|
||||
|
||||
// --- OAuth pattern tests ---
|
||||
|
||||
#[test]
|
||||
fn oauth_implicit_grant_matches() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[0]; // oauth-implicit-grant
|
||||
assert!(pattern.pattern.is_match("response_type = \"token\""));
|
||||
assert!(pattern.pattern.is_match("grant_type: implicit"));
|
||||
assert!(pattern.pattern.is_match("response_type='token'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_implicit_grant_no_false_positive() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[0];
|
||||
assert!(!pattern.pattern.is_match("response_type = \"code\""));
|
||||
assert!(!pattern.pattern.is_match("grant_type: authorization_code"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_authorization_code_matches() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[1]; // oauth-missing-pkce
|
||||
assert!(pattern.pattern.is_match("uses authorization_code flow"));
|
||||
assert!(pattern.pattern.is_match("authorization code grant"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_token_localstorage_matches() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[2]; // oauth-token-localstorage
|
||||
assert!(pattern
|
||||
.pattern
|
||||
.is_match("localStorage.setItem('access_token', tok)"));
|
||||
assert!(pattern
|
||||
.pattern
|
||||
.is_match("localStorage.getItem(\"refresh_token\")"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_token_localstorage_no_false_positive() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[2];
|
||||
assert!(!pattern
|
||||
.pattern
|
||||
.is_match("localStorage.setItem('theme', 'dark')"));
|
||||
assert!(!pattern
|
||||
.pattern
|
||||
.is_match("sessionStorage.setItem('token', t)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_token_url_matches() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[3]; // oauth-token-url
|
||||
assert!(pattern.pattern.is_match("access_token = build_url(query)"));
|
||||
assert!(pattern.pattern.is_match("bearer = url.param"));
|
||||
}
|
||||
|
||||
// --- Pattern rule file extension filtering ---
|
||||
|
||||
#[test]
|
||||
fn gdpr_patterns_cover_common_languages() {
|
||||
let scanner = GdprPatternScanner::new();
|
||||
for pattern in &scanner.patterns {
|
||||
assert!(
|
||||
pattern.file_extensions.contains(&"rs".to_string()),
|
||||
"Pattern {} should cover .rs files",
|
||||
pattern.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_localstorage_only_js_ts() {
|
||||
let scanner = OAuthPatternScanner::new();
|
||||
let pattern = &scanner.patterns[2]; // oauth-token-localstorage
|
||||
assert!(pattern.file_extensions.contains(&"js".to_string()));
|
||||
assert!(pattern.file_extensions.contains(&"ts".to_string()));
|
||||
assert!(!pattern.file_extensions.contains(&"rs".to_string()));
|
||||
assert!(!pattern.file_extensions.contains(&"py".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user