fix(dto): align daily_login_bonus DTOs with prod wire shape
- numeric fields ship as quoted strings (campaign_id, img, effect_id, reward_type, reward_detail_id, reward_number) - DailyLoginBonus.Campaign is a List<>, not a single object - LoginBonusCampaign.Img replaces Image (string, asset-key) - add IsOneDayMultiRewards (SpecialData-only on client, byte-faithful)
This commit is contained in:
@@ -3,16 +3,24 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Wire shape: { normal?, total?, campaign? (array) }. Client parser at
|
||||
/// LoadDetail.cs:553 reads exactly these three keys. <c>normal</c> presence drives
|
||||
/// IsExistLoginBonusData() / popup eligibility.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class DailyLoginBonus
|
||||
{
|
||||
[JsonPropertyName("total")]
|
||||
[Key("total")]
|
||||
public LoginBonusCampaign? Total { get; set; }
|
||||
[JsonPropertyName("normal")]
|
||||
[Key("normal")]
|
||||
[JsonPropertyName("normal")] [Key("normal")]
|
||||
public LoginBonusCampaign? Normal { get; set; }
|
||||
[JsonPropertyName("campaign")]
|
||||
[Key("campaign")]
|
||||
public LoginBonusCampaign? Campaign { get; set; }
|
||||
|
||||
[JsonPropertyName("total")] [Key("total")]
|
||||
public LoginBonusCampaign? Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of campaign panels (LoadDetail.cs:583 iterates jsonData3[i]). Empty list
|
||||
/// when no specials are active — prod sends [] not null in that case.
|
||||
/// </summary>
|
||||
[JsonPropertyName("campaign")] [Key("campaign")]
|
||||
public List<LoginBonusCampaign> Campaign { get; set; } = new();
|
||||
}
|
||||
@@ -3,25 +3,38 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// One login-bonus campaign panel. Used for normal, each campaign array entry, and (when
|
||||
/// populated) total. <c>campaign_id</c> and <c>img</c> ship as strings on the wire. Client
|
||||
/// consumers: NormalData (reads name, now_count, is_next_reward, reward[]),
|
||||
/// SpecialData (also reads start_date, end_date, img, is_one_day_multi_rewards),
|
||||
/// ContinuousData (reads only reward[]).
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class LoginBonusCampaign
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
[Key("name")]
|
||||
[JsonPropertyName("name")] [Key("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
[JsonPropertyName("campaign_id")]
|
||||
[Key("campaign_id")]
|
||||
public int CampaignId { get; set; }
|
||||
[JsonPropertyName("img")]
|
||||
[Key("img")]
|
||||
public int Image { get; set; }
|
||||
[JsonPropertyName("now_count")]
|
||||
[Key("now_count")]
|
||||
|
||||
[JsonPropertyName("campaign_id")] [Key("campaign_id")]
|
||||
public string CampaignId { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("img")] [Key("img")]
|
||||
public string Img { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("now_count")] [Key("now_count")]
|
||||
public int NowCount { get; set; }
|
||||
[JsonPropertyName("is_next_reward")]
|
||||
[Key("is_next_reward")]
|
||||
|
||||
[JsonPropertyName("is_next_reward")] [Key("is_next_reward")]
|
||||
public bool IsNextReward { get; set; }
|
||||
[JsonPropertyName("reward")]
|
||||
[Key("reward")]
|
||||
public List<LoginBonusReward> Rewards { get; set; } = new List<LoginBonusReward>();
|
||||
|
||||
[JsonPropertyName("reward")] [Key("reward")]
|
||||
public List<LoginBonusReward> Rewards { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// SpecialData-only on the client (SpecialData.cs:50). Server emits it on every panel
|
||||
/// for byte-faithful replay; client ignores it on Normal/Total paths.
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_one_day_multi_rewards")] [Key("is_one_day_multi_rewards")]
|
||||
public bool IsOneDayMultiRewards { get; set; }
|
||||
}
|
||||
@@ -3,34 +3,23 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// One day's reward in a login-bonus cycle. All numeric fields ship as quoted strings on
|
||||
/// the wire — prod-confirmed shape from traffic_prod_tutorial.ndjson. Client uses
|
||||
/// JsonData.ToInt() so it tolerates either form, but we match prod exactly.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class LoginBonusReward
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect shown ingame.
|
||||
/// </summary>
|
||||
[JsonPropertyName("effect_id")]
|
||||
[Key("effect_id")]
|
||||
public int EffectId { get; set; }
|
||||
[JsonPropertyName("effect_id")] [Key("effect_id")]
|
||||
public string EffectId { get; set; } = "0";
|
||||
|
||||
/// <summary>
|
||||
/// The type of reward.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reward_type")]
|
||||
[Key("reward_type")]
|
||||
public int RewardType { get; set; }
|
||||
[JsonPropertyName("reward_type")] [Key("reward_type")]
|
||||
public string RewardType { get; set; } = "0";
|
||||
|
||||
/// <summary>
|
||||
/// A more specified reward type (ie if a pack, which pack).
|
||||
/// </summary>
|
||||
[JsonPropertyName("reward_detail_id")]
|
||||
[Key("reward_detail_id")]
|
||||
public int RewardDetailId { get; set; }
|
||||
[JsonPropertyName("reward_detail_id")] [Key("reward_detail_id")]
|
||||
public string RewardDetailId { get; set; } = "0";
|
||||
|
||||
/// <summary>
|
||||
/// The count of the reward.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reward_number")]
|
||||
[Key("reward_number")]
|
||||
public int RewardNumber { get; set; }
|
||||
[JsonPropertyName("reward_number")] [Key("reward_number")]
|
||||
public string RewardNumber { get; set; } = "0";
|
||||
}
|
||||
@@ -149,8 +149,10 @@ public class IndexResponse
|
||||
public List<UserRankedMatches> UserRankedMatches { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Spec: optional. Shape is {normal?, total?, campaign?[]} per common/types.ts.md DailyLoginBonus.
|
||||
/// Populated by ILoginBonusService when the viewer has an active bonus period; null otherwise.
|
||||
/// Spec: optional. Shape {normal?, total?, campaign?[]} — client parses three keys
|
||||
/// in LoadDetail.cs:553. <c>normal</c> presence triggers the login-bonus popup
|
||||
/// (NextSceneSwitcher.cs:103). Populated by ILoginBonusService at /load/index time;
|
||||
/// null when the viewer has already claimed today's bonus.
|
||||
/// </summary>
|
||||
[JsonPropertyName("daily_login_bonus")]
|
||||
[Key("daily_login_bonus")]
|
||||
|
||||
59
SVSim.UnitTests/Dtos/DailyLoginBonusWireShapeTests.cs
Normal file
59
SVSim.UnitTests/Dtos/DailyLoginBonusWireShapeTests.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Text.Json;
|
||||
using NUnit.Framework;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
namespace SVSim.UnitTests.Dtos;
|
||||
|
||||
public class DailyLoginBonusWireShapeTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions Opts = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void Reward_numeric_fields_serialize_as_strings()
|
||||
{
|
||||
var r = new LoginBonusReward
|
||||
{
|
||||
EffectId = "1", RewardType = "9", RewardDetailId = "0", RewardNumber = "20"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(r, Opts);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.That(doc.RootElement.GetProperty("effect_id").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
Assert.That(doc.RootElement.GetProperty("reward_type").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
Assert.That(doc.RootElement.GetProperty("reward_detail_id").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
Assert.That(doc.RootElement.GetProperty("reward_number").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Campaign_id_and_img_serialize_as_strings()
|
||||
{
|
||||
var c = new LoginBonusCampaign { CampaignId = "3", Img = "0", Name = "Daily Bonus" };
|
||||
var json = JsonSerializer.Serialize(c, Opts);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.That(doc.RootElement.GetProperty("campaign_id").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
Assert.That(doc.RootElement.GetProperty("img").ValueKind, Is.EqualTo(JsonValueKind.String));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Campaign_supports_is_one_day_multi_rewards_flag()
|
||||
{
|
||||
var c = new LoginBonusCampaign { IsOneDayMultiRewards = false };
|
||||
var json = JsonSerializer.Serialize(c, Opts);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.That(doc.RootElement.TryGetProperty("is_one_day_multi_rewards", out var v), Is.True);
|
||||
Assert.That(v.ValueKind, Is.EqualTo(JsonValueKind.False));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DailyLoginBonus_campaign_is_an_array()
|
||||
{
|
||||
var d = new DailyLoginBonus { Normal = new LoginBonusCampaign(), Campaign = new List<LoginBonusCampaign>() };
|
||||
var json = JsonSerializer.Serialize(d, Opts);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.That(doc.RootElement.GetProperty("campaign").ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user