feat(inventory): add GrantSource enum + message lookup

Introduces GrantSource (17 values, DB-persisted) and GrantSourceMessages.For()
for the item-acquire-history feature. Values 1/2 mirror prod captures;
coverage test verifies every enum value has a non-empty message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-09 14:08:01 -04:00
parent 998402ebc3
commit 9f65326449
2 changed files with 87 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
namespace SVSim.Database.Services.Inventory;
/// <summary>
/// Logical source of a grant routed through <see cref="IInventoryTransaction.GrantAsync"/>.
/// Stored verbatim in <c>viewer_acquire_history.AcquireType</c> and surfaced on the
/// <c>/item_acquire_history/info</c> wire as <c>acquire_type</c>.
/// </summary>
/// <remarks>
/// Values are persisted to the database — renumbering after ship requires a migration.
/// Values 1 and 2 mirror the prod capture in
/// <c>data_dumps/captures/traffic_prod_misc_clicking.ndjson</c>; the rest are our own.
/// </remarks>
public enum GrantSource
{
Unknown = 0,
DailyBonus = 1,
PackOpen = 2,
PuzzleReward = 3,
StoryFinish = 4,
BattlePassClaim = 5,
MissionReward = 6,
ArenaTwoPickFinish = 7,
ItemPurchase = 8,
BuildDeckBuy = 9,
SleeveBuy = 10,
LeaderSkinBuy = 11,
GachaPointExchange = 12,
AchievementReward = 13,
SerialCodeRedeem = 14,
CardCosmeticCascade = 15,
AdminGrant = 99,
}
/// <summary>
/// Pre-localized text written into the <c>message</c> field of an item-acquire-history row.
/// The client renders this string verbatim, so all entries are user-facing English.
/// </summary>
public static class GrantSourceMessages
{
public static string For(GrantSource source) => source switch
{
GrantSource.Unknown => "Unknown",
GrantSource.DailyBonus => "Daily Bonus",
GrantSource.PackOpen => "From buying card packs",
GrantSource.PuzzleReward => "From puzzle reward",
GrantSource.StoryFinish => "From story reward",
GrantSource.BattlePassClaim => "From battle pass reward",
GrantSource.MissionReward => "From mission reward",
GrantSource.ArenaTwoPickFinish => "From 2Pick reward",
GrantSource.ItemPurchase => "From shop purchase",
GrantSource.BuildDeckBuy => "From starter set purchase",
GrantSource.SleeveBuy => "From sleeve purchase",
GrantSource.LeaderSkinBuy => "From leader skin purchase",
GrantSource.GachaPointExchange => "From point exchange",
GrantSource.AchievementReward => "From achievement reward",
GrantSource.SerialCodeRedeem => "From serial code",
GrantSource.CardCosmeticCascade => "Card cosmetic",
GrantSource.AdminGrant => "From admin grant",
_ => throw new ArgumentOutOfRangeException(nameof(source), source, "Unhandled GrantSource"),
};
}

View File

@@ -0,0 +1,26 @@
using SVSim.Database.Services.Inventory;
namespace SVSim.UnitTests.Services.Inventory;
public class InventoryHistoryTests
{
[Test]
public void GrantSourceMessages_returns_known_messages_for_seeded_sources()
{
Assert.That(GrantSourceMessages.For(GrantSource.DailyBonus), Is.EqualTo("Daily Bonus"));
Assert.That(GrantSourceMessages.For(GrantSource.PackOpen), Is.EqualTo("From buying card packs"));
Assert.That(GrantSourceMessages.For(GrantSource.CardCosmeticCascade), Is.EqualTo("Card cosmetic"));
Assert.That(GrantSourceMessages.For(GrantSource.Unknown), Is.EqualTo("Unknown"));
}
[Test]
public void GrantSourceMessages_covers_every_enum_value()
{
foreach (GrantSource source in Enum.GetValues<GrantSource>())
{
var message = GrantSourceMessages.For(source);
Assert.That(message, Is.Not.Null.And.Not.Empty,
$"GrantSource.{source} has no message defined.");
}
}
}