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