返回 CodeWhale
tests.rs
根目录 / crates / tui / src / computer_meter / tests.rs
1 use super::*;
2
3 const FIXTURES: &str = include_str!("fixtures/cases.json");
4
5 #[derive(Debug, Deserialize)]
6 #[serde(rename_all = "camelCase")]
7 struct FixtureFile {
8 cases: Vec<FixtureCase>,
9 }
10
11 #[derive(Debug, Deserialize)]
12 #[serde(rename_all = "camelCase")]
13 struct FixtureCase {
14 id: String,
15 expect: String,
16 #[serde(default)]
17 code: Option<String>,
18 #[serde(default)]
19 accepted_seconds: Option<u64>,
20 #[serde(default)]
21 standard_equivalent_seconds: Option<u64>,
22 #[serde(default)]
23 admission: Option<ComputerAdmissionRequest>,
24 #[serde(default)]
25 observation: Option<ProviderObservation>,
26 #[serde(default)]
27 restated_observation: Option<ProviderObservation>,
28 #[serde(default)]
29 intervals: Vec<FixtureInterval>,
30 #[serde(default)]
31 profile_input: Option<String>,
32 #[serde(default)]
33 profile_id: Option<String>,
34 #[serde(default)]
35 multiplier: Option<u32>,
36 }
37
38 #[derive(Debug, Deserialize)]
39 #[serde(rename_all = "camelCase")]
40 struct FixtureInterval {
41 admission: ComputerAdmissionRequest,
42 observation: ProviderObservation,
43 }
44
45 fn fixtures() -> Vec<FixtureCase> {
46 serde_json::from_str::<FixtureFile>(FIXTURES)
47 .expect("computer meter fixtures must parse")
48 .cases
49 }
50
51 fn issue_case(case: &FixtureCase) -> Result<ComputerMeterReceipt, ComputerMeterError> {
52 let admission = bind_computer_admission(
53 case.admission
54 .clone()
55 .expect("receipt fixture requires admission"),
56 )?;
57 issue_computer_meter_receipt(
58 &admission,
59 case.observation
60 .clone()
61 .expect("receipt fixture requires observation"),
62 )
63 }
64
65 #[test]
66 fn fixtures_cover_provider_accepted_active_seconds_only() {
67 let cases = fixtures();
68 assert!(
69 cases.len() >= 12,
70 "fixture pack must cover accept, idle, queued, stopped, teardown, mismatch, concurrent, historic, correction"
71 );
72
73 for case in &cases {
74 match case.expect.as_str() {
75 "receipt" => {
76 let receipt = issue_case(case).unwrap_or_else(|error| {
77 panic!("{} should mint a receipt, got {}", case.id, error.code())
78 });
79 assert_eq!(receipt.schema_id, COMPUTER_METER_RECEIPT_SCHEMA);
80 assert_eq!(receipt.kind, COMPUTER_METER_RECEIPT_KIND);
81 assert_eq!(receipt.accepted_seconds, case.accepted_seconds.unwrap());
82 assert_eq!(
83 receipt.standard_equivalent_seconds,
84 case.standard_equivalent_seconds.unwrap()
85 );
86 assert_eq!(
87 receipt.standard_equivalent_seconds,
88 receipt.accepted_seconds * u64::from(receipt.multiplier)
89 );
90 let admission = bind_computer_admission(case.admission.clone().unwrap()).unwrap();
91 assert_eq!(receipt.admission_id, admission.admission_id);
92 assert_eq!(receipt.account_id, admission.account_id);
93 assert_eq!(receipt.profile_id, admission.profile_id);
94 assert_eq!(receipt.multiplier, admission.multiplier);
95 assert_eq!(receipt.provider_allocation.cpu, admission.cpu);
96 assert_eq!(receipt.provider_allocation.memory_gib, admission.memory_gib);
97 assert_eq!(receipt.provider_allocation.disk_gib, admission.disk_gib);
98 assert!(receipt.provider_allocation.accepted);
99 assert_eq!(receipt.meter_revision, COMPUTER_METER_REVISION);
100 assert_eq!(receipt.catalog_revision, COMPUTER_CATALOG_REVISION);
101 assert_ne!(receipt.binding_digest, admission.binding_digest);
102 }
103 "error" => {
104 let error = issue_case(case).expect_err(&case.id);
105 assert_eq!(error.code(), case.code.as_deref().unwrap(), "{}", case.id);
106 }
107 "sum" => {
108 let receipts: Vec<_> = case
109 .intervals
110 .iter()
111 .map(|interval| {
112 let admission = bind_computer_admission(interval.admission.clone())
113 .unwrap_or_else(|error| panic!("{}: {}", case.id, error));
114 issue_computer_meter_receipt(&admission, interval.observation.clone())
115 .unwrap_or_else(|error| panic!("{}: {}", case.id, error))
116 })
117 .collect();
118 assert_eq!(
119 sum_standard_equivalent_seconds(&receipts),
120 case.standard_equivalent_seconds.unwrap()
121 );
122 }
123 "decode" => {
124 let profile = decode_computer_profile(case.profile_input.as_deref().unwrap())
125 .unwrap_or_else(|error| panic!("{}: {}", case.id, error));
126 assert_eq!(profile.id.as_str(), case.profile_id.as_deref().unwrap());
127 assert_eq!(profile.multiplier, case.multiplier.unwrap());
128 }
129 "admit_error" => {
130 let error = admit_computer_profile(case.profile_input.as_deref().unwrap())
131 .expect_err(&case.id);
132 assert_eq!(error.code(), case.code.as_deref().unwrap());
133 }
134 "correction" => {
135 let admission = bind_computer_admission(case.admission.clone().unwrap()).unwrap();
136 let original =
137 issue_computer_meter_receipt(&admission, case.observation.clone().unwrap())
138 .unwrap();
139 let original_clone = original.clone();
140 let correction = correct_computer_meter_receipt(
141 &original,
142 &admission,
143 case.restated_observation.clone().unwrap(),
144 )
145 .unwrap();
146 assert_eq!(
147 original, original_clone,
148 "corrections must not mutate the original receipt"
149 );
150 assert_eq!(
151 correction.correction_of.as_deref(),
152 Some(original.receipt_id.as_str())
153 );
154 assert!(correction.lineage.contains(&original.receipt_id));
155 assert_ne!(correction.receipt_id, original.receipt_id);
156 assert_eq!(correction.accepted_seconds, case.accepted_seconds.unwrap());
157 assert_eq!(
158 correction.standard_equivalent_seconds,
159 case.standard_equivalent_seconds.unwrap()
160 );
161 assert_eq!(correction.account_id, original.account_id);
162 assert_eq!(correction.admission_id, original.admission_id);
163 assert_eq!(correction.profile_id, original.profile_id);
164 }
165 other => panic!("unknown fixture expect `{other}`"),
166 }
167 }
168 }
169
170 #[test]
171 fn exact_replay_is_idempotent_and_a_mutated_binding_conflicts() {
172 let case = fixtures()
173 .into_iter()
174 .find(|case| case.id == "accepted-standard-8")
175 .unwrap();
176 let first = issue_case(&case).unwrap();
177 let replay = issue_case(&case).unwrap();
178 assert_eq!(first.receipt_id, replay.receipt_id);
179 assert_eq!(first.binding_digest, replay.binding_digest);
180 assert_computer_meter_receipt_replay(&first, &replay).unwrap();
181
182 let mut mutated = replay.clone();
183 mutated.accepted_seconds = first.accepted_seconds + 1;
184 mutated.standard_equivalent_seconds = mutated.accepted_seconds;
185 assert_eq!(
186 assert_computer_meter_receipt_replay(&first, &mutated)
187 .unwrap_err()
188 .code(),
189 "computer_meter_receipt_replay_conflict"
190 );
191 }
192
193 #[test]
194 fn admission_binds_profile_multiplier_account_and_expiry_before_dispatch() {
195 let admission = bind_computer_admission(ComputerAdmissionRequest {
196 admission_id: "adm_bind".to_string(),
197 account_id: "acct_demo".to_string(),
198 computer_id: "cmp_bind".to_string(),
199 run_id: "run_bind".to_string(),
200 provider: "daytona".to_string(),
201 profile_id: "standard-16".to_string(),
202 funding_authority: "coding_membership_included".to_string(),
203 quote_id: "quote_bind".to_string(),
204 expires_at: "2026-08-31T18:00:00.000Z".to_string(),
205 meter_revision: String::new(),
206 catalog_revision: String::new(),
207 })
208 .unwrap();
209 assert_eq!(admission.schema_id, COMPUTER_ADMISSION_SCHEMA);
210 assert_eq!(admission.profile_id, ComputerProfileId::Standard16);
211 assert_eq!(admission.cpu, 4);
212 assert_eq!(admission.memory_gib, 16);
213 assert_eq!(admission.disk_gib, 16);
214 assert_eq!(admission.multiplier, 2);
215 assert_eq!(admission.account_id, "acct_demo");
216 assert_eq!(admission.meter_revision, COMPUTER_METER_REVISION);
217 assert!(!admission.binding_digest.is_empty());
218 }
219
220 #[test]
221 fn one_standard_16_matches_two_standard_8_over_the_same_ten_minutes() {
222 let cases = fixtures();
223 let two_eights = cases
224 .iter()
225 .find(|case| case.id == "concurrent-two-standard-8")
226 .unwrap();
227 let sixteen = cases
228 .iter()
229 .find(|case| case.id == "accepted-standard-16")
230 .unwrap();
231 let eight_receipts: Vec<_> = two_eights
232 .intervals
233 .iter()
234 .map(|interval| {
235 let admission = bind_computer_admission(interval.admission.clone()).unwrap();
236 issue_computer_meter_receipt(&admission, interval.observation.clone()).unwrap()
237 })
238 .collect();
239 let sixteen_receipt = issue_case(sixteen).unwrap();
240 assert_eq!(
241 sum_standard_equivalent_seconds(&eight_receipts),
242 sixteen_receipt.standard_equivalent_seconds
243 );
244 assert_eq!(sum_standard_equivalent_seconds(&eight_receipts), 1200);
245 }
246
246 lines RUST