headless_lms_server/programs/seed/builder/
page.rs1use anyhow::Result;
2use headless_lms_utils::document_schema_processor::GutenbergBlock;
3
4use headless_lms_models::pages::{
5 CmsPageExercise, CmsPageExerciseSlide, CmsPageExerciseTask, CmsPageUpdate,
6};
7use sqlx::PgConnection;
8
9use crate::programs::seed::builder::{context::SeedContext, exercise::ExerciseBuilder};
10
11use crate::programs::seed::seed_helpers::create_page;
12
13#[derive(Debug, Clone)]
15pub struct PageBuilder {
16 pub url: String,
17 pub title: String,
18 pub blocks: Vec<GutenbergBlock>,
19 pub exercises: Vec<ExerciseBuilder>,
20}
21
22impl PageBuilder {
23 pub fn new(url: impl Into<String>, title: impl Into<String>) -> Self {
24 Self {
25 url: url.into(),
26 title: title.into(),
27 blocks: vec![],
28 exercises: vec![],
29 }
30 }
31 pub fn block(mut self, b: GutenbergBlock) -> Self {
32 self.blocks.push(b);
33 self
34 }
35 pub fn exercise(mut self, e: ExerciseBuilder) -> Self {
36 self.exercises.push(e);
37 self
38 }
39 pub fn blocks<I: IntoIterator<Item = GutenbergBlock>>(mut self, it: I) -> Self {
40 self.blocks.extend(it);
41 self
42 }
43 pub fn exercises<I: IntoIterator<Item = ExerciseBuilder>>(mut self, it: I) -> Self {
44 self.exercises.extend(it);
45 self
46 }
47
48 pub(crate) async fn seed(
49 self,
50 conn: &mut PgConnection,
51 cx: &SeedContext,
52 course_id: uuid::Uuid,
53 chapter_id: uuid::Uuid,
54 ) -> Result<uuid::Uuid> {
55 let mut cms_exercises: Vec<CmsPageExercise> = vec![];
56 let mut cms_slides: Vec<CmsPageExerciseSlide> = vec![];
57 let mut cms_tasks: Vec<CmsPageExerciseTask> = vec![];
58 let mut blocks = self.blocks;
59
60 for e in self.exercises {
61 let (b, e_, s, t) = e.to_cms(cx)?;
62 blocks.push(b);
63 cms_exercises.push(e_);
64 cms_slides.push(s);
65 cms_tasks.push(t);
66 }
67
68 create_page(
69 conn,
70 course_id,
71 cx.teacher,
72 Some(chapter_id),
73 CmsPageUpdate {
74 url_path: self.url,
75 title: self.title,
76 chapter_id: Some(chapter_id),
77 content: blocks,
78 exercises: cms_exercises,
79 exercise_slides: cms_slides,
80 exercise_tasks: cms_tasks,
81 },
82 )
83 .await
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use crate::programs::seed::{
90 builder::{
91 exercise::{ExerciseBuilder, ExerciseIds},
92 json_source::JsonSource,
93 page::PageBuilder,
94 },
95 seed_helpers::paragraph,
96 };
97
98 use headless_lms_utils::document_schema_processor::GutenbergBlock;
99 use serde_json::{Map, json};
100 use uuid::Uuid;
101
102 fn create_test_gutenberg_block() -> GutenbergBlock {
103 let mut attributes = Map::new();
104 attributes.insert("content".to_string(), json!("Test content"));
105
106 GutenbergBlock {
107 client_id: Uuid::new_v4(),
108 name: "core/paragraph".to_string(),
109 is_valid: true,
110 attributes,
111 inner_blocks: vec![],
112 }
113 }
114
115 fn create_test_exercise_builder() -> ExerciseBuilder {
116 let ids = ExerciseIds {
117 exercise_id: Uuid::new_v4(),
118 slide_id: Uuid::new_v4(),
119 task_id: Uuid::new_v4(),
120 block_id: Uuid::new_v4(),
121 };
122 let assignment_blocks = vec![paragraph("Answer this question.", Uuid::new_v4())];
123 let spec = JsonSource::Inline(json!({"type": "multiple_choice"}));
124
125 ExerciseBuilder::quizzes("Test Exercise", ids, false, None, spec, assignment_blocks)
126 }
127
128 #[test]
129 fn test_page_builder_creation_with_string() {
130 let page = PageBuilder::new("test-url".to_string(), "Test Title".to_string());
131
132 assert_eq!(page.url, "test-url");
133 assert_eq!(page.title, "Test Title");
134 assert!(page.blocks.is_empty());
135 assert!(page.exercises.is_empty());
136 }
137
138 #[test]
139 fn test_page_builder_creation_with_str() {
140 let page = PageBuilder::new("test-url", "Test Title");
141
142 assert_eq!(page.url, "test-url");
143 assert_eq!(page.title, "Test Title");
144 assert!(page.blocks.is_empty());
145 assert!(page.exercises.is_empty());
146 }
147
148 #[test]
149 fn test_page_builder_creation_mixed_types() {
150 let page = PageBuilder::new("test-url".to_string(), "Test Title");
151
152 assert_eq!(page.url, "test-url");
153 assert_eq!(page.title, "Test Title");
154 }
155
156 #[test]
157 fn test_page_builder_add_single_block() {
158 let block = create_test_gutenberg_block();
159 let page = PageBuilder::new("test-url", "Test Title").block(block.clone());
160
161 assert_eq!(page.blocks.len(), 1);
162 assert_eq!(page.blocks[0].name, block.name);
163 assert_eq!(page.blocks[0].client_id, block.client_id);
164 }
165
166 #[test]
167 fn test_page_builder_add_multiple_blocks() {
168 let block1 = create_test_gutenberg_block();
169 let block2 = create_test_gutenberg_block();
170
171 let page = PageBuilder::new("test-url", "Test Title")
172 .block(block1.clone())
173 .block(block2.clone());
174
175 assert_eq!(page.blocks.len(), 2);
176 assert_eq!(page.blocks[0].client_id, block1.client_id);
177 assert_eq!(page.blocks[1].client_id, block2.client_id);
178 }
179
180 #[test]
181 fn test_page_builder_add_single_exercise() {
182 let exercise = create_test_exercise_builder();
183 let page = PageBuilder::new("test-url", "Test Title").exercise(exercise);
184
185 assert_eq!(page.exercises.len(), 1);
186 }
187
188 #[test]
189 fn test_page_builder_add_multiple_exercises() {
190 let exercise1 = create_test_exercise_builder();
191 let exercise2 = create_test_exercise_builder();
192
193 let page = PageBuilder::new("test-url", "Test Title")
194 .exercise(exercise1)
195 .exercise(exercise2);
196
197 assert_eq!(page.exercises.len(), 2);
198 }
199
200 #[test]
201 fn test_page_builder_fluent_api_chaining() {
202 let block1 = create_test_gutenberg_block();
203 let block2 = create_test_gutenberg_block();
204 let exercise1 = create_test_exercise_builder();
205 let exercise2 = create_test_exercise_builder();
206
207 let page = PageBuilder::new("test-url", "Test Title")
208 .block(block1.clone())
209 .exercise(exercise1)
210 .block(block2.clone())
211 .exercise(exercise2);
212
213 assert_eq!(page.url, "test-url");
214 assert_eq!(page.title, "Test Title");
215 assert_eq!(page.blocks.len(), 2);
216 assert_eq!(page.exercises.len(), 2);
217 assert_eq!(page.blocks[0].client_id, block1.client_id);
218 assert_eq!(page.blocks[1].client_id, block2.client_id);
219 }
220
221 #[test]
222 fn test_page_builder_immutability() {
223 let block = create_test_gutenberg_block();
224 let exercise1 = create_test_exercise_builder();
225 let exercise2 = create_test_exercise_builder();
226
227 let original_page = PageBuilder::new("test-url", "Test Title");
228 let page_with_block = original_page.block(block.clone());
229 let page_with_both = page_with_block.exercise(exercise1);
230
231 let fresh_original = PageBuilder::new("test-url", "Test Title");
232 assert!(fresh_original.blocks.is_empty());
233 assert!(fresh_original.exercises.is_empty());
234
235 let test_page_with_block = PageBuilder::new("test-url", "Test Title").block(block.clone());
236 assert_eq!(test_page_with_block.blocks.len(), 1);
237 assert!(test_page_with_block.exercises.is_empty());
238
239 assert_eq!(page_with_both.blocks.len(), 1);
240 assert_eq!(page_with_both.exercises.len(), 1);
241
242 let another_page = PageBuilder::new("another-url", "Another Title").exercise(exercise2);
243 assert_eq!(another_page.exercises.len(), 1);
244 assert!(another_page.blocks.is_empty());
245 }
246
247 #[test]
248 fn test_page_builder_empty_initialization() {
249 let page = PageBuilder::new("", "");
250
251 assert_eq!(page.url, "");
252 assert_eq!(page.title, "");
253 assert!(page.blocks.is_empty());
254 assert!(page.exercises.is_empty());
255 }
256
257 #[test]
258 fn test_page_builder_with_special_characters() {
259 let page = PageBuilder::new("/path/with-special_chars", "Title with émojis 🚀");
260
261 assert_eq!(page.url, "/path/with-special_chars");
262 assert_eq!(page.title, "Title with émojis 🚀");
263 }
264
265 #[test]
266 fn test_page_builder_blocks_preserve_order() {
267 let block1 = create_test_gutenberg_block();
268 let block2 = create_test_gutenberg_block();
269 let block3 = create_test_gutenberg_block();
270
271 let page = PageBuilder::new("test-url", "Test Title")
272 .block(block1.clone())
273 .block(block2.clone())
274 .block(block3.clone());
275
276 assert_eq!(page.blocks.len(), 3);
277 assert_eq!(page.blocks[0].client_id, block1.client_id);
278 assert_eq!(page.blocks[1].client_id, block2.client_id);
279 assert_eq!(page.blocks[2].client_id, block3.client_id);
280 }
281
282 #[test]
283 fn test_page_builder_exercises_preserve_order() {
284 let exercise1 = create_test_exercise_builder();
285 let exercise2 = create_test_exercise_builder();
286 let exercise3 = create_test_exercise_builder();
287
288 let page = PageBuilder::new("test-url", "Test Title")
289 .exercise(exercise1)
290 .exercise(exercise2)
291 .exercise(exercise3);
292
293 assert_eq!(page.exercises.len(), 3);
294 }
295}