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(
126 "Test Exercise",
127 ids,
128 false,
129 None,
130 spec,
131 assignment_blocks,
132 true,
133 )
134 }
135
136 #[test]
137 fn test_page_builder_creation_with_string() {
138 let page = PageBuilder::new("test-url".to_string(), "Test Title".to_string());
139
140 assert_eq!(page.url, "test-url");
141 assert_eq!(page.title, "Test Title");
142 assert!(page.blocks.is_empty());
143 assert!(page.exercises.is_empty());
144 }
145
146 #[test]
147 fn test_page_builder_creation_with_str() {
148 let page = PageBuilder::new("test-url", "Test Title");
149
150 assert_eq!(page.url, "test-url");
151 assert_eq!(page.title, "Test Title");
152 assert!(page.blocks.is_empty());
153 assert!(page.exercises.is_empty());
154 }
155
156 #[test]
157 fn test_page_builder_creation_mixed_types() {
158 let page = PageBuilder::new("test-url".to_string(), "Test Title");
159
160 assert_eq!(page.url, "test-url");
161 assert_eq!(page.title, "Test Title");
162 }
163
164 #[test]
165 fn test_page_builder_add_single_block() {
166 let block = create_test_gutenberg_block();
167 let page = PageBuilder::new("test-url", "Test Title").block(block.clone());
168
169 assert_eq!(page.blocks.len(), 1);
170 assert_eq!(page.blocks[0].name, block.name);
171 assert_eq!(page.blocks[0].client_id, block.client_id);
172 }
173
174 #[test]
175 fn test_page_builder_add_multiple_blocks() {
176 let block1 = create_test_gutenberg_block();
177 let block2 = create_test_gutenberg_block();
178
179 let page = PageBuilder::new("test-url", "Test Title")
180 .block(block1.clone())
181 .block(block2.clone());
182
183 assert_eq!(page.blocks.len(), 2);
184 assert_eq!(page.blocks[0].client_id, block1.client_id);
185 assert_eq!(page.blocks[1].client_id, block2.client_id);
186 }
187
188 #[test]
189 fn test_page_builder_add_single_exercise() {
190 let exercise = create_test_exercise_builder();
191 let page = PageBuilder::new("test-url", "Test Title").exercise(exercise);
192
193 assert_eq!(page.exercises.len(), 1);
194 }
195
196 #[test]
197 fn test_page_builder_add_multiple_exercises() {
198 let exercise1 = create_test_exercise_builder();
199 let exercise2 = create_test_exercise_builder();
200
201 let page = PageBuilder::new("test-url", "Test Title")
202 .exercise(exercise1)
203 .exercise(exercise2);
204
205 assert_eq!(page.exercises.len(), 2);
206 }
207
208 #[test]
209 fn test_page_builder_fluent_api_chaining() {
210 let block1 = create_test_gutenberg_block();
211 let block2 = create_test_gutenberg_block();
212 let exercise1 = create_test_exercise_builder();
213 let exercise2 = create_test_exercise_builder();
214
215 let page = PageBuilder::new("test-url", "Test Title")
216 .block(block1.clone())
217 .exercise(exercise1)
218 .block(block2.clone())
219 .exercise(exercise2);
220
221 assert_eq!(page.url, "test-url");
222 assert_eq!(page.title, "Test Title");
223 assert_eq!(page.blocks.len(), 2);
224 assert_eq!(page.exercises.len(), 2);
225 assert_eq!(page.blocks[0].client_id, block1.client_id);
226 assert_eq!(page.blocks[1].client_id, block2.client_id);
227 }
228
229 #[test]
230 fn test_page_builder_immutability() {
231 let block = create_test_gutenberg_block();
232 let exercise1 = create_test_exercise_builder();
233 let exercise2 = create_test_exercise_builder();
234
235 let original_page = PageBuilder::new("test-url", "Test Title");
236 let page_with_block = original_page.block(block.clone());
237 let page_with_both = page_with_block.exercise(exercise1);
238
239 let fresh_original = PageBuilder::new("test-url", "Test Title");
240 assert!(fresh_original.blocks.is_empty());
241 assert!(fresh_original.exercises.is_empty());
242
243 let test_page_with_block = PageBuilder::new("test-url", "Test Title").block(block.clone());
244 assert_eq!(test_page_with_block.blocks.len(), 1);
245 assert!(test_page_with_block.exercises.is_empty());
246
247 assert_eq!(page_with_both.blocks.len(), 1);
248 assert_eq!(page_with_both.exercises.len(), 1);
249
250 let another_page = PageBuilder::new("another-url", "Another Title").exercise(exercise2);
251 assert_eq!(another_page.exercises.len(), 1);
252 assert!(another_page.blocks.is_empty());
253 }
254
255 #[test]
256 fn test_page_builder_empty_initialization() {
257 let page = PageBuilder::new("", "");
258
259 assert_eq!(page.url, "");
260 assert_eq!(page.title, "");
261 assert!(page.blocks.is_empty());
262 assert!(page.exercises.is_empty());
263 }
264
265 #[test]
266 fn test_page_builder_with_special_characters() {
267 let page = PageBuilder::new("/path/with-special_chars", "Title with émojis 🚀");
268
269 assert_eq!(page.url, "/path/with-special_chars");
270 assert_eq!(page.title, "Title with émojis 🚀");
271 }
272
273 #[test]
274 fn test_page_builder_blocks_preserve_order() {
275 let block1 = create_test_gutenberg_block();
276 let block2 = create_test_gutenberg_block();
277 let block3 = create_test_gutenberg_block();
278
279 let page = PageBuilder::new("test-url", "Test Title")
280 .block(block1.clone())
281 .block(block2.clone())
282 .block(block3.clone());
283
284 assert_eq!(page.blocks.len(), 3);
285 assert_eq!(page.blocks[0].client_id, block1.client_id);
286 assert_eq!(page.blocks[1].client_id, block2.client_id);
287 assert_eq!(page.blocks[2].client_id, block3.client_id);
288 }
289
290 #[test]
291 fn test_page_builder_exercises_preserve_order() {
292 let exercise1 = create_test_exercise_builder();
293 let exercise2 = create_test_exercise_builder();
294 let exercise3 = create_test_exercise_builder();
295
296 let page = PageBuilder::new("test-url", "Test Title")
297 .exercise(exercise1)
298 .exercise(exercise2)
299 .exercise(exercise3);
300
301 assert_eq!(page.exercises.len(), 3);
302 }
303}