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
286",
287 id
288 )
289 .execute(conn)
290 .await?;
291 Ok(())
292}
293
294pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<CourseModule> {
295 let res = sqlx::query_as!(
296 CourseModulesSchema,
297 "
298SELECT *
299FROM course_modules
300WHERE id = $1
301 AND deleted_at IS NULL
302 ",
303 id,
304 )
305 .fetch_one(conn)
306 .await?;
307 Ok(res.into())
308}
309
310pub async fn get_by_course_id(
311 conn: &mut PgConnection,
312 course_id: Uuid,
313) -> ModelResult<Vec<CourseModule>> {
314 let res = sqlx::query_as!(
315 CourseModulesSchema,
316 "
317SELECT *
318FROM course_modules
319WHERE course_id = $1
320AND deleted_at IS NULL
321",
322 course_id
323 )
324 .map(|x| x.into())
325 .fetch_all(conn)
326 .await?;
327 Ok(res)
328}
329
330pub async fn get_by_exercise_id(
333 conn: &mut PgConnection,
334 exercise_id: Uuid,
335) -> ModelResult<CourseModule> {
336 let res = sqlx::query_as!(
337 CourseModulesSchema,
338 "
339SELECT course_modules.*
340FROM exercises
341 LEFT JOIN chapters ON (exercises.chapter_id = chapters.id)
342 LEFT JOIN course_modules ON (chapters.course_module_id = course_modules.id)
343WHERE exercises.id = $1
344AND chapters.deleted_at IS NULL
345AND course_modules.deleted_at IS NULL
346 ",
347 exercise_id,
348 )
349 .fetch_one(conn)
350 .await?;
351 Ok(res.into())
352}
353
354pub async fn get_course_module_id_by_chapter(
355 conn: &mut PgConnection,
356 chapter_id: Uuid,
357) -> ModelResult<Uuid> {
358 let res: Uuid = sqlx::query!(
359 r#"
360SELECT c.course_module_id
361from chapters c
362where c.id = $1
363 AND deleted_at IS NULL
364 "#,
365 chapter_id
366 )
367 .map(|record| record.course_module_id)
368 .fetch_one(conn)
369 .await?;
370 Ok(res)
371}
372
373pub async fn get_default_by_course_id(
374 conn: &mut PgConnection,
375 course_id: Uuid,
376) -> ModelResult<CourseModule> {
377 let res = sqlx::query_as!(
378 CourseModulesSchema,
379 "
380SELECT *
381FROM course_modules
382WHERE course_id = $1
383 AND name IS NULL
384 AND deleted_at IS NULL
385 ",
386 course_id,
387 )
388 .fetch_one(conn)
389 .await?;
390 Ok(res.into())
391}
392
393pub async fn get_ids_by_course_slug_or_uh_course_code(
398 conn: &mut PgConnection,
399 course_slug_or_code: &str,
400) -> ModelResult<Vec<Uuid>> {
401 let res = sqlx::query!(
402 "
403SELECT course_modules.id
404FROM course_modules
405 LEFT JOIN courses ON (course_modules.course_id = courses.id)
406WHERE (
407 course_modules.uh_course_code = $1
408 OR courses.slug = $1
409 )
410 AND course_modules.deleted_at IS NULL
411 ",
412 course_slug_or_code,
413 )
414 .map(|record| record.id)
415 .fetch_all(conn)
416 .await?;
417 Ok(res)
418}
419
420pub async fn get_by_course_id_as_map(
422 conn: &mut PgConnection,
423 course_id: Uuid,
424) -> ModelResult<HashMap<Uuid, CourseModule>> {
425 let res = get_by_course_id(conn, course_id)
426 .await?
427 .into_iter()
428 .map(|course_module| (course_module.id, course_module))
429 .collect();
430 Ok(res)
431}
432
433pub async fn get_all_uh_course_codes_for_open_university(
434 conn: &mut PgConnection,
435) -> ModelResult<Vec<String>> {
436 let res = sqlx::query!(
437 "
438SELECT DISTINCT uh_course_code
439FROM course_modules
440WHERE uh_course_code IS NOT NULL
441 AND enable_registering_completion_to_uh_open_university = true
442 AND deleted_at IS NULL
443"
444 )
445 .fetch_all(conn)
446 .await?
447 .into_iter()
448 .filter_map(|x| x.uh_course_code)
449 .collect();
450 Ok(res)
451}
452
453#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
454#[cfg_attr(feature = "ts_rs", derive(TS))]
455pub struct AutomaticCompletionRequirements {
456 pub course_module_id: Uuid,
458 pub number_of_exercises_attempted_treshold: Option<i32>,
459 pub number_of_points_treshold: Option<i32>,
460 pub requires_exam: bool,
461}
462
463impl AutomaticCompletionRequirements {
464 pub fn passes_exercise_tresholds(
467 &self,
468 exercises_attempted: i32,
469 exercise_points: i32,
470 ) -> bool {
471 self.passes_number_of_exercises_attempted_treshold(exercises_attempted)
472 && self.passes_number_of_exercise_points_treshold(exercise_points)
473 }
474
475 pub fn passes_number_of_exercises_attempted_treshold(&self, exercises_attempted: i32) -> bool {
478 self.number_of_exercises_attempted_treshold
479 .map(|x| x <= exercises_attempted)
480 .unwrap_or(true)
481 }
482
483 pub fn passes_number_of_exercise_points_treshold(&self, exercise_points: i32) -> bool {
486 self.number_of_points_treshold
487 .map(|x| x <= exercise_points)
488 .unwrap_or(true)
489 }
490}
491
492#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
493#[serde(tag = "policy", rename_all = "kebab-case")]
494#[cfg_attr(feature = "ts_rs", derive(TS))]
495pub enum CompletionPolicy {
496 Automatic(AutomaticCompletionRequirements),
497 Manual,
498}
499
500impl CompletionPolicy {
501 pub fn automatic(&self) -> Option<&AutomaticCompletionRequirements> {
503 match self {
504 CompletionPolicy::Automatic(requirements) => Some(requirements),
505 CompletionPolicy::Manual => None,
506 }
507 }
508
509 fn to_database_fields(&self) -> (bool, Option<i32>, Option<i32>, bool) {
510 match self {
511 CompletionPolicy::Automatic(requirements) => (
512 true,
513 requirements.number_of_exercises_attempted_treshold,
514 requirements.number_of_points_treshold,
515 requirements.requires_exam,
516 ),
517 CompletionPolicy::Manual => (false, None, None, false),
518 }
519 }
520}
521
522pub async fn update_automatic_completion_status(
523 conn: &mut PgConnection,
524 id: Uuid,
525 automatic_completion_policy: &CompletionPolicy,
526) -> ModelResult<CourseModule> {
527 let (automatic_completion, exercises_treshold, points_treshold, requires_exam) =
528 automatic_completion_policy.to_database_fields();
529 let res = sqlx::query_as!(
530 CourseModulesSchema,
531 "
532UPDATE course_modules
533SET automatic_completion = $1,
534 automatic_completion_number_of_exercises_attempted_treshold = $2,
535 automatic_completion_number_of_points_treshold = $3,
536 automatic_completion_requires_exam = $4
537WHERE id = $5
538 AND deleted_at IS NULL
539RETURNING *
540 ",
541 automatic_completion,
542 exercises_treshold,
543 points_treshold,
544 requires_exam,
545 id,
546 )
547 .fetch_one(conn)
548 .await?;
549 Ok(res.into())
550}
551
552pub async fn update_uh_course_code(
553 conn: &mut PgConnection,
554 id: Uuid,
555 uh_course_code: Option<String>,
556) -> ModelResult<CourseModule> {
557 let res = sqlx::query_as!(
558 CourseModulesSchema,
559 "
560UPDATE course_modules
561SET uh_course_code = $1
562WHERE id = $2
563 AND deleted_at IS NULL
564RETURNING *
565 ",
566 uh_course_code,
567 id,
568 )
569 .fetch_one(conn)
570 .await?;
571 Ok(res.into())
572}
573
574pub async fn update_enable_registering_completion_to_uh_open_university(
575 conn: &mut PgConnection,
576 id: Uuid,
577 enable_registering_completion_to_uh_open_university: bool,
578) -> ModelResult<CourseModule> {
579 let res = sqlx::query_as!(
580 CourseModulesSchema,
581 "
582UPDATE course_modules
583SET enable_registering_completion_to_uh_open_university = $1
584WHERE id = $2
585 AND deleted_at IS NULL
586RETURNING *
587 ",
588 enable_registering_completion_to_uh_open_university,
589 id,
590 )
591 .fetch_one(conn)
592 .await?;
593 Ok(res.into())
594}
595
596#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
597#[cfg_attr(feature = "ts_rs", derive(TS))]
598pub struct NewModule {
599 name: String,
600 order_number: i32,
601 chapters: Vec<Uuid>,
602 uh_course_code: Option<String>,
603 ects_credits: Option<f32>,
604 completion_policy: CompletionPolicy,
605 completion_registration_link_override: Option<String>,
606 enable_registering_completion_to_uh_open_university: bool,
607}
608
609#[derive(Debug, Deserialize)]
610#[cfg_attr(feature = "ts_rs", derive(TS))]
611pub struct ModifiedModule {
612 id: Uuid,
613 name: Option<String>,
614 order_number: i32,
615 uh_course_code: Option<String>,
616 ects_credits: Option<f32>,
617 completion_policy: CompletionPolicy,
618 completion_registration_link_override: Option<String>,
619 enable_registering_completion_to_uh_open_university: bool,
620}
621
622#[derive(Debug, Deserialize)]
623#[cfg_attr(feature = "ts_rs", derive(TS))]
624pub struct ModuleUpdates {
625 new_modules: Vec<NewModule>,
626 deleted_modules: Vec<Uuid>,
627 modified_modules: Vec<ModifiedModule>,
628 moved_chapters: Vec<(Uuid, Uuid)>,
629}
630
631pub async fn update_with_order_number(
632 conn: &mut PgConnection,
633 id: Uuid,
634 name: Option<&str>,
635 order_number: i32,
636) -> ModelResult<()> {
637 sqlx::query!(
638 "
639UPDATE course_modules
640SET name = COALESCE($1, name),
641 order_number = $2
642WHERE id = $3
643",
644 name,
645 order_number,
646 id,
647 )
648 .execute(conn)
649 .await?;
650 Ok(())
651}
652
653pub async fn update(
654 conn: &mut PgConnection,
655 id: Uuid,
656 updated_course_module: &NewCourseModule,
657) -> ModelResult<()> {
658 let NewCourseModule {
660 completion_policy: _,
661 course_id: _,
662 ects_credits,
663 order_number,
664 name,
665 uh_course_code,
666 completion_registration_link_override,
667 enable_registering_completion_to_uh_open_university,
668 } = updated_course_module;
669 let (automatic_completion, exercises_treshold, points_treshold, requires_exam) =
670 updated_course_module.completion_policy.to_database_fields();
671 sqlx::query!(
672 "
673UPDATE course_modules
674SET name = COALESCE($2, name),
675 order_number = $3,
676 uh_course_code = $4,
677 ects_credits = $5,
678 automatic_completion = $6,
679 automatic_completion_number_of_exercises_attempted_treshold = $7,
680 automatic_completion_number_of_points_treshold = $8,
681 automatic_completion_requires_exam = $9,
682 completion_registration_link_override = $10,
683 enable_registering_completion_to_uh_open_university = $11
684WHERE id = $1
685 ",
686 id,
687 name.as_ref(),
688 order_number,
689 uh_course_code.as_ref(),
690 ects_credits.as_ref(),
691 automatic_completion,
692 exercises_treshold,
693 points_treshold,
694 requires_exam,
695 completion_registration_link_override.as_ref(),
696 enable_registering_completion_to_uh_open_university
697 )
698 .execute(conn)
699 .await?;
700 Ok(())
701}
702
703pub async fn update_modules(
704 conn: &mut PgConnection,
705 course_id: Uuid,
706 updates: ModuleUpdates,
707) -> ModelResult<()> {
708 let mut tx = conn.begin().await?;
709
710 for module_id in updates
712 .modified_modules
713 .iter()
714 .filter(|m| m.order_number != 0)
716 .map(|m| m.id)
717 .chain(updates.deleted_modules.iter().copied())
718 {
719 update_with_order_number(&mut tx, module_id, None, rand::random()).await?;
720 }
721 let mut modified_and_new_modules = updates.modified_modules;
722 for new in updates.new_modules {
723 let NewModule {
725 name,
726 order_number,
727 chapters,
728 uh_course_code,
729 ects_credits,
730 completion_policy,
731 completion_registration_link_override,
732 enable_registering_completion_to_uh_open_university,
733 } = new;
734 let new_course_module = NewCourseModule::new(course_id, Some(name.clone()), rand::random())
736 .set_completion_policy(completion_policy.clone())
737 .set_completion_registration_link_override(completion_registration_link_override)
738 .set_ects_credits(ects_credits)
739 .set_uh_course_code(uh_course_code)
740 .set_enable_registering_completion_to_uh_open_university(
741 enable_registering_completion_to_uh_open_university,
742 );
743 let module = insert(&mut tx, PKeyPolicy::Generate, &new_course_module).await?;
744 for chapter in chapters {
745 chapters::set_module(&mut tx, chapter, module.id).await?;
746 }
747 modified_and_new_modules.push(ModifiedModule {
749 id: module.id,
750 name: None,
751 order_number,
752 uh_course_code: module.uh_course_code,
753 ects_credits,
754 completion_policy,
755 completion_registration_link_override: module.completion_registration_link_override,
756 enable_registering_completion_to_uh_open_university: module
757 .enable_registering_completion_to_uh_open_university,
758 })
759 }
760 for module in modified_and_new_modules {
762 let ModifiedModule {
764 id,
765 name,
766 order_number,
767 uh_course_code,
768 ects_credits,
769 completion_policy,
770 completion_registration_link_override,
771 enable_registering_completion_to_uh_open_university,
772 } = module;
773 update(
774 &mut tx,
775 id,
776 &NewCourseModule::new(course_id, name.clone(), order_number)
777 .set_completion_policy(completion_policy)
778 .set_completion_registration_link_override(completion_registration_link_override)
779 .set_ects_credits(ects_credits)
780 .set_uh_course_code(uh_course_code)
781 .set_enable_registering_completion_to_uh_open_university(
782 enable_registering_completion_to_uh_open_university,
783 ),
784 )
785 .await?;
786 }
787 for (chapter, module) in updates.moved_chapters {
788 chapters::set_module(&mut tx, chapter, module).await?;
789 }
790 for deleted in updates.deleted_modules {
791 delete(&mut tx, deleted).await?;
792 }
793
794 tx.commit().await?;
795 Ok(())
796}
797
798pub async fn update_certification_enabled(
799 conn: &mut PgConnection,
800 id: Uuid,
801 enabled: bool,
802) -> ModelResult<()> {
803 sqlx::query!(
804 "
805UPDATE course_modules
806SET certification_enabled = $1
807WHERE id = $2
808",
809 enabled,
810 id
811 )
812 .execute(conn)
813 .await?;
814 Ok(())
815}
816
817#[cfg(test)]
818mod tests {
819
820 mod automatic_completion_requirements {
821 use uuid::Uuid;
822
823 use super::super::AutomaticCompletionRequirements;
824
825 #[test]
826 fn passes_exercise_tresholds() {
827 let requirements1 = AutomaticCompletionRequirements {
828 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
829 number_of_exercises_attempted_treshold: Some(10),
830 number_of_points_treshold: Some(50),
831 requires_exam: false,
832 };
833 let requirements2 = AutomaticCompletionRequirements {
834 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
835 number_of_exercises_attempted_treshold: Some(50),
836 number_of_points_treshold: Some(10),
837 requires_exam: false,
838 };
839
840 let requirements3 = AutomaticCompletionRequirements {
841 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
842 number_of_exercises_attempted_treshold: Some(0),
843 number_of_points_treshold: Some(0),
844 requires_exam: false,
845 };
846
847 let requirements4 = AutomaticCompletionRequirements {
848 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
849 number_of_exercises_attempted_treshold: Some(10),
850 number_of_points_treshold: None,
851 requires_exam: false,
852 };
853
854 let requirements5 = AutomaticCompletionRequirements {
855 course_module_id: Uuid::parse_str("66d98fc6-784a-4b39-a494-24ae9b1c9b14").unwrap(),
856 number_of_exercises_attempted_treshold: None,
857 number_of_points_treshold: Some(10),
858 requires_exam: false,
859 };
860 assert!(requirements1.passes_exercise_tresholds(10, 50));
861 assert!(requirements2.passes_exercise_tresholds(50, 10));
862
863 assert!(!requirements1.passes_exercise_tresholds(50, 10));
864 assert!(!requirements2.passes_exercise_tresholds(10, 50));
865
866 assert!(!requirements1.passes_exercise_tresholds(100, 0));
867 assert!(!requirements2.passes_exercise_tresholds(100, 0));
868
869 assert!(requirements3.passes_exercise_tresholds(1, 1));
870 assert!(requirements3.passes_exercise_tresholds(0, 0));
871
872 assert!(requirements4.passes_exercise_tresholds(10, 1));
873 assert!(!requirements4.passes_exercise_tresholds(1, 10));
874
875 assert!(requirements5.passes_exercise_tresholds(0, 10));
876 assert!(!requirements5.passes_exercise_tresholds(10, 0));
877 }
878 }
879}