Skip to main content

flix_db/entity/
watched.rs

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