using System.Text.Json; using PLib.Infrastructure.Metadata; using Shouldly; namespace PLib.Tests.Metadata; /// /// Decoding a stash-box reply, against payloads shaped like the real ones. /// /// /// This is the part of the feature that cannot be checked without a server and a key, and the /// part where a wrong guess looks exactly like "the source knows nothing about your video". /// public sealed class StashBoxPayloadTests { /// The newer query, whose result is one group of scenes per group of fingerprints. private static StashBoxMetadataProvider.GraphQlQuery Nested => StashBoxMetadataProvider.Queries[0]; /// The older query, kept for instances that have not been updated. private static StashBoxMetadataProvider.GraphQlQuery Flat => StashBoxMetadataProvider.Queries[1]; [Fact] public void The_newer_query_returns_scenes_one_level_deeper_and_they_are_flattened() { const string payload = """ { "data": { "findScenesBySceneFingerprints": [ [ { "id": "abc", "title": "Первая", "details": "Описание", "studio": { "name": "Студия" }, "tags": [ { "name": "драма" }, { "name": "нуар" } ], "performers": [ { "performer": { "name": "Актёр" }, "as": "Псевдоним" } ] }, { "id": "def", "title": "Вторая", "studio": null, "tags": [], "performers": [] } ] ] } } """; var matches = Read(payload, Nested); matches.Count.ShouldBe(2); var first = matches[0]; first.SourceName.ShouldBe("StashDB"); first.RemoteId.ShouldBe("abc"); first.Title.ShouldBe("Первая"); first.Description.ShouldBe("Описание"); first.Studios.ShouldBe(["Студия"]); first.Tags.ShouldBe(["драма", "нуар"]); // The credited alias is not the person; the label has to be the performer's own name. first.Performers.ShouldBe(["Актёр"]); matches[1].Studios.ShouldBeEmpty(); } [Fact] public void The_older_query_returns_a_flat_list() { const string payload = """ { "data": { "findSceneByFingerprint": [ { "id": "abc", "title": "Сцена", "studio": { "name": "Студия" }, "tags": [], "performers": [] } ] } } """; Read(payload, Flat).ShouldHaveSingleItem().Title.ShouldBe("Сцена"); } [Fact] public void A_reply_with_no_matches_is_an_empty_list_rather_than_a_failure() { Read("""{ "data": { "findScenesBySceneFingerprints": [ [] ] } }""", Nested).ShouldBeEmpty(); Read("""{ "data": { "findScenesBySceneFingerprints": null } }""", Nested).ShouldBeEmpty(); } [Fact] public void A_scene_without_a_title_still_produces_a_match_that_can_be_shown() { var match = Read( """{ "data": { "findSceneByFingerprint": [ { "id": "abc", "title": null } ] } }""", Flat).ShouldHaveSingleItem(); match.Title.ShouldNotBeNullOrWhiteSpace(); match.Tags.ShouldBeEmpty(); } [Fact] public void Errors_are_raised_even_though_the_server_answered_two_hundred() { // A rejected key comes back as 200 with an errors array; treating that as "nothing // found" would tell the user their video is unknown rather than their key is wrong. var payload = """{ "errors": [ { "message": "unauthorized" } ], "data": null }"""; Should.Throw(() => Read(payload, Nested)) .Message.ShouldBe("unauthorized"); } [Fact] public void A_missing_field_is_reported_apart_from_other_errors_so_the_older_query_gets_a_turn() { // Verbatim from a live instance that only has the newer query. var payload = """ { "errors": [ { "message": "Cannot query field \"findSceneByFingerprint\" on type \"Query\". Did you mean \"findScenesBySceneFingerprints\"?" } ] } """; var rejected = Should.Throw(() => Read(payload, Flat)); // Not the type the caller reports as a failure: this one means "try the next query". rejected.ShouldNotBeOfType(); } private static IReadOnlyList Read( string payload, StashBoxMetadataProvider.GraphQlQuery query) { using var document = JsonDocument.Parse(payload); return StashBoxMetadataProvider.ReadPayload(document.RootElement, query, "StashDB"); } }