From 467a8185575489f970d42fa448581d1a78565ff4 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Mon, 4 Dec 2023 12:34:33 +0200 Subject: [PATCH] Allow course material viewers to view closed chapters --- bin/cargo-clippy | 2 +- bin/cargo-clippy-fix | 2 +- bin/cargo-doc-open | 2 +- services/headless-lms/models/src/pages.rs | 14 -- .../controllers/course_material/courses.rs | 25 ++-- .../server/src/domain/authorization.rs | 32 +++-- services/headless-lms/server/src/prelude.rs | 2 +- .../server/src/programs/seed/mod.rs | 23 +++- .../programs/seed/seed_certificate_fonts.rs | 2 +- .../src/programs/seed/seed_file_storage.rs | 7 +- .../programs/seed/seed_organizations/uh_cs.rs | 21 ++- .../seed/seed_organizations/uh_mathstat.rs | 26 +++- .../server/src/programs/seed/seed_roles.rs | 6 +- .../seed/seed_user_research_consents.rs | 127 ++++++------------ .../server/src/programs/seed/seed_users.rs | 32 ++++- system-tests/src/setup/global.setup.spec.ts | 1 + system-tests/src/tests/draft.spec.ts | 2 +- .../previewing-not-open-chapters.spec.ts | 23 ++++ 18 files changed, 204 insertions(+), 145 deletions(-) diff --git a/bin/cargo-clippy b/bin/cargo-clippy index 7c4629a07c92..10f48fa449d0 100755 --- a/bin/cargo-clippy +++ b/bin/cargo-clippy @@ -8,4 +8,4 @@ RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$FOLDER_PATH") run_command cd "$RELATIVE_PATH" || exit -run_command cargo clippy "$@" +run_command env SQLX_OFFLINE=1 cargo clippy "$@" diff --git a/bin/cargo-clippy-fix b/bin/cargo-clippy-fix index a49c0015e858..2dd1ddfe8986 100755 --- a/bin/cargo-clippy-fix +++ b/bin/cargo-clippy-fix @@ -12,4 +12,4 @@ echo -e "${BLUE}Note: please git add all your changes before running this." echo -e "That way you can easily see what this command changed with git add -p" echo -e "$RESET_EVERYTHING" -run_command cargo clippy --fix --allow-staged "$@" +run_command env SQLX_OFFLINE=1 cargo clippy --fix --allow-staged "$@" diff --git a/bin/cargo-doc-open b/bin/cargo-doc-open index 432954e37d76..290ce338370b 100755 --- a/bin/cargo-doc-open +++ b/bin/cargo-doc-open @@ -8,4 +8,4 @@ RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$FOLDER_PATH") run_command cd "$RELATIVE_PATH" || exit -run_command cargo doc -p headless-lms-server --open --document-private-items "$@" +run_command env SQLX_OFFLINE=1 cargo doc -p headless-lms-server --open --document-private-items "$@" diff --git a/services/headless-lms/models/src/pages.rs b/services/headless-lms/models/src/pages.rs index 32cd3b418ef0..f18467a3cb73 100644 --- a/services/headless-lms/models/src/pages.rs +++ b/services/headless-lms/models/src/pages.rs @@ -667,7 +667,6 @@ pub async fn get_page_with_user_data_by_path( user_id: Option, course_data: &CourseContextData, url_path: &str, - can_view_not_open_chapters: bool, ) -> ModelResult { let page_option = get_page_by_path(conn, course_data.id, url_path).await?; @@ -678,7 +677,6 @@ pub async fn get_page_with_user_data_by_path( page, false, course_data.is_test_mode, - can_view_not_open_chapters, ) .await; } else { @@ -691,7 +689,6 @@ pub async fn get_page_with_user_data_by_path( redirected_page, true, course_data.is_test_mode, - can_view_not_open_chapters, ) .await; } @@ -747,18 +744,7 @@ pub async fn get_course_page_with_user_data_from_selected_page( page: Page, was_redirected: bool, is_test_mode: bool, - can_view_not_open_chapters: bool, ) -> ModelResult { - if let Some(chapter_id) = page.chapter_id { - if !can_view_not_open_chapters && !crate::chapters::is_open(conn, chapter_id).await? { - return Err(ModelError::new( - ModelErrorType::PreconditionFailed, - "Chapter is not open yet.".to_string(), - None, - )); - } - } - if let Some(course_id) = page.course_id { if let Some(user_id) = user_id { let instance = diff --git a/services/headless-lms/server/src/controllers/course_material/courses.rs b/services/headless-lms/server/src/controllers/course_material/courses.rs index a6eb287dae1b..e95678007eb5 100644 --- a/services/headless-lms/server/src/controllers/course_material/courses.rs +++ b/services/headless-lms/server/src/controllers/course_material/courses.rs @@ -34,7 +34,7 @@ use models::{ use crate::{ domain::authorization::{ - authorize_access_to_course_material, can_user_view_not_open_chapter, skip_authorize, + authorize_access_to_course_material, can_user_view_chapter, skip_authorize, }, prelude::*, }; @@ -82,18 +82,25 @@ async fn get_course_page_by_path( }; let user_id = user.map(|u| u.id); let course_data = get_nondeleted_course_id_by_slug(&mut conn, &course_slug).await?; + let page_with_user_data = + models::pages::get_page_with_user_data_by_path(&mut conn, user_id, &course_data, &path) + .await?; - let can_view_not_open_chapters = - can_user_view_not_open_chapter(&mut conn, user_id, course_data.id).await?; - - let page_with_user_data = models::pages::get_page_with_user_data_by_path( + // Chapters may be closed + if !can_user_view_chapter( &mut conn, user_id, - &course_data, - &path, - can_view_not_open_chapters, + page_with_user_data.page.course_id, + page_with_user_data.page.chapter_id, ) - .await?; + .await? + { + return Err(ControllerError::new( + ControllerErrorType::Unauthorized, + "Chapter is not open yet.".to_string(), + None, + )); + } let token = authorize_access_to_course_material( &mut conn, diff --git a/services/headless-lms/server/src/domain/authorization.rs b/services/headless-lms/server/src/domain/authorization.rs index fb18b4c3df0c..e2cb3759af5b 100644 --- a/services/headless-lms/server/src/domain/authorization.rs +++ b/services/headless-lms/server/src/domain/authorization.rs @@ -331,17 +331,29 @@ pub async fn authorize_access_to_tmc_server( )) } -/** Can be used to check whether user is allowed to view some course material */ -pub async fn can_user_view_not_open_chapter( +/** Can be used to check whether user is allowed to view some course material. Chapters can be closed and and limited to certain people only. */ +pub async fn can_user_view_chapter( conn: &mut PgConnection, user_id: Option, - course_id: Uuid, + course_id: Option, + chapter_id: Option, ) -> Result { - if user_id.is_none() { - return Ok(false); + if let Some(course_id) = course_id { + if let Some(chapter_id) = chapter_id { + if !models::chapters::is_open(&mut *conn, chapter_id).await? { + if user_id.is_none() { + return Ok(false); + } + // If the user has been granted access to view the material, then they can see the unopened chapters too + // This is important because sometimes teachers wish to test unopened chapters with real students + let permission = + authorize(conn, Act::ViewMaterial, user_id, Res::Course(course_id)).await; + + return Ok(permission.is_ok()); + } + } } - let permission = authorize(conn, Act::Edit, user_id, Res::Course(course_id)).await; - Ok(permission.is_ok()) + Ok(true) } /** @@ -404,10 +416,10 @@ pub async fn authorize_with_fetched_list_of_roles( // for some resources, we need to get more information from the database match resource { Resource::Chapter(id) => { - // if trying to View a chapter that is not open, check for permission to Teach + // if trying to View a chapter that is not open, check for permission to view the material let action = if matches!(action, Action::View) && !models::chapters::is_open(conn, id).await? { - Action::Teach + Action::ViewMaterial } else { action }; @@ -869,6 +881,8 @@ pub async fn authenticate_test_user( models::users::get_by_email(conn, "teacher@example.com").await? } else if email == "language.teacher@example.com" && password == "language.teacher" { models::users::get_by_email(conn, "language.teacher@example.com").await? + } else if email == "material.viewer@example.com" && password == "material.viewer" { + models::users::get_by_email(conn, "material.viewer@example.com").await? } else if email == "user@example.com" && password == "user" { models::users::get_by_email(conn, "user@example.com").await? } else if email == "assistant@example.com" && password == "assistant" { diff --git a/services/headless-lms/server/src/prelude.rs b/services/headless-lms/server/src/prelude.rs index 2edea92d5aee..78b16f0a9e6a 100644 --- a/services/headless-lms/server/src/prelude.rs +++ b/services/headless-lms/server/src/prelude.rs @@ -26,7 +26,7 @@ pub use headless_lms_utils::{ file_store::FileStore, pagination::Pagination, ApplicationConfiguration, }; pub use serde::{Deserialize, Serialize}; -pub use sqlx::{Connection, FromRow, PgConnection, PgPool, Type}; +pub use sqlx::{Connection, FromRow, PgConnection, PgPool, Pool, Postgres, Type}; #[cfg(feature = "ts_rs")] pub use ts_rs::TS; pub use uuid::Uuid; diff --git a/services/headless-lms/server/src/programs/seed/mod.rs b/services/headless-lms/server/src/programs/seed/mod.rs index 411c2083da86..653c6290eb50 100644 --- a/services/headless-lms/server/src/programs/seed/mod.rs +++ b/services/headless-lms/server/src/programs/seed/mod.rs @@ -36,7 +36,8 @@ pub async fn main() -> anyhow::Result<()> { )), )?; - seed_file_storage::seed_file_storage().await?; + // Not run parallely because waits another future that is not send. + let seed_file_storage_result = seed_file_storage::seed_file_storage().await?; let (uh_cs_organization_result, _uh_mathstat_organization_id) = try_join!( run_parallelly(seed_organizations::uh_cs::seed_organization_uh_cs( @@ -44,6 +45,7 @@ pub async fn main() -> anyhow::Result<()> { seed_users_result.clone(), base_url.clone(), Arc::clone(&jwt_key), + seed_file_storage_result.clone() )), run_parallelly( seed_organizations::uh_mathstat::seed_organization_uh_mathstat( @@ -51,14 +53,25 @@ pub async fn main() -> anyhow::Result<()> { seed_users_result.clone(), base_url.clone(), Arc::clone(&jwt_key), + seed_file_storage_result.clone() ) ) )?; - seed_roles::seed_roles(&db_pool, &seed_users_result, &uh_cs_organization_result).await?; - seed_user_research_consents::seed_user_research_consents(&db_pool).await?; - - seed_certificate_fonts::seed_certificate_fonts(&db_pool).await?; + try_join!( + run_parallelly(seed_roles::seed_roles( + db_pool.clone(), + seed_users_result.clone(), + uh_cs_organization_result + )), + run_parallelly(seed_user_research_consents::seed_user_research_consents( + db_pool.clone(), + seed_users_result.clone() + )), + run_parallelly(seed_certificate_fonts::seed_certificate_fonts( + db_pool.clone() + )) + )?; Ok(()) } diff --git a/services/headless-lms/server/src/programs/seed/seed_certificate_fonts.rs b/services/headless-lms/server/src/programs/seed/seed_certificate_fonts.rs index d85a60b674e8..38ad9b9bfd3f 100644 --- a/services/headless-lms/server/src/programs/seed/seed_certificate_fonts.rs +++ b/services/headless-lms/server/src/programs/seed/seed_certificate_fonts.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use models::certificate_fonts::NewCertificateFont; -pub async fn seed_certificate_fonts(db_pool: &PgPool) -> anyhow::Result<()> { +pub async fn seed_certificate_fonts(db_pool: PgPool) -> anyhow::Result<()> { let mut conn = db_pool.acquire().await?; let file_upload_id = models::file_uploads::insert( diff --git a/services/headless-lms/server/src/programs/seed/seed_file_storage.rs b/services/headless-lms/server/src/programs/seed/seed_file_storage.rs index e7318bced59f..8912e6eebee6 100644 --- a/services/headless-lms/server/src/programs/seed/seed_file_storage.rs +++ b/services/headless-lms/server/src/programs/seed/seed_file_storage.rs @@ -7,7 +7,10 @@ const FONT_LATO_REGULAR: &[u8] = include_bytes!("./data/Lato-Regular.ttf"); const CERTIFICATE_BACKGROUND: &[u8] = include_bytes!("./data/certificate-background.svg"); const AUTHOR_IMAGE: &[u8] = include_bytes!("./data/lilo-and-stitch.jpg"); -pub async fn seed_file_storage() -> anyhow::Result<()> { +#[derive(Clone)] +pub struct SeedFileStorageResult {} + +pub async fn seed_file_storage() -> anyhow::Result { info!("seeding file storage"); let file_storage = LocalFileStore::new( @@ -51,5 +54,5 @@ pub async fn seed_file_storage() -> anyhow::Result<()> { "application/octet-stream", ) .await?; - Ok(()) + Ok(SeedFileStorageResult {}) } diff --git a/services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs b/services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs index e18dd7e7eb86..16f6dbf3c2c7 100644 --- a/services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs +++ b/services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs @@ -26,6 +26,7 @@ use crate::{ create_glossary_course, seed_cs_course_material, seed_peer_review_course_without_submissions, seed_sample_course, CommonCourseData, }, + seed_file_storage::SeedFileStorageResult, seed_helpers::create_exam, }, }; @@ -43,19 +44,27 @@ pub async fn seed_organization_uh_cs( seed_users_result: SeedUsersResult, base_url: String, jwt_key: Arc, + // Passed to this function to ensure the seed file storage has been ran before this. This function will not work is seed file storage has not been ran + seed_file_storage_result: SeedFileStorageResult, ) -> anyhow::Result { info!("inserting organization uh-cs"); let SeedUsersResult { admin_user_id, teacher_user_id, language_teacher_user_id, + material_viewer_user_id, assistant_user_id: _, course_or_exam_creator_user_id, - student_user_id, example_normal_user_ids, teaching_and_learning_services_user_id: _, student_without_research_consent: _, + user_user_id: _, + student_1_user_id: _, + student_2_user_id: _, + student_3_user_id, + langs_user_id: _, } = seed_users_result; + let _ = seed_file_storage_result; let mut conn = db_pool.acquire().await?; @@ -68,6 +77,14 @@ pub async fn seed_organization_uh_cs( ) .await?; + roles::insert( + &mut conn, + material_viewer_user_id, + UserRole::MaterialViewer, + RoleDomain::Organization(uh_cs_organization_id), + ) + .await?; + info!("inserting uh-cs courses"); // Seed courses in groups to improve performance. We cannot create a new task for each course because it is causing stack overflows in headless-lms entrypoint in seemingly unrelated code. @@ -75,7 +92,7 @@ pub async fn seed_organization_uh_cs( db_pool: db_pool.clone(), organization_id: uh_cs_organization_id, admin_user_id, - student_user_id, + student_user_id: student_3_user_id, example_normal_user_ids: Arc::new(example_normal_user_ids.clone()), jwt_key: Arc::clone(&jwt_key), base_url: base_url.clone(), diff --git a/services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs b/services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs index 91c40c18158b..db76eb12123c 100644 --- a/services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs +++ b/services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs @@ -16,7 +16,10 @@ use sqlx::{Pool, Postgres}; use crate::{ domain::models_requests::{self, JwtKey}, - programs::seed::seed_courses::{seed_sample_course, CommonCourseData}, + programs::seed::{ + seed_courses::{seed_sample_course, CommonCourseData}, + seed_file_storage::SeedFileStorageResult, + }, }; use super::super::seed_users::SeedUsersResult; @@ -26,6 +29,8 @@ pub async fn seed_organization_uh_mathstat( seed_users_result: SeedUsersResult, base_url: String, jwt_key: Arc, + // Passed to this function to ensure the seed file storage has been ran before this. This function will not work is seed file storage has not been ran + seed_file_storage_result: SeedFileStorageResult, ) -> anyhow::Result { info!("seeding organization uh-mathstat"); @@ -33,13 +38,19 @@ pub async fn seed_organization_uh_mathstat( admin_user_id, teacher_user_id, language_teacher_user_id: _, + material_viewer_user_id, assistant_user_id: _, course_or_exam_creator_user_id: _, - student_user_id, example_normal_user_ids, teaching_and_learning_services_user_id: _, student_without_research_consent: _, + user_user_id: _, + student_1_user_id: _, + student_2_user_id: _, + student_3_user_id, + langs_user_id: _, } = seed_users_result; + let _ = seed_file_storage_result; let mut conn = db_pool.acquire().await?; @@ -51,6 +62,15 @@ pub async fn seed_organization_uh_mathstat( "Organization for Mathematics and Statistics courses. This organization creates courses that do require prior experience in mathematics, such as integration and induction.", ) .await?; + + roles::insert( + &mut conn, + material_viewer_user_id, + UserRole::MaterialViewer, + RoleDomain::Organization(uh_mathstat_id), + ) + .await?; + let new_course = NewCourse { name: "Introduction to Statistics".to_string(), slug: "introduction-to-statistics".to_string(), @@ -125,7 +145,7 @@ pub async fn seed_organization_uh_mathstat( db_pool: db_pool.clone(), organization_id: uh_mathstat_id, admin_user_id, - student_user_id, + student_user_id: student_3_user_id, example_normal_user_ids: Arc::new(example_normal_user_ids.clone()), jwt_key: Arc::clone(&jwt_key), base_url, diff --git a/services/headless-lms/server/src/programs/seed/seed_roles.rs b/services/headless-lms/server/src/programs/seed/seed_roles.rs index 398315924aac..6bb5f50b07ec 100644 --- a/services/headless-lms/server/src/programs/seed/seed_roles.rs +++ b/services/headless-lms/server/src/programs/seed/seed_roles.rs @@ -4,9 +4,9 @@ use sqlx::{Pool, Postgres}; use super::{seed_organizations::uh_cs::SeedOrganizationUhCsResult, seed_users::SeedUsersResult}; pub async fn seed_roles( - db_pool: &Pool, - seed_users_result: &SeedUsersResult, - uh_cs_organization_result: &SeedOrganizationUhCsResult, + db_pool: Pool, + seed_users_result: SeedUsersResult, + uh_cs_organization_result: SeedOrganizationUhCsResult, ) -> anyhow::Result<()> { // roles info!("inserting roles"); diff --git a/services/headless-lms/server/src/programs/seed/seed_user_research_consents.rs b/services/headless-lms/server/src/programs/seed/seed_user_research_consents.rs index b6907739db79..db9b3dc7e28c 100644 --- a/services/headless-lms/server/src/programs/seed/seed_user_research_consents.rs +++ b/services/headless-lms/server/src/programs/seed/seed_user_research_consents.rs @@ -1,120 +1,77 @@ -use crate::prelude::Uuid; use headless_lms_models::{user_research_consents, PKeyPolicy}; use sqlx::{Pool, Postgres}; -pub async fn seed_user_research_consents(db_pool: &Pool) -> anyhow::Result<()> { +use super::seed_users::SeedUsersResult; + +pub async fn seed_user_research_consents( + db_pool: Pool, + seed_users_result: SeedUsersResult, +) -> anyhow::Result<()> { info!("inserting research consents for users"); let mut conn = db_pool.acquire().await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("02c79854-da22-4cfc-95c4-13038af25d2e")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("90643204-7656-4570-bdd9-aad5d297f9ce")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("0fd8bd2d-cb4e-4035-b7db-89e798fe4df0")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("24342539-f1ba-453e-ae13-14aa418db921")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("c9f9f9f9-f9f9-f9f9-f9f9-f9f9f9f9f9f9")?, - true, - ) - .await?; - let _student_user_research_consent = user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("849b8d32-d5f8-4994-9d21-5aa6259585b1")?, - true, - ) - .await?; + let SeedUsersResult { + admin_user_id, + teacher_user_id, + language_teacher_user_id, + assistant_user_id, + course_or_exam_creator_user_id, + example_normal_user_ids, + teaching_and_learning_services_user_id, + student_without_research_consent: _, + material_viewer_user_id, + user_user_id, + student_1_user_id, + student_2_user_id, + student_3_user_id, + langs_user_id, + } = seed_users_result; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, admin_user_id, true).await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, teacher_user_id, true).await?; user_research_consents::upsert( &mut conn, PKeyPolicy::Generate, - Uuid::parse_str("02364d40-2aac-4763-8a06-2381fd298d79")?, + language_teacher_user_id, true, ) .await?; - + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, assistant_user_id, true) + .await?; user_research_consents::upsert( &mut conn, PKeyPolicy::Generate, - Uuid::parse_str("d7d6246c-45a8-4ff4-bf4d-31dedfaac159")?, + course_or_exam_creator_user_id, true, ) .await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, user_user_id, true).await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, student_1_user_id, true) + .await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, student_2_user_id, true) + .await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, student_3_user_id, true) + .await?; + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, langs_user_id, true).await?; user_research_consents::upsert( &mut conn, PKeyPolicy::Generate, - Uuid::parse_str("6b9b61f2-012a-4dc2-9e35-5da81cd3936b")?, + teaching_and_learning_services_user_id, true, ) .await?; - user_research_consents::upsert( &mut conn, PKeyPolicy::Generate, - Uuid::parse_str("5d081ccb-1dab-4367-9549-267fd3f1dd9c")?, + material_viewer_user_id, true, ) .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("00e249d8-345f-4eff-aedb-7bdc4c44c1d5")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("8d7d6c8c-4c31-48ae-8e20-c68fa95c25cc")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("fbeb9286-3dd8-4896-a6b8-3faffa3fabd6")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("3524d694-7fa8-4e73-aa1a-de9a20fd514b")?, - true, - ) - .await?; - user_research_consents::upsert( - &mut conn, - PKeyPolicy::Generate, - Uuid::parse_str("c60ca874-bab9-452a-895f-02597cf60886")?, - true, - ) - .await?; + for user_id in example_normal_user_ids { + user_research_consents::upsert(&mut conn, PKeyPolicy::Generate, user_id, true).await?; + } + Ok(()) } diff --git a/services/headless-lms/server/src/programs/seed/seed_users.rs b/services/headless-lms/server/src/programs/seed/seed_users.rs index 325ef021f4e1..59bd7f0f345d 100644 --- a/services/headless-lms/server/src/programs/seed/seed_users.rs +++ b/services/headless-lms/server/src/programs/seed/seed_users.rs @@ -9,10 +9,15 @@ pub struct SeedUsersResult { pub language_teacher_user_id: Uuid, pub assistant_user_id: Uuid, pub course_or_exam_creator_user_id: Uuid, - pub student_user_id: Uuid, pub example_normal_user_ids: Vec, pub teaching_and_learning_services_user_id: Uuid, pub student_without_research_consent: Uuid, + pub material_viewer_user_id: Uuid, + pub user_user_id: Uuid, + pub student_1_user_id: Uuid, + pub student_2_user_id: Uuid, + pub student_3_user_id: Uuid, + pub langs_user_id: Uuid, } pub async fn seed_users(db_pool: Pool) -> anyhow::Result { @@ -43,6 +48,14 @@ pub async fn seed_users(db_pool: Pool) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result { }) test("cannot directly navigate to the draft course page", async ({ page }) => { await page.goto("http://project-331.local/org/uh-mathstat/courses/introduction-to-drafts") - await expect(page.locator("text=Forbidden")).toBeVisible() + await page.getByText("Unauthorized").waitFor() await expect(page.locator("text=Introduction to Drafts")).toBeHidden() }) }) diff --git a/system-tests/src/tests/previewing-not-open-chapters.spec.ts b/system-tests/src/tests/previewing-not-open-chapters.spec.ts index a704cfccbfe1..1abcc4fd8787 100644 --- a/system-tests/src/tests/previewing-not-open-chapters.spec.ts +++ b/system-tests/src/tests/previewing-not-open-chapters.spec.ts @@ -46,3 +46,26 @@ test("Teachers can preview chapters that are not open yet", async ({ page, brows await page3.getByText("Chapter is not open yet").waitFor() await context2.close() }) + +test("Material viewers can preview chapters that are not open yet by directly visiting an url", async ({ + browser, +}) => { + // Users with the material viewer role **intentionally** can view chapters that are not open yet by visiting the page url directly. + // This role is used by teachers to let students test unopened chapters. + const context = await browser.newContext({ + storageState: "src/states/material.viewer@example.com.json", + }) + const page = await context.newPage() + await page.goto( + "http://project-331.local/org/uh-mathstat/courses/preview-unopened-chapters/chapter-1/page-1", + ) + await selectCourseInstanceIfPrompted(page) + await page.getByText("Everything is a big topic.").click() + await page + .frameLocator('iframe[title="Exercise 1\\, task 1 content"]') + .getByRole("checkbox", { name: "b" }) + .click() + await page.getByRole("button", { name: "Submit" }).click() + await page.getByText("Good job!").click() + await context.close() +})