mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
501 lines
23 KiB
C#
501 lines
23 KiB
C#
using System.Reflection;
|
|
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Compatibility;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Auth.Cached;
|
|
using RadicadeServer.RecNet.Enums.Players;
|
|
using RadicadeServer.API.Keepsakes;
|
|
using RadicadeServer.RecNet.Keepsakes;
|
|
using RadicadeServer.API.IncentivizedReferrals;
|
|
using RadicadeServer.API.InfluencerPartnerProgram;
|
|
using RadicadeServer.API.Progression.SubscriptionSeasons.V1;
|
|
using RadicadeServer.API.Storefront;
|
|
using RadicadeServer.API.Storefront.V1;
|
|
using RadicadeServer.RoomServer;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
public class StartupApiCompatibilityTests
|
|
{
|
|
[Fact]
|
|
public void KeepsakeCategoriesReturnsCountedResults()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse("""{ "AllRooms": true }""");
|
|
try
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(new KeepsakesAPI(null!).Categories());
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
// A COUNTED set, whatever it holds: a bare list fails the client's parser outright
|
|
// ("expected:'{', actual:'['", logged as "Failed to get keepsake categories") and takes
|
|
// the keepsake load down with it. The entries themselves are the real catalog now.
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
var results = json.GetProperty("Results");
|
|
Assert.Equal(JsonValueKind.Array, results.ValueKind);
|
|
Assert.Equal(results.GetArrayLength(), json.GetProperty("TotalResults").GetInt32());
|
|
Assert.Equal(KeepsakeCatalog.Results().Length, results.GetArrayLength());
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The disabled state, route by route: no keepsake room configured means the whole feature is
|
|
/// off (<see cref="KeepsakeFeature"/>), and every answer a client could read a keepsake from has
|
|
/// to say so - an empty counted catalog rather than a list, NO CONTENT for the room, no active
|
|
/// event, and a placement refused instead of stored.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task WithNoKeepsakeRoomsTheFeatureAnswersOffEverywhere()
|
|
{
|
|
var previousRooms = KeepsakeRoomConfig.Override;
|
|
var previousFeature = KeepsakeFeature.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse(null);
|
|
KeepsakeFeature.Override = null;
|
|
try
|
|
{
|
|
var config = JsonSerializer.SerializeToElement(
|
|
Assert.IsType<OkObjectResult>(new KeepsakesAPI(null!).GlobalConfig()).Value);
|
|
Assert.False(config.GetProperty("KeepsakeFeatureEnabled").GetBoolean());
|
|
Assert.Equal(0, config.GetProperty("KeepsakeRoomLimit").GetInt32());
|
|
|
|
var catalog = JsonSerializer.SerializeToElement(
|
|
Assert.IsType<OkObjectResult>(new KeepsakesAPI(null!).Categories()).Value);
|
|
Assert.Equal(0, catalog.GetProperty("Results").GetArrayLength());
|
|
Assert.Equal(0, catalog.GetProperty("TotalResults").GetInt32());
|
|
|
|
Assert.IsType<NoContentResult>(await new KeepsakesAPI(null!).Room(25));
|
|
Assert.IsType<NotFoundResult>(await new KeepsakesAPI(null!).Place(25));
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previousRooms;
|
|
KeepsakeFeature.Override = previousFeature;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The emptied catalog is still a counted set rather than a bare list - the shape the endpoint
|
|
/// has to keep even when there is nothing in it.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AnEmptyKeepsakeCatalogIsStillACountedSet()
|
|
{
|
|
var json = JsonSerializer.SerializeToElement(KeepsakeCatalog.Results(enabled: false));
|
|
|
|
Assert.Equal(JsonValueKind.Array, json.ValueKind);
|
|
Assert.Equal(0, json.GetArrayLength());
|
|
}
|
|
|
|
/// <summary>
|
|
/// A room's keepsakes answer with NO body. The client reads any body here as keepsakes it
|
|
/// has to resolve, so an empty list is not the safe answer for it - the absence of one is.
|
|
/// A room listed in <see cref="KeepsakeRoomConfig"/> is the deliberate exception and answers
|
|
/// with the room fields, because that answer is the only place its keepsake permission can
|
|
/// come from - see KeepsakeRoomConfigTests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task RoomKeepsakesAnswerNoContent()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse(null);
|
|
try
|
|
{
|
|
Assert.IsType<NoContentResult>(await new KeepsakesAPI(null!).Room(25));
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The active progression event is the event's ID and nothing else - a bare number, which is
|
|
/// what the client's no-argument call returns (`MFBKDKPGMNP<long?>`). An array, an object or a
|
|
/// whole record does not deserialize into a nullable long, and every one of those shapes has
|
|
/// been answered here across these runs, each logged by the client as
|
|
/// `Malformed Response: '`body`'`.
|
|
/// </summary>
|
|
[Fact]
|
|
public void TheActiveProgressionEventIsTheIdTheRoomNames()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse(
|
|
"""{ "RoomIds": [25], "RoomListId": 7, "RoomLimit": 5, "EventId": 42, "EventName": "Test Event" }""");
|
|
try
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(
|
|
new CompatibilityAPI(null!).ActiveProgressionEvents());
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
Assert.Equal(JsonValueKind.Number, json.ValueKind);
|
|
Assert.Equal(42, json.GetInt64());
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The caller's current subscription season is absent, not `{}`. The client's DTO for this route
|
|
/// has six members (`Guid`, `string`, `string`, `DateTime`, `DateTime?`, `List<…>` - the
|
|
/// 2023-09-12 reference's `ALGDPMHOPGG`), so an empty object is a body it cannot bind: the
|
|
/// 2026-10-01 21:37 log's `Malformed Response: '{}'` is thrown right after this route is fetched.
|
|
/// No season exists on this server, so "none" is answered at the HTTP level instead of inventing
|
|
/// six member names and putting a season in front of the player.
|
|
/// </summary>
|
|
[Fact]
|
|
public void TheCurrentSubscriptionSeasonIsAbsentRatherThanAnEmptyObject()
|
|
{
|
|
Assert.IsType<NotFoundResult>(new SubscriptionSeasonsAPI().GetCurrentSeason());
|
|
|
|
var method = typeof(SubscriptionSeasonsAPI).GetMethod(nameof(SubscriptionSeasonsAPI.GetCurrentSeason))!;
|
|
var template = Assert.Single(method.GetCustomAttributes<HttpGetAttribute>()).Template;
|
|
Assert.Equal("seasons/current", template);
|
|
|
|
var classRoutes = typeof(SubscriptionSeasonsAPI).GetCustomAttributes<RouteAttribute>()
|
|
.Select(route => route.Template)
|
|
.ToArray();
|
|
Assert.Contains("econ/api/subscriptionseasons/v1", classRoutes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event itself, by id: the record the client fetches once it has the id above, carrying
|
|
/// the same one-list entry the room's own keepsake payload carries. The list is what the client
|
|
/// applies to the room, so a room the event does not name would be overridden back to having no
|
|
/// keepsake permission.
|
|
/// </summary>
|
|
[Fact]
|
|
public void TheProgressionEventByIdCarriesItsKeepsakeRoomLists()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse(
|
|
"""{ "RoomIds": [25], "RoomListId": 7, "RoomLimit": 5, "EventId": 42, "EventName": "Test Event" }""");
|
|
try
|
|
{
|
|
var api = new CompatibilityAPI(null!);
|
|
|
|
// The definition owns the room lists. The player record has a separate DTO.
|
|
foreach (var (route, response) in new (string, IActionResult)[]
|
|
{
|
|
("event", api.ProgressionEventDefinition(42)),
|
|
("event", api.ProgressionEvent(42)),
|
|
})
|
|
{
|
|
var json = JsonSerializer.SerializeToElement(Assert.IsType<OkObjectResult>(response).Value);
|
|
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
Assert.Equal(route, json.GetProperty("_route").GetString());
|
|
Assert.Equal(42, json.GetProperty("ProgressionEventId").GetInt64());
|
|
Assert.Equal("Test Event", json.GetProperty("Name").GetString());
|
|
Assert.True(json.GetProperty("IsEnabled").GetBoolean());
|
|
|
|
// Running, or the client applies nothing.
|
|
Assert.True(json.GetProperty("StartTime").GetDateTimeOffset() < DateTimeOffset.UtcNow);
|
|
Assert.True(json.GetProperty("EndTime").GetDateTimeOffset() > DateTimeOffset.UtcNow);
|
|
|
|
var list = Assert.Single(json.GetProperty("KeepsakeRoomLists").EnumerateArray());
|
|
Assert.Equal(7, list.GetProperty("KeepsakeRoomListId").GetInt64());
|
|
Assert.Equal(5, list.GetProperty("KeepsakeRoomLimit").GetInt32());
|
|
Assert.Equal(
|
|
new long[] { 25 },
|
|
list.GetProperty("KeepsakeRooms").EnumerateArray().Select(e => e.GetProperty("RoomId").GetInt64()).ToArray());
|
|
}
|
|
|
|
// The record also carries the definition nested, which is the other shape a record takes
|
|
// in this API - an unknown member there is ignored, so carrying both costs nothing.
|
|
var record = JsonSerializer.SerializeToElement(
|
|
Assert.IsType<OkObjectResult>(api.ProgressionEventRecord(42)).Value);
|
|
Assert.Equal(0, record.GetProperty("Xp").GetInt32());
|
|
Assert.Equal(0, record.GetProperty("RewardsCollected").GetInt32());
|
|
Assert.Equal(JsonValueKind.Null, record.GetProperty("XpBoostLastPurchasedAt").ValueKind);
|
|
Assert.False(record.TryGetProperty("ProgressionEvent", out _));
|
|
|
|
// An id this server does not have an event for is a 404, not a made-up record.
|
|
Assert.IsType<NotFoundResult>(api.ProgressionEvent(43));
|
|
Assert.IsType<NotFoundResult>(api.ProgressionEventDefinition(43));
|
|
Assert.IsType<NotFoundResult>(api.ProgressionEventRecord(43));
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// With no keepsake room configured there is no event to report, and the endpoint answers the
|
|
/// JSON literal `null` - the body its declared `long?` reads as "no event". The empty array it
|
|
/// used to answer is logged by the client as `Malformed Response: '[]'` (2026-10-01 17:54 and
|
|
/// 21:37 runs), which is a parse failure and not a "nothing running" answer, and a 404 is an
|
|
/// empty body the client reports as a failed call it retries.
|
|
/// </summary>
|
|
[Fact]
|
|
public void NoKeepsakeRoomsMeansNoActiveEvent()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse(null);
|
|
try
|
|
{
|
|
var response = Assert.IsType<ContentResult>(
|
|
new CompatibilityAPI(null!).ActiveProgressionEvents());
|
|
|
|
Assert.Equal("null", response.Content);
|
|
Assert.Equal("application/json", response.ContentType);
|
|
Assert.True(response.StatusCode is null or 200);
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void KeepsakeConfigUsesTheClientFieldNames()
|
|
{
|
|
var previous = KeepsakeRoomConfig.Override;
|
|
KeepsakeRoomConfig.Override = KeepsakeRoomConfig.Parse("""{ "AllRooms": true }""");
|
|
try
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(new KeepsakesAPI(null!).GlobalConfig());
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
// These are the names the client's parser reads. A config carrying anything else
|
|
// leaves every switch at its default instead of the value that was sent.
|
|
Assert.True(json.GetProperty("KeepsakeFeatureEnabled").GetBoolean());
|
|
Assert.Equal(10, json.GetProperty("KeepsakeRoomLimit").GetInt32());
|
|
Assert.False(json.GetProperty("SocialXpBoostEnabled").GetBoolean());
|
|
}
|
|
finally
|
|
{
|
|
KeepsakeRoomConfig.Override = previous;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A room's XP settings are a BARE two-key object - the room page parses this one
|
|
/// directly, so an envelope around it would fail the same way the keepsake catalog does.
|
|
/// A 404 here is what the client logs as `HTTP Error 404` as the room page opens, with the
|
|
/// maker buttons on it never built.
|
|
/// </summary>
|
|
[Fact]
|
|
public void RoomExperienceAnswersTheTwoKeysTheClientReads()
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(new V2_RoomsAPI(null!).GetRoomExperience(25));
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
Assert.False(json.GetProperty("Enabled").GetBoolean());
|
|
Assert.Equal(1000, json.GetProperty("DailyLimit").GetInt32());
|
|
Assert.False(json.TryGetProperty("value", out _));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The caller's per-room progress is an empty LIST: the client reads that as "no progress
|
|
/// in this room", where a 404 is an HTTP error and stalls the room page it is loading.
|
|
/// </summary>
|
|
[Fact]
|
|
public void RoomExperienceForPlayerAnswersAnEmptyList()
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(new V2_RoomsAPI(null!).GetRoomExperienceForPlayer(25));
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
Assert.Equal(JsonValueKind.Array, json.ValueKind);
|
|
Assert.Equal(0, json.GetArrayLength());
|
|
}
|
|
|
|
/// <summary>
|
|
/// A room's gift-drop shops are an empty LIST. The client asks for this route as a room
|
|
/// page opens and re-asks when it 404s, which is what leaves the page's shop entries
|
|
/// waiting while it logs an HTTP error.
|
|
/// </summary>
|
|
[Fact]
|
|
public void RoomGiftDropShopsAnswerAnEmptyList()
|
|
{
|
|
var response = Assert.IsType<OkObjectResult>(new RoomGiftDropShopAPI().GetRoomGiftDropShops(25));
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
Assert.Equal(JsonValueKind.Array, json.ValueKind);
|
|
Assert.Equal(0, json.GetArrayLength());
|
|
}
|
|
|
|
[Fact]
|
|
public void UserSegmentCompatibilityRouteIsRegisteredAndReturnsTheThreeSegmentMembers()
|
|
{
|
|
var route = typeof(CommonQueryAPI).GetMethod(nameof(CommonQueryAPI.GetUserSegment))!
|
|
.GetCustomAttributes<HttpGetAttribute>()
|
|
.Single();
|
|
Assert.Equal("/data/commonQuery/userSegment", route.Template);
|
|
|
|
// The empty object this route used to answer is a `Malformed Response: '{}'` in the client
|
|
// (no member to bind), so the shape under test is "an object with the segment members",
|
|
// not merely "an object".
|
|
var response = Assert.IsType<OkObjectResult>(new CommonQueryAPI().GetUserSegment());
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
Assert.Equal(
|
|
new[] { "User_segment_algo_name", "User_segment_algo_version", "User_segment_value" },
|
|
json.EnumerateObject().Select(property => property.Name).ToArray());
|
|
}
|
|
|
|
[Fact]
|
|
public void PurchaseCampaignsReturnABareList()
|
|
{
|
|
// CompatibilityAPI now takes the database (it stores room comments), which this shape
|
|
// check does not touch - a null context is enough for a route that reads no data.
|
|
var response = Assert.IsType<OkObjectResult>(new CompatibilityAPI(null!).CurrentPurchaseCampaigns());
|
|
var json = JsonSerializer.SerializeToElement(response.Value);
|
|
|
|
// PurchaseCampaignManager holds these as List<T>; the paged shape fails its list reader.
|
|
Assert.Equal(JsonValueKind.Array, json.ValueKind);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(ReferralsAPI), "incentivizedreferrals", "GetReferrealProgress", "progress")]
|
|
[InlineData(typeof(InfluencerPartnerProgramAPI), "influencerpartnerprogram", "GetInfluencers", "influencers")]
|
|
public void EconomyStartupEndpointsExposeBothRoutePrefixes(
|
|
Type controller, string resource, string method, string action)
|
|
{
|
|
var routes = controller.GetCustomAttributes<RouteAttribute>()
|
|
.SelectMany(prefix => controller.GetMethod(method)!.GetCustomAttributes<HttpGetAttribute>()
|
|
.Select(suffix => $"{prefix.Template}/{suffix.Template}"))
|
|
.ToArray();
|
|
|
|
Assert.Contains($"api/{resource}/{action}", routes);
|
|
Assert.Contains($"econ/api/{resource}/{action}", routes);
|
|
Assert.Contains($"econ/api/{resource}/v1/{action}", routes);
|
|
}
|
|
|
|
[Fact]
|
|
public void StorefrontCarouselExposesEconomyRoute()
|
|
{
|
|
var routes = typeof(StorefrontsV1).GetCustomAttributes<RouteAttribute>()
|
|
.SelectMany(prefix => typeof(StorefrontsV1).GetMethod("GetAdCarouselItems")!
|
|
.GetCustomAttributes<HttpGetAttribute>()
|
|
.Select(suffix => $"{prefix.Template}/{suffix.Template}"))
|
|
.ToArray();
|
|
|
|
Assert.Contains("api/storefronts/v1/adcarouselitems", routes);
|
|
Assert.Contains("econ/api/storefronts/v1/adcarouselitems", routes);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CachedLoginDeleteRemovesOnlyTheAuthenticatedPlatformLogin()
|
|
{
|
|
var options = TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
await using var db = new RecDBContext(options);
|
|
db.CachedLogins.AddRange(
|
|
new CachedLogin { accountId = 71, platform = Platforms.Steam, platformId = "steam-71" },
|
|
new CachedLogin { accountId = 71, platform = Platforms.Oculus, platformId = "quest-71" },
|
|
new CachedLogin { accountId = 72, platform = Platforms.Steam, platformId = "steam-71" });
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = new CompatibilityAPI(db);
|
|
var identity = new System.Security.Claims.ClaimsIdentity("test");
|
|
identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "71"));
|
|
identity.AddClaim(new System.Security.Claims.Claim("rn.platid", "steam-71"));
|
|
identity.AddClaim(new System.Security.Claims.Claim("rn.plat", ((int)Platforms.Steam).ToString()));
|
|
api.ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext { User = new System.Security.Claims.ClaimsPrincipal(identity) }
|
|
};
|
|
|
|
Assert.IsType<OkResult>(await api.DeleteCurrentCachedLogin());
|
|
Assert.Equal(new[] { (71L, Platforms.Oculus), (72L, Platforms.Steam) },
|
|
await db.CachedLogins.OrderBy(login => login.accountId).ThenBy(login => login.platform)
|
|
.Select(login => new ValueTuple<long, Platforms>(login.accountId, login.platform)).ToArrayAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InfluencerRemovalUsesTheCapturedEconomyRouteAndRequestShape()
|
|
{
|
|
var method = typeof(InfluencerPartnerProgramAPI).GetMethod(nameof(InfluencerPartnerProgramAPI.RemoveInfluencer))!;
|
|
Assert.Contains(method.GetCustomAttributes<HttpPostAttribute>(), attribute => attribute.Template == "remove");
|
|
|
|
// No action parameter on purpose: the economy client posts this required body
|
|
// urlencoded, and binding it with [FromBody] answered 415 Unsupported Media Type.
|
|
Assert.Empty(method.GetParameters());
|
|
var accountId = typeof(InfluencerRemoveRequest).GetProperty("influencerAccountId");
|
|
Assert.NotNull(accountId);
|
|
Assert.Equal(typeof(int), accountId!.PropertyType);
|
|
|
|
var body = System.Text.Encoding.UTF8.GetBytes("influencerAccountId=7");
|
|
var api = new InfluencerPartnerProgramAPI
|
|
{
|
|
ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext
|
|
{
|
|
Request =
|
|
{
|
|
ContentType = "application/x-www-form-urlencoded",
|
|
Body = new MemoryStream(body)
|
|
}
|
|
}
|
|
}
|
|
};
|
|
api.HttpContext.Request.ContentLength = body.Length;
|
|
|
|
var result = Assert.IsType<OkObjectResult>(await api.RemoveInfluencer());
|
|
var response = JsonSerializer.SerializeToElement(result.Value);
|
|
Assert.True(response.GetProperty("success").GetBoolean());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Both influencer reads answer a BARE NUMBER, because that is what the client's own signatures
|
|
/// return: `HAHEDHADMKF(int) -> KDCMFGDLEKL<int?>` for `influencer?accountId=` and
|
|
/// `BJLFMAMBEEJ() -> KDCMFGDLEKL<int?>` for `myinfluencer`. The captured real answer to the
|
|
/// first is a bare number (`28848275`, "Influencer Support" in
|
|
/// `research/RecNet API.postman_collection.json`); sending `{AccountId, Code}` there made every
|
|
/// profile open with `Exception: Malformed Response: '{ "AccountId": 2, "Code": "COACH" }'`,
|
|
/// and `myinfluencer` then collected BOTH of its own rejections - `'{}'` for the empty object
|
|
/// and `'{ "influencerAccountId": 0 }'` for the one-member object it was replaced with, which
|
|
/// is still in the log on every boot. 0 means "nobody".
|
|
/// </summary>
|
|
[Fact]
|
|
public void BothInfluencerReadsServeABareNumber()
|
|
{
|
|
var previousInfluencers = DBConfig.TestInfluencerAccountIds;
|
|
DBConfig.TestInfluencerAccountIds = new List<long> { 2 };
|
|
try
|
|
{
|
|
var api = new InfluencerPartnerProgramAPI
|
|
{
|
|
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }
|
|
};
|
|
|
|
Assert.Equal(2L, Assert.IsType<OkObjectResult>(api.GetInfluencer(2)).Value);
|
|
Assert.IsType<NotFoundResult>(api.GetInfluencer(999));
|
|
|
|
api.ControllerContext.HttpContext.User = new ClaimsPrincipal(
|
|
new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "2") }, "TestAuth"));
|
|
|
|
var mine = Assert.IsType<OkObjectResult>(api.GetMyInfluencer());
|
|
var mineJson = JsonSerializer.SerializeToElement(mine.Value);
|
|
Assert.Equal(JsonValueKind.Number, mineJson.ValueKind);
|
|
Assert.Equal(2L, mineJson.GetInt64());
|
|
|
|
// A caller who supports nobody gets the number 0, not an object carrying it.
|
|
api.ControllerContext.HttpContext.User = new ClaimsPrincipal(
|
|
new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "7") }, "TestAuth"));
|
|
var none = Assert.IsType<OkObjectResult>(api.GetMyInfluencer());
|
|
var noneJson = JsonSerializer.SerializeToElement(none.Value);
|
|
Assert.Equal(JsonValueKind.Number, noneJson.ValueKind);
|
|
Assert.Equal(0L, noneJson.GetInt64());
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.TestInfluencerAccountIds = previousInfluencers;
|
|
}
|
|
}
|
|
}
|