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