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