flix_db/entity/
info.rs

1//! This module contains entities for storing media information such as
2//! titles and overviews
3
4/// Collection entity
5pub mod collections {
6	use flix_model::id::CollectionId;
7
8	use sea_orm::entity::prelude::*;
9
10	use crate::entity;
11
12	/// The database representation of a flix collection
13	#[sea_orm::model]
14	#[derive(Debug, Clone, DeriveEntityModel)]
15	#[sea_orm(table_name = "flix_info_collections")]
16	pub struct Model {
17		/// The collection's ID
18		#[sea_orm(primary_key, auto_increment = false)]
19		pub id: CollectionId,
20		/// The collection's title
21		pub title: String,
22		/// The collection's overview
23		pub overview: String,
24
25		/// The sortable title
26		#[sea_orm(indexed)]
27		pub sort_title: String,
28		/// The filesystem-safe slug
29		#[sea_orm(indexed, unique)]
30		pub fs_slug: String,
31		/// The url-safe slug
32		#[sea_orm(indexed, unique)]
33		pub web_slug: String,
34
35		/// Potential content for this collection
36		#[sea_orm(has_one)]
37		pub content: HasOne<entity::content::collections::Entity>,
38	}
39
40	impl ActiveModelBehavior for ActiveModel {}
41}
42
43/// Movie entity
44pub mod movies {
45	use flix_model::id::MovieId;
46
47	use chrono::NaiveDate;
48	use sea_orm::entity::prelude::*;
49
50	use crate::entity;
51
52	/// The database representation of a flix movie
53	#[sea_orm::model]
54	#[derive(Debug, Clone, DeriveEntityModel)]
55	#[sea_orm(table_name = "flix_info_movies")]
56	pub struct Model {
57		/// The movie's ID
58		#[sea_orm(primary_key, auto_increment = false)]
59		pub id: MovieId,
60		/// The movie's title
61		pub title: String,
62		/// The movie's tagline
63		pub tagline: String,
64		/// The movie's overview
65		pub overview: String,
66		/// The movie's release date
67		#[sea_orm(indexed)]
68		pub date: NaiveDate,
69
70		/// The sortable title
71		#[sea_orm(indexed)]
72		pub sort_title: String,
73		/// The filesystem-safe slug
74		#[sea_orm(indexed, unique)]
75		pub fs_slug: String,
76		/// The url-safe slug
77		#[sea_orm(indexed, unique)]
78		pub web_slug: String,
79
80		/// Potential content for this movie
81		#[sea_orm(has_one)]
82		pub content: HasOne<entity::content::movies::Entity>,
83	}
84
85	impl ActiveModelBehavior for ActiveModel {}
86}
87
88/// Show entity
89pub mod shows {
90	use flix_model::id::ShowId;
91
92	use chrono::NaiveDate;
93	use sea_orm::entity::prelude::*;
94
95	use crate::entity;
96
97	/// The database representation of a flix show
98	#[sea_orm::model]
99	#[derive(Debug, Clone, DeriveEntityModel)]
100	#[sea_orm(table_name = "flix_info_shows")]
101	pub struct Model {
102		/// The show's ID
103		#[sea_orm(primary_key, auto_increment = false)]
104		pub id: ShowId,
105		/// The show's title
106		pub title: String,
107		/// The show's tagline
108		pub tagline: String,
109		/// The show's overview
110		pub overview: String,
111		/// The show's air date
112		#[sea_orm(indexed)]
113		pub date: NaiveDate,
114
115		/// The sortable title
116		#[sea_orm(indexed)]
117		pub sort_title: String,
118		/// The filesystem-safe slug
119		#[sea_orm(indexed, unique)]
120		pub fs_slug: String,
121		/// The url-safe slug
122		#[sea_orm(indexed, unique)]
123		pub web_slug: String,
124
125		/// Seasons that are part of this show
126		#[sea_orm(has_many)]
127		pub seasons: HasMany<super::seasons::Entity>,
128		/// Episodes that are part of this show
129		#[sea_orm(has_many)]
130		pub episodes: HasMany<super::episodes::Entity>,
131
132		/// Potential content for this show
133		#[sea_orm(has_one)]
134		pub content: HasOne<entity::content::shows::Entity>,
135	}
136
137	impl ActiveModelBehavior for ActiveModel {}
138}
139
140/// Season entity
141pub mod seasons {
142	use flix_model::id::ShowId;
143	use flix_model::numbers::SeasonNumber;
144
145	use chrono::NaiveDate;
146	use sea_orm::entity::prelude::*;
147
148	use crate::entity;
149
150	/// The database representation of a flix season
151	#[sea_orm::model]
152	#[derive(Debug, Clone, DeriveEntityModel)]
153	#[sea_orm(table_name = "flix_info_seasons")]
154	pub struct Model {
155		/// The season's show's ID
156		#[sea_orm(primary_key, auto_increment = false)]
157		pub show_id: ShowId,
158		/// The season's number
159		#[sea_orm(primary_key, auto_increment = false)]
160		pub season_number: SeasonNumber,
161		/// The season's title
162		pub title: String,
163		/// The season's overview
164		pub overview: String,
165		/// The season's air date
166		#[sea_orm(indexed)]
167		pub date: NaiveDate,
168
169		/// The show this season belongs to
170		#[sea_orm(
171			belongs_to,
172			from = "show_id",
173			to = "id",
174			on_update = "Cascade",
175			on_delete = "Cascade"
176		)]
177		pub show: HasOne<super::shows::Entity>,
178		/// Episodes that are part of this season
179		#[sea_orm(has_many)]
180		pub episodes: HasMany<super::episodes::Entity>,
181
182		/// Potential content for this season
183		#[sea_orm(has_one)]
184		pub content: HasOne<entity::content::seasons::Entity>,
185	}
186
187	impl ActiveModelBehavior for ActiveModel {}
188}
189
190/// Episode entity
191pub mod episodes {
192	use flix_model::id::ShowId;
193	use flix_model::numbers::{EpisodeNumber, SeasonNumber};
194
195	use chrono::NaiveDate;
196	use sea_orm::entity::prelude::*;
197
198	use crate::entity;
199
200	/// The database representation of a flix episode
201	#[sea_orm::model]
202	#[derive(Debug, Clone, DeriveEntityModel)]
203	#[sea_orm(table_name = "flix_info_episodes")]
204	pub struct Model {
205		/// The episode's show's ID
206		#[sea_orm(primary_key, auto_increment = false)]
207		pub show_id: ShowId,
208		/// The episode's season's number
209		#[sea_orm(primary_key, auto_increment = false)]
210		pub season_number: SeasonNumber,
211		/// The episode's number
212		#[sea_orm(primary_key, auto_increment = false)]
213		pub episode_number: EpisodeNumber,
214		/// The episode's title
215		pub title: String,
216		/// The episode's overview
217		pub overview: String,
218		/// The episode's air date
219		#[sea_orm(indexed)]
220		pub date: NaiveDate,
221
222		/// The show this episode belongs to
223		#[sea_orm(
224			belongs_to,
225			from = "show_id",
226			to = "id",
227			on_update = "Cascade",
228			on_delete = "Cascade"
229		)]
230		pub show: HasOne<super::shows::Entity>,
231		/// The season this episode belongs to
232		#[sea_orm(
233			belongs_to,
234			from = "(show_id, season_number)",
235			to = "(show_id, season_number)",
236			on_update = "Cascade",
237			on_delete = "Cascade"
238		)]
239		pub season: HasOne<super::seasons::Entity>,
240
241		/// Potential content for this episode
242		#[sea_orm(has_one)]
243		pub content: HasOne<entity::content::episodes::Entity>,
244	}
245
246	impl ActiveModelBehavior for ActiveModel {}
247}
248
249/// Macros for creating info entities
250#[cfg(test)]
251pub mod test {
252	macro_rules! make_info_collection {
253		($db:expr, $id:expr) => {
254			$crate::entity::info::collections::ActiveModel {
255				id: Set(::flix_model::id::CollectionId::from_raw($id)),
256				title: Set(::std::string::String::new()),
257				overview: Set(::std::string::String::new()),
258				sort_title: Set(::std::string::String::new()),
259				fs_slug: Set(format!("C FS {}", $id)),
260				web_slug: Set(format!("C Web {}", $id)),
261			}
262			.insert($db)
263			.await
264			.expect("insert");
265		};
266	}
267	pub(crate) use make_info_collection;
268
269	macro_rules! make_info_movie {
270		($db:expr, $id:expr) => {
271			$crate::entity::info::movies::ActiveModel {
272				id: Set(::flix_model::id::MovieId::from_raw($id)),
273				title: Set(::std::string::String::new()),
274				tagline: Set(::std::string::String::new()),
275				overview: Set(::std::string::String::new()),
276				date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
277				sort_title: Set(::std::string::String::new()),
278				fs_slug: Set(format!("M FS {}", $id)),
279				web_slug: Set(format!("M Web {}", $id)),
280			}
281			.insert($db)
282			.await
283			.expect("insert");
284		};
285	}
286	pub(crate) use make_info_movie;
287
288	macro_rules! make_info_show {
289		($db:expr, $id:expr) => {
290			$crate::entity::info::shows::ActiveModel {
291				id: Set(::flix_model::id::ShowId::from_raw($id)),
292				title: Set(::std::string::String::new()),
293				tagline: Set(::std::string::String::new()),
294				overview: Set(::std::string::String::new()),
295				date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
296				sort_title: Set(::std::string::String::new()),
297				fs_slug: Set(format!("S FS {}", $id)),
298				web_slug: Set(format!("S Web {}", $id)),
299			}
300			.insert($db)
301			.await
302			.expect("insert");
303		};
304	}
305	pub(crate) use make_info_show;
306
307	macro_rules! make_info_season {
308		($db:expr, $show:expr, $season:expr) => {
309			$crate::entity::info::seasons::ActiveModel {
310				show_id: Set(::flix_model::id::ShowId::from_raw($show)),
311				season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
312				title: Set(::std::string::String::new()),
313				overview: Set(::std::string::String::new()),
314				date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
315			}
316			.insert($db)
317			.await
318			.expect("insert");
319		};
320	}
321	pub(crate) use make_info_season;
322
323	macro_rules! make_info_episode {
324		($db:expr, $show:expr, $season:expr, $episode:expr) => {
325			$crate::entity::info::episodes::ActiveModel {
326				show_id: Set(::flix_model::id::ShowId::from_raw($show)),
327				season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
328				episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
329				title: Set(::std::string::String::new()),
330				overview: Set(::std::string::String::new()),
331				date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
332			}
333			.insert($db)
334			.await
335			.expect("insert");
336		};
337	}
338	pub(crate) use make_info_episode;
339}
340
341#[cfg(test)]
342mod tests {
343	use flix_model::id::{CollectionId, MovieId, ShowId};
344
345	use chrono::NaiveDate;
346	use sea_orm::ActiveValue::{NotSet, Set};
347	use sea_orm::entity::prelude::*;
348	use sea_orm::sqlx::error::ErrorKind;
349
350	use crate::tests::new_initialized_memory_db;
351
352	use super::super::tests::get_error_kind;
353	use super::super::tests::notsettable;
354	use super::test::{
355		make_info_collection, make_info_episode, make_info_movie, make_info_season, make_info_show,
356	};
357
358	#[tokio::test]
359	async fn use_test_macros() {
360		let db = new_initialized_memory_db().await;
361
362		make_info_collection!(&db, 1);
363		make_info_movie!(&db, 1);
364		make_info_show!(&db, 1);
365		make_info_season!(&db, 1, 1);
366		make_info_episode!(&db, 1, 1, 1);
367	}
368
369	#[tokio::test]
370	async fn test_round_trip_collections() {
371		let db = new_initialized_memory_db().await;
372
373		macro_rules! assert_collection {
374			($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
375				let model = assert_collection!(@insert, $db, $id $(; $($skip),+)?)
376					.expect("insert");
377
378				assert_eq!(model.id, CollectionId::from_raw($id));
379				assert_eq!(model.title, concat!("C Title ", $id));
380				assert_eq!(model.overview, concat!("C Overview ", $id));
381			};
382			($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
383				let model = assert_collection!(@insert, $db, $id $(; $($skip),+)?)
384					.expect_err("insert");
385
386				assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
387			};
388			(@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
389				super::collections::ActiveModel {
390					id: notsettable!(id, CollectionId::from_raw($id) $(, $($skip),+)?),
391					title: notsettable!(title, concat!("C Title ", $id).to_string() $(, $($skip),+)?),
392					overview: notsettable!(overview, concat!("C Overview ", $id).to_string() $(, $($skip),+)?),
393					sort_title: notsettable!(sort_title, concat!("C Sort Title ", $id).to_string() $(, $($skip),+)?),
394					fs_slug: notsettable!(fs_slug, concat!("C FS Slug ", $id).to_string() $(, $($skip),+)?),
395					web_slug: notsettable!(web_slug, concat!("C Web Slug ", $id).to_string() $(, $($skip),+)?),
396				}.insert($db).await
397			};
398		}
399
400		assert_collection!(&db, 1, Success);
401		assert_collection!(&db, 1, UniqueViolation);
402		assert_collection!(&db, 2, Success);
403
404		assert_collection!(&db, 3, Success; id);
405		assert_collection!(&db, 4, NotNullViolation; title);
406		assert_collection!(&db, 5, NotNullViolation; overview);
407		assert_collection!(&db, 6, NotNullViolation; sort_title);
408		assert_collection!(&db, 7, NotNullViolation; fs_slug);
409		assert_collection!(&db, 8, NotNullViolation; web_slug);
410	}
411
412	#[tokio::test]
413	async fn test_round_trip_movies() {
414		let db = new_initialized_memory_db().await;
415
416		macro_rules! assert_movie {
417			($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
418				let model = assert_movie!(@insert, $db, $id $(; $($skip),+)?)
419					.expect("insert");
420
421				assert_eq!(model.id, MovieId::from_raw($id));
422				assert_eq!(model.title, concat!("M Title ", $id));
423				assert_eq!(model.tagline, concat!("M Tagline ", $id));
424				assert_eq!(model.overview, concat!("M Overview ", $id));
425				assert_eq!(model.date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt"));
426			};
427			($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
428				let model = assert_movie!(@insert, $db, $id $(; $($skip),+)?)
429					.expect_err("insert");
430
431				assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
432			};
433			(@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
434				super::movies::ActiveModel {
435					id: notsettable!(id, MovieId::from_raw($id) $(, $($skip),+)?),
436					title: notsettable!(title, concat!("M Title ", $id).to_string() $(, $($skip),+)?),
437					tagline: notsettable!(tagline, concat!("M Tagline ", $id).to_string() $(, $($skip),+)?),
438					overview: notsettable!(overview, concat!("M Overview ", $id).to_string() $(, $($skip),+)?),
439					date: notsettable!(date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt") $(, $($skip),+)?),
440					sort_title: notsettable!(sort_title, concat!("M Sort Title ", $id).to_string() $(, $($skip),+)?),
441					fs_slug: notsettable!(fs_slug, concat!("M FS Slug ", $id).to_string() $(, $($skip),+)?),
442					web_slug: notsettable!(web_slug, concat!("M Web Slug ", $id).to_string() $(, $($skip),+)?),
443				}.insert($db).await
444			};
445		}
446
447		assert_movie!(&db, 1, Success);
448		assert_movie!(&db, 1, UniqueViolation);
449		assert_movie!(&db, 2, Success);
450
451		assert_movie!(&db, 3, Success; id);
452		assert_movie!(&db, 4, NotNullViolation; title);
453		assert_movie!(&db, 5, NotNullViolation; tagline);
454		assert_movie!(&db, 6, NotNullViolation; overview);
455		assert_movie!(&db, 7, NotNullViolation; date);
456		assert_movie!(&db, 8, NotNullViolation; sort_title);
457		assert_movie!(&db, 9, NotNullViolation; fs_slug);
458		assert_movie!(&db, 10, NotNullViolation; web_slug);
459	}
460
461	#[tokio::test]
462	async fn test_round_trip_shows() {
463		let db = new_initialized_memory_db().await;
464
465		macro_rules! assert_show {
466			($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
467				let model = assert_show!(@insert, $db, $id $(; $($skip),+)?)
468					.expect("insert");
469
470				assert_eq!(model.id, ShowId::from_raw($id));
471				assert_eq!(model.title, concat!("S Title ", $id));
472				assert_eq!(model.tagline, concat!("S Tagline ", $id));
473				assert_eq!(model.overview, concat!("S Overview ", $id));
474				assert_eq!(model.date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt"));
475			};
476			($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
477				let model = assert_show!(@insert, $db, $id $(; $($skip),+)?)
478					.expect_err("insert");
479
480				assert_eq!(
481					get_error_kind(model).expect("get_error_kind"),
482					ErrorKind::$error
483				);
484			};
485			(@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
486				super::shows::ActiveModel {
487					id: notsettable!(id, ShowId::from_raw($id) $(, $($skip),+)?),
488					title: notsettable!(title, concat!("S Title ", $id).to_string() $(, $($skip),+)?),
489					tagline: notsettable!(tagline, concat!("S Tagline ", $id).to_string() $(, $($skip),+)?),
490					overview: notsettable!(overview, concat!("S Overview ", $id).to_string() $(, $($skip),+)?),
491					date: notsettable!(date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt") $(, $($skip),+)?),
492					sort_title: notsettable!(sort_title, concat!("S Sort Title ", $id).to_string() $(, $($skip),+)?),
493					fs_slug: notsettable!(fs_slug, concat!("S FS Slug ", $id).to_string() $(, $($skip),+)?),
494					web_slug: notsettable!(web_slug, concat!("S Web Slug ", $id).to_string() $(, $($skip),+)?),
495				}.insert($db).await
496			};
497		}
498
499		assert_show!(&db, 1, Success);
500		assert_show!(&db, 1, UniqueViolation);
501		assert_show!(&db, 2, Success);
502
503		assert_show!(&db, 3, Success; id);
504		assert_show!(&db, 4, NotNullViolation; title);
505		assert_show!(&db, 5, NotNullViolation; tagline);
506		assert_show!(&db, 6, NotNullViolation; overview);
507		assert_show!(&db, 7, NotNullViolation; date);
508		assert_show!(&db, 8, NotNullViolation; sort_title);
509		assert_show!(&db, 9, NotNullViolation; fs_slug);
510		assert_show!(&db, 10, NotNullViolation; web_slug);
511	}
512
513	#[tokio::test]
514	async fn test_round_trip_seasons() {
515		let db = new_initialized_memory_db().await;
516
517		macro_rules! assert_season {
518			($db:expr, $show:literal, $season:literal, Success $(; $($skip:ident),+)?) => {
519				let model = assert_season!(@insert, $db, $show, $season $(; $($skip),+)?)
520					.expect("insert");
521
522				assert_eq!(model.show_id, ShowId::from_raw($show));
523				assert_eq!(model.season_number, ::flix_model::numbers::SeasonNumber::new($season));
524				assert_eq!(model.title, concat!("SS Title ", $show, ",", $season));
525				assert_eq!(model.overview, concat!("SS Overview ", $show, ",", $season));
526				assert_eq!(model.date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt"));
527			};
528			($db:expr, $show:literal, $season:literal, $error:ident $(; $($skip:ident),+)?) => {
529				let model = assert_season!(@insert, $db, $show, $season $(; $($skip),+)?)
530					.expect_err("insert");
531
532				assert_eq!(
533					get_error_kind(model).expect("get_error_kind"),
534					ErrorKind::$error
535				);
536			};
537			(@insert, $db:expr, $show:literal, $season:literal $(; $($skip:ident),+)?) => {
538				super::seasons::ActiveModel {
539					show_id: notsettable!(show_id, ShowId::from_raw($show) $(, $($skip),+)?),
540					season_number: notsettable!(season_number, ::flix_model::numbers::SeasonNumber::new($season) $(, $($skip),+)?),
541					title: notsettable!(title, concat!("SS Title ", $show, ",", $season).to_string() $(, $($skip),+)?),
542					overview: notsettable!(overview, concat!("SS Overview ", $show, ",", $season).to_string() $(, $($skip),+)?),
543					date: notsettable!(date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt") $(, $($skip),+)?),
544				}.insert($db).await
545			};
546		}
547
548		assert_season!(&db, 1, 1, ForeignKeyViolation);
549		make_info_show!(&db, 1);
550		make_info_show!(&db, 2);
551
552		assert_season!(&db, 1, 1, Success);
553		assert_season!(&db, 1, 1, UniqueViolation);
554		assert_season!(&db, 2, 1, Success);
555		assert_season!(&db, 1, 2, Success);
556
557		assert_season!(&db, 1, 3, NotNullViolation; show_id);
558		assert_season!(&db, 1, 4, NotNullViolation; season_number);
559		assert_season!(&db, 1, 5, NotNullViolation; title);
560		assert_season!(&db, 1, 6, NotNullViolation; overview);
561		assert_season!(&db, 1, 7, NotNullViolation; date);
562	}
563
564	#[tokio::test]
565	async fn test_round_trip_episodes() {
566		let db = new_initialized_memory_db().await;
567
568		macro_rules! assert_episode {
569			($db:expr, $show:literal, $season:literal, $episode:literal, Success $(; $($skip:ident),+)?) => {
570				let model = assert_episode!(@insert, $db, $show, $season, $episode $(; $($skip),+)?)
571					.expect("insert");
572
573				assert_eq!(model.show_id, ShowId::from_raw($show));
574				assert_eq!(model.season_number, ::flix_model::numbers::SeasonNumber::new($season));
575				assert_eq!(model.episode_number, ::flix_model::numbers::EpisodeNumber::new($episode));
576				assert_eq!(model.title, concat!("SSE Title ", $show, ",", $season, ",", $episode));
577				assert_eq!(model.overview, concat!("SSE Overview ", $show, ",", $season, ",", $episode));
578				assert_eq!(model.date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt"));
579			};
580			($db:expr, $show:literal, $season:literal, $episode:literal, $error:ident $(; $($skip:ident),+)?) => {
581				let model = assert_episode!(@insert, $db, $show, $season, $episode $(; $($skip),+)?)
582					.expect_err("insert");
583
584				assert_eq!(
585					get_error_kind(model).expect("get_error_kind"),
586					ErrorKind::$error
587				);
588			};
589			(@insert, $db:expr, $show:literal, $season:literal, $episode:literal $(; $($skip:ident),+)?) => {
590				super::episodes::ActiveModel {
591					show_id: notsettable!(show_id, ShowId::from_raw($show) $(, $($skip),+)?),
592					season_number: notsettable!(season_number, ::flix_model::numbers::SeasonNumber::new($season) $(, $($skip),+)?),
593					episode_number: notsettable!(episode_number, ::flix_model::numbers::EpisodeNumber::new($episode) $(, $($skip),+)?),
594					title: notsettable!(title, concat!("SSE Title ", $show, ",", $season, ",", $episode).to_string() $(, $($skip),+)?),
595					overview: notsettable!(overview, concat!("SSE Overview ", $show, ",", $season, ",", $episode).to_string() $(, $($skip),+)?),
596					date: notsettable!(date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt") $(, $($skip),+)?),
597				}.insert($db).await
598			};
599		}
600
601		assert_episode!(&db, 1, 1, 1, ForeignKeyViolation);
602		make_info_show!(&db, 1);
603		make_info_show!(&db, 2);
604		assert_episode!(&db, 1, 1, 1, ForeignKeyViolation);
605		make_info_season!(&db, 1, 1);
606		make_info_season!(&db, 1, 2);
607		make_info_season!(&db, 2, 1);
608
609		assert_episode!(&db, 1, 1, 1, Success);
610		assert_episode!(&db, 1, 1, 1, UniqueViolation);
611		assert_episode!(&db, 2, 1, 1, Success);
612		assert_episode!(&db, 1, 2, 1, Success);
613		assert_episode!(&db, 1, 1, 2, Success);
614
615		assert_episode!(&db, 1, 1, 3, NotNullViolation; show_id);
616		assert_episode!(&db, 1, 1, 4, NotNullViolation; season_number);
617		assert_episode!(&db, 1, 1, 4, NotNullViolation; episode_number);
618		assert_episode!(&db, 1, 1, 5, NotNullViolation; title);
619		assert_episode!(&db, 1, 1, 6, NotNullViolation; overview);
620		assert_episode!(&db, 1, 1, 7, NotNullViolation; date);
621	}
622}