1use std::collections::HashMap;
2
3use crate::{chapters, prelude::*};
4
5struct CourseModulesSchema {
7 id: Uuid,
8 created_at: DateTime<Utc>,
9 updated_at: DateTime<Utc>,
10 deleted_at: Option<DateTime<Utc>>,
11 name: Option<String>,
12 course_id: Uuid,
13 order_number: i32,
14 copied_from: Option<Uuid>,
15 uh_course_code: Option<String>,
16 automatic_completion: bool,
17 automatic_completion_number_of_exercises_attempted_treshold: Option<i32>,
18 automatic_completion_number_of_points_treshold: Option<i32>,
19 automatic_completion_requires_exam: bool,
20 completion_registration_link_override: Option<String>,
21 ects_credits: Option<f32>,
22 enable_registering_completion_to_uh_open_university: bool,
23 certification_enabled: bool,
24}
25#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
29#[cfg_attr(feature = "ts_rs", derive(TS))]
30pub struct CourseModule {
31 pub id: Uuid,
32 pub created_at: DateTime<Utc>,
33 pub updated_at: DateTime<Utc>,
34 pub deleted_at: Option<DateTime<Utc>>,
35 pub name: Option<String>,
36 pub course_id: Uuid,
37 pub order_number: i32,
38 pub copied_from: Option<Uuid>,
39 pub uh_course_code: Option<String>,
40 pub completion_policy: CompletionPolicy,
41 pub completion_registration_link_override: Option<String>,
43 pub ects_credits: Option<f32>,
44 pub enable_registering_completion_to_uh_open_university: bool,
45 pub certification_enabled: bool,
46}
47
48impl CourseModule {
49 pub fn new(id: Uuid, course_id: Uuid) -> Self {
50 Self {
51 id,
52 created_at: Utc::now(),
53 updated_at: Utc::now(),
54 deleted_at: None,
55 name: None,
56 course_id,
57 order_number: 0,
58 copied_from: None,
59 uh_course_code: None,
60 completion_policy: CompletionPolicy::Manual,
61 completion_registration_link_override: None,
62 ects_credits: None,
63 enable_registering_completion_to_uh_open_university: false,
64 certification_enabled: false,
65 }
66 }
67 pub fn set_timestamps(
68 mut self,
69 created_at: DateTime<Utc>,
70 updated_at: DateTime<Utc>,
71 deleted_at: Option<DateTime<Utc>>,
72 ) -> Self {
73 self.created_at = created_at;
74 self.updated_at = updated_at;
75 self.deleted_at = deleted_at;
76 self
77 }
78
79 pub fn set_name_and_order_number(mut self, name: Option<String>, order_number: i32) -> Self {
81 self.name = name;
82 self.order_number = order_number;
83 self
84 }
85
86 pub fn set_completion_policy(mut self, completion_policy: CompletionPolicy) -> Self {
87 self.completion_policy = completion_policy;
88 self
89 }
90
91 pub fn set_registration_info(
92 mut self,
93 uh_course_code: Option<String>,
94 ects_credits: Option<f32>,
95 completion_registration_link_override: Option<String>,
96 enable_registering_completion_to_uh_open_university: bool,
97 ) -> Self {
98 self.uh_course_code = uh_course_code;
99 self.ects_credits = ects_credits;
100 self.completion_registration_link_override = completion_registration_link_override;
101 self.enable_registering_completion_to_uh_open_university =
102 enable_registering_completion_to_uh_open_university;
103 self
104 }
105
106 pub fn set_certification_enabled(mut self, certification_enabled: bool) -> Self {
107 self.certification_enabled = certification_enabled;
108 self
109 }
110
111 pub fn is_default_module(&self) -> bool {
112 self.name.is_none()
113 }
114}
115
116impl From<CourseModulesSchema> for CourseModule {
117 fn from(schema: CourseModulesSchema) -> Self {
118 let completion_policy = if schema.automatic_completion {
119 CompletionPolicy::Automatic(AutomaticCompletionRequirements {
120 course_module_id: schema.id,
121 number_of_exercises_attempted_treshold: schema
122 .automatic_completion_number_of_exercises_attempted_treshold,
123 number_of_points_treshold: schema.automatic_completion_number_of_points_treshold,
124 requires_exam: schema.automatic_completion_requires_exam,
125 })
126 } else {
127 CompletionPolicy::Manual
128 };
129 Self {
130 id: schema.id,
131 created_at: schema.created_at,
132 updated_at: schema.updated_at,
133 deleted_at: schema.deleted_at,
134 name: schema.name,
135 course_id: schema.course_id,
136 order_number: schema.order_number,
137 copied_from: schema.copied_from,
138 uh_course_code: schema.uh_course_code,
139 completion_policy,
140 completion_registration_link_override: schema.completion_registration_link_override,
141 ects_credits: schema.ects_credits,
142 enable_registering_completion_to_uh_open_university: schema
143 .enable_registering_completion_to_uh_open_university,
144 certification_enabled: schema.certification_enabled,
145 }
146 }
147}
148
149#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
150#[cfg_attr(feature = "ts_rs", derive(TS))]
151pub struct NewCourseModule {
152 completion_policy: CompletionPolicy,
153 completion_registration_link_override: Option<String>,
154 course_id: Uuid,
155 ects_credits: Option<f32>,
156 name: Option<String>,
157 order_number: i32,
158 uh_course_code: Option<String>,
159 enable_registering_completion_to_uh_open_university: bool,
160}
161
162impl NewCourseModule {
163 pub fn new(course_id: Uuid, name: Option<String>, order_number: i32) -> Self {
164 Self {
165 completion_policy: CompletionPolicy::Manual,
166 completion_registration_link_override: None,
167 course_id,
168 ects_credits: None,
169 name,
170 order_number,
171 uh_course_code: None,
172 enable_registering_completion_to_uh_open_university: false,
173 }
174 }
175
176 pub fn new_course_default(course_id: Uuid) -> Self {
177 Self::new(course_id, None, 0)
178 }
179
180 pub fn set_uh_course_code(mut self, uh_course_code: Option<String>) -> Self {
181 self.uh_course_code = uh_course_code;
182 self
183 }
184
185 pub fn set_completion_policy(mut self, completion_policy: CompletionPolicy) -> Self {
186 self.completion_policy = completion_policy;
187 self
188 }
189
190 pub fn set_completion_registration_link_override(
191 mut self,
192 completion_registration_link_override: Option<String>,
193 ) -> Self {
194 self.completion_registration_link_override = completion_registration_link_override;
195 self
196 }
197
198 pub fn set_ects_credits(mut self, ects_credits: Option<f32>) -> Self {
199 self.ects_credits = ects_credits;
200 self
201 }
202
203 pub fn set_enable_registering_completion_to_uh_open_university(
204 mut self,
205 enable_registering_completion_to_uh_open_university: bool,
206 ) -> Self {
207 self.enable_registering_completion_to_uh_open_university =
208 enable_registering_completion_to_uh_open_university;
209 self
210 }
211}
212
213pub async fn insert(
214 conn: &mut PgConnection,
215 pkey_policy: PKeyPolicy<Uuid>,
216 new_course_module: &NewCourseModule,
217) -> ModelResult<CourseModule> {
218 let (automatic_completion, exercises_treshold, points_treshold, requires_exam) =
219 new_course_module.completion_policy.to_database_fields();
220 let res = sqlx::query_as!(
221 CourseModulesSchema,
222 "
223INSERT INTO course_modules (
224 id,
225 course_id,
226 name,
227 order_number,
228 automatic_completion,
229 automatic_completion_number_of_exercises_attempted_treshold,
230 automatic_completion_number_of_points_treshold,
231 automatic_completion_requires_exam,
232 ects_credits,
233 enable_registering_completion_to_uh_open_university
234 )
235VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
236RETURNING *
237 ",
238 pkey_policy.into_uuid(),
239 new_course_module.course_id,
240 new_course_module.name,
241 new_course_module.order_number,
242 automatic_completion,
243 exercises_treshold,
244 points_treshold,
245 requires_exam,
246 new_course_module.ects_credits,
247 new_course_module.enable_registering_completion_to_uh_open_university
248 )
249 .fetch_one(conn)
250 .await?;
251 Ok(res.into())
252}
253
254pub async fn rename(conn: &mut PgConnection, id: Uuid, name: &str) -> ModelResult<()> {
255 sqlx::query!(
256 "
257UPDATE course_modules
258SET name = $1
259WHERE id = $2
260",
261 name,
262 id
263 )
264 .execute(conn)
265 .await?;
266 Ok(())
267}
268
269pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
270 let associated_chapters = chapters::get_for_module(conn, id).await?;
271 if !associated_chapters.is_empty() {
272 return Err(ModelError::new(
273 ModelErrorType::InvalidRequest,
274 format!(
275 "Cannot remove module {id} because it has {} chapters associated with it",
276 associated_chapters.len()
277 ),
278 None,
279 ));
280 }
281 sqlx::query!(
282 "
283UPDATE course_modules
284SET deleted_at = now()
285WHERE id = $1
286AND deleted_at IS NULL
287",
288 id
289 )
290 .execute(conn)
291 .await?;
292 Ok(())
293}
294
295pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<CourseModule> {
296 let res = sqlx::query_as!(
297 CourseModulesSchema,
298 "
299SELECT *
300FROM course_modules
301WHERE id = $1
302 AND deleted_at IS NULL
303 ",
304 id,
305 )
306 .fetch_one(conn)
307 .await?;
308 Ok(res.into())
309}
310
311pub async fn get_by_course_id(
312 conn: &mut PgConnection,
313 course_id: Uuid,
314) -> ModelResult<Vec<CourseModule>> {
315 let res = sqlx::query_as!(
316 CourseModulesSchema,
317 "
318SELECT *
319FROM course_modules
320WHERE course_id = $1
321AND deleted_at IS NULL
322",
323 course_id
324 )
325 .map(|x| x.into())
326 .fetch_all(conn)
327 .await?;
328 Ok(res)
329}
330
331pub async fn get_by_exercise_id(
334 conn: &mut PgConnection,
335 exercise_id: Uuid,
336) -> ModelResult<CourseModule> {
337 let res = sqlx::query_as!(
338 CourseModulesSchema,
339 "
340SELECT course_modules.*
341FROM exercises
342 LEFT JOIN chapters ON (exercises.chapter_id = chapters.id)
343 LEFT JOIN course_modules ON (chapters.course_module_id = course_modules.id)
344WHERE exercises.id = $1
345AND chapters.deleted_at IS NULL
346AND course_modules.deleted_at IS NULL
347 ",
348 exercise_id,
349 )
350 .fetch_one(conn)
351 .await?;
352 Ok(res.into())
353}
354
355pub async fn get_course_module_id_by_chapter(
356 conn: &mut PgConnection,
357 chapter_id: Uuid,
358) -> ModelResult<Uuid> {
359 let res: Uuid = sqlx::query!(
360 r#"
361SELECT c.course_module_id
362from chapters c
363where c.id = $1
364 AND deleted_at IS NULL
365 "#,
366 chapter_id
367 )
368 .map(|record| record.course_module_id)
369 .fetch_one(conn)
370 .await?;
371 Ok(res)
372}
373
374pub async fn get_default_by_course_id(
375 conn: &mut PgConnection,
376 course_id: Uuid,
377) -> ModelResult<CourseModule> {
378 let res = sqlx::query_as!(
379 CourseModulesSchema,
380 "
381SELECT *
382FROM course_modules
383WHERE course_id = $1
384 AND name IS NULL
385 AND deleted_at IS NULL
386 ",
387 course_id,
388 )
389 .fetch_one(conn)
390 .await?;
391 Ok(res.into())
392}
393
394pub async fn get_ids_by_course_slug_or_uh_course_code(
399 conn: &mut PgConnection,
400 course_slug_or_code: &str,
401) -> ModelResult<Vec<Uuid>> {
402 let res = sqlx::query!(
403 "
404SELECT course_modules.id
405FROM course_modules
406 LEFT JOIN courses ON (course_modules.course_id = courses.id)
407WHERE (
408 course_modules.uh_course_code = $1
409 OR courses.slug = $1
410 )
411 AND course_modules.deleted_at IS NULL
412 ",
413 course_slug_or_code,
414 )
415 .map(|record| record.id)
416 .fetch_all(conn)
417 .await?;
418 Ok(res)
419}
420
421pub async fn get_by_course_id_as_map(
423 conn: &mut PgConnection,
424 course_id: Uuid,
425) -> ModelResult<HashMap<Uuid, CourseModule>> {
426 let res = get_by_course_id(conn, course_id)
427 .await?
428 .into_iter()
429 .map(|course_module| (course_module.id, course_module))
430 .collect();
431 Ok(res)
432}
433
434pub async fn get_all_uh_course_codes_for_open_university(
435 conn: &mut PgConnection,
436) -> ModelResult<Vec<String>> {
437 let res = sqlx::query!(
438 "
439SELECT DISTINCT uh_course_code
440FROM course_modules
441WHERE uh_course_code IS NOT NULL
442 AND enable_registering_completion_to_uh_open_university = true
443 AND deleted_at IS NULL
444"
445 )
446 .fetch_all(conn)
447 .await?
448 .into_iter()
449 .filter_map(|x| x.uh_course_code)
450 .collect();
451 Ok(res)
452}
453
454#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
455#[cfg_attr(feature = "ts_rs", derive(TS))]
456pub struct AutomaticCompletionRequirements {
457 pub course_module_id: Uuid,
459 pub number_of_exercises_attempted_treshold: Option<i32>,
460 pub number_of_points_treshold: Option<i32>,
461 pub requires_exam: bool,
462}
463
464impl AutomaticCompletionRequirements {
465 pub fn passes_exercise_tresholds(
468 &self,
469 exercises_attempted: i32,
470 exercise_points: i32,
471 ) -> bool {
472 self.passes_number_of_exercises_attempted_treshold(exercises_attempted)
473 && self.passes_number_of_exercise_points_treshold(exercise_points)
474 }
475
476 pub fn passes_number_of_exercises_attempted_treshold(&self, exercises_attempted: i32) -> bool {
479 self.number_of_exercises_attempted_treshold
480 .map(|x| x <= exercises_attempted)
481 .unwrap_or(true)
482 }
483
484 pub fn passes_number_of_exercise_points_treshold(&self, exercise_points: i32) -> bool {
487 self.number_of_points_treshold
488 .map(|x| x <= exercise_points)
489 .unwrap_or(true)
490 }
491}
492
493#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
494#[serde(tag = "policy", rename_all = "kebab-case")]
495#[cfg_attr(feature = "ts_rs", derive(TS))]
496pub enum CompletionPolicy {
497 Automatic(AutomaticCompletionRequirements),
498 Manual,
499}
500
501impl CompletionPolicy {
502 pub fn automatic(&self) -> Option<&AutomaticCompletionRequirements> {
504 match self {
505 CompletionPolicy::Automatic(requirements) => Some(requirements),
506 CompletionPolicy::Manual => None,
507 }
508 }
509
510 fn to_database_fields(&self) -> (bool, Option<i32>, Option<i32>, bool) {
511 match self {
512 CompletionPolicy::Automatic(requirements) => (
513 true,
514 requirements.number_of_exercises_attempted_treshold,
515 requirements.number_of_points_treshold,
516 requirements.requires_exam,
517 ),
518 CompletionPolicy::Manual => (false, None, None, false),
519 }
520 }
521}
522
523pub async fn update_automatic_completion_status(
524 conn: &mut PgConnection,
525 id: Uuid,
526 automatic_completion_policy: &CompletionPolicy,
527) -> ModelResult<CourseModule> {
528 let (automatic_completion, exercises_treshold, points_treshold, requires_exam) =
529 automatic_completion_policy.to_database_fields();
530 let res = sqlx::query_as!(
531 CourseModulesSchema,
532 "
533UPDATE course_modules
534SET automatic_completion = $1,
535 automatic_completion_number_of_exercises_attempted_treshold = $2,
536 automatic_completion_number_of_points_treshold = $3,
537 automatic_completion_requires_exam = $4
538WHERE id = $5
539 AND deleted_at IS NULL
540RETURNING *
541 ",
542 automatic_completion,
543 exercises_treshold,
544 points_treshold,
545 requires_exam,
546 id,
547 )
548 .fetch_one(conn)
549 .await?;
550 Ok(res.into())
551}
552
553pub async fn update_uh_course_code(
554 conn: &mut PgConnection,
555 id: Uuid,
556 uh_course_code: Option<String>,
557) -> ModelResult<CourseModule> {
558 let res = sqlx::query_as!(
559 CourseModulesSchema,
560 "
561UPDATE course_modules
562SET uh_course_code = $1
563WHERE id = $2
564 AND deleted_at IS NULL
565RETURNING *
566 ",
567 uh_course_code,
568 id,
569 )
570 .fetch_one(conn)
571 .await?;
572 Ok(res.into())
573}
574
575pub async fn update_enable_registering_completion_to_uh_open_university(
576 conn: &mut PgConnection,
577 id: Uuid,
578 enable_registering_completion_to_uh_open_university: bool,
579) -> ModelResult<CourseModule> {
580 let res = sqlx::query_as!(
581 CourseModulesSchema,
582 "
583UPDATE course_modules
584SET enable_registering_completion_to_uh_open_university = $1
585WHERE id = $2
586 AND deleted_at IS NULL
587RETURNING *
588 ",
589 enable_registering_completion_to_uh_open_university,
590 id,
591 )
592 .fetch_one(conn)
593 .await?;
594 Ok(res.into())
595}
596
597#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
598#[cfg_attr(feature = "ts_rs", derive(TS))]
599pub struct NewModule {
600 name: String,
601 order_number: i32,
602 chapters: Vec<Uuid>,
603 uh_course_code: Option<String>,
604 ects_credits: Option<f32>,
605 completion_policy: CompletionPolicy,
606 completion_registration_link_override: Option<String>,
607 enable_registering_completion_to_uh_open_university: bool,
608}
609
610#[derive(Debug, Deserialize)]
611#[cfg_attr(feature = "ts_rs", derive(TS))]
612pub struct ModifiedModule {
613 id: Uuid,
614 name: Option<String>,
615 order_number: i32,
616 uh_course_code: Option<String>,
617 ects_credits: Option<f32>,
618 completion_policy: CompletionPolicy,
619 completion_registration_link_override: Option<String>,
620 enable_registering_completion_to_uh_open_university: bool,
621}
622
623#[derive(Debug, Deserialize)]
624#[cfg_attr(feature = "ts_rs", derive(TS))]
625pub struct ModuleUpdates {
626 new_modules: Vec<NewModule>,
627 deleted_modules: Vec<Uuid>,
628 modified_modules: Vec<ModifiedModule>,
629 moved_chapters: Vec<(Uuid, Uuid)>,
630}
631
632pub async fn update_with_order_number(
633 conn: &mut PgConnection,
634 id: Uuid,
635 name: Option<&str>,
636 order_number: i32,
637) -> ModelResult<()> {
638 sqlx::query!(
639 "
640UPDATE course_modules
641SET name = COALESCE($1, name),
642 order_number = $2
643WHERE id = $3
644",
645 name,
646 order_number,
647 id,
648 )
649 .execute(conn)
650 .await?;
651 Ok(())
652}
653
654pub async fn update(
655 conn: &mut PgConnection,
656 id: Uuid,
657 updated_course_module: &NewCourseModule,
658) -> ModelResult<()> {
659 let NewCourseModule {
661 completion_policy: _,
662 course_id: _,
663 ects_credits,
664 order_number,
665 name,
666 uh_course_code,
667 completion_registration_link_override,
668 enable_registering_completion_to_uh_open_university,
669 } = updated_course_module;
670 let (automatic_completion, exercises_treshold, points_treshold, requires_exam) =
671 updated_course_module.completion_policy.to_database_fields();
672 sqlx::query!(
673 "
674UPDATE course_modules
675SET name = COALESCE($2, name),
676 order_number = $3,
677 uh_course_code = $4,
678 ects_credits = $5,
679 automatic_completion = $6,
680 automatic_completion_number_of_exercises_attempted_treshold = $7,
681 automatic_completion_number_of_points_treshold = $8,
682 automatic_completion_requires_exam = $9,
683 completion_registration_link_override = $10,
684 enable_registering_completion_to_uh_open_university = $11
685WHERE id = $1
686 ",
687 id,
688 name.as_ref(),
689 order_number,
690 uh_course_code.as_ref(),
691 ects_credits.as_ref(),
692 automatic_completion,
693 exercises_treshold,
694 points_treshold,
695 requires_exam,
696 completion_registration_link_override.as_ref(),
697 enable_registering_completion_to_uh_open_university
698 )
699 .execute(conn)
700 .await?;
701 Ok(())
702}
703
704pub async fn update_modules(
705 conn: &mut PgConnection,
706 course_id: Uuid,
707 updates: ModuleUpdates,
708) -> ModelResult<()> {
709 let mut tx = conn.begin().await?;
710
711 for module_id in updates
713 .modified_modules
714 .iter()
715 .filter(|m| m.order_number != 0)
717 .map(|m| m.id)
718 .chain(updates.deleted_modules.iter().copied())
719 {
720 update_with_order_number(&mut tx, module_id, None, rand::random()).await?;
721 }
722 let mut modified_and_new_modules = updates.modified_modules;
723 for new in updates.new_modules {
724 let NewModule {
726 name,
727 order_number,
728 chapters,
729 uh_course_code,
730 ects_credits,
731 completion_policy,
732 completion_registration_link_override,
733 enable_registering_completion_to_uh_open_university,
734 } = new;
735 let new_course_module = NewCourseModule::new(course_id, Some(name.clone()), rand::random())
737 .set_completion_policy(completion_policy.clone())
738 .set_completion_registration_link_override(completion_registration_link_override)
739 .set_ects_credits(ects_credits)
740 .set_uh_course_code(uh_course_code)
741 .set_enable_registering_completion_to_uh_open_university(
742 enable_registering_completion_to_uh_open_university,
743 );
744 let module = insert(&mut tx, PKeyPolicy::Generate, &new_course_module).await?;
745 for chapter in chapters {
746 chapters::set_module(&mut tx, chapter, module.id).await?;
747 }
748 modified_and_new_modules.push(ModifiedModule {
750 id: module.id,
751 name: None,
752 order_number,
753 uh_course_code: module.uh_course_code,
754 ects_credits,
755 completion_policy,
756 completion_registration_link_override: module.completion_registration_link_override,
757 enable_registering_completion_to_uh_open_university: module
758 .enable_registering_completion_to_uh_open_university,
759 })
760 }
761 for module in modified_and_new_modules {
763 let ModifiedModule {
765 id,
766 name,
767 order_number,
768 uh_course_code,
769 ects_credits,
770 completion_policy,
771 completion_registration_link_override,
772 enable_registering_completion_to_uh_open_university,
773 } = module;
774 update(
775 &mut tx,
776 id,
777 &NewCourseModule::new(course_id, name.clone(), order_number)
778 .set_completion_policy(completion_policy)
779 .set_completion_registration_link_override(completion_registration_link_override)
780 .set_ects_credits(ects_credits)
781 .set_uh_course_code(uh_course_code)
782 .set_enable_registering_completion_to_uh_open_university(
783 enable_registering_completion_to_uh_open_university,
784 ),
785 )
786 .await?;
787 }
788 for (chapter, module) in updates.moved_chapters {
789 chapters::set_module(&mut tx, chapter, module).await?;
790 }
791 for deleted in updates.deleted_modules {
792 delete(&mut tx, deleted).await?;
793 }
794
795 tx.commit().await?;
796 Ok(())
797}
798
799pub async fn update_certification_enabled(
800 conn: &mut PgConnection,
801 id: Uuid,
802 enabled: bool,
803) -> ModelResult<()> {
804 sqlx::query!(
805 "
806UPDATE course_modules
807SET certification_enabled = $1
808WHERE id = $2
809",
810 enabled,
811 id
812 )
813 .execute(conn)
814 .await?;
815 Ok(())
816}
817
818#[cfg(test)]
819mod tests {
820
821 mod automatic_completion_requirements {
822 use uuid::Uuid;
823
824 use super::super::AutomaticCompletionRequirements;
825
826 #[test]
827 fn passes_exercise_tresholds() {
828 let requirements1 = AutomaticCompletionRequirements {
829 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
830 number_of_exercises_attempted_treshold: Some(10),
831 number_of_points_treshold: Some(50),
832 requires_exam: false,
833 };
834 let requirements2 = AutomaticCompletionRequirements {
835 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
836 number_of_exercises_attempted_treshold: Some(50),
837 number_of_points_treshold: Some(10),
838 requires_exam: false,
839 };
840
841 let requirements3 = AutomaticCompletionRequirements {
842 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
843 number_of_exercises_attempted_treshold: Some(0),
844 number_of_points_treshold: Some(0),
845 requires_exam: false,
846 };
847
848 let requirements4 = AutomaticCompletionRequirements {
849 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
850 number_of_exercises_attempted_treshold: Some(10),
851 number_of_points_treshold: None,
852 requires_exam: false,
853 };
854
855 let requirements5 = AutomaticCompletionRequirements {
856 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
857 number_of_exercises_attempted_treshold: None,
858 number_of_points_treshold: Some(10),
859 requires_exam: false,
860 };
861 assert!(requirements1.passes_exercise_tresholds(10, 50));
862 assert!(requirements2.passes_exercise_tresholds(50, 10));
863
864 assert!(!requirements1.passes_exercise_tresholds(50, 10));
865 assert!(!requirements2.passes_exercise_tresholds(10, 50));
866
867 assert!(!requirements1.passes_exercise_tresholds(100, 0));
868 assert!(!requirements2.passes_exercise_tresholds(100, 0));
869
870 assert!(requirements3.passes_exercise_tresholds(1, 1));
871 assert!(requirements3.passes_exercise_tresholds(0, 0));
872
873 assert!(requirements4.passes_exercise_tresholds(10, 1));
874 assert!(!requirements4.passes_exercise_tresholds(1, 10));
875
876 assert!(requirements5.passes_exercise_tresholds(0, 10));
877 assert!(!requirements5.passes_exercise_tresholds(10, 0));
878 }
879 }
880}