1pub mod libraries {
5 use flix_model::id::LibraryId;
6
7 use seamantic::model::path::PathBytes;
8
9 use sea_orm::entity::prelude::*;
10
11 #[sea_orm::model]
13 #[derive(Debug, Clone, DeriveEntityModel)]
14 #[sea_orm(table_name = "flix_libraries")]
15 pub struct Model {
16 #[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
18 pub id: LibraryId,
19 pub directory: PathBytes,
21
22 #[sea_orm(has_many, on_update = "Cascade", on_delete = "Cascade")]
24 pub collections: HasMany<super::collections::Entity>,
25 #[sea_orm(has_many, on_update = "Cascade", on_delete = "Cascade")]
27 pub movies: HasMany<super::movies::Entity>,
28 #[sea_orm(has_many, on_update = "Cascade", on_delete = "Cascade")]
30 pub shows: HasMany<super::shows::Entity>,
31 #[sea_orm(has_many, on_update = "Cascade", on_delete = "Cascade")]
33 pub seasons: HasMany<super::seasons::Entity>,
34 #[sea_orm(has_many, on_update = "Cascade", on_delete = "Cascade")]
36 pub episodes: HasMany<super::episodes::Entity>,
37 }
38
39 impl ActiveModelBehavior for ActiveModel {}
40}
41
42pub mod collections {
44 use flix_model::id::{CollectionId, LibraryId};
45
46 use seamantic::model::path::PathBytes;
47
48 use sea_orm::entity::prelude::*;
49
50 use crate::entity;
51
52 #[sea_orm::model]
54 #[derive(Debug, Clone, DeriveEntityModel)]
55 #[sea_orm(table_name = "flix_collections")]
56 pub struct Model {
57 #[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
59 pub id: CollectionId,
60 #[sea_orm(indexed)]
62 pub parent_id: Option<CollectionId>,
63 #[sea_orm(unique)]
65 pub slug: String,
66 pub library_id: LibraryId,
68 pub directory: PathBytes,
70 pub relative_poster_path: Option<String>,
72
73 #[sea_orm(self_ref, relation_enum = "Parent", from = "parent_id", to = "id")]
75 pub parent: HasOne<Entity>,
76 #[sea_orm(belongs_to, from = "library_id", to = "id")]
78 pub library: HasOne<super::libraries::Entity>,
79 #[sea_orm(belongs_to, relation_enum = "Info", from = "id", to = "id")]
81 pub info: HasOne<entity::info::collections::Entity>,
82 #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
84 pub watched: HasMany<entity::watched::collections::Entity>,
85 }
86
87 impl ActiveModelBehavior for ActiveModel {}
88}
89
90pub mod movies {
92 use flix_model::id::{CollectionId, LibraryId, MovieId};
93
94 use seamantic::model::path::PathBytes;
95
96 use sea_orm::entity::prelude::*;
97
98 use crate::entity;
99
100 #[sea_orm::model]
102 #[derive(Debug, Clone, DeriveEntityModel)]
103 #[sea_orm(table_name = "flix_movies")]
104 pub struct Model {
105 #[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
107 pub id: MovieId,
108 #[sea_orm(indexed)]
110 pub parent_id: Option<CollectionId>,
111 #[sea_orm(unique)]
113 pub slug: String,
114 pub library_id: LibraryId,
116 pub directory: PathBytes,
118 pub relative_media_path: String,
120 pub relative_poster_path: Option<String>,
122
123 #[sea_orm(belongs_to, from = "parent_id", to = "id")]
125 pub parent: HasOne<super::collections::Entity>,
126 #[sea_orm(belongs_to, from = "library_id", to = "id")]
128 pub library: HasOne<super::libraries::Entity>,
129 #[sea_orm(belongs_to, relation_enum = "Info", from = "id", to = "id")]
131 pub info: HasOne<entity::info::movies::Entity>,
132 #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
134 pub watched: HasMany<entity::watched::movies::Entity>,
135 }
136
137 impl ActiveModelBehavior for ActiveModel {}
138}
139
140pub mod shows {
142 use flix_model::id::{CollectionId, LibraryId, ShowId};
143
144 use seamantic::model::path::PathBytes;
145
146 use sea_orm::entity::prelude::*;
147
148 use crate::entity;
149
150 #[sea_orm::model]
152 #[derive(Debug, Clone, DeriveEntityModel)]
153 #[sea_orm(table_name = "flix_shows")]
154 pub struct Model {
155 #[sea_orm(column_type = "Integer", primary_key, nullable, auto_increment = false)]
157 pub id: ShowId,
158 #[sea_orm(indexed)]
160 pub parent_id: Option<CollectionId>,
161 #[sea_orm(unique)]
163 pub slug: String,
164 pub library_id: LibraryId,
166 pub directory: PathBytes,
168 pub relative_poster_path: Option<String>,
170
171 #[sea_orm(belongs_to, from = "parent_id", to = "id")]
173 pub parent: HasOne<super::collections::Entity>,
174 #[sea_orm(belongs_to, from = "library_id", to = "id")]
176 pub library: HasOne<super::libraries::Entity>,
177 #[sea_orm(belongs_to, relation_enum = "Info", from = "id", to = "id")]
179 pub info: HasOne<entity::info::shows::Entity>,
180 #[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
182 pub watched: HasMany<entity::watched::shows::Entity>,
183 }
184
185 impl ActiveModelBehavior for ActiveModel {}
186}
187
188pub mod seasons {
190 use flix_model::id::{LibraryId, ShowId};
191 use flix_model::numbers::SeasonNumber;
192
193 use seamantic::model::path::PathBytes;
194
195 use sea_orm::entity::prelude::*;
196
197 use crate::entity;
198
199 #[sea_orm::model]
201 #[derive(Debug, Clone, DeriveEntityModel)]
202 #[sea_orm(table_name = "flix_seasons")]
203 pub struct Model {
204 #[sea_orm(primary_key, auto_increment = false)]
206 pub show_id: ShowId,
207 #[sea_orm(primary_key, auto_increment = false)]
209 pub season_number: SeasonNumber,
210 #[sea_orm(unique)]
212 pub slug: String,
213 pub library_id: LibraryId,
215 pub directory: PathBytes,
217 pub relative_poster_path: Option<String>,
219
220 #[sea_orm(belongs_to, from = "library_id", to = "id")]
222 pub library: HasOne<super::libraries::Entity>,
223 #[sea_orm(
225 belongs_to,
226 relation_enum = "Info",
227 from = "(show_id, season_number)",
228 to = "(show_id, season_number)"
229 )]
230 pub info: HasOne<entity::info::seasons::Entity>,
231 #[sea_orm(
233 has_many,
234 relation_enum = "Watched",
235 from = "(show_id, season_number)",
236 to = "(show_id, season_number)"
237 )]
238 pub watched: HasMany<entity::watched::seasons::Entity>,
239 }
240
241 impl ActiveModelBehavior for ActiveModel {}
242}
243
244pub mod episodes {
246 use flix_model::id::{LibraryId, ShowId};
247 use flix_model::numbers::{EpisodeNumber, SeasonNumber};
248
249 use seamantic::model::path::PathBytes;
250
251 use sea_orm::entity::prelude::*;
252
253 use crate::entity;
254
255 #[sea_orm::model]
257 #[derive(Debug, Clone, DeriveEntityModel)]
258 #[sea_orm(table_name = "flix_episodes")]
259 pub struct Model {
260 #[sea_orm(primary_key, auto_increment = false)]
262 pub show_id: ShowId,
263 #[sea_orm(primary_key, auto_increment = false)]
265 pub season_number: SeasonNumber,
266 #[sea_orm(primary_key, auto_increment = false)]
268 pub episode_number: EpisodeNumber,
269 pub count: u8,
271 #[sea_orm(unique)]
273 pub slug: String,
274 pub library_id: LibraryId,
276 pub directory: PathBytes,
278 pub relative_media_path: String,
280 pub relative_poster_path: Option<String>,
282
283 #[sea_orm(belongs_to, from = "library_id", to = "id")]
285 pub library: HasOne<super::libraries::Entity>,
286 #[sea_orm(
288 belongs_to,
289 relation_enum = "Info",
290 from = "(show_id, season_number, episode_number)",
291 to = "(show_id, season_number, episode_number)"
292 )]
293 pub info: HasOne<entity::info::episodes::Entity>,
294 #[sea_orm(
296 has_many,
297 relation_enum = "Watched",
298 from = "(show_id, season_number, episode_number)",
299 to = "(show_id, season_number, episode_number)"
300 )]
301 pub watched: HasMany<entity::watched::episodes::Entity>,
302 }
303
304 impl ActiveModelBehavior for ActiveModel {}
305}
306
307#[cfg(test)]
309pub mod test {
310 macro_rules! make_content_library {
311 ($db:expr, $id:literal) => {
312 $crate::entity::content::libraries::ActiveModel {
313 id: Set(::flix_model::id::LibraryId::from_raw($id)),
314 directory: Set(::std::path::PathBuf::new().into()),
315 }
316 .insert($db)
317 .await
318 .expect("insert");
319 };
320 }
321 pub(crate) use make_content_library;
322
323 macro_rules! make_content_collection {
324 ($db:expr, $lid:literal, $id:literal, $pid:expr) => {
325 $crate::entity::info::test::make_info_collection!($db, $id);
326 $crate::entity::content::collections::ActiveModel {
327 id: Set(::flix_model::id::CollectionId::from_raw($id)),
328 parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
329 slug: Set(concat!("C ", $id).to_string()),
330 library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
331 directory: Set(::std::path::PathBuf::new().into()),
332 relative_poster_path: Set(::core::option::Option::None),
333 }
334 .insert($db)
335 .await
336 .expect("insert");
337 };
338 }
339 pub(crate) use make_content_collection;
340
341 macro_rules! make_content_movie {
342 ($db:expr, $lid:literal, $id:literal, $pid:expr) => {
343 $crate::entity::info::test::make_info_movie!($db, $id);
344 $crate::entity::content::movies::ActiveModel {
345 id: Set(::flix_model::id::MovieId::from_raw($id)),
346 parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
347 slug: Set(concat!("< ", $id).to_string()),
348 library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
349 directory: Set(::std::path::PathBuf::new().into()),
350 relative_media_path: Set(::std::string::String::new()),
351 relative_poster_path: Set(::core::option::Option::None),
352 }
353 .insert($db)
354 .await
355 .expect("insert");
356 };
357 }
358 pub(crate) use make_content_movie;
359
360 macro_rules! make_content_show {
361 ($db:expr, $lid:literal, $id:literal, $pid:expr) => {
362 $crate::entity::info::test::make_info_show!($db, $id);
363 $crate::entity::content::shows::ActiveModel {
364 id: Set(::flix_model::id::ShowId::from_raw($id)),
365 parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
366 slug: Set(concat!("S ", $id).to_string()),
367 library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
368 directory: Set(::std::path::PathBuf::new().into()),
369 relative_poster_path: Set(::core::option::Option::None),
370 }
371 .insert($db)
372 .await
373 .expect("insert");
374 };
375 }
376 pub(crate) use make_content_show;
377
378 macro_rules! make_content_season {
379 ($db:expr, $lid:literal, $show:literal, $season:literal) => {
380 $crate::entity::info::test::make_info_season!($db, $show, $season);
381 $crate::entity::content::seasons::ActiveModel {
382 show_id: Set(::flix_model::id::ShowId::from_raw($show)),
383 season_number: Set($season),
384 slug: Set(concat!("SS ", $show, $season).to_string()),
385 library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
386 directory: Set(::std::path::PathBuf::new().into()),
387 relative_poster_path: Set(::core::option::Option::None),
388 }
389 .insert($db)
390 .await
391 .expect("insert");
392 };
393 }
394 pub(crate) use make_content_season;
395
396 macro_rules! make_content_episode {
397 ($db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal) => {
398 make_content_episode!(@make, $db, $lid, $show, $season, $episode, 0);
399 };
400 ($db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal, >1) => {
401 make_content_episode!(@make, $db, $lid, $show, $season, $episode, 1);
402 };
403 (@make, $db:expr, $lid:literal, $show:literal, $season:literal, $episode:literal, $count:literal) => {
404 $crate::entity::info::test::make_info_episode!($db, $show, $season, $episode);
405 $crate::entity::content::episodes::ActiveModel {
406 show_id: Set(::flix_model::id::ShowId::from_raw($show)),
407 season_number: Set($season),
408 episode_number: Set($episode),
409 count: Set($count),
410 slug: Set(concat!("SSE ", $show, $season, $episode).to_string()),
411 library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
412 directory: Set(::std::path::PathBuf::new().into()),
413 relative_media_path: Set(::std::string::String::new()),
414 relative_poster_path: Set(::core::option::Option::None),
415 }
416 .insert($db)
417 .await
418 .expect("insert");
419 };
420 }
421 pub(crate) use make_content_episode;
422}
423
424#[cfg(test)]
425mod tests {
426 use std::path::Path;
427
428 use flix_model::id::{CollectionId, LibraryId, MovieId, ShowId};
429
430 use sea_orm::ActiveValue::{NotSet, Set};
431 use sea_orm::entity::prelude::*;
432 use sea_orm::sqlx::error::ErrorKind;
433
434 use crate::entity::content::test::{
435 make_content_collection, make_content_episode, make_content_library, make_content_movie,
436 make_content_season, make_content_show,
437 };
438 use crate::entity::info::test::{
439 make_info_collection, make_info_episode, make_info_movie, make_info_season, make_info_show,
440 };
441 use crate::tests::new_initialized_memory_db;
442
443 use super::super::tests::get_error_kind;
444 use super::super::tests::{noneable, notsettable};
445
446 #[tokio::test]
447 async fn use_test_macros() {
448 let db = new_initialized_memory_db().await;
449
450 make_content_library!(&db, 1);
451 make_content_collection!(&db, 1, 1, None);
452 make_content_movie!(&db, 1, 1, None);
453 make_content_show!(&db, 1, 1, None);
454 make_content_season!(&db, 1, 1, 1);
455 make_content_episode!(&db, 1, 1, 1, 1);
456 }
457
458 #[tokio::test]
459 async fn test_round_trip_libraries() {
460 let db = new_initialized_memory_db().await;
461
462 macro_rules! assert_library {
463 ($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
464 let model = assert_library!(@insert, $db, $id $(; $($skip),+)?)
465 .expect("insert");
466
467 assert_eq!(model.id, LibraryId::from_raw($id));
468 assert_eq!(model.directory, Path::new(concat!("L Directory ", $id)).to_owned().into());
469 };
470 ($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
471 let model = assert_library!(@insert, $db, $id $(; $($skip),+)?)
472 .expect_err("insert");
473
474 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
475 };
476 (@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
477 super::libraries::ActiveModel {
478 id: notsettable!(id, LibraryId::from_raw($id) $(, $($skip),+)?),
479 directory: notsettable!(directory, Path::new(concat!("L Directory ", $id)).to_owned().into() $(, $($skip),+)?),
480 }.insert($db).await
481 };
482 }
483
484 assert_library!(&db, 1, Success);
485 assert_library!(&db, 1, UniqueViolation);
486 assert_library!(&db, 2, Success);
487 assert_library!(&db, 3, Success; id);
488 assert_library!(&db, 4, NotNullViolation; directory);
489 }
490
491 #[tokio::test]
492 async fn test_round_trip_collections() {
493 let db = new_initialized_memory_db().await;
494
495 macro_rules! assert_collection {
496 ($db:expr, $id:literal, $pid:expr, $lid:literal, Success $(; $($skip:ident),+)?) => {
497 let model = assert_collection!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
498 .expect("insert");
499
500 assert_eq!(model.id, CollectionId::from_raw($id));
501 assert_eq!(model.parent_id, $pid.map(CollectionId::from_raw));
502 assert_eq!(model.slug, concat!("C Slug ", $id).to_string());
503 assert_eq!(model.library_id, LibraryId::from_raw($lid));
504 assert_eq!(model.directory, Path::new(concat!("C Directory ", $id)).to_owned().into());
505 assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("C Poster ", $id).to_owned() $(, $($skip),+)?));
506 };
507 ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => {
508 let model = assert_collection!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
509 .expect_err("insert");
510
511 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
512 };
513 (@insert, $db:expr, $id:literal, $pid:expr, $lid:literal $(; $($skip:ident),+)?) => {
514 super::collections::ActiveModel {
515 id: notsettable!(id, CollectionId::from_raw($id) $(, $($skip),+)?),
516 parent_id: notsettable!(parent_id, $pid.map(CollectionId::from_raw) $(, $($skip),+)?),
517 slug: notsettable!(slug, concat!("C Slug ", $id).to_string() $(, $($skip),+)?),
518 library_id: notsettable!(library_id, LibraryId::from_raw($lid) $(, $($skip),+)?),
519 directory: notsettable!(directory, Path::new(concat!("C Directory ", $id)).to_owned().into() $(, $($skip),+)?),
520 relative_poster_path: notsettable!(relative_poster_path, Some(concat!("C Poster ", $id).to_owned()) $(, $($skip),+)?),
521 }.insert($db).await
522 };
523 }
524
525 make_content_library!(&db, 1);
526 assert_collection!(&db, 1, None, 1, ForeignKeyViolation);
527 make_info_collection!(&db, 1);
528 assert_collection!(&db, 1, None, 1, Success);
529 make_info_collection!(&db, 2);
530 assert_collection!(&db, 2, None, 2, ForeignKeyViolation);
531 make_content_library!(&db, 2);
532 assert_collection!(&db, 2, None, 2, Success);
533
534 assert_collection!(&db, 1, None, 1, UniqueViolation);
535 make_info_collection!(&db, 3);
536 make_info_collection!(&db, 4);
537 make_info_collection!(&db, 5);
538 make_info_collection!(&db, 6);
539 make_info_collection!(&db, 7);
540 make_info_collection!(&db, 8);
541 assert_collection!(&db, 3, None, 1, Success; id);
542 assert_collection!(&db, 4, None, 1, Success; parent_id);
543 assert_collection!(&db, 5, None, 1, NotNullViolation; slug);
544 assert_collection!(&db, 6, None, 1, NotNullViolation; library_id);
545 assert_collection!(&db, 7, None, 1, NotNullViolation; directory);
546 assert_collection!(&db, 8, None, 1, Success; relative_poster_path);
547 }
548
549 #[tokio::test]
550 async fn test_round_trip_movies() {
551 let db = new_initialized_memory_db().await;
552
553 macro_rules! assert_movie {
554 ($db:expr, $id:literal, $pid:expr, $lid:literal, Success $(; $($skip:ident),+)?) => {
555 let model = assert_movie!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
556 .expect("insert");
557
558 assert_eq!(model.id, MovieId::from_raw($id));
559 assert_eq!(model.parent_id, $pid.map(CollectionId::from_raw));
560 assert_eq!(model.slug, concat!("M Slug ", $id).to_string());
561 assert_eq!(model.library_id, LibraryId::from_raw($lid));
562 assert_eq!(model.directory, Path::new(concat!("M Directory ", $id)).to_owned().into());
563 assert_eq!(model.relative_media_path, concat!("M Media ", $id));
564 assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("M Poster ", $id).to_owned() $(, $($skip),+)?));
565 };
566 ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => {
567 let model = assert_movie!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
568 .expect_err("insert");
569
570 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
571 };
572 (@insert, $db:expr, $id:literal, $pid:expr, $lid:literal $(; $($skip:ident),+)?) => {
573 super::movies::ActiveModel {
574 id: notsettable!(id, MovieId::from_raw($id) $(, $($skip),+)?),
575 parent_id: notsettable!(parent_id, $pid.map(CollectionId::from_raw) $(, $($skip),+)?),
576 slug: notsettable!(slug, concat!("M Slug ", $id).to_string() $(, $($skip),+)?),
577 library_id: notsettable!(library_id, LibraryId::from_raw($lid) $(, $($skip),+)?),
578 directory: notsettable!(directory, Path::new(concat!("M Directory ", $id)).to_owned().into() $(, $($skip),+)?),
579 relative_media_path: notsettable!(relative_media_path, concat!("M Media ", $id).to_owned() $(, $($skip),+)?),
580 relative_poster_path: notsettable!(relative_poster_path, Some(concat!("M Poster ", $id).to_owned()) $(, $($skip),+)?),
581 }.insert($db).await
582 };
583 }
584
585 make_content_library!(&db, 1);
586 assert_movie!(&db, 1, None, 1, ForeignKeyViolation);
587 make_info_movie!(&db, 1);
588 assert_movie!(&db, 1, Some(1), 1, ForeignKeyViolation);
589 make_content_collection!(&db, 1, 1, None);
590 assert_movie!(&db, 1, Some(1), 1, Success);
591 assert_movie!(&db, 2, None, 2, ForeignKeyViolation);
592 make_info_movie!(&db, 2);
593 assert_movie!(&db, 2, None, 2, ForeignKeyViolation);
594 make_content_library!(&db, 2);
595 assert_movie!(&db, 2, None, 2, Success);
596
597 assert_movie!(&db, 1, None, 1, UniqueViolation);
598 make_info_movie!(&db, 3);
599 make_info_movie!(&db, 4);
600 make_info_movie!(&db, 5);
601 make_info_movie!(&db, 6);
602 make_info_movie!(&db, 7);
603 make_info_movie!(&db, 8);
604 make_info_movie!(&db, 9);
605 assert_movie!(&db, 3, None, 1, Success; id);
606 assert_movie!(&db, 4, None, 1, Success; parent_id);
607 assert_movie!(&db, 5, None, 1, NotNullViolation; slug);
608 assert_movie!(&db, 6, None, 1, NotNullViolation; library_id);
609 assert_movie!(&db, 7, None, 1, NotNullViolation; directory);
610 assert_movie!(&db, 8, None, 1, NotNullViolation; relative_media_path);
611 assert_movie!(&db, 9, None, 1, Success; relative_poster_path);
612 }
613
614 #[tokio::test]
615 async fn test_round_trip_shows() {
616 let db = new_initialized_memory_db().await;
617
618 macro_rules! assert_show {
619 ($db:expr, $id:literal, $pid:expr, $lid:literal, Success $(; $($skip:ident),+)?) => {
620 let model = assert_show!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
621 .expect("insert");
622
623 assert_eq!(model.id, ShowId::from_raw($id));
624 assert_eq!(model.parent_id, $pid.map(CollectionId::from_raw));
625 assert_eq!(model.slug, concat!("S Slug ", $id).to_string());
626 assert_eq!(model.library_id, LibraryId::from_raw($lid));
627 assert_eq!(model.directory, Path::new(concat!("S Directory ", $id)).to_owned().into());
628 assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("S Poster ", $id).to_owned() $(, $($skip),+)?));
629 };
630 ($db:expr, $id:literal, $pid:expr, $lid:literal, $error:ident $(; $($skip:ident),+)?) => {
631 let model = assert_show!(@insert, $db, $id, $pid, $lid $(; $($skip),+)?)
632 .expect_err("insert");
633
634 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
635 };
636 (@insert, $db:expr, $id:literal, $pid:expr, $lid:literal $(; $($skip:ident),+)?) => {
637 super::shows::ActiveModel {
638 id: notsettable!(id, ShowId::from_raw($id) $(, $($skip),+)?),
639 parent_id: notsettable!(parent_id, $pid.map(CollectionId::from_raw) $(, $($skip),+)?),
640 slug: notsettable!(slug, concat!("S Slug ", $id).to_string() $(, $($skip),+)?),
641 library_id: notsettable!(library_id, LibraryId::from_raw($lid) $(, $($skip),+)?),
642 directory: notsettable!(directory, Path::new(concat!("S Directory ", $id)).to_owned().into() $(, $($skip),+)?),
643 relative_poster_path: notsettable!(relative_poster_path, Some(concat!("S Poster ", $id).to_owned()) $(, $($skip),+)?),
644 }.insert($db).await
645 };
646 }
647
648 make_content_library!(&db, 1);
649 assert_show!(&db, 1, None, 1, ForeignKeyViolation);
650 make_info_show!(&db, 1);
651 assert_show!(&db, 1, Some(1), 1, ForeignKeyViolation);
652 make_content_collection!(&db, 1, 1, None);
653 assert_show!(&db, 1, Some(1), 1, Success);
654 assert_show!(&db, 2, None, 2, ForeignKeyViolation);
655 make_info_show!(&db, 2);
656 assert_show!(&db, 2, None, 2, ForeignKeyViolation);
657 make_content_library!(&db, 2);
658 assert_show!(&db, 2, None, 2, Success);
659
660 assert_show!(&db, 1, None, 1, UniqueViolation);
661 make_info_show!(&db, 3);
662 make_info_show!(&db, 4);
663 make_info_show!(&db, 5);
664 make_info_show!(&db, 6);
665 make_info_show!(&db, 7);
666 make_info_show!(&db, 8);
667 assert_show!(&db, 3, None, 1, Success; id);
668 assert_show!(&db, 4, None, 1, Success; parent_id);
669 assert_show!(&db, 5, None, 1, NotNullViolation; slug);
670 assert_show!(&db, 6, None, 1, NotNullViolation; library_id);
671 assert_show!(&db, 7, None, 1, NotNullViolation; directory);
672 assert_show!(&db, 8, None, 1, Success; relative_poster_path);
673 }
674
675 #[tokio::test]
676 async fn test_round_trip_seasons() {
677 let db = new_initialized_memory_db().await;
678
679 macro_rules! assert_season {
680 ($db:expr, $id:literal, $season:literal, $lid:literal, Success $(; $($skip:ident),+)?) => {
681 let model = assert_season!(@insert, $db, $id, $season, $lid $(; $($skip),+)?)
682 .expect("insert");
683
684 assert_eq!(model.show_id, ShowId::from_raw($id));
685 assert_eq!(model.season_number, $season);
686 assert_eq!(model.slug, concat!("SS Slug ", $id, ",", $season).to_string());
687 assert_eq!(model.library_id, LibraryId::from_raw($lid));
688 assert_eq!(model.directory, Path::new(concat!("SS Directory ", $id, ",", $season)).to_owned().into());
689 assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("SS Poster ", $id, ",", $season).to_owned() $(, $($skip),+)?));
690 };
691 ($db:expr, $id:literal, $season:literal, $lid:literal, $error:ident $(; $($skip:ident),+)?) => {
692 let model = assert_season!(@insert, $db, $id, $season, $lid $(; $($skip),+)?)
693 .expect_err("insert");
694
695 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
696 };
697 (@insert, $db:expr, $id:literal, $season:literal, $lid:literal $(; $($skip:ident),+)?) => {
698 super::seasons::ActiveModel {
699 show_id: notsettable!(show_id, ShowId::from_raw($id) $(, $($skip),+)?),
700 season_number: notsettable!(season_number, $season $(, $($skip),+)?),
701 slug: notsettable!(slug, concat!("SS Slug ", $id, ",", $season).to_string() $(, $($skip),+)?),
702 library_id: notsettable!(library_id, LibraryId::from_raw($lid) $(, $($skip),+)?),
703 directory: notsettable!(directory, Path::new(concat!("SS Directory ", $id, ",", $season)).to_owned().into() $(, $($skip),+)?),
704 relative_poster_path: notsettable!(relative_poster_path, Some(concat!("SS Poster ", $id, ",", $season).to_owned()) $(, $($skip),+)?),
705 }.insert($db).await
706 };
707 }
708
709 make_content_library!(&db, 1);
710 make_info_show!(&db, 1);
711 assert_season!(&db, 1, 1, 1, ForeignKeyViolation);
712 make_info_season!(&db, 1, 1);
713 assert_season!(&db, 1, 1, 1, Success);
714
715 assert_season!(&db, 1, 1, 1, UniqueViolation);
716 make_info_season!(&db, 1, 3);
717 make_info_season!(&db, 1, 4);
718 make_info_season!(&db, 1, 5);
719 make_info_season!(&db, 1, 6);
720 make_info_season!(&db, 1, 7);
721 make_info_season!(&db, 1, 8);
722 assert_season!(&db, 1, 3, 1, NotNullViolation; show_id);
723 assert_season!(&db, 1, 4, 1, NotNullViolation; season_number);
724 assert_season!(&db, 1, 5, 1, NotNullViolation; slug);
725 assert_season!(&db, 1, 6, 1, NotNullViolation; library_id);
726 assert_season!(&db, 1, 7, 1, NotNullViolation; directory);
727 assert_season!(&db, 1, 8, 1, Success; relative_poster_path);
728 }
729
730 #[tokio::test]
731 async fn test_round_trip_episodes() {
732 let db = new_initialized_memory_db().await;
733
734 macro_rules! assert_episode {
735 ($db:expr, $id:literal, $season:literal, $episode:literal, $lid:literal, Success $(; $($skip:ident),+)?) => {
736 let model = assert_episode!(@insert, $db, $id, $season, $episode, $lid $(; $($skip),+)?)
737 .expect("insert");
738
739 assert_eq!(model.show_id, ShowId::from_raw($id));
740 assert_eq!(model.season_number, $season);
741 assert_eq!(model.episode_number, $episode);
742 assert_eq!(model.slug, concat!("SS Slug ", $id, ",", $season, $episode).to_string());
743 assert_eq!(model.library_id, LibraryId::from_raw($lid));
744 assert_eq!(model.directory, Path::new(concat!("SS Directory ", $id, ",", $season, $episode)).to_owned().into());
745 assert_eq!(model.relative_media_path, concat!("SS Media ", $id, ",", $season, $episode));
746 assert_eq!(model.relative_poster_path, noneable!(relative_poster_path, concat!("SS Poster ", $id, ",", $season, $episode).to_owned() $(, $($skip),+)?));
747 };
748 ($db:expr, $id:literal, $season:literal, $episode:literal, $lid:literal, $error:ident $(; $($skip:ident),+)?) => {
749 let model = assert_episode!(@insert, $db, $id, $season, $episode, $lid $(; $($skip),+)?)
750 .expect_err("insert");
751
752 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
753 };
754 (@insert, $db:expr, $id:literal, $season:literal, $episode:literal, $lid:literal $(; $($skip:ident),+)?) => {
755 super::episodes::ActiveModel {
756 show_id: notsettable!(show_id, ShowId::from_raw($id) $(, $($skip),+)?),
757 season_number: notsettable!(season_number, $season $(, $($skip),+)?),
758 episode_number: notsettable!(episode_number, $episode $(, $($skip),+)?),
759 count: notsettable!(count, 0 $(, $($skip),+)?),
760 slug: notsettable!(slug, concat!("SS Slug ", $id, ",", $season, $episode).to_string() $(, $($skip),+)?),
761 library_id: notsettable!(library_id, LibraryId::from_raw($lid) $(, $($skip),+)?),
762 directory: notsettable!(directory, Path::new(concat!("SS Directory ", $id, ",", $season, $episode)).to_owned().into() $(, $($skip),+)?),
763 relative_media_path: notsettable!(relative_media_path, concat!("SS Media ", $id, ",", $season, $episode).to_owned() $(, $($skip),+)?),
764 relative_poster_path: notsettable!(relative_poster_path, Some(concat!("SS Poster ", $id, ",", $season, $episode).to_owned()) $(, $($skip),+)?),
765 }.insert($db).await
766 };
767 }
768
769 make_content_library!(&db, 1);
770 make_info_show!(&db, 1);
771 make_info_season!(&db, 1, 1);
772 assert_episode!(&db, 1, 1, 1, 1, ForeignKeyViolation);
773 make_info_episode!(&db, 1, 1, 1);
774 assert_episode!(&db, 1, 1, 1, 1, Success);
775
776 assert_episode!(&db, 1, 1, 1, 1, UniqueViolation);
777 make_info_episode!(&db, 1, 1, 3);
778 make_info_episode!(&db, 1, 1, 4);
779 make_info_episode!(&db, 1, 1, 5);
780 make_info_episode!(&db, 1, 1, 6);
781 make_info_episode!(&db, 1, 1, 7);
782 make_info_episode!(&db, 1, 1, 8);
783 make_info_episode!(&db, 1, 1, 9);
784 make_info_episode!(&db, 1, 1, 10);
785 assert_episode!(&db, 1, 1, 3, 1, NotNullViolation; show_id);
786 assert_episode!(&db, 1, 1, 4, 1, NotNullViolation; season_number);
787 assert_episode!(&db, 1, 1, 5, 1, NotNullViolation; episode_number);
788 assert_episode!(&db, 1, 1, 6, 1, NotNullViolation; slug);
789 assert_episode!(&db, 1, 1, 7, 1, NotNullViolation; library_id);
790 assert_episode!(&db, 1, 1, 8, 1, NotNullViolation; directory);
791 assert_episode!(&db, 1, 1, 9, 1, NotNullViolation; relative_media_path);
792 assert_episode!(&db, 1, 1, 10, 1, Success; relative_poster_path);
793 }
794}