mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
201 lines
8.4 KiB
C#
201 lines
8.4 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The shipped consumable and equipment-skin catalogs come from DorkNet's extraction of the
|
|
/// 2023 client's own asset configs (tools/import_dorknet_catalogs.py). These tests keep the two
|
|
/// halves of each row honest: a consumable that has no client token spawns as
|
|
/// "Unable to find consumable", and a skin without an exact prefab + guid pair does not resolve
|
|
/// at all.
|
|
/// </summary>
|
|
public class DorkNetCatalogTests
|
|
{
|
|
private static string Fixture(string name) => Path.Combine(AppContext.BaseDirectory, "Fixtures", name);
|
|
|
|
private static List<ConsumableItem> ShippedConsumables()
|
|
{
|
|
var path = Fixture("consumables.json");
|
|
Assert.True(File.Exists(path), $"missing fixture {path}");
|
|
// Rarity is written as a name in the catalog, which is what the admin panel's own
|
|
// options (JsonStringEnumConverter) accept.
|
|
return JsonSerializer.Deserialize<List<ConsumableItem>>(
|
|
File.ReadAllText(path),
|
|
new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
Converters = { new JsonStringEnumConverter() }
|
|
})
|
|
?? new List<ConsumableItem>();
|
|
}
|
|
|
|
private static List<EquipmentSkinItem> ShippedSkins()
|
|
{
|
|
var path = Fixture("equipment_skins.json");
|
|
Assert.True(File.Exists(path), $"missing fixture {path}");
|
|
return EquipmentSkinCatalog.Parse(File.ReadAllText(path));
|
|
}
|
|
|
|
[Fact]
|
|
public void ShippedConsumablesAllResolveToAClientToken()
|
|
{
|
|
var rows = ShippedConsumables();
|
|
|
|
Assert.True(rows.Count >= 53, $"only {rows.Count} consumable(s) shipped");
|
|
Assert.DoesNotContain(rows, row => string.IsNullOrWhiteSpace(row.ConsumableItemDesc));
|
|
|
|
// ToClientDesc leaves unknown names alone, so a row that comes back unchanged would be
|
|
// served as its friendly name - exactly the bug that made gift boxes fail to spawn.
|
|
var unresolved = rows
|
|
.Where(row => ConsumableCatalog.ToClientDesc(row.ConsumableItemDesc) == row.ConsumableItemDesc)
|
|
.Select(row => row.ConsumableItemDesc)
|
|
.ToList();
|
|
|
|
Assert.Empty(unresolved);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShippedConsumablesAreDistinctAndCarryAFriendlyName()
|
|
{
|
|
var rows = ShippedConsumables();
|
|
|
|
Assert.Equal(
|
|
rows.Count,
|
|
rows.Select(row => row.ConsumableItemDesc).Distinct(StringComparer.OrdinalIgnoreCase).Count());
|
|
Assert.DoesNotContain(rows, row => string.IsNullOrWhiteSpace(row.FriendlyName));
|
|
Assert.Contains(rows, row => row.Rarity != Rarity.Common);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShippedSkinsAllCarryThePairTheClientResolvesThemFrom()
|
|
{
|
|
var skins = ShippedSkins();
|
|
|
|
Assert.True(skins.Count >= 194, $"only {skins.Count} skin(s) shipped");
|
|
Assert.DoesNotContain(skins, skin => string.IsNullOrWhiteSpace(skin.PrefabName));
|
|
Assert.DoesNotContain(skins, skin => string.IsNullOrWhiteSpace(skin.ModificationGuid));
|
|
Assert.DoesNotContain(skins, skin => string.IsNullOrWhiteSpace(skin.DisplayName));
|
|
Assert.Equal(
|
|
skins.Count,
|
|
skins.Select(skin => skin.ModificationGuid).Distinct(StringComparer.OrdinalIgnoreCase).Count());
|
|
}
|
|
|
|
[Fact]
|
|
public void SkinsBecomeEquipmentRowsNamedFromTheCatalog()
|
|
{
|
|
var skin = new EquipmentSkinItem
|
|
{
|
|
PrefabName = "[PaintballGun]",
|
|
ModificationGuid = "00000000-0000-0000-0000-000000000001",
|
|
DisplayName = "Paintball Gun Skin Test",
|
|
Tooltip = "Equipment skin for PaintballGun.",
|
|
Rarity = Rarity.Uncommon
|
|
};
|
|
|
|
var equipment = Assert.Single(EquipmentSkinCatalog.ToEquipment([skin]));
|
|
|
|
Assert.Equal("[PaintballGun]", equipment.PrefabName);
|
|
Assert.Equal(skin.ModificationGuid, equipment.ModificationGuid);
|
|
Assert.Equal("Paintball Gun Skin Test", equipment.FriendlyName);
|
|
Assert.Equal("Equipment skin for PaintballGun.", equipment.Tooltip);
|
|
Assert.Equal(Rarity.Uncommon, equipment.Rarity);
|
|
}
|
|
|
|
[Fact]
|
|
public void SkinsWithoutAPrefabOrGuidAreDropped()
|
|
{
|
|
var parsed = EquipmentSkinCatalog.Parse("""
|
|
{ "items": [
|
|
{ "prefab_name": "[Arrow]", "modification_guid": "aaa", "display_name": "Arrow Skin" },
|
|
{ "prefab_name": "", "modification_guid": "bbb", "display_name": "No prefab" },
|
|
{ "prefab_name": "[Basketball]", "modification_guid": "", "display_name": "No guid" }
|
|
] }
|
|
""");
|
|
|
|
Assert.Equal("[Arrow]", Assert.Single(parsed).PrefabName);
|
|
Assert.Empty(EquipmentSkinCatalog.Parse("not json at all"));
|
|
Assert.Empty(EquipmentSkinCatalog.Parse(null));
|
|
}
|
|
|
|
[Fact]
|
|
public void MergingSkinsAddsOnlyThePairsMissingFromTheCatalog()
|
|
{
|
|
const string equipmentJson = """
|
|
[ { "PrefabName": "[Arrow]", "ModificationGuid": "b862383e-0de3-492d-8f72-25e756287687",
|
|
"FriendlyName": "[Arrow]", "Tooltip": "", "Rarity": 0 } ]
|
|
""";
|
|
const string skinsJson = """
|
|
{ "items": [
|
|
{ "prefab_name": "[Arrow]", "modification_guid": "b862383e-0de3-492d-8f72-25e756287687",
|
|
"display_name": "Arrow Skin Vitton", "rarity": 10 },
|
|
{ "prefab_name": "[Arrow]", "modification_guid": "aaaaaaaa-0000-0000-0000-000000000000",
|
|
"display_name": "Arrow Skin Fresh", "rarity": 10 }
|
|
] }
|
|
""";
|
|
|
|
var merged = JsonSerializer.Deserialize<List<Equipment>>(
|
|
EquipmentSkinCatalog.MergeInto(equipmentJson, skinsJson))!;
|
|
|
|
// The existing row keeps its place: a skin contributes only pairs the catalog lacks.
|
|
Assert.Equal(2, merged.Count);
|
|
Assert.Equal("[Arrow]", merged[0].FriendlyName);
|
|
Assert.Equal("Arrow Skin Fresh", merged[1].FriendlyName);
|
|
Assert.Equal(Rarity.Uncommon, merged[1].Rarity);
|
|
|
|
// Idempotent: merging the merged catalog again adds nothing.
|
|
var again = JsonSerializer.Deserialize<List<Equipment>>(
|
|
EquipmentSkinCatalog.MergeInto(
|
|
EquipmentSkinCatalog.MergeInto(equipmentJson, skinsJson), skinsJson))!;
|
|
Assert.Equal(2, again.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void MergingAnUnreadableCatalogLeavesItAlone()
|
|
{
|
|
const string broken = "{ this is not an equipment list }";
|
|
|
|
Assert.Equal(broken, EquipmentSkinCatalog.MergeInto(broken, """{ "items": [ { "prefab_name": "[Arrow]", "modification_guid": "aaa" } ] }"""));
|
|
Assert.Equal("[]", EquipmentSkinCatalog.MergeInto("[]", "{}"));
|
|
Assert.Equal("[]", EquipmentSkinCatalog.MergeInto(null, null));
|
|
}
|
|
|
|
/// <summary>How many rows repeat a prefab/guid pair another row already carries.</summary>
|
|
private static int DuplicatePairs(List<Equipment> rows) =>
|
|
rows.Count - rows
|
|
.Select(row => $"{row.PrefabName}\u001f{row.ModificationGuid}")
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.Count();
|
|
|
|
[Fact]
|
|
public void ShippedSkinsSurviveAMergeIntoTheShippedEquipmentCatalog()
|
|
{
|
|
var equipmentPath = Fixture("equipment.json");
|
|
var skinsPath = Fixture("equipment_skins.json");
|
|
if (!File.Exists(equipmentPath) || !File.Exists(skinsPath))
|
|
{
|
|
// Both fixtures are optional here; the shipped files themselves are what matters.
|
|
return;
|
|
}
|
|
|
|
var equipment = File.ReadAllText(equipmentPath);
|
|
var merged = JsonSerializer.Deserialize<List<Equipment>>(
|
|
EquipmentSkinCatalog.MergeInto(equipment, File.ReadAllText(skinsPath)))!;
|
|
var before = JsonSerializer.Deserialize<List<Equipment>>(equipment)!;
|
|
|
|
Assert.True(merged.Count >= before.Count);
|
|
// The shipped equipment.json already repeats some prefab/guid pairs, so the invariant is
|
|
// that merging a skin never adds another row for a pair the catalog already had.
|
|
Assert.Equal(DuplicatePairs(before), DuplicatePairs(merged));
|
|
foreach (var skin in ShippedSkins())
|
|
{
|
|
Assert.Contains(merged, row =>
|
|
string.Equals(row.PrefabName, skin.PrefabName, StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(row.ModificationGuid, skin.ModificationGuid, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
}
|