use super::*;
use std::io::Write;
fn load_yaml(content: &str) -> crate::classify::errors::Result<RuleSet> {
let mut f = tempfile::NamedTempFile::with_suffix(".yaml").expect("create temp file");
f.write_all(content.as_bytes()).expect("write yaml");
load_rules(f.path())
}
#[test]
fn load_rules_singular_pattern_key_accepted() {
let yaml = r#"
rules:
- id: singular-test
category: new_feature
pattern: "(?i)^feat[:(]"
- id: plural-test
category: bugfix
patterns:
- "(?i)^fix[:(]"
- "(?i)^bug[:(]"
"#;
let set = load_yaml(yaml).expect("load");
let singular = set.rules.iter().find(|r| r.id == "singular-test").unwrap();
assert_eq!(
singular.patterns,
vec!["(?i)^feat[:(]".to_string()],
"singular `pattern:` must load as a single-element vec"
);
let plural = set.rules.iter().find(|r| r.id == "plural-test").unwrap();
assert_eq!(plural.patterns.len(), 2);
}
#[test]
#[tracing_test::traced_test]
fn load_rules_warns_on_empty_matchers() {
let yaml = r#"
rules:
- id: no-matchers
category: custom_category
- id: has-keyword
category: bugfix
keywords:
- "fix:"
"#;
let set = load_yaml(yaml).expect("load");
assert_eq!(set.rules.len(), 2);
assert!(
logs_contain("no-matchers"),
"expected warn log containing the rule id `no-matchers`"
);
assert!(
!logs_contain("has-keyword"),
"rule `has-keyword` should not warn — it has a keyword"
);
}
#[test]
fn end_to_end_singular_pattern_rule_fires() {
use crate::classify::classifier::{ClassificationEngine, ClassificationEngineConfig};
let yaml = r#"
extend_defaults: false
rules:
- id: custom-feat
category: new_feature
pattern: "(?i)^feat[:(]"
"#;
let set = load_yaml(yaml).expect("load");
let engine = ClassificationEngine::new(
set,
ClassificationEngineConfig {
use_llm: false,
..Default::default()
},
)
.expect("engine");
let result = engine
.classify_sync("feat: add login flow", false)
.expect("classified");
assert_eq!(
result.category, "new_feature",
"singular `pattern:` rule must fire and produce custom category"
);
}
#[test]
fn end_to_end_plural_patterns_rule_fires() {
use crate::classify::classifier::{ClassificationEngine, ClassificationEngineConfig};
let yaml = r#"
extend_defaults: false
rules:
- id: custom-feat
category: new_feature
patterns:
- "(?i)^feat[:(]"
- "(?i)^feature[:(]"
"#;
let set = load_yaml(yaml).expect("load");
let engine = ClassificationEngine::new(
set,
ClassificationEngineConfig {
use_llm: false,
..Default::default()
},
)
.expect("engine");
for msg in ["feat: add login", "feature: dark mode"] {
let result = engine.classify_sync(msg, false).expect("classified");
assert_eq!(
result.category, "new_feature",
"plural `patterns:` rule must fire for `{msg}`"
);
}
}