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