mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
155 lines
7.3 KiB
C#
155 lines
7.3 KiB
C#
using System.Text.Json;
|
|
using RadicadeServer.API.Storefront.V3;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Players;
|
|
using RadicadeServer.RecNet.Store;
|
|
using RadicadeServer.RecNet.Storefront;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
public class StorefrontRewardConfigTests
|
|
{
|
|
[Fact]
|
|
public void QuestStorefrontProjectionHidesOnlyUnsupportedAvatarOnlyItems()
|
|
{
|
|
var supported = Purchasable(10, "supported,");
|
|
var unsupported = Purchasable(11, "not-in-quest-build,");
|
|
var mixedReward = Purchasable(12, "also-unsupported,");
|
|
var originalGift = mixedReward.GiftDrops[0];
|
|
originalGift.ConsumableItemDesc = "consumable";
|
|
originalGift.Currency = 25;
|
|
|
|
var projected = StorefrontsV3.ProjectStoreItemsForClient(
|
|
[supported, unsupported, mixedReward],
|
|
((int)Platforms.Standalone).ToString(),
|
|
description => description == "supported,");
|
|
|
|
Assert.Equal(new[] { 10, 12 }, projected.Select(item => item.PurchasableItemId));
|
|
Assert.Equal(250, Assert.Single(projected[0].Prices).Price);
|
|
Assert.Equal(string.Empty, projected[1].GiftDrops[0].AvatarItemDesc);
|
|
Assert.Equal("consumable", projected[1].GiftDrops[0].ConsumableItemDesc);
|
|
Assert.Equal(25, projected[1].GiftDrops[0].Currency);
|
|
|
|
// The response is a projection only; purchase resolution still sees the original gifts.
|
|
Assert.Equal("also-unsupported,", originalGift.AvatarItemDesc);
|
|
Assert.Equal("not-in-quest-build,", unsupported.GiftDrops[0].AvatarItemDesc);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuestStorefrontProjectionLeavesOtherPlatformsAndResolvableGiftDropsAlone()
|
|
{
|
|
var item = Purchasable(20, "supported,");
|
|
item.GiftDrops.Add(new Gift { GiftDropId = 201, AvatarItemDesc = "unsupported," });
|
|
|
|
var nonStandalone = StorefrontsV3.ProjectStoreItemsForClient(
|
|
[item],
|
|
((int)Platforms.Oculus).ToString(),
|
|
_ => false);
|
|
var standalone = StorefrontsV3.ProjectStoreItemsForClient(
|
|
[item],
|
|
((int)Platforms.Standalone).ToString(),
|
|
description => description == "supported,");
|
|
|
|
Assert.Same(item, Assert.Single(nonStandalone));
|
|
Assert.Single(Assert.Single(standalone).GiftDrops);
|
|
Assert.Equal("supported,", standalone[0].GiftDrops[0].AvatarItemDesc);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuestStorefrontProjectionFiltersCapturedCatalogWithoutChangingPurchaseLookup()
|
|
{
|
|
using var lookup = JsonDocument.Parse(File.ReadAllText(
|
|
Path.Combine(AppContext.BaseDirectory, "Fixtures", "avatar-item-lookup.json")));
|
|
var resolvable = lookup.RootElement.GetProperty("descriptions")
|
|
.EnumerateArray()
|
|
.Select(value => AvatarItemDescription.Normalize(value.GetString()))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
var canonicalItems = StorefrontCatalog.GetItems((long)StorefrontTypes.Watch);
|
|
var supportedItem = canonicalItems.First(item =>
|
|
resolvable.Contains(AvatarItemDescription.Normalize(item.GiftDrop.AvatarItemDesc)));
|
|
var unsupportedItem = canonicalItems.First(item =>
|
|
!string.IsNullOrWhiteSpace(item.GiftDrop.AvatarItemDesc)
|
|
&& !resolvable.Contains(AvatarItemDescription.Normalize(item.GiftDrop.AvatarItemDesc)));
|
|
var projected = StorefrontsV3.ProjectStoreItemsForClient(
|
|
canonicalItems,
|
|
((int)Platforms.Standalone).ToString(),
|
|
description => resolvable.Contains(AvatarItemDescription.Normalize(description)));
|
|
|
|
Assert.Contains(projected, item => item.PurchasableItemId == supportedItem.PurchasableItemId);
|
|
Assert.DoesNotContain(projected, item => item.PurchasableItemId == unsupportedItem.PurchasableItemId);
|
|
Assert.All(projected.SelectMany(item => item.GiftDrops)
|
|
.Where(gift => !string.IsNullOrWhiteSpace(gift.AvatarItemDesc)),
|
|
gift => Assert.Contains(
|
|
AvatarItemDescription.Normalize(gift.AvatarItemDesc), resolvable));
|
|
|
|
var canonicalUnsupported = StorefrontCatalog.FindItem(
|
|
(long)StorefrontTypes.Watch, unsupportedItem.PurchasableItemId);
|
|
Assert.NotNull(canonicalUnsupported);
|
|
Assert.Equal(unsupportedItem.GiftDrop.AvatarItemDesc, canonicalUnsupported.GiftDrop.AvatarItemDesc);
|
|
}
|
|
|
|
private static PurchasableGiftDrop Purchasable(int itemId, string avatarItemDesc) => new()
|
|
{
|
|
PurchasableItemId = itemId,
|
|
Prices = [new PurchasablePrice { Price = 250 }],
|
|
GiftDrops = [new Gift { GiftDropId = itemId + 100, AvatarItemDesc = avatarItemDesc }]
|
|
};
|
|
|
|
[Fact]
|
|
public void PartialMultiplierSerializesAsNumber()
|
|
{
|
|
var json = JsonSerializer.SerializeToElement(new StorefrontBalanceAddConfigDTO());
|
|
Assert.Equal(JsonValueKind.Number, json.GetProperty("MaxPartialMultiplier").ValueKind);
|
|
Assert.Equal(1f, json.GetProperty("MaxPartialMultiplier").GetSingle());
|
|
Assert.Equal(JsonValueKind.False, json.GetProperty("IgnorePartialMultiplier").ValueKind);
|
|
Assert.Equal(JsonValueKind.False, json.GetProperty("BalanceInGiftBox").ValueKind);
|
|
}
|
|
|
|
// The client asks for a storefront by number and the MVC binder drops any id that is not a
|
|
// member of StorefrontTypes down to None, so those ids have to stay defined and keep the
|
|
// exact numbers the 20230831 build sends (RecNet.Runtime's StorefrontTypes).
|
|
[Theory]
|
|
[InlineData(StorefrontTypes.LaserTag, 1)]
|
|
[InlineData(StorefrontTypes.Watch, 3)]
|
|
[InlineData(StorefrontTypes.Quest_GoldenTrophy, 102)]
|
|
[InlineData(StorefrontTypes.Quest_CrimsonCauldron, 103)]
|
|
[InlineData(StorefrontTypes.Paintball_SunsetDriveIn, 406)]
|
|
[InlineData(StorefrontTypes.StuntRunner, 600)]
|
|
[InlineData(StorefrontTypes.DormMirror, 700)]
|
|
[InlineData(StorefrontTypes.InventionStore, 800)]
|
|
[InlineData(StorefrontTypes.Player_Profile, 1000)]
|
|
[InlineData(StorefrontTypes.PopUpShop_1, 1700)]
|
|
[InlineData(StorefrontTypes.CustomAvatarItems, 1800)]
|
|
[InlineData(StorefrontTypes.ProgressionEventPurchasableXpBoosts, 1900)]
|
|
[InlineData(StorefrontTypes.PopUpShop_ProgressionEvent_1, 2000)]
|
|
[InlineData(StorefrontTypes.PopUpShop_ProgressionEvent_5, 2004)]
|
|
public void GameStorefrontIdStaysBound(StorefrontTypes type, int clientId)
|
|
{
|
|
Assert.Equal(clientId, (int)type);
|
|
Assert.True(
|
|
Enum.IsDefined(typeof(StorefrontTypes), clientId),
|
|
$"storefront {clientId} must stay a defined StorefrontTypes member or the route binder serves it as None");
|
|
}
|
|
|
|
// Every storefront the client knows carries a capture (RadicadeServer-Config/storefronts/sf{id}.json,
|
|
// embedded into the server at build), so the catalog covers the whole game and every storefront is
|
|
// editable there. A storefront with no items of its own still serves the shared storefront 0 list.
|
|
[Fact]
|
|
public void EveryGameStorefrontHasACapture()
|
|
{
|
|
var captured = StorefrontCatalog.ReadCaptures()
|
|
.Select(capture => capture.StorefrontId)
|
|
.ToHashSet();
|
|
|
|
var storefronts = Enum.GetValues<StorefrontTypes>()
|
|
.Where(type => type != StorefrontTypes.None)
|
|
.Select(type => (long)type)
|
|
.ToList();
|
|
|
|
Assert.Equal(44, storefronts.Count);
|
|
Assert.Empty(storefronts.Where(id => !captured.Contains(id)));
|
|
}
|
|
}
|