gonzalo_ticket/
conformance.rs1use crate::{SourceError, TicketSource};
12use gonzalo_domain::{StateCategory, Ticket};
13
14pub fn assert_ticket_invariants(t: &Ticket) {
19 assert!(!t.uid.is_empty(), "ticket uid must be non-empty");
20 assert!(!t.display.is_empty(), "ticket display must be non-empty");
21 assert!(
22 !t.item_type.is_empty(),
23 "ticket item_type must be non-empty"
24 );
25 assert!(
26 !t.state.raw_name.is_empty(),
27 "state.raw_name must be retained for fidelity"
28 );
29 let primaries = t.containers.iter().filter(|c| c.primary).count();
30 assert!(
31 primaries <= 1,
32 "at most one primary container is allowed, found {primaries}"
33 );
34}
35
36pub async fn assert_write_gating<S: TicketSource + Sync>(source: &S, uid: &str) {
40 if source.capabilities().push {
41 return;
42 }
43 let err = source.set_state(uid, StateCategory::Done).await.err();
44 assert!(
45 matches!(err, Some(SourceError::Unsupported(_))),
46 "a read-only source must reject set_state with Unsupported, got {err:?}"
47 );
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::InMemorySource;
54 use gonzalo_domain::{BodyFormat, Container, Provider, State, TicketBody};
55 use std::collections::BTreeMap;
56
57 fn ticket() -> Ticket {
58 Ticket {
59 provider: Provider::GitHub,
60 uid: "o/r#1".into(),
61 display: "#1".into(),
62 item_type: "issue".into(),
63 title: "t".into(),
64 state: State {
65 category: StateCategory::Open,
66 resolution: None,
67 raw_name: "open".into(),
68 raw_id: None,
69 },
70 priority: None,
71 actors: vec![],
72 labels: vec![],
73 containers: vec![Container {
74 kind: "repo".into(),
75 id: "o/r".into(),
76 name: None,
77 primary: true,
78 }],
79 links: vec![],
80 body: TicketBody {
81 markdown: String::new(),
82 format: BodyFormat::Markdown,
83 raw: None,
84 },
85 fields: BTreeMap::new(),
86 }
87 }
88
89 #[test]
90 fn invariants_pass_for_a_well_formed_ticket() {
91 assert_ticket_invariants(&ticket());
92 }
93
94 #[test]
95 #[should_panic(expected = "uid must be non-empty")]
96 fn invariants_catch_empty_uid() {
97 let mut t = ticket();
98 t.uid = String::new();
99 assert_ticket_invariants(&t);
100 }
101
102 #[tokio::test]
103 async fn write_gating_holds_for_read_only_source() {
104 let src = InMemorySource::new(vec![ticket()]);
105 assert_write_gating(&src, "o/r#1").await;
106 }
107}