headless_lms_server/programs/seed/builder/
module.rs1use anyhow::{Context, Result};
2
3use headless_lms_models::course_modules::{
4 self, AutomaticCompletionRequirements, CompletionPolicy, CourseModule, NewCourseModule,
5};
6
7use crate::programs::seed::builder::{chapter::ChapterBuilder, context::SeedContext};
8
9#[derive(Debug, Clone)]
11pub struct ModuleBuilder {
12 pub name: Option<String>,
13 pub order: Option<i32>,
14 pub ects: Option<f32>,
15 pub chapters: Vec<ChapterBuilder>,
16 pub register_to_open_university: bool,
17 pub completion_policy: CompletionPolicy,
18}
19
20impl Default for ModuleBuilder {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl ModuleBuilder {
27 pub fn new() -> Self {
28 Self {
29 name: None,
30 order: None,
31 ects: None,
32 chapters: vec![],
33 register_to_open_university: false,
34 completion_policy: CompletionPolicy::Manual,
35 }
36 }
37 pub fn order(mut self, n: i32) -> Self {
38 self.order = Some(n);
39 self
40 }
41 pub fn name(mut self, n: impl Into<String>) -> Self {
42 self.name = Some(n.into());
43 self
44 }
45 pub fn ects(mut self, e: f32) -> Self {
46 self.ects = Some(e);
47 self
48 }
49 pub fn register_to_open_university(mut self, v: bool) -> Self {
50 self.register_to_open_university = v;
51 self
52 }
53 pub fn chapter(mut self, c: ChapterBuilder) -> Self {
54 self.chapters.push(c);
55 self
56 }
57 pub fn chapters<I: IntoIterator<Item = ChapterBuilder>>(mut self, it: I) -> Self {
58 self.chapters.extend(it);
59 self
60 }
61 pub fn completion_policy(mut self, policy: CompletionPolicy) -> Self {
62 self.completion_policy = policy;
63 self
64 }
65 pub fn manual_completion(mut self) -> Self {
66 self.completion_policy = CompletionPolicy::Manual;
67 self
68 }
69 pub fn automatic_completion(
70 mut self,
71 exercises_threshold: Option<i32>,
72 points_threshold: Option<i32>,
73 requires_exam: bool,
74 ) -> Self {
75 self.completion_policy = CompletionPolicy::Automatic(AutomaticCompletionRequirements {
77 course_module_id: uuid::Uuid::new_v4(), number_of_exercises_attempted_treshold: exercises_threshold,
79 number_of_points_treshold: points_threshold,
80 requires_exam,
81 });
82 self
83 }
84
85 pub(crate) async fn seed(
86 self,
87 cx: &mut SeedContext<'_>,
88 course_id: uuid::Uuid,
89 fallback_order: i32,
90 ) -> Result<CourseModule> {
91 let order = self.order.unwrap_or(fallback_order);
92
93 let module = course_modules::insert(
94 cx.conn,
95 headless_lms_models::PKeyPolicy::Generate,
96 &NewCourseModule::new(course_id, self.name, order)
97 .set_ects_credits(self.ects)
98 .set_completion_policy(self.completion_policy.clone()),
99 )
100 .await
101 .with_context(|| format!("inserting module (order {:?})", order))?;
102
103 if let CompletionPolicy::Automatic(mut requirements) = self.completion_policy {
105 requirements.course_module_id = module.id;
106 let updated_policy = CompletionPolicy::Automatic(requirements);
107 course_modules::update_automatic_completion_status(cx.conn, module.id, &updated_policy)
108 .await
109 .context("updating automatic completion policy")?;
110 }
111
112 if self.register_to_open_university {
113 course_modules::update_enable_registering_completion_to_uh_open_university(
114 cx.conn, module.id, true,
115 )
116 .await
117 .context("enabling OU registration for module")?;
118 }
119
120 for ch in self.chapters {
121 ch.seed(cx, course_id, module.id).await?;
122 }
123
124 Ok(module)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use crate::programs::seed::builder::chapter::ChapterBuilder;
131
132 use super::*;
133
134 #[test]
135 fn module_builder_new() {
136 let module = ModuleBuilder::new();
137
138 assert!(module.order.is_none());
139 assert!(module.name.is_none());
140 assert!(module.ects.is_none());
141 assert!(module.chapters.is_empty());
142 assert!(!module.register_to_open_university);
143 assert_eq!(module.completion_policy, CompletionPolicy::Manual);
144 }
145
146 #[test]
147 fn module_builder_order() {
148 let module = ModuleBuilder::new().order(5);
149
150 assert_eq!(module.order, Some(5));
151 }
152
153 #[test]
154 fn module_builder_name() {
155 let module = ModuleBuilder::new().name("Test Module");
156
157 assert_eq!(module.name, Some("Test Module".to_string()));
158 }
159
160 #[test]
161 fn module_builder_name_string_conversion() {
162 let module1 = ModuleBuilder::new().name("String literal");
163 let module2 = ModuleBuilder::new().name(String::from("Owned string"));
164
165 assert_eq!(module1.name, Some("String literal".to_string()));
166 assert_eq!(module2.name, Some("Owned string".to_string()));
167 }
168
169 #[test]
170 fn module_builder_ects() {
171 let module = ModuleBuilder::new().ects(5.0);
172
173 assert_eq!(module.ects, Some(5.0));
174 }
175
176 #[test]
177 fn module_builder_ects_fractional() {
178 let module = ModuleBuilder::new().ects(2.5);
179
180 assert_eq!(module.ects, Some(2.5));
181 }
182
183 #[test]
184 fn module_builder_register_to_open_university_true() {
185 let module = ModuleBuilder::new().register_to_open_university(true);
186
187 assert!(module.register_to_open_university);
188 }
189
190 #[test]
191 fn module_builder_register_to_open_university_false() {
192 let module = ModuleBuilder::new().register_to_open_university(false);
193
194 assert!(!module.register_to_open_university);
195 }
196
197 #[test]
198 fn module_builder_chapter() {
199 let chapter = ChapterBuilder::new(1, "Test Chapter");
200 let module = ModuleBuilder::new().chapter(chapter);
201
202 assert_eq!(module.chapters.len(), 1);
203 assert_eq!(module.chapters[0].number, 1);
204 assert_eq!(module.chapters[0].name, "Test Chapter");
205 }
206
207 #[test]
208 fn module_builder_multiple_chapters() {
209 let chapter1 = ChapterBuilder::new(1, "Chapter 1");
210 let chapter2 = ChapterBuilder::new(2, "Chapter 2");
211 let module = ModuleBuilder::new().chapter(chapter1).chapter(chapter2);
212
213 assert_eq!(module.chapters.len(), 2);
214 assert_eq!(module.chapters[0].number, 1);
215 assert_eq!(module.chapters[0].name, "Chapter 1");
216 assert_eq!(module.chapters[1].number, 2);
217 assert_eq!(module.chapters[1].name, "Chapter 2");
218 }
219
220 #[test]
221 fn module_builder_fluent_interface() {
222 let chapter1 = ChapterBuilder::new(1, "Chapter 1");
223 let chapter2 = ChapterBuilder::new(2, "Chapter 2");
224
225 let module = ModuleBuilder::new()
226 .order(1)
227 .name("Advanced Module")
228 .ects(3.5)
229 .register_to_open_university(true)
230 .chapter(chapter1)
231 .chapter(chapter2);
232
233 assert_eq!(module.order, Some(1));
234 assert_eq!(module.name, Some("Advanced Module".to_string()));
235 assert_eq!(module.ects, Some(3.5));
236 assert!(module.register_to_open_university);
237 assert_eq!(module.chapters.len(), 2);
238 }
239
240 #[test]
241 fn module_builder_method_chaining_order() {
242 let chapter1 = ChapterBuilder::new(1, "Test Chapter");
243 let chapter2 = ChapterBuilder::new(1, "Test Chapter");
244
245 let module1 = ModuleBuilder::new()
246 .order(1)
247 .name("Module 1")
248 .ects(2.0)
249 .register_to_open_university(true)
250 .chapter(chapter1);
251
252 let module2 = ModuleBuilder::new()
253 .register_to_open_university(true)
254 .chapter(chapter2)
255 .ects(2.0)
256 .name("Module 1")
257 .order(1);
258
259 assert_eq!(module1.name, module2.name);
260 assert_eq!(module1.ects, module2.ects);
261 assert_eq!(
262 module1.register_to_open_university,
263 module2.register_to_open_university
264 );
265 assert_eq!(module1.chapters.len(), module2.chapters.len());
266 assert_eq!(module1.order, module2.order);
267 }
268
269 #[test]
270 fn module_builder_default_values() {
271 let module = ModuleBuilder::new();
272
273 assert!(module.order.is_none());
274 assert!(module.name.is_none());
275 assert!(module.ects.is_none());
276 assert!(module.chapters.is_empty());
277 assert!(!module.register_to_open_university);
278 }
279
280 #[test]
281 fn module_builder_order_preservation() {
282 let module = ModuleBuilder::new().order(999);
283
284 assert_eq!(module.order, Some(999));
285 }
286
287 #[test]
288 fn module_builder_manual_completion() {
289 let module = ModuleBuilder::new().manual_completion();
290
291 assert_eq!(module.completion_policy, CompletionPolicy::Manual);
292 }
293
294 #[test]
295 fn module_builder_automatic_completion() {
296 let module = ModuleBuilder::new().automatic_completion(Some(5), Some(100), true);
297
298 match module.completion_policy {
299 CompletionPolicy::Automatic(requirements) => {
300 assert_eq!(requirements.number_of_exercises_attempted_treshold, Some(5));
301 assert_eq!(requirements.number_of_points_treshold, Some(100));
302 assert!(requirements.requires_exam);
303 }
304 CompletionPolicy::Manual => panic!("Expected automatic completion policy"),
305 }
306 }
307
308 #[test]
309 fn module_builder_automatic_completion_no_thresholds() {
310 let module = ModuleBuilder::new().automatic_completion(None, None, false);
311
312 match module.completion_policy {
313 CompletionPolicy::Automatic(requirements) => {
314 assert_eq!(requirements.number_of_exercises_attempted_treshold, None);
315 assert_eq!(requirements.number_of_points_treshold, None);
316 assert!(!requirements.requires_exam);
317 }
318 CompletionPolicy::Manual => panic!("Expected automatic completion policy"),
319 }
320 }
321
322 #[test]
323 fn module_builder_completion_policy_fluent_interface() {
324 let chapter1 = ChapterBuilder::new(1, "Chapter 1");
325 let chapter2 = ChapterBuilder::new(2, "Chapter 2");
326
327 let module = ModuleBuilder::new()
328 .order(1)
329 .name("Advanced Module")
330 .ects(3.5)
331 .register_to_open_university(true)
332 .automatic_completion(Some(10), Some(200), true)
333 .chapter(chapter1)
334 .chapter(chapter2);
335
336 assert_eq!(module.order, Some(1));
337 assert_eq!(module.name, Some("Advanced Module".to_string()));
338 assert_eq!(module.ects, Some(3.5));
339 assert!(module.register_to_open_university);
340 assert_eq!(module.chapters.len(), 2);
341
342 match module.completion_policy {
343 CompletionPolicy::Automatic(requirements) => {
344 assert_eq!(
345 requirements.number_of_exercises_attempted_treshold,
346 Some(10)
347 );
348 assert_eq!(requirements.number_of_points_treshold, Some(200));
349 assert!(requirements.requires_exam);
350 }
351 CompletionPolicy::Manual => panic!("Expected automatic completion policy"),
352 }
353 }
354
355 #[test]
356 fn module_builder_completion_policy_override() {
357 let module1 = ModuleBuilder::new().manual_completion();
358 let module2 = ModuleBuilder::new()
359 .automatic_completion(Some(5), Some(100), false)
360 .manual_completion();
361
362 assert_eq!(module1.completion_policy, CompletionPolicy::Manual);
363 assert_eq!(module2.completion_policy, CompletionPolicy::Manual);
364 }
365}