using System.Security.Claims; using System.Text.Json; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.API.Inventions.V1; using RadicadeServer.DB; using RadicadeServer.RecNet.Accounts; using RadicadeServer.RecNet.Inventions; using Xunit; namespace RadicadeServer.Tests; /// /// The two invention contracts the store reads a player's owned state through. /// /// `api/inventions/v1/dormskinsfromids` answers the 20230831 client's /// `HCNIEKLCFOG(IEnumerable<long>) -> Response<IReadOnlyList<long>>`, so the body is a bare array of /// numbers and never a list of invention objects - an object body makes the client's number reader /// throw ("expected:'Number Token', actual:'{'") instead of resolving anything. /// /// `api/inventions/v1/fulllineageowner` answers whether the player owns the lineage, which has to be /// true for an invention they *bought*: the purchase path writes nothing but the id into /// PlayerUnlocks.Inventions, and the route used to ignore that list entirely (and overwrote its /// answer on every id it was asked about, so only the last id ever decided). /// public class InventionOwnershipTests { private const long Buyer = 501; private const long Creator = 502; private static RecDBContext NewDb() => new( TestDbOptions.Create() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options); private static InventionsV1 Api(RecDBContext db, long playerId) => new(db) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity( [new Claim(ClaimTypes.NameIdentifier, playerId.ToString())], "test")) } } }; private static InventionDB Invention(long id, long creator, bool hidden = false) => new() { InventionId = id, CreatorPlayerId = creator, Name = "Invention " + id, IsPublished = true, HideFromPlayer = hidden, BlobName = "blob-" + id }; [Fact] public async Task DormSkinsFromIds_AnswersABareArrayOfNumbers() { await using var db = NewDb(); db.Inventions.AddRange(Invention(11, Creator), Invention(12, Creator), Invention(13, Creator, hidden: true)); await db.SaveChangesAsync(); var result = await Api(db, Buyer).GetDormSkinsFromIds(["11,12,13", "-1"]); Assert.Equal(new List { 11, 12 }, result); Assert.Equal("[11,12]", JsonSerializer.Serialize(result)); } [Fact] public async Task DormSkinsFromIds_AnswersInTheCallersOrder() { await using var db = NewDb(); db.Inventions.AddRange(Invention(61, Creator), Invention(62, Creator)); await db.SaveChangesAsync(); var result = await Api(db, Buyer).GetDormSkinsFromIds(["62", "9999", "61"]); Assert.Equal(new List { 62, 61 }, result); } [Fact] public async Task FullLineageOwner_IsTrueForABoughtInvention() { await using var db = NewDb(); db.Inventions.Add(Invention(21, Creator)); db.Unlocks.Add(new PlayerUnlocks { AccountId = Buyer, Inventions = "[21]" }); await db.SaveChangesAsync(); Assert.True(await Api(db, Buyer).OwnsInventionLineage([21])); } [Fact] public async Task FullLineageOwner_IsFalseForAnInventionThePlayerNeitherMadeNorBought() { await using var db = NewDb(); db.Inventions.Add(Invention(31, Creator)); await db.SaveChangesAsync(); Assert.False(await Api(db, Buyer).OwnsInventionLineage([31])); } [Fact] public async Task FullLineageOwner_AccumulatesAcrossTheIdsItIsAsked() { await using var db = NewDb(); db.Inventions.AddRange(Invention(41, Creator), Invention(42, Creator)); db.Unlocks.Add(new PlayerUnlocks { AccountId = Buyer, Inventions = "[42]" }); await db.SaveChangesAsync(); Assert.False(await Api(db, Buyer).OwnsInventionLineage([41])); Assert.True(await Api(db, Buyer).OwnsInventionLineage([41, 42])); Assert.True(await Api(db, Buyer).OwnsInventionLineage([42, 41])); } [Fact] public async Task FullLineageOwner_IsTrueForTheCreator() { await using var db = NewDb(); db.Inventions.Add(Invention(51, Buyer)); await db.SaveChangesAsync(); Assert.True(await Api(db, Buyer).OwnsInventionLineage([51])); } }