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");
}
}