1use crate::{course_module_completions, prelude::*};
2use rand::Rng;
3
4#[derive(Clone, PartialEq, Deserialize, Serialize)]
5pub struct CourseModuleCompletionRegisteredToStudyRegistry {
6 pub id: Uuid,
7 pub created_at: DateTime<Utc>,
8 pub updated_at: DateTime<Utc>,
9 pub deleted_at: Option<DateTime<Utc>>,
10 pub course_id: Uuid,
11 pub course_module_completion_id: Uuid,
12 pub course_module_id: Uuid,
13 pub study_registry_registrar_id: Uuid,
14 pub user_id: Uuid,
15 pub real_student_number: String,
16}
17
18#[derive(Clone, PartialEq, Deserialize, Serialize)]
19pub struct NewCourseModuleCompletionRegisteredToStudyRegistry {
20 pub course_id: Uuid,
21 pub course_module_completion_id: Uuid,
22 pub course_module_id: Uuid,
23 pub study_registry_registrar_id: Uuid,
24 pub user_id: Uuid,
25 pub real_student_number: String,
26}
27
28pub async fn insert(
29 conn: &mut PgConnection,
30 pkey_policy: PKeyPolicy<Uuid>,
31 new_completion_registration: &NewCourseModuleCompletionRegisteredToStudyRegistry,
32) -> ModelResult<Uuid> {
33 let res = sqlx::query!(
34 "
35INSERT INTO course_module_completion_registered_to_study_registries (
36 id,
37 course_id,
38 course_module_completion_id,
39 course_module_id,
40 study_registry_registrar_id,
41 user_id,
42 real_student_number
43 )
44VALUES (
45 $1,
46 $2,
47 $3,
48 $4,
49 $5,
50 $6,
51 $7
52 )
53RETURNING id
54 ",
55 pkey_policy.into_uuid(),
56 new_completion_registration.course_id,
57 new_completion_registration.course_module_completion_id,
58 new_completion_registration.course_module_id,
59 new_completion_registration.study_registry_registrar_id,
60 new_completion_registration.user_id,
61 new_completion_registration.real_student_number,
62 )
63 .fetch_one(conn)
64 .await?;
65 Ok(res.id)
66}
67
68pub async fn insert_bulk(
69 conn: &mut PgConnection,
70 new_completion_registrations: Vec<NewCourseModuleCompletionRegisteredToStudyRegistry>,
71) -> ModelResult<Vec<Uuid>> {
72 if new_completion_registrations.is_empty() {
73 return Ok(vec![]);
74 }
75
76 let ids: Vec<Uuid> = (0..new_completion_registrations.len())
78 .map(|_| Uuid::new_v4())
79 .collect();
80 let course_ids: Vec<Uuid> = new_completion_registrations
81 .iter()
82 .map(|r| r.course_id)
83 .collect();
84 let completion_ids: Vec<Uuid> = new_completion_registrations
85 .iter()
86 .map(|r| r.course_module_completion_id)
87 .collect();
88 let module_ids: Vec<Uuid> = new_completion_registrations
89 .iter()
90 .map(|r| r.course_module_id)
91 .collect();
92 let registrar_ids: Vec<Uuid> = new_completion_registrations
93 .iter()
94 .map(|r| r.study_registry_registrar_id)
95 .collect();
96 let user_ids: Vec<Uuid> = new_completion_registrations
97 .iter()
98 .map(|r| r.user_id)
99 .collect();
100 let student_numbers: Vec<String> = new_completion_registrations
101 .iter()
102 .map(|r| r.real_student_number.clone())
103 .collect();
104
105 let res = sqlx::query!(
106 r#"
107INSERT INTO course_module_completion_registered_to_study_registries (
108 id,
109 course_id,
110 course_module_completion_id,
111 course_module_id,
112 study_registry_registrar_id,
113 user_id,
114 real_student_number
115)
116SELECT * FROM UNNEST(
117 $1::uuid[],
118 $2::uuid[],
119 $3::uuid[],
120 $4::uuid[],
121 $5::uuid[],
122 $6::uuid[],
123 $7::text[]
124)
125RETURNING id
126 "#,
127 &ids[..],
128 &course_ids[..],
129 &completion_ids[..],
130 &module_ids[..],
131 ®istrar_ids[..],
132 &user_ids[..],
133 &student_numbers[..],
134 )
135 .fetch_all(conn)
136 .await?;
137
138 Ok(res.into_iter().map(|r| r.id).collect())
139}
140
141#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, Debug)]
142pub struct RegisteredCompletion {
144 pub completion_id: Uuid,
146 pub student_number: String,
148 pub registration_date: DateTime<Utc>,
150}
151
152pub async fn mark_completions_as_registered_to_study_registry(
153 conn: &mut PgConnection,
154 completions: Vec<RegisteredCompletion>,
155 study_registry_registrar_id: Uuid,
156) -> ModelResult<()> {
157 if completions.is_empty() {
158 return Ok(());
159 }
160
161 let ids: Vec<Uuid> = completions.iter().map(|x| x.completion_id).collect();
162 let completions_by_id = course_module_completions::get_by_ids_as_map(conn, &ids).await?;
163
164 for completion in &completions {
166 if !completions_by_id.contains_key(&completion.completion_id) {
167 return Err(ModelError::new(
168 ModelErrorType::PreconditionFailed,
169 format!(
170 "Cannot find completion with id: {}. This completion does not exist in the database.",
171 completion.completion_id
172 ),
173 None,
174 ));
175 }
176 }
177
178 let new_registrations: Vec<NewCourseModuleCompletionRegisteredToStudyRegistry> = completions
179 .into_iter()
180 .map(|completion| {
181 let module_completion = completions_by_id.get(&completion.completion_id).unwrap();
182 NewCourseModuleCompletionRegisteredToStudyRegistry {
183 course_id: module_completion.course_id,
184 course_module_completion_id: completion.completion_id,
185 course_module_id: module_completion.course_module_id,
186 study_registry_registrar_id,
187 user_id: module_completion.user_id,
188 real_student_number: completion.student_number,
189 }
190 })
191 .collect();
192
193 let mut tx = conn.begin().await?;
194
195 insert_bulk(&mut tx, new_registrations).await?;
196
197 let mut rng = rand::rng();
200 let delete_all = rng.random_range(0..50) == 0; if delete_all {
203 delete_all_duplicates(&mut tx).await?;
204 } else {
205 delete_duplicates_for_specific_completions(&mut tx, &ids).await?;
206 }
207
208 tx.commit().await?;
209
210 Ok(())
211}
212
213pub async fn get_by_id(
214 conn: &mut PgConnection,
215 id: Uuid,
216) -> ModelResult<CourseModuleCompletionRegisteredToStudyRegistry> {
217 let res = sqlx::query_as!(
218 CourseModuleCompletionRegisteredToStudyRegistry,
219 "
220SELECT *
221FROM course_module_completion_registered_to_study_registries
222WHERE id = $1
223 AND deleted_at IS NULL
224 ",
225 id,
226 )
227 .fetch_one(conn)
228 .await?;
229 Ok(res)
230}
231
232pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
233 sqlx::query!(
234 "
235UPDATE course_module_completion_registered_to_study_registries
236SET deleted_at = now()
237WHERE id = $1
238 ",
239 id
240 )
241 .execute(conn)
242 .await?;
243 Ok(())
244}
245
246pub async fn get_count_of_distinct_users_with_registrations_by_course_id(
248 conn: &mut PgConnection,
249 course_id: Uuid,
250) -> ModelResult<i64> {
251 let res = sqlx::query!(
252 "
253SELECT COUNT(DISTINCT user_id) as count
254FROM course_module_completion_registered_to_study_registries
255WHERE course_id = $1
256 AND deleted_at IS NULL
257",
258 course_id,
259 )
260 .fetch_one(conn)
261 .await?;
262 Ok(res.count.unwrap_or(0))
263}
264
265pub async fn get_by_completion_id_and_registrar_id(
266 conn: &mut PgConnection,
267 completion_id: Uuid,
268 study_registry_registrar_id: Uuid,
269) -> ModelResult<Vec<CourseModuleCompletionRegisteredToStudyRegistry>> {
270 let registrations = sqlx::query_as!(
271 CourseModuleCompletionRegisteredToStudyRegistry,
272 r#"
273 SELECT *
274 FROM course_module_completion_registered_to_study_registries
275 WHERE course_module_completion_id = $1 AND study_registry_registrar_id = $2
276 AND deleted_at IS NULL
277 "#,
278 completion_id,
279 study_registry_registrar_id
280 )
281 .fetch_all(conn)
282 .await?;
283
284 Ok(registrations)
285}
286
287async fn delete_duplicates_for_specific_completions(
288 conn: &mut PgConnection,
289 completion_ids: &[Uuid],
290) -> ModelResult<i64> {
291 let res = sqlx::query!(
292 r#"
293WITH duplicate_rows AS (
294 SELECT id,
295 ROW_NUMBER() OVER (
296 PARTITION BY course_module_completion_id
297 ORDER BY created_at ASC -- Keep the oldest, delete the rest
298 ) AS rn
299 FROM course_module_completion_registered_to_study_registries
300 WHERE deleted_at IS NULL
301 AND course_module_completion_id = ANY($1)
302)
303UPDATE course_module_completion_registered_to_study_registries
304SET deleted_at = NOW()
305WHERE id IN (
306 SELECT id
307 FROM duplicate_rows
308 WHERE rn > 1
309 )
310RETURNING id
311 "#,
312 completion_ids,
313 )
314 .fetch_all(conn)
315 .await?;
316
317 Ok(res.len() as i64)
318}
319
320async fn delete_all_duplicates(conn: &mut PgConnection) -> ModelResult<i64> {
321 let res = sqlx::query!(
322 r#"
323WITH duplicate_rows AS (
324 SELECT id,
325 ROW_NUMBER() OVER (
326 PARTITION BY course_module_completion_id
327 ORDER BY created_at ASC -- Keep the oldest, delete the rest
328 ) AS rn
329 FROM course_module_completion_registered_to_study_registries
330 WHERE deleted_at IS NULL
331)
332UPDATE course_module_completion_registered_to_study_registries
333SET deleted_at = NOW()
334WHERE id IN (
335 SELECT id
336 FROM duplicate_rows
337 WHERE rn > 1
338 )
339RETURNING id
340 "#
341 )
342 .fetch_all(conn)
343 .await?;
344
345 Ok(res.len() as i64)
346}
347
348#[cfg(test)]
349mod test {
350 use super::*;
351 use crate::{course_module_completions::CourseModuleCompletionGranter, test_helper::*};
352
353 #[tokio::test]
354 async fn bulk_insert_works() {
355 insert_data!(:tx, :user, :org, :course, :instance, :course_module);
356
357 let registrar_id = crate::study_registry_registrars::insert(
358 tx.as_mut(),
359 PKeyPolicy::Generate,
360 "Test Registrar",
361 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
362 )
363 .await
364 .unwrap();
365
366 let new_completion_id = crate::course_module_completions::insert(
367 tx.as_mut(),
368 PKeyPolicy::Generate,
369 &crate::course_module_completions::NewCourseModuleCompletion {
370 course_id: course,
371 course_module_id: course_module.id,
372 user_id: user,
373 course_instance_id: instance.id,
374 completion_date: Utc::now(),
375 completion_registration_attempt_date: None,
376 completion_language: "en-US".to_string(),
377 eligible_for_ects: true,
378 email: "test@example.com".to_string(),
379 grade: Some(10),
380 passed: true,
381 },
382 CourseModuleCompletionGranter::User(user),
383 )
384 .await
385 .unwrap();
386
387 let registrations = vec![
388 NewCourseModuleCompletionRegisteredToStudyRegistry {
389 course_id: course,
390 course_module_completion_id: new_completion_id.id,
391 course_module_id: course_module.id,
392 study_registry_registrar_id: registrar_id,
393 user_id: user,
394 real_student_number: "12345".to_string(),
395 },
396 NewCourseModuleCompletionRegisteredToStudyRegistry {
397 course_id: course,
398 course_module_completion_id: new_completion_id.id,
399 course_module_id: course_module.id,
400 study_registry_registrar_id: registrar_id,
401 user_id: user,
402 real_student_number: "67890".to_string(),
403 },
404 ];
405
406 let inserted_ids = insert_bulk(tx.as_mut(), registrations).await.unwrap();
407 assert_eq!(inserted_ids.len(), 2);
408
409 for id in inserted_ids {
411 let registration = get_by_id(tx.as_mut(), id).await.unwrap();
412 assert_eq!(registration.course_id, course);
413 assert_eq!(
414 registration.course_module_completion_id,
415 new_completion_id.id
416 );
417 assert_eq!(registration.course_module_id, course_module.id);
418 assert_eq!(registration.study_registry_registrar_id, registrar_id);
419 assert_eq!(registration.user_id, user);
420 }
421 }
422
423 #[tokio::test]
424 async fn bulk_insert_empty_vec_works() {
425 insert_data!(:tx);
426
427 let empty_vec = vec![];
428 let result = insert_bulk(tx.as_mut(), empty_vec).await.unwrap();
429 assert!(result.is_empty());
430 }
431
432 #[tokio::test]
433 async fn insert_completions_works() {
434 insert_data!(:tx, :user, :org, :course, :instance, :course_module);
435
436 let registrar_id = crate::study_registry_registrars::insert(
437 tx.as_mut(),
438 PKeyPolicy::Generate,
439 "Test Registrar",
440 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
441 )
442 .await
443 .unwrap();
444
445 let completion = crate::course_module_completions::insert(
446 tx.as_mut(),
447 PKeyPolicy::Generate,
448 &crate::course_module_completions::NewCourseModuleCompletion {
449 course_id: course,
450 course_module_id: course_module.id,
451 user_id: user,
452 course_instance_id: instance.id,
453 completion_date: Utc::now(),
454 completion_registration_attempt_date: None,
455 completion_language: "en-US".to_string(),
456 eligible_for_ects: true,
457 email: "test@example.com".to_string(),
458 grade: Some(5),
459 passed: true,
460 },
461 CourseModuleCompletionGranter::User(user),
462 )
463 .await
464 .unwrap();
465
466 let registered_completions = vec![RegisteredCompletion {
467 completion_id: completion.id,
468 student_number: "12345".to_string(),
469 registration_date: Utc::now(),
470 }];
471
472 mark_completions_as_registered_to_study_registry(
473 tx.as_mut(),
474 registered_completions,
475 registrar_id,
476 )
477 .await
478 .unwrap();
479
480 let registrations =
481 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
482 .await
483 .unwrap();
484
485 assert_eq!(registrations.len(), 1);
486 assert_eq!(registrations[0].course_id, course);
487 assert_eq!(registrations[0].course_module_id, course_module.id);
488 assert_eq!(registrations[0].user_id, user);
489 assert_eq!(registrations[0].real_student_number, "12345");
490 }
491
492 #[tokio::test]
493 async fn insert_completions_with_invalid_completion_id_fails() {
494 insert_data!(:tx);
495
496 let registrar_id = crate::study_registry_registrars::insert(
497 tx.as_mut(),
498 PKeyPolicy::Generate,
499 "Test Registrar",
500 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
501 )
502 .await
503 .unwrap();
504
505 let invalid_uuid = Uuid::new_v4(); let registered_completions = vec![RegisteredCompletion {
507 completion_id: invalid_uuid,
508 student_number: "12345".to_string(),
509 registration_date: Utc::now(),
510 }];
511
512 let result = mark_completions_as_registered_to_study_registry(
514 tx.as_mut(),
515 registered_completions,
516 registrar_id,
517 )
518 .await;
519
520 assert!(result.is_err());
521 let error = result.unwrap_err();
522 assert_eq!(*error.error_type(), ModelErrorType::PreconditionFailed);
523 assert!(error.message().contains("Cannot find completion with id"));
524 assert!(error.message().contains(&invalid_uuid.to_string()));
525 }
526
527 #[tokio::test]
528 async fn delete_duplicate_registrations_works() {
529 insert_data!(:tx, :user, :org, :course, :instance, :course_module);
530
531 let registrar_id = crate::study_registry_registrars::insert(
532 tx.as_mut(),
533 PKeyPolicy::Generate,
534 "Test Registrar",
535 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
536 )
537 .await
538 .unwrap();
539
540 let completion = crate::course_module_completions::insert(
541 tx.as_mut(),
542 PKeyPolicy::Generate,
543 &crate::course_module_completions::NewCourseModuleCompletion {
544 course_id: course,
545 course_module_id: course_module.id,
546 user_id: user,
547 course_instance_id: instance.id,
548 completion_date: Utc::now(),
549 completion_registration_attempt_date: None,
550 completion_language: "en-US".to_string(),
551 eligible_for_ects: true,
552 email: "test@example.com".to_string(),
553 grade: Some(5),
554 passed: true,
555 },
556 CourseModuleCompletionGranter::User(user),
557 )
558 .await
559 .unwrap();
560
561 let first_registration = NewCourseModuleCompletionRegisteredToStudyRegistry {
563 course_id: course,
564 course_module_completion_id: completion.id,
565 course_module_id: course_module.id,
566 study_registry_registrar_id: registrar_id,
567 user_id: user,
568 real_student_number: "12345".to_string(),
569 };
570 let first_id = insert(tx.as_mut(), PKeyPolicy::Generate, &first_registration)
571 .await
572 .unwrap();
573
574 let later_registrations = vec![
576 NewCourseModuleCompletionRegisteredToStudyRegistry {
577 course_id: course,
578 course_module_completion_id: completion.id,
579 course_module_id: course_module.id,
580 study_registry_registrar_id: registrar_id,
581 user_id: user,
582 real_student_number: "67890".to_string(),
583 },
584 NewCourseModuleCompletionRegisteredToStudyRegistry {
585 course_id: course,
586 course_module_completion_id: completion.id,
587 course_module_id: course_module.id,
588 study_registry_registrar_id: registrar_id,
589 user_id: user,
590 real_student_number: "54321".to_string(),
591 },
592 ];
593 insert_bulk(tx.as_mut(), later_registrations).await.unwrap();
594
595 let before_registrations =
596 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
597 .await
598 .unwrap();
599 assert_eq!(before_registrations.len(), 3);
600
601 let deleted_count =
602 delete_duplicates_for_specific_completions(tx.as_mut(), &[completion.id])
603 .await
604 .unwrap();
605 assert_eq!(deleted_count, 2); let after_registrations =
608 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
609 .await
610 .unwrap();
611 assert_eq!(after_registrations.len(), 1);
612
613 assert_eq!(after_registrations[0].id, first_id);
615 assert_eq!(after_registrations[0].real_student_number, "12345");
616 }
617
618 #[tokio::test]
619 async fn delete_duplicate_registrations_with_no_duplicates() {
620 insert_data!(:tx, :user, :org, :course, :instance, :course_module);
621
622 let registrar_id = crate::study_registry_registrars::insert(
623 tx.as_mut(),
624 PKeyPolicy::Generate,
625 "Test Registrar",
626 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
627 )
628 .await
629 .unwrap();
630
631 let completion1 = crate::course_module_completions::insert(
632 tx.as_mut(),
633 PKeyPolicy::Generate,
634 &crate::course_module_completions::NewCourseModuleCompletion {
635 course_id: course,
636 course_module_id: course_module.id,
637 user_id: user,
638 course_instance_id: instance.id,
639 completion_date: Utc::now(),
640 completion_registration_attempt_date: None,
641 completion_language: "en-US".to_string(),
642 eligible_for_ects: true,
643 email: "test1@example.com".to_string(),
644 grade: Some(5),
645 passed: true,
646 },
647 CourseModuleCompletionGranter::User(user),
648 )
649 .await
650 .unwrap();
651
652 let completion2 = crate::course_module_completions::insert(
653 tx.as_mut(),
654 PKeyPolicy::Generate,
655 &crate::course_module_completions::NewCourseModuleCompletion {
656 course_id: course,
657 course_module_id: course_module.id,
658 user_id: user,
659 course_instance_id: instance.id,
660 completion_date: Utc::now(),
661 completion_registration_attempt_date: None,
662 completion_language: "en-US".to_string(),
663 eligible_for_ects: true,
664 email: "test2@example.com".to_string(),
665 grade: Some(4),
666 passed: true,
667 },
668 CourseModuleCompletionGranter::User(user),
669 )
670 .await
671 .unwrap();
672
673 let registrations = vec![
675 NewCourseModuleCompletionRegisteredToStudyRegistry {
676 course_id: course,
677 course_module_completion_id: completion1.id,
678 course_module_id: course_module.id,
679 study_registry_registrar_id: registrar_id,
680 user_id: user,
681 real_student_number: "12345".to_string(),
682 },
683 NewCourseModuleCompletionRegisteredToStudyRegistry {
684 course_id: course,
685 course_module_completion_id: completion2.id,
686 course_module_id: course_module.id,
687 study_registry_registrar_id: registrar_id,
688 user_id: user,
689 real_student_number: "67890".to_string(),
690 },
691 ];
692 insert_bulk(tx.as_mut(), registrations).await.unwrap();
693
694 let before_reg1 =
696 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
697 .await
698 .unwrap();
699 let before_reg2 =
700 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
701 .await
702 .unwrap();
703 assert_eq!(before_reg1.len(), 1);
704 assert_eq!(before_reg2.len(), 1);
705
706 let deleted_count = delete_all_duplicates(tx.as_mut()).await.unwrap();
708 assert_eq!(deleted_count, 0); let after_reg1 =
712 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
713 .await
714 .unwrap();
715 let after_reg2 =
716 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
717 .await
718 .unwrap();
719 assert_eq!(after_reg1.len(), 1);
720 assert_eq!(after_reg2.len(), 1);
721 }
722
723 #[tokio::test]
724 async fn delete_duplicate_registrations_filters_by_completion_id() {
725 insert_data!(:tx, :user, :org, :course, :instance, :course_module);
726
727 let registrar_id = crate::study_registry_registrars::insert(
728 tx.as_mut(),
729 PKeyPolicy::Generate,
730 "Test Registrar",
731 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
732 )
733 .await
734 .unwrap();
735
736 let completion1 = crate::course_module_completions::insert(
738 tx.as_mut(),
739 PKeyPolicy::Generate,
740 &crate::course_module_completions::NewCourseModuleCompletion {
741 course_id: course,
742 course_module_id: course_module.id,
743 user_id: user,
744 course_instance_id: instance.id,
745 completion_date: Utc::now(),
746 completion_registration_attempt_date: None,
747 completion_language: "en-US".to_string(),
748 eligible_for_ects: true,
749 email: "test1@example.com".to_string(),
750 grade: Some(5),
751 passed: true,
752 },
753 CourseModuleCompletionGranter::User(user),
754 )
755 .await
756 .unwrap();
757
758 let completion2 = crate::course_module_completions::insert(
759 tx.as_mut(),
760 PKeyPolicy::Generate,
761 &crate::course_module_completions::NewCourseModuleCompletion {
762 course_id: course,
763 course_module_id: course_module.id,
764 user_id: user,
765 course_instance_id: instance.id,
766 completion_date: Utc::now(),
767 completion_registration_attempt_date: None,
768 completion_language: "en-US".to_string(),
769 eligible_for_ects: true,
770 email: "test2@example.com".to_string(),
771 grade: Some(5),
772 passed: true,
773 },
774 CourseModuleCompletionGranter::User(user),
775 )
776 .await
777 .unwrap();
778
779 let first_registrations = vec![
781 NewCourseModuleCompletionRegisteredToStudyRegistry {
782 course_id: course,
783 course_module_completion_id: completion1.id,
784 course_module_id: course_module.id,
785 study_registry_registrar_id: registrar_id,
786 user_id: user,
787 real_student_number: "12345-1".to_string(),
788 },
789 NewCourseModuleCompletionRegisteredToStudyRegistry {
790 course_id: course,
791 course_module_completion_id: completion2.id,
792 course_module_id: course_module.id,
793 study_registry_registrar_id: registrar_id,
794 user_id: user,
795 real_student_number: "12345-2".to_string(),
796 },
797 ];
798 insert_bulk(tx.as_mut(), first_registrations).await.unwrap();
799
800 let second_registrations = vec![
802 NewCourseModuleCompletionRegisteredToStudyRegistry {
803 course_id: course,
804 course_module_completion_id: completion1.id,
805 course_module_id: course_module.id,
806 study_registry_registrar_id: registrar_id,
807 user_id: user,
808 real_student_number: "67890-1".to_string(),
809 },
810 NewCourseModuleCompletionRegisteredToStudyRegistry {
811 course_id: course,
812 course_module_completion_id: completion2.id,
813 course_module_id: course_module.id,
814 study_registry_registrar_id: registrar_id,
815 user_id: user,
816 real_student_number: "67890-2".to_string(),
817 },
818 ];
819 insert_bulk(tx.as_mut(), second_registrations)
820 .await
821 .unwrap();
822
823 let before_reg1 =
825 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
826 .await
827 .unwrap();
828 let before_reg2 =
829 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
830 .await
831 .unwrap();
832 assert_eq!(before_reg1.len(), 2);
833 assert_eq!(before_reg2.len(), 2);
834
835 let deleted_count =
837 delete_duplicates_for_specific_completions(tx.as_mut(), &[completion1.id])
838 .await
839 .unwrap();
840 assert_eq!(deleted_count, 1); let after_reg1 =
844 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
845 .await
846 .unwrap();
847 let after_reg2 =
848 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
849 .await
850 .unwrap();
851 assert_eq!(after_reg1.len(), 1);
852 assert_eq!(after_reg2.len(), 2);
853
854 assert_eq!(after_reg1[0].real_student_number, "12345-1");
856 }
857}