1use koi_common::posture::Posture;
2use koi_common::types::ServiceRecord;
3use koi_health::HealthStatus;
4use koi_proxy::ProxyEntry;
5
6#[derive(Debug, Clone)]
7pub enum KoiEvent {
8 MdnsFound(ServiceRecord),
9 MdnsResolved(ServiceRecord),
10 MdnsRemoved {
11 name: String,
12 service_type: String,
13 },
14 DnsEntryUpdated {
15 name: String,
16 ip: String,
17 },
18 DnsEntryRemoved {
19 name: String,
20 },
21 HealthChanged {
22 name: String,
23 status: HealthStatus,
24 },
25 CertmeshMemberJoined {
26 hostname: String,
27 fingerprint: String,
28 },
29 CertmeshMemberRevoked {
30 hostname: String,
31 },
32 CertmeshDestroyed,
33 PostureChanged {
38 from: Posture,
39 to: Posture,
40 },
41 ProxyEntryUpdated {
42 entry: ProxyEntry,
43 },
44 ProxyEntryRemoved {
45 name: String,
46 },
47 RuntimeInstanceStarted {
48 name: String,
49 backend: String,
50 },
51 RuntimeInstanceStopped {
52 name: String,
53 },
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use std::collections::HashMap;
60
61 fn sample_record() -> ServiceRecord {
62 ServiceRecord {
63 name: "My App".to_string(),
64 service_type: "_http._tcp".to_string(),
65 host: Some("server.local".to_string()),
66 ip: Some("192.168.1.42".to_string()),
67 port: Some(8080),
68 txt: HashMap::new(),
69 }
70 }
71
72 #[test]
73 fn mdns_found_variant_construction() {
74 let event = KoiEvent::MdnsFound(sample_record());
75 assert!(matches!(event, KoiEvent::MdnsFound(ref r) if r.name == "My App"));
76 }
77
78 #[test]
79 fn mdns_resolved_variant_construction() {
80 let event = KoiEvent::MdnsResolved(sample_record());
81 assert!(matches!(event, KoiEvent::MdnsResolved(ref r) if r.port == Some(8080)));
82 }
83
84 #[test]
85 fn mdns_removed_variant_construction() {
86 let event = KoiEvent::MdnsRemoved {
87 name: "Old Service".to_string(),
88 service_type: "_http._tcp".to_string(),
89 };
90 assert!(matches!(event, KoiEvent::MdnsRemoved { ref name, .. } if name == "Old Service"));
91 }
92
93 #[test]
94 fn dns_entry_updated_variant() {
95 let event = KoiEvent::DnsEntryUpdated {
96 name: "grafana".to_string(),
97 ip: "10.0.0.5".to_string(),
98 };
99 assert!(
100 matches!(event, KoiEvent::DnsEntryUpdated { ref name, ref ip } if name == "grafana" && ip == "10.0.0.5")
101 );
102 }
103
104 #[test]
105 fn dns_entry_removed_variant() {
106 let event = KoiEvent::DnsEntryRemoved {
107 name: "grafana".to_string(),
108 };
109 assert!(matches!(event, KoiEvent::DnsEntryRemoved { ref name } if name == "grafana"));
110 }
111
112 #[test]
113 fn health_changed_variant() {
114 let event = KoiEvent::HealthChanged {
115 name: "web-api".to_string(),
116 status: HealthStatus::Up,
117 };
118 assert!(
119 matches!(event, KoiEvent::HealthChanged { ref name, status: HealthStatus::Up } if name == "web-api")
120 );
121 }
122
123 #[test]
124 fn certmesh_member_joined_variant() {
125 let event = KoiEvent::CertmeshMemberJoined {
126 hostname: "node1".to_string(),
127 fingerprint: "abc123".to_string(),
128 };
129 assert!(
130 matches!(event, KoiEvent::CertmeshMemberJoined { ref hostname, .. } if hostname == "node1")
131 );
132 }
133
134 #[test]
135 fn certmesh_member_revoked_variant() {
136 let event = KoiEvent::CertmeshMemberRevoked {
137 hostname: "node2".to_string(),
138 };
139 assert!(
140 matches!(event, KoiEvent::CertmeshMemberRevoked { ref hostname } if hostname == "node2")
141 );
142 }
143
144 #[test]
145 fn certmesh_destroyed_variant() {
146 let event = KoiEvent::CertmeshDestroyed;
147 assert!(matches!(event, KoiEvent::CertmeshDestroyed));
148 }
149
150 #[test]
151 fn posture_changed_variant() {
152 let event = KoiEvent::PostureChanged {
153 from: Posture::OPEN,
154 to: Posture::new(true, false),
155 };
156 assert!(
157 matches!(event, KoiEvent::PostureChanged { from, to } if !from.signed && to.signed)
158 );
159 }
160
161 #[test]
162 fn proxy_entry_updated_variant() {
163 let entry = ProxyEntry {
164 name: "grafana".to_string(),
165 listen_port: 443,
166 backend: "http://localhost:3000".to_string(),
167 allow_remote: false,
168 };
169 let event = KoiEvent::ProxyEntryUpdated {
170 entry: entry.clone(),
171 };
172 assert!(
173 matches!(event, KoiEvent::ProxyEntryUpdated { ref entry } if entry.name == "grafana")
174 );
175 }
176
177 #[test]
178 fn proxy_entry_removed_variant() {
179 let event = KoiEvent::ProxyEntryRemoved {
180 name: "grafana".to_string(),
181 };
182 assert!(matches!(event, KoiEvent::ProxyEntryRemoved { ref name } if name == "grafana"));
183 }
184
185 #[test]
186 fn runtime_instance_started_variant() {
187 let event = KoiEvent::RuntimeInstanceStarted {
188 name: "nginx".to_string(),
189 backend: "docker".to_string(),
190 };
191 assert!(
192 matches!(event, KoiEvent::RuntimeInstanceStarted { ref name, ref backend } if name == "nginx" && backend == "docker")
193 );
194 }
195
196 #[test]
197 fn runtime_instance_stopped_variant() {
198 let event = KoiEvent::RuntimeInstanceStopped {
199 name: "nginx".to_string(),
200 };
201 assert!(matches!(event, KoiEvent::RuntimeInstanceStopped { ref name } if name == "nginx"));
202 }
203
204 #[test]
205 fn clone_preserves_data() {
206 let event = KoiEvent::MdnsFound(sample_record());
207 let cloned = event.clone();
208 match (&event, &cloned) {
209 (KoiEvent::MdnsFound(a), KoiEvent::MdnsFound(b)) => {
210 assert_eq!(a.name, b.name);
211 assert_eq!(a.port, b.port);
212 assert_eq!(a.service_type, b.service_type);
213 }
214 _ => panic!("clone should preserve variant"),
215 }
216 }
217
218 #[test]
219 fn debug_does_not_panic() {
220 let events = vec![
221 KoiEvent::MdnsFound(sample_record()),
222 KoiEvent::MdnsRemoved {
223 name: "x".to_string(),
224 service_type: "y".to_string(),
225 },
226 KoiEvent::DnsEntryUpdated {
227 name: "a".to_string(),
228 ip: "1.2.3.4".to_string(),
229 },
230 KoiEvent::DnsEntryRemoved {
231 name: "a".to_string(),
232 },
233 KoiEvent::HealthChanged {
234 name: "svc".to_string(),
235 status: HealthStatus::Down,
236 },
237 KoiEvent::CertmeshMemberJoined {
238 hostname: "h".to_string(),
239 fingerprint: "f".to_string(),
240 },
241 KoiEvent::CertmeshMemberRevoked {
242 hostname: "h".to_string(),
243 },
244 KoiEvent::CertmeshDestroyed,
245 KoiEvent::PostureChanged {
246 from: Posture::OPEN,
247 to: Posture::new(true, true),
248 },
249 KoiEvent::ProxyEntryUpdated {
250 entry: ProxyEntry {
251 name: "p".to_string(),
252 listen_port: 443,
253 backend: "http://localhost".to_string(),
254 allow_remote: false,
255 },
256 },
257 KoiEvent::ProxyEntryRemoved {
258 name: "p".to_string(),
259 },
260 KoiEvent::RuntimeInstanceStarted {
261 name: "web".to_string(),
262 backend: "docker".to_string(),
263 },
264 KoiEvent::RuntimeInstanceStopped {
265 name: "web".to_string(),
266 },
267 ];
268 for event in &events {
269 let _ = format!("{event:?}");
270 }
271 }
272}