1use strum::{Display, EnumString};
2
3use boulder::{BuildableWithPersianRug, GeneratableWithPersianRug};
4use boulder::{Cycle, Inc, Pattern, Some as GSome};
5use django_query::{
6 filtering::FilterableWithPersianRug, row::IntoRowWithPersianRug,
7 sorting::SortableWithPersianRug,
8};
9use persian_rug::{contextual, Context, Proxy};
10
11#[derive(
13 Clone,
14 Debug,
15 FilterableWithPersianRug,
16 SortableWithPersianRug,
17 IntoRowWithPersianRug,
18 BuildableWithPersianRug,
19 GeneratableWithPersianRug,
20)]
21#[boulder(persian_rug(context = C, access(Alias<C>)))]
22#[django(persian_rug(context = C, access(Alias<C>)))]
23#[contextual(C)]
24pub struct Alias<C: Context + 'static> {
25 #[django(exclude)]
26 _marker: core::marker::PhantomData<C>,
27 #[boulder(default="test-alias", generator=Pattern!("test-alias-{}", Inc(1)))]
28 #[django(sort, op(in, contains, icontains, startswith, endswith))]
29 pub name: String,
30}
31
32#[derive(
35 Clone,
36 Debug,
37 FilterableWithPersianRug,
38 IntoRowWithPersianRug,
39 BuildableWithPersianRug,
40 GeneratableWithPersianRug,
41)]
42#[boulder(persian_rug(context = C, access(Architecture<C>)))]
43#[django(persian_rug(context = C, access(Architecture<C>)))]
44#[contextual(C)]
45pub struct Architecture<C: Context + 'static> {
46 #[django(exclude)]
47 _marker: core::marker::PhantomData<C>,
48 #[boulder(default="test-arch", generator=Pattern!("test-arch-{}", Inc(1)))]
49 #[django(op(in, contains, icontains, startswith, endswith))]
50 pub name: String,
51}
52
53#[derive(
56 Clone,
57 Debug,
58 FilterableWithPersianRug,
59 IntoRowWithPersianRug,
60 BuildableWithPersianRug,
61 GeneratableWithPersianRug,
62)]
63#[boulder(persian_rug(context = C, access(BitWidth<C>)))]
64#[django(persian_rug(context = C, access(BitWidth<C>)))]
65#[contextual(C)]
66pub struct BitWidth<C: Context + 'static> {
67 #[django(exclude)]
68 _marker: core::marker::PhantomData<C>,
69 #[boulder(default=64u64, generator=Cycle::new(vec![32u64,64u64].into_iter()))]
70 #[django(op(in))]
71 pub width: u64,
72}
73
74#[derive(
77 Clone,
78 Debug,
79 FilterableWithPersianRug,
80 IntoRowWithPersianRug,
81 BuildableWithPersianRug,
82 GeneratableWithPersianRug,
83)]
84#[boulder(persian_rug(context = C, access(Core<C>)))]
85#[django(persian_rug(context = C, access(Core<C>)))]
86#[contextual(C)]
87pub struct Core<C: Context + 'static> {
88 #[django(exclude)]
89 _marker: core::marker::PhantomData<C>,
90 #[boulder(default="test-core", generator=Pattern!("test-core-{}", Inc(1)))]
91 #[django(op(in, contains, icontains, startswith, endswith))]
92 pub name: String,
93}
94
95#[derive(
98 Clone,
99 Debug,
100 FilterableWithPersianRug,
101 IntoRowWithPersianRug,
102 BuildableWithPersianRug,
103 GeneratableWithPersianRug,
104)]
105#[boulder(persian_rug(context = C, access(ProcessorFamily<C>)))]
106#[django(persian_rug(context = C, access(ProcessorFamily<C>)))]
107#[contextual(C)]
108pub struct ProcessorFamily<C: Context + 'static> {
109 #[django(exclude)]
110 _marker: core::marker::PhantomData<C>,
111 #[boulder(default="test-processor-family", generator=Pattern!("test-processor-family-{}", Inc(1)))]
112 #[django(op(in, contains, icontains, startswith, endswith))]
113 pub name: String,
114}
115
116#[derive(
120 Clone,
121 Debug,
122 FilterableWithPersianRug,
123 SortableWithPersianRug,
124 IntoRowWithPersianRug,
125 BuildableWithPersianRug,
126 GeneratableWithPersianRug,
127)]
128#[boulder(
129 persian_rug(
130 context = C,
131 access(
132 DeviceType<C>,
133 Alias<C>,
134 Architecture<C>,
135 BitWidth<C>,
136 Core<C>,
137 ProcessorFamily<C>
138 )
139 )
140)]
141#[django(
142 persian_rug(
143 context = C,
144 access(
145 DeviceType<C>,
146 Alias<C>,
147 Architecture<C>,
148 BitWidth<C>,
149 Core<C>,
150 ProcessorFamily<C>
151 )
152 )
153)]
154#[contextual(C)]
155pub struct DeviceType<C: Context + 'static> {
156 #[boulder(default="test-device-type", generator=Pattern!("test-device-type-{}", Inc(0)))]
157 #[django(sort, op(in, contains, icontains, startswith, endswith))]
158 pub name: String,
159 #[boulder(buildable_with_persian_rug, generatable_with_persian_rug)]
160 #[django(traverse, foreign_key = "name")]
161 pub architecture: Option<Proxy<Architecture<C>>>,
162 #[boulder(buildable_with_persian_rug, generatable_with_persian_rug)]
163 #[django(traverse, foreign_key = "name")]
164 pub processor: Option<Proxy<ProcessorFamily<C>>>,
165 #[boulder(default=Some("test-cpu-model".to_string()), generator=GSome(Pattern!("test-cpu-model-{}", Inc(0))))]
166 #[django(op(in, contains, icontains, startswith, endswith))]
167 pub cpu_model: Option<String>,
168 #[boulder(generatable_with_persian_rug, sequence = 2usize)]
169 #[django(traverse, foreign_key = "name")]
170 pub aliases: Vec<Proxy<Alias<C>>>,
171 #[boulder(buildable_with_persian_rug, generatable_with_persian_rug)]
172 #[django(traverse, foreign_key = "width")]
173 pub bits: Option<Proxy<BitWidth<C>>>,
174 #[boulder(generatable_with_persian_rug,sequence=4usize,sequence_generator=Cycle::new(vec![4usize,8usize,16usize].into_iter()))]
175 #[django(traverse, foreign_key = "name")]
176 pub cores: Vec<Proxy<Core<C>>>,
177 #[boulder(default=Some(4), generator=GSome(Cycle::new(vec![4,8,16].into_iter())))]
178 #[django(op(in))]
179 pub core_count: Option<u64>,
180 #[boulder(default=Some("Example device type description.".to_string()))]
181 #[django(op(in, contains, icontains, startswith, endswith))]
182 pub description: Option<String>,
183 #[boulder(default = 10)]
184 #[django(op(in))]
185 pub health_frequency: i64,
186 #[boulder(default = false)]
187 #[django(op(in))]
188 pub disable_health_check: bool,
189 #[boulder(default=HealthDenominator::Hours)]
190 pub health_denominator: HealthDenominator,
191 #[boulder(default = true)]
192 #[django(op(in))]
193 pub display: bool,
194}
195
196#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Display, EnumString)]
199#[strum(serialize_all = "snake_case")]
200pub enum HealthDenominator {
201 Hours,
202 Jobs,
203}
204
205impl django_query::filtering::ops::Scalar for HealthDenominator {}
206impl django_query::row::StringCellValue for HealthDenominator {}
207
208#[cfg(test)]
209mod test {
210 use super::*;
211
212 use crate::state::{SharedState, State};
213
214 use boulder::{GeneratorWithPersianRugIterator, Repeat};
215 use test_log::test;
216
217 #[test(tokio::test)]
218 async fn test_output() {
219 let mut p = SharedState::new();
220 {
221 let m = p.mutate();
222
223 let gen = Proxy::<DeviceType<State>>::generator()
224 .architecture(|| None)
225 .processor(|| None)
226 .cpu_model(Repeat::new([None, Some("".to_string())]))
227 .aliases(Vec::new)
228 .bits(|| None)
229 .cores(Vec::new)
230 .core_count(|| None)
231 .description(Repeat::new([None, Some("".to_string())]))
232 .health_frequency(|| 10);
233
234 let _ = GeneratorWithPersianRugIterator::new(gen, m)
235 .take(5)
236 .collect::<Vec<_>>();
237 }
238
239 let server = wiremock::MockServer::start().await;
240
241 let ep = p.endpoint::<DeviceType<_>>(Some(&server.uri()), None);
242
243 wiremock::Mock::given(wiremock::matchers::method("GET"))
244 .and(wiremock::matchers::path("/api/v0.2/devicetypes/"))
245 .respond_with(ep)
246 .mount(&server)
247 .await;
248
249 let body: serde_json::Value = reqwest::get(&format!(
250 "{}/api/v0.2/devicetypes/?limit=2&offset=2",
251 server.uri()
252 ))
253 .await
254 .expect("error getting device types")
255 .json()
256 .await
257 .expect("error parsing device types");
258
259 let next = format!("{}/api/v0.2/devicetypes/?limit=2&offset=4", server.uri());
260 let prev = format!("{}/api/v0.2/devicetypes/?limit=2", server.uri());
261
262 assert_eq!(
263 body,
264 serde_json::json! {
265 {
266 "count": 5,
267 "next": next,
268 "previous": prev,
269 "results": [
270 {
271 "name": "test-device-type-2",
272 "architecture": null,
273 "processor": null,
274 "cpu_model": null,
275 "aliases": [],
276 "bits": null,
277 "cores": [],
278 "core_count": null,
279 "description": null,
280 "health_frequency": 10,
281 "disable_health_check": false,
282 "health_denominator": "hours",
283 "display": true
284 },
285 {
286 "name": "test-device-type-3",
287 "architecture": null,
288 "processor": null,
289 "cpu_model": "",
290 "aliases": [],
291 "bits": null,
292 "cores": [],
293 "core_count": null,
294 "description": "",
295 "health_frequency": 10,
296 "disable_health_check": false,
297 "health_denominator": "hours",
298 "display": true
299 }
300 ]
301 }
302 }
303 );
304 }
305}