Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,4 @@ tokio-current-thread = "=0.2.0-alpha.1"
serde_test = "1.0"
assert_matches = "1.5"
pretty_assertions = "1"
insta = { version = "1", features = ["json"] }
1 change: 1 addition & 0 deletions src/unit_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ mod link;
mod meta_details;
mod player;
mod serde;
mod snapshots;
mod streaming_server;
91 changes: 91 additions & 0 deletions src/unit_tests/snapshots.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Public-contract tripwire.
//!
//! These snapshots fix the **serialized JSON shape** of every bucket that
//! `stremio-core` writes to persistent storage. The storage keys themselves
//! are declared `pub const` in [`crate::constants`] and are installed on
//! end-user devices; any rename or schema change breaks existing installs
//! silently. The snapshots here make such a change impossible to land
//! without a visible `cargo insta review` diff, forcing a deliberate
//! migration story rather than a surprise drop.
//!
//! To update a snapshot intentionally: run `cargo insta review`,
//! inspect the diff, accept it, and include the `.snap` change in the
//! same PR as the schema change (paired with a migration step in
//! [`crate::runtime::env`] if the wire format is not backward-compatible).
//!
//! What's covered here:
//! - Default `Profile` serialization (the `profile` storage key).
//! - Empty bucket types for the remaining storage keys, as declared in
//! [`crate::constants`]:
//! `library`, `library_recent`, `streams`, `search_history`,
//! `streaming_server_urls`, `notifications`, `calendar`,
//! `dismissed_events`.
Comment on lines +21 to +22
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docs claim the snapshot suite covers the calendar storage key, but this file doesn't define a calendar snapshot test and there is no .snap fixture for it. Also CALENDAR_STORAGE_KEY appears to be unused outside src/constants.rs, so either add the missing coverage (if something is persisted under this key) or remove calendar from the documented coverage list to avoid misleading future readers.

Suggested change
//! `streaming_server_urls`, `notifications`, `calendar`,
//! `dismissed_events`.
//! `streaming_server_urls`, `notifications`, `dismissed_events`.

Copilot uses AI. Check for mistakes.
//!
//! What's intentionally NOT covered (deliberate scope limit):
//! - Every [`crate::runtime::msg`] variant — the `Msg` JSON shape is
//! dispatch-internal, not persisted to storage; its stability matters
//! for live clients but not for long-term data compatibility.
//! - Deep-link URLs — already covered semantically by
//! [`crate::unit_tests::deep_links::helpers::assert_player_url`] and the
//! ordinary `assert_eq!` checks in the deep-link tests.

use insta::assert_json_snapshot;

use crate::types::events::DismissedEventsBucket;
use crate::types::library::LibraryBucket;
use crate::types::notifications::NotificationsBucket;
use crate::types::profile::Profile;
use crate::types::search_history::SearchHistoryBucket;
use crate::types::server_urls::ServerUrlsBucket;
use crate::types::streams::StreamsBucket;

/// Default `Profile` — the seed state for a fresh install. Covers the
/// `profile` storage key at [`crate::constants::PROFILE_STORAGE_KEY`].
#[test]
fn snapshot_profile_default() {
assert_json_snapshot!(Profile::default());
}

/// Empty `LibraryBucket` — covers both the `library` and `library_recent`
/// storage keys (same wire shape, split by recency of access).
#[test]
fn snapshot_library_bucket_empty() {
assert_json_snapshot!(LibraryBucket::default());
}
Comment on lines +49 to +54
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snapshotting LibraryBucket::default() (and the other buckets) only verifies the top-level field names/container types; it won’t catch breaking JSON changes inside persisted item types (e.g., LibraryItem field rename/default-value changes) because items is empty. If the goal is a “shape tripwire” for persisted data, consider snapshotting a minimal non-empty bucket with one representative item so nested serialization changes also produce an insta diff.

Copilot uses AI. Check for mistakes.

/// Empty `StreamsBucket` — covers [`crate::constants::STREAMS_STORAGE_KEY`].
#[test]
fn snapshot_streams_bucket_empty() {
assert_json_snapshot!(StreamsBucket::default());
}

/// Empty `SearchHistoryBucket` — covers
/// [`crate::constants::SEARCH_HISTORY_STORAGE_KEY`].
#[test]
fn snapshot_search_history_bucket_empty() {
assert_json_snapshot!(SearchHistoryBucket::default());
}

/// Empty `ServerUrlsBucket` — covers
/// [`crate::constants::STREAMING_SERVER_URLS_STORAGE_KEY`].
#[test]
fn snapshot_server_urls_bucket_empty() {
// The bucket's `new::<E>()` is parameterized on Env; default() is
// the neutral construction path and is what the ctx storage layer
// falls through to on a fresh install.
assert_json_snapshot!(ServerUrlsBucket::default());
}

/// Empty `NotificationsBucket` — covers
/// [`crate::constants::NOTIFICATIONS_STORAGE_KEY`].
#[test]
fn snapshot_notifications_bucket_empty() {
assert_json_snapshot!(NotificationsBucket::default());
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses NotificationsBucket::default(), but NotificationsBucket’s default impl is only derived under cfg(test) and sets created to the Unix epoch, while production code initializes this bucket via NotificationsBucket::new::<E>(...) (which uses E::now()). To better reflect the real persisted wire format (and avoid test-only defaults masking changes), consider constructing the bucket via the production constructor with a deterministic test Env/time, or redact the timestamp field in the snapshot.

Suggested change
assert_json_snapshot!(NotificationsBucket::default());
assert_json_snapshot!(
NotificationsBucket::default(),
{
".created" => "[created]",
}
);

Copilot uses AI. Check for mistakes.
}

/// Empty `DismissedEventsBucket` — covers
/// [`crate::constants::DISMISSED_EVENTS_STORAGE_KEY`].
#[test]
fn snapshot_dismissed_events_bucket_empty() {
assert_json_snapshot!(DismissedEventsBucket::default());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: src/unit_tests/snapshots.rs
expression: "DismissedEventsBucket::default()"
snapshot_kind: text
---
{
"uid": null,
"items": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: src/unit_tests/snapshots.rs
expression: "LibraryBucket::default()"
snapshot_kind: text
---
{
"uid": null,
"items": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: src/unit_tests/snapshots.rs
expression: "NotificationsBucket::default()"
snapshot_kind: text
---
{
"uid": null,
"items": {},
"lastUpdated": null,
"created": "1970-01-01T00:00:00Z"
}
Loading
Loading