use super::*;
use crate::findings::types::Severity;
use std::path::PathBuf;
#[test]
fn reports_go_panic_in_domain_code_as_high() {
let file = facts(
"src/domain/user.go",
Some("Go"),
"package domain\nfunc Parse() { panic(\"bad\") }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.go.panic-exit-risk");
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn downgrades_python_assert_in_tests() {
let file = facts(
"tests/test_user.py",
Some("Python"),
"assert user.is_valid\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn reports_js_process_exit_but_not_string_literal() {
let file = facts(
"src/cli/main.ts",
Some("TypeScript"),
"const text = \"process.exit(1)\";\nprocess.exit(1);\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.javascript.runtime-exit-risk");
}
#[test]
fn downgrades_js_process_exit_in_script_paths() {
let file = facts("scripts/check.js", Some("JavaScript"), "process.exit(1);\n");
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn downgrades_process_exit_in_bin_path_to_low() {
let file = facts(
"bin/repopilot.js",
Some("JavaScript"),
"#!/usr/bin/env node\nprocess.exit(1);\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.javascript.runtime-exit-risk");
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn downgrades_process_exit_in_cli_executable_package_to_low() {
let file = cli_package_facts(
"src/commands/new.ts",
Some("TypeScript"),
"export const run = async () => { process.exit(1) }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn reports_process_exit_in_reusable_module_of_cli_package_as_high() {
let file = cli_package_facts(
"src/lib/parser.ts",
Some("TypeScript"),
"export function parseConfig(input: string) { if (!input) { process.exit(1); } }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn reports_process_exit_in_non_cli_commands_dir_as_high() {
let file = facts(
"src/domain/commands/run.ts",
Some("TypeScript"),
"export function run() { process.exit(1); }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn reports_js_process_exit_in_library_code_as_high() {
let file = facts(
"src/lib/runtime.js",
Some("JavaScript"),
"export function stop() { process.exit(1); }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn reports_csharp_not_implemented_placeholder() {
let file = facts(
"src/domain/UserService.cs",
Some("C#"),
"public void Run() { throw new NotImplementedException(); }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn downgrades_kotlin_todo_in_build_logic_to_low() {
let file = facts(
"build-logic/convention/src/main/kotlin/Plugin.kt",
Some("Kotlin"),
"fun configure(): Unit = TODO()",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn downgrades_kotlin_todo_in_gradle_testing_module_to_low() {
let file = facts(
"core/testing/src/main/kotlin/com/app/core/testing/FakeSync.kt",
Some("Kotlin"),
"class FakeSync { fun sync(): Unit = TODO() }",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn test_support_role_wins_over_domain_role_for_managed_fatal_risk() {
let file = facts(
"core/testing/src/main/kotlin/com/app/models/FakeUser.kt",
Some("Kotlin"),
"class FakeUser { fun create(): Unit = TODO() }",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::Low);
}
#[test]
fn kotlin_todo_in_feature_application_code_stays_high() {
let file = facts(
"feature/topic/impl/src/main/kotlin/com/app/feature/topic/TopicScreen.kt",
Some("Kotlin"),
"fun TopicScreen(state: State): Unit = TODO()",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn kotlin_todo_in_testing_package_namespace_stays_high() {
let file = facts(
"feature/topic/impl/src/main/kotlin/com/app/testing/TopicScreen.kt",
Some("Kotlin"),
"fun TopicScreen(state: State): Unit = TODO()",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.managed.fatal-exception-risk");
assert_eq!(findings[0].severity, Severity::High);
}
#[test]
fn ignores_functional_iterator_style_without_risky_pattern() {
let file = facts(
"src/domain/users.ts",
Some("TypeScript"),
"export const names = users.filter(u => u.active).map(u => u.name);\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert!(findings.is_empty());
}
#[test]
fn ast_runtime_finding_reports_ast_provenance() {
use crate::rules::SignalSource;
let file = facts(
"src/lib/runtime.js",
Some("JavaScript"),
"export function stop() { process.exit(1); }\n",
);
let mut findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
findings[0].populate_rule_metadata();
assert_eq!(findings[0].provenance.signal_source, SignalSource::Ast);
}
#[test]
fn python_bare_raise_not_implemented_is_flagged_exactly_once() {
let file = facts(
"src/service.py",
Some("Python"),
"def handler():\n raise NotImplementedError\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].rule_id, "language.python.exception-risk");
}
#[test]
fn js_throw_new_error_is_never_flagged() {
for path in [
"src/cli/main.ts",
"packages/core/src/index.ts",
"src/lib/x.ts",
] {
let file = facts(
path,
Some("TypeScript"),
"export function run() { throw new Error(\"boom\"); }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert!(findings.is_empty(), "throw must not be flagged in {path}");
}
}
#[test]
fn go_ast_runtime_finding_reports_ast_provenance() {
use crate::rules::SignalSource;
let file = facts(
"src/domain/user.go",
Some("Go"),
"package domain\nfunc Parse() { panic(\"bad\") }\n",
);
let mut findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 1);
findings[0].populate_rule_metadata();
assert_eq!(findings[0].provenance.signal_source, SignalSource::Ast);
}
#[test]
fn go_panic_inside_string_literal_is_not_flagged() {
let file = facts(
"src/domain/user.go",
Some("Go"),
"package domain\nfunc label() string { return \"panic(\" }\n",
);
let findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert!(findings.is_empty());
}
#[test]
fn parse_failure_fallback_reports_text_heuristic_provenance() {
use crate::analysis::parse::ParsedFile;
use crate::rules::SignalSource;
let file = facts(
"src/domain/user.go",
Some("Go"),
"package domain\nfunc Parse() { panic(\"bad\") }\n",
);
let unparsed = ParsedFile::new(file.content.as_deref().unwrap(), Some("Ruby"));
let mut findings = LanguageRiskAudit.analyze(&file, &unparsed, &ScanConfig::default());
assert_eq!(findings.len(), 1);
findings[0].populate_rule_metadata();
assert_eq!(
findings[0].provenance.signal_source,
SignalSource::TextHeuristic
);
}
fn facts(path: &str, language: Option<&str>, content: &str) -> FileFacts {
FileFacts {
path: PathBuf::from(path),
language: language.map(str::to_string),
non_empty_lines: content.lines().count(),
branch_count: 0,
imports: Vec::new(),
content: Some(content.to_string()),
has_inline_tests: false,
in_executable_package: false,
deferred_imports: Vec::new(),
}
}
fn cli_package_facts(path: &str, language: Option<&str>, content: &str) -> FileFacts {
FileFacts {
in_executable_package: true,
deferred_imports: Vec::new(),
..facts(path, language, content)
}
}
#[test]
fn reports_kotlin_ast_fatal_exceptions_and_todo() {
use crate::rules::SignalSource;
let file = facts(
"src/domain/UserService.kt",
Some("Kotlin"),
"fun test() {\n // throw NotImplementedError()\n val msg = \"TODO()\";\n throw RuntimeException(\"err\")\n TODO()\n}\n",
);
let mut findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 2);
findings[0].populate_rule_metadata();
findings[1].populate_rule_metadata();
assert_eq!(findings[0].provenance.signal_source, SignalSource::Ast);
assert_eq!(findings[1].provenance.signal_source, SignalSource::Ast);
}
#[test]
fn reports_java_ast_fatal_exceptions_and_todo() {
use crate::rules::SignalSource;
let file = facts(
"src/domain/UserService.java",
Some("Java"),
"class Service {\n void test() {\n // throw new RuntimeException();\n String val = \"TODO()\";\n throw new IllegalStateException();\n TODO();\n }\n}\n",
);
let mut findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 2);
findings[0].populate_rule_metadata();
findings[1].populate_rule_metadata();
assert_eq!(findings[0].provenance.signal_source, SignalSource::Ast);
assert_eq!(findings[1].provenance.signal_source, SignalSource::Ast);
}
#[test]
fn reports_csharp_ast_fatal_exceptions_and_todo() {
use crate::rules::SignalSource;
let file = facts(
"src/domain/UserService.cs",
Some("C#"),
"class Service {\n void Test() {\n // throw new Exception();\n string val = \"TODO()\";\n throw new NotImplementedException();\n TODO();\n }\n}\n",
);
let mut findings = LanguageRiskAudit.audit(&file, &ScanConfig::default());
assert_eq!(findings.len(), 2);
findings[0].populate_rule_metadata();
findings[1].populate_rule_metadata();
assert_eq!(findings[0].provenance.signal_source, SignalSource::Ast);
assert_eq!(findings[1].provenance.signal_source, SignalSource::Ast);
}
#[test]
fn managed_fallback_reports_text_heuristic_provenance() {
use crate::analysis::parse::ParsedFile;
use crate::rules::SignalSource;
let file = facts(
"src/domain/UserService.kt",
Some("Kotlin"),
"fun test() { TODO() }\n",
);
let unparsed = ParsedFile::new(file.content.as_deref().unwrap(), Some("Ruby"));
let mut findings = LanguageRiskAudit.analyze(&file, &unparsed, &ScanConfig::default());
assert_eq!(findings.len(), 1);
findings[0].populate_rule_metadata();
assert_eq!(
findings[0].provenance.signal_source,
SignalSource::TextHeuristic
);
}