auto_gitmoji/matcher/
mod.rs1#[cfg(feature = "llm")]
2pub mod llm;
3pub mod simple;
4
5use anyhow::Result;
6
7pub type MatcherResult = Option<(String, String)>; pub trait GitmojiMatcher {
12 fn match_emoji(&self, message: &str) -> Result<MatcherResult>;
15
16 fn name(&self) -> &'static str;
18}
19
20pub struct MatcherFactory;
22
23impl MatcherFactory {
24 pub fn simple() -> Box<dyn GitmojiMatcher> {
26 Box::new(simple::SimpleMatcher::new())
27 }
28
29 #[cfg(feature = "llm")]
31 pub fn llm(config: llm::LLMConfig) -> Box<dyn GitmojiMatcher> {
32 Box::new(llm::LLMMatcher::new(config))
33 }
34
35 #[cfg(feature = "llm")]
37 pub fn llm_with_fallback(config: llm::LLMConfig) -> Box<dyn GitmojiMatcher> {
38 Box::new(llm::LLMWithFallbackMatcher::new(config))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn test_matcher_factory_simple() {
48 let matcher = MatcherFactory::simple();
49 assert_eq!(matcher.name(), "simple");
50 }
51
52 #[test]
53 fn test_matcher_result_none() {
54 let result: MatcherResult = None;
55 assert!(result.is_none());
56 }
57
58 #[test]
59 fn test_gitmoji_matcher_trait() {
60 let matcher = MatcherFactory::simple();
61
62 assert_eq!(matcher.name(), "simple");
64
65 let result = matcher.match_emoji("fix bug").unwrap();
67 assert!(result.is_some());
68
69 let (code, format_message) = result.unwrap();
70 assert!(!code.is_empty());
71 assert!(!format_message.is_empty());
72 }
73
74 #[test]
75 fn test_multiple_matcher_instances() {
76 let matcher1 = MatcherFactory::simple();
77 let matcher2 = MatcherFactory::simple();
78
79 assert_eq!(matcher1.name(), matcher2.name());
81
82 let message = "fix authentication bug";
84 let result1 = matcher1.match_emoji(message).unwrap();
85 let result2 = matcher2.match_emoji(message).unwrap();
86
87 assert_eq!(result1, result2);
88 }
89
90 #[test]
91 fn test_matcher_error_handling() {
92 let matcher = MatcherFactory::simple();
93
94 let test_cases = vec![
96 "",
97 " ",
98 "\n\t",
99 "🎉🐛✨",
100 "very_long_message_with_no_punctuation_or_spaces_that_might_cause_issues",
101 "Mix3d c@se w1th numb3rs & $ymb0ls!",
102 ];
103
104 for message in test_cases {
105 let result = matcher.match_emoji(message);
106 assert!(result.is_ok(), "Matcher should handle: '{message}'");
107 assert!(
108 result.unwrap().is_some(),
109 "Should always return some result for: '{message}'"
110 );
111 }
112 }
113
114 #[test]
115 fn test_format_message_structure() {
116 let matcher = MatcherFactory::simple();
117
118 let test_messages = vec![
119 "fix critical bug", "add new feature", "random text here", ];
123
124 for message in test_messages {
125 let result = matcher.match_emoji(message).unwrap();
126 assert!(result.is_some());
127 let (code, format_message) = result.unwrap();
128
129 assert!(code.starts_with(':'), "Code should start with ':': {code}");
131 assert!(code.ends_with(':'), "Code should end with ':': {code}");
132
133 assert!(
135 format_message.contains(message),
136 "Format message should contain original message: '{format_message}' should contain '{message}'"
137 );
138
139 assert!(
141 format_message.starts_with(&code),
142 "Format message should start with emoji code: '{format_message}' should start with '{code}'"
143 );
144 }
145 }
146
147 #[test]
148 fn test_emoji_code_format() {
149 let matcher = MatcherFactory::simple();
150
151 let result = matcher.match_emoji("fix bug").unwrap().unwrap();
152 let (code, _format_message) = result;
153
154 assert!(code.starts_with(':'), "Code should start with ':': {code}");
156 assert!(code.ends_with(':'), "Code should end with ':': {code}");
157 assert!(code.len() > 2, "Code should be more than just '::': {code}");
158 }
159
160 #[test]
161 fn test_formatted_message_content() {
162 let matcher = MatcherFactory::simple();
163
164 let result = matcher.match_emoji("fix bug").unwrap().unwrap();
165 let (_code, format_message) = result;
166
167 assert!(
169 !format_message.is_empty(),
170 "Format message should not be empty"
171 );
172 assert!(
173 format_message.contains("fix bug"),
174 "Format message should contain original text"
175 );
176 }
177
178 #[test]
179 fn test_consistent_results() {
180 let matcher = MatcherFactory::simple();
181
182 let message = "fix authentication issue";
184 let results: Vec<_> = (0..5)
185 .map(|_| matcher.match_emoji(message).unwrap())
186 .collect();
187
188 let first_result = &results[0];
190 for result in &results[1..] {
191 assert_eq!(result, first_result, "Results should be consistent");
192 }
193 }
194
195 #[test]
196 fn test_matcher_trait_object() {
197 let matcher: Box<dyn GitmojiMatcher> = MatcherFactory::simple();
199
200 let result = matcher.match_emoji("add feature").unwrap();
201 assert!(result.is_some());
202
203 let (code, format_message) = result.unwrap();
204 assert!(!code.is_empty());
205 assert!(!format_message.is_empty());
206 }
207
208 #[test]
209 fn test_factory_pattern() {
210 let matcher = MatcherFactory::simple();
212
213 assert_eq!(matcher.name(), "simple");
215
216 let result = matcher.match_emoji("test message");
218 assert!(result.is_ok());
219 }
220}