mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
197 lines
8.6 KiB
C#
197 lines
8.6 KiB
C#
using RadicadeServer.RecNet.Avatar;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The loader prefers the fuller 2022-avatar.json and keeps avatar.json as a fallback, and it
|
|
/// only backfills metadata when the chosen catalog is missing it. These tests pin that
|
|
/// preference order so the served avatar list cannot silently fall back.
|
|
/// </summary>
|
|
public class AvatarCatalogSourceTests
|
|
{
|
|
[Fact]
|
|
public void NewerCatalogIsPreferredOverTheFallback()
|
|
{
|
|
Assert.Equal("2022-avatar.json", DBConfig.AvatarCatalogFileNames[0]);
|
|
Assert.Contains("avatar.json", DBConfig.AvatarCatalogFileNames);
|
|
}
|
|
|
|
[Fact]
|
|
public void CandidatePathsTryThePreferredFileInEveryDirectoryFirst()
|
|
{
|
|
var paths = DBConfig.AvatarCatalogCandidatePaths().ToArray();
|
|
|
|
Assert.NotEmpty(paths);
|
|
var firstFallback = Array.FindIndex(paths, path => path.EndsWith("avatar.json", StringComparison.OrdinalIgnoreCase)
|
|
&& !path.EndsWith("2022-avatar.json", StringComparison.OrdinalIgnoreCase));
|
|
var lastPreferred = Array.FindLastIndex(paths, path => path.EndsWith("2022-avatar.json", StringComparison.OrdinalIgnoreCase));
|
|
|
|
Assert.True(lastPreferred >= 0, "2022-avatar.json should be among the candidate paths.");
|
|
Assert.True(firstFallback > lastPreferred,
|
|
"every 2022-avatar.json location must be tried before any avatar.json location.");
|
|
}
|
|
|
|
[Fact]
|
|
public void CompleteCatalogNeedsNoEnrichment()
|
|
{
|
|
var catalogJson = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "avatar.json"));
|
|
var items = AvatarUnlockCatalog.ParseCatalog(catalogJson);
|
|
|
|
// The compact avatar.json carries no item ids, so it is enriched from merged-avatar.json.
|
|
Assert.True(AvatarUnlockCatalog.NeedsMetadataEnrichment(items));
|
|
Assert.False(AvatarUnlockCatalog.NeedsMetadataEnrichment(null));
|
|
Assert.False(AvatarUnlockCatalog.NeedsMetadataEnrichment(Array.Empty<AvatarItem>()));
|
|
Assert.False(AvatarUnlockCatalog.NeedsMetadataEnrichment(
|
|
[new AvatarItem { AvatarItemDesc = "item,", AvatarItemId = 12, ThumbnailImage = "a.png" }]));
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolvableFilterDropsOnlyUnknownItems()
|
|
{
|
|
var original = DBConfig.ResolvableAvatarItemDescs;
|
|
try
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"8aa79563-ace1-4ba7-ad0c-f3210a78142f,"
|
|
};
|
|
|
|
var kept = AvatarUnlockCatalog.KeepResolvableItems(
|
|
[
|
|
new AvatarItem { AvatarItemDesc = "8aa79563-ace1-4ba7-ad0c-f3210a78142f,,," },
|
|
new AvatarItem { AvatarItemDesc = "not-in-the-client-database,,," }
|
|
]);
|
|
|
|
var item = Assert.Single(kept);
|
|
Assert.Equal("8aa79563-ace1-4ba7-ad0c-f3210a78142f,", item.AvatarItemDesc);
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = original;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolvableFilterIsANoOpWithoutALookup()
|
|
{
|
|
var original = DBConfig.ResolvableAvatarItemDescs;
|
|
try
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
var kept = AvatarUnlockCatalog.KeepResolvableItems(
|
|
[new AvatarItem { AvatarItemDesc = "anything," }]);
|
|
|
|
Assert.Equal("anything,", Assert.Single(kept).AvatarItemDesc);
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = original;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The client only ships assets for items released before its build, so a newer row makes it
|
|
/// log "Unknown Material guid" and throw "Bad outfit string. No prefab found!". Rain Cloud
|
|
/// Hat (2023-04-07) is the item that actually crashed a boot in the 20230301 build.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ItemsReleasedAfterTheClientBuildAreNotServed()
|
|
{
|
|
const string rainCloudHat = "24781418-38ad-411f-925e-2321fe616b51,";
|
|
const string turkeySweater = "002a0f2f-1a24-4439-b578-470818ef8325,";
|
|
|
|
var kept = AvatarUnlockCatalog.KeepClientAvailableItems(
|
|
[
|
|
new AvatarItem { AvatarItemDesc = rainCloudHat, CreatedAt = new DateTime(2023, 4, 7, 18, 16, 3, DateTimeKind.Utc) },
|
|
new AvatarItem { AvatarItemDesc = turkeySweater, CreatedAt = new DateTime(2022, 11, 1, 17, 51, 50, DateTimeKind.Utc) },
|
|
new AvatarItem { AvatarItemDesc = "no-release-date," },
|
|
new AvatarItem { AvatarItemDesc = "offsetless,", CreatedAt = new DateTime(2023, 4, 7, 18, 16, 3, DateTimeKind.Unspecified) }
|
|
]);
|
|
|
|
Assert.DoesNotContain(kept, item => item.AvatarItemDesc == rainCloudHat);
|
|
Assert.DoesNotContain(kept, item => item.AvatarItemDesc == "offsetless,");
|
|
Assert.Contains(kept, item => item.AvatarItemDesc == turkeySweater);
|
|
Assert.Contains(kept, item => item.AvatarItemDesc == "no-release-date,");
|
|
}
|
|
|
|
[Fact]
|
|
public void ReleaseCutoffIsTheClientBuildDate()
|
|
{
|
|
Assert.Equal(new DateTime(2023, 3, 1, 0, 0, 0, DateTimeKind.Utc), DBConfig.AvatarItemReleaseCutoffUtc);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShippedLookupCoversTheCatalogServedToClients()
|
|
{
|
|
var lookupPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "avatar-item-lookup.json");
|
|
var catalogPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "2022-avatar.json");
|
|
if (!File.Exists(lookupPath) || !File.Exists(catalogPath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var descriptions = System.Text.Json.JsonDocument.Parse(File.ReadAllText(lookupPath))
|
|
.RootElement.GetProperty("descriptions")
|
|
.EnumerateArray()
|
|
.Select(element => element.GetString()!)
|
|
.Where(description => !string.IsNullOrWhiteSpace(description))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
Assert.True(descriptions.Count > 2000, $"lookup only lists {descriptions.Count} descriptions");
|
|
|
|
var original = DBConfig.ResolvableAvatarItemDescs;
|
|
var originalCatalog = DBConfig.DefaultAvatarCatalog;
|
|
var originalFallback = DBConfig.DefaultAvatarUnlocks;
|
|
try
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = descriptions;
|
|
DBConfig.DefaultAvatarCatalog = File.ReadAllText(catalogPath);
|
|
DBConfig.DefaultAvatarUnlocks = "[]";
|
|
|
|
var served = AvatarUnlockCatalog.GetSafeDefaultItems();
|
|
|
|
Assert.NotEmpty(served);
|
|
Assert.Equal(2990, descriptions.Count);
|
|
Assert.Equal(AvatarUnlockCatalog.ParseCatalog(DBConfig.DefaultAvatarCatalog).Count, served.Count);
|
|
Assert.DoesNotContain(served, item => !descriptions.Contains(item.AvatarItemDesc));
|
|
Assert.DoesNotContain(served, item => AvatarItemDescription.HasEmptyMaterialSlot(item.AvatarItemDesc));
|
|
Assert.DoesNotContain(served, item =>
|
|
item.CreatedAt != default && item.CreatedAt.ToUniversalTime() >= DBConfig.AvatarItemReleaseCutoffUtc);
|
|
// This item is present in the September client, unlike the old March lookup.
|
|
Assert.Contains(served, item =>
|
|
item.AvatarItemDesc.StartsWith("24781418-38ad-411f-925e-2321fe616b51", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.ResolvableAvatarItemDescs = original;
|
|
DBConfig.DefaultAvatarCatalog = originalCatalog;
|
|
DBConfig.DefaultAvatarUnlocks = originalFallback;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ShippedCatalogHasUniqueServerIdsAndRetainsKnownMetadata()
|
|
{
|
|
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "2022-avatar.json");
|
|
if (!File.Exists(path))
|
|
{
|
|
// The fixture is optional: without it the loader simply falls back to avatar.json.
|
|
return;
|
|
}
|
|
|
|
var items = AvatarUnlockCatalog.ParseCatalog(File.ReadAllText(path));
|
|
|
|
Assert.NotEmpty(items);
|
|
Assert.DoesNotContain(items, item => AvatarItemDescription.HasEmptyMaterialSlot(item.AvatarItemDesc));
|
|
Assert.DoesNotContain(items, item => item.AvatarItemId <= 0);
|
|
Assert.Equal(items.Count, items.Select(item => item.AvatarItemId).Distinct().Count());
|
|
Assert.DoesNotContain(items, item => string.IsNullOrWhiteSpace(item.FriendlyName));
|
|
Assert.Contains(items, item => item.FriendlyName == "NFL Houston Texans Hat"
|
|
&& !string.IsNullOrWhiteSpace(item.ThumbnailImage));
|
|
// New client-only entries have no known store thumbnail; do not invent image URLs.
|
|
Assert.True(AvatarUnlockCatalog.NeedsMetadataEnrichment(items));
|
|
}
|
|
}
|