Radicade Public Code

This commit is contained in:
JogmansonReal
2026-10-16 23:33:13 -07:00
commit 55175acdf6
832 changed files with 363261 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using RadicadeServer.API.Compatibility;
using RadicadeServer.API.Config;
using RadicadeServer.API.Storefront;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Accounts;
using RadicadeServer.RecNet.Avatar;
using RadicadeServer.RecNet.Enums;
using RadicadeServer.RecNet.Enums.Store;
using RadicadeServer.RecNet.Store;
using Xunit;
namespace RadicadeServer.Tests;
/// <summary>
/// The free-gift button and the "Star Boxes" in the Rec Center storefront.
///
/// Three separate causes sat behind the gift complaints: the free-gift button config answered
/// <c>false</c> (the client's "this feature has been temporarily disabled" screen), sendmultiple
/// answered an empty array where the client reads a two-field result (so a 200 still surfaced as
/// "Failed to SendFreeGift"), and a star box - a drop that names a rarity instead of an item - was
/// granted empty, so buying the 2/3/4-star box did nothing at all.
/// </summary>
public class GiftClusterTests
{
private const long BuyerId = 55;
private static DbContextOptions<RecDBContext> MemoryOptions() =>
TestDbOptions.Create()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
/// <summary>
/// The purchase path opens its own transaction, which the in-memory provider refuses, so this
/// suite uses SQLite - the same reason the registration tests do.
/// </summary>
/// <summary>
/// The star boxes are resolved from the avatar catalogue the server ships, so the test loads the
/// same files the server does. Without it the catalogue is the one-item starter fallback and no
/// box could resolve anything, which would make the assertion below pass for the wrong reason.
/// </summary>
private static void LoadShippedCatalog() => DBConfig.LoadFiles();
private static async Task<(SqliteConnection Connection, RecDBContext Db)> OpenDbAsync()
{
var connection = new SqliteConnection("DataSource=:memory:");
await connection.OpenAsync();
var options = TestDbOptions.Create().UseSqlite(connection).Options;
var db = new RecDBContext(options);
await db.Database.EnsureCreatedAsync();
return (connection, db);
}
[Fact]
public void TheFreeGiftButtonIsReportedAsEnabled()
{
using var db = new RecDBContext(MemoryOptions());
var result = Assert.IsType<OkObjectResult>(new ConfigAPI(db).GetFreeGiftButtonConfig());
Assert.Equal(true, result.Value);
}
[Fact]
public async Task SendMultipleAnswersTheClientsTwoFieldResult()
{
// The response DTO (RecNet.OMJLLFDFEJL) is a success flag and an error string, so even a
// refusal has to come back in that shape - the empty array this route once answered could
// not be read into it and surfaced as "Failed to SendFreeGift" against a 200. The send
// itself (boxes, items, the daily cooldown) is covered by FriendotronTests.
using var db = new RecDBContext(MemoryOptions());
var api = new CompatibilityAPI(db);
var identity = new ClaimsIdentity("TestAuth");
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, BuyerId.ToString()));
api.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }
};
var result = Assert.IsType<OkObjectResult>(await api.SendFreeGifts());
using var document = JsonDocument.Parse(JsonSerializer.Serialize(result.Value));
Assert.False(document.RootElement.GetProperty("ALOCDGMHFKA").GetBoolean());
Assert.Equal("No recipients were given.", document.RootElement.GetProperty("FBILGKFKFJE").GetString());
}
[Fact]
public async Task SendMultipleRefusesACallWithNoResolvedAccount()
{
using var db = new RecDBContext(MemoryOptions());
var api = new CompatibilityAPI(db)
{
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity()) }
}
};
Assert.IsType<UnauthorizedResult>(await api.SendFreeGifts());
}
[Theory]
[InlineData(541, 10)]
[InlineData(538, 20)]
[InlineData(539, 30)]
public async Task AStarBoxGrantsARandomCatalogueItemOfThatRarity(int purchasableItemId, int expectedRarity)
{
LoadShippedCatalog();
var (connection, db) = await OpenDbAsync();
await using var disposeConnection = connection;
await using var disposeDb = db;
db.Currency.Add(new Currency { AccountId = BuyerId, RecTokens = 100_000 });
await db.SaveChangesAsync();
var outcome = await StorefrontShared.ExecutePurchaseAsync(
db, BuyerId, StorefrontTypes.RecCenter, purchasableItemId,
CurrencyType.RecCenterTokens, null, null);
Assert.True(outcome.Success, outcome.ErrorDetail);
var update = Assert.Single(outcome.Response!.BalanceUpdates);
Assert.Equal(UpdateResponseTypes.OK, update.UpdateResponse);
var gifted = Assert.Single(update.Data);
Assert.False(gifted.IsQuery);
Assert.False(string.IsNullOrWhiteSpace(gifted.FriendlyName));
Assert.Equal(expectedRarity, (int)gifted.Rarity);
Assert.False(string.IsNullOrWhiteSpace(gifted.AvatarItemDesc));
// The box must hand over a real catalogued item of that rarity - not an empty drop.
var catalogueItem = AvatarUnlockCatalog.FindItem(gifted.AvatarItemDesc);
Assert.NotNull(catalogueItem);
Assert.Equal(expectedRarity, catalogueItem!.Rarity);
var row = await db.Gifts.AsNoTracking().SingleAsync(x => x.AccountId == BuyerId);
Assert.Contains(StorefrontShared.ParseGiftList(row.Data),
pending => pending.AvatarItemDesc == gifted.AvatarItemDesc);
}
}