Skip to main content

headless_lms_models/credit_registrations/
testing.rs

1//! Test-mode setup the system tests drive through the mock Suotar control routes. Nothing here
2//! belongs on a live path: each helper writes a stamp or a hold the pipeline otherwise owns.
3
4use super::claims::RegistrationScope;
5use crate::prelude::*;
6use chrono::TimeDelta;
7
8/// Backdates `state_entered_at` for a registration, so a test can simulate a row that has been
9/// sitting in its state long enough for a backoff or timeout to fire.
10///
11/// Exists only for test setup: [`transition`](super::transition::transition) owns this stamp, and calling this
12/// from a live path would desynchronize it from the state it is supposed to describe.
13pub async fn set_state_entered_at_for_testing(
14    conn: &mut PgConnection,
15    id: Uuid,
16    state_entered_at: DateTime<Utc>,
17) -> ModelResult<()> {
18    sqlx::query!(
19        "
20UPDATE credit_registrations
21SET state_entered_at = $2
22WHERE id = $1
23        ",
24        id,
25        state_entered_at,
26    )
27    .execute(conn)
28    .await?;
29    Ok(())
30}
31
32/// Backdates `first_failed_at` for a registration, so a test can simulate a retry window that
33/// started long enough ago for its retry limit to have elapsed.
34///
35/// Exists only for test setup: [`transition`](super::transition::transition) owns this stamp, and calling this
36/// from a live path would desynchronize it from the failure it is supposed to describe.
37pub async fn set_first_failed_at_for_testing(
38    conn: &mut PgConnection,
39    id: Uuid,
40    first_failed_at: DateTime<Utc>,
41) -> ModelResult<()> {
42    sqlx::query!(
43        "
44UPDATE credit_registrations
45SET first_failed_at = $2
46WHERE id = $1
47        ",
48        id,
49        first_failed_at,
50    )
51    .execute(conn)
52    .await?;
53    Ok(())
54}
55
56/// Excuses every one of a user's rows (or, with `course_id`, just that course's) from every
57/// unscoped claim until `held_until`; a scoped claim ignores every hold regardless. Keyed on
58/// identity rather than a row id so a spec can hold before materialize creates the row it means to
59/// protect, closing the window a row-id hold could only ever narrow: the live background worker
60/// ticks every 10s regardless of any single test, so a hold applied after the row exists still
61/// races the worker's own next tick.
62///
63/// Exists only for test setup: nothing in the product ever needs to hide a user's rows from the
64/// worker that owns them.
65pub async fn set_test_exclusive_hold_for_testing(
66    conn: &mut PgConnection,
67    user_id: Uuid,
68    course_id: Option<Uuid>,
69    held_until: DateTime<Utc>,
70) -> ModelResult<()> {
71    sqlx::query!(
72        "
73INSERT INTO credit_registration_test_exclusive_holds (user_id, course_id, held_until)
74VALUES ($1, $2, $3)
75        ",
76        user_id,
77        course_id,
78        held_until,
79    )
80    .execute(conn)
81    .await?;
82    Ok(())
83}
84
85/// Backdates the row's last enrolment check and last check request past the limit on asking, and the
86/// row itself past the student's first-hour wait, so a spec can press a recheck button without
87/// waiting either out. With `clear_restarts`, also forgets the day's restarts. Exists only for test
88/// setup.
89pub async fn expire_enrolment_recheck_allowance_for_testing(
90    conn: &mut PgConnection,
91    id: Uuid,
92    clear_restarts: bool,
93) -> ModelResult<()> {
94    use crate::library::credit_registration::enrolment_check_schedule::{
95        CHECK_REQUEST_MIN_INTERVAL, STUDENT_CHECK_REQUEST_MIN_ROW_AGE,
96    };
97    sqlx::query!(
98        "
99UPDATE credit_registrations
100SET created_at = LEAST(created_at, now() - $4::interval),
101  enrolment_checked_at = enrolment_checked_at - $2::interval,
102  enrolment_check_requested_at = enrolment_check_requested_at - $2::interval,
103  enrolment_check_restart_count = CASE
104    WHEN $3 THEN 0
105    ELSE enrolment_check_restart_count
106  END
107WHERE id = $1
108        ",
109        id,
110        CHECK_REQUEST_MIN_INTERVAL as TimeDelta,
111        clear_restarts,
112        STUDENT_CHECK_REQUEST_MIN_ROW_AGE as TimeDelta,
113    )
114    .execute(conn)
115    .await?;
116    Ok(())
117}
118
119/// Makes every row in `scope` that waits for an enrolment check due now, as if its next rung had
120/// come, and returns how many it moved. Exists only for test setup: specs cannot wait out a day for
121/// a row's first check.
122pub async fn make_enrolment_checks_due_for_testing(
123    conn: &mut PgConnection,
124    scope: &RegistrationScope,
125) -> ModelResult<u64> {
126    let res = sqlx::query!(
127        r#"
128UPDATE credit_registrations
129SET next_attempt_at = now()
130WHERE state = 'no_usable_enrolment'
131  AND next_attempt_at > now()
132  AND deleted_at IS NULL
133  AND ($1::uuid IS NULL OR course_id = $1)
134  AND ($2::uuid IS NULL OR user_id = $2)
135  AND (
136    cardinality($3::uuid []) = 0
137    OR id = ANY($3::uuid [])
138  )
139        "#,
140        scope.course_id,
141        scope.user_id,
142        &scope.credit_registration_ids,
143    )
144    .execute(conn)
145    .await?;
146    Ok(res.rows_affected())
147}