Merge story-build-trial-decks: serve build/trial/default deck lists on get_deck_list
This commit is contained in:
1346
SVSim.Bootstrap/Data/seeds/story-decks.json
Normal file
1346
SVSim.Bootstrap/Data/seeds/story-decks.json
Normal file
File diff suppressed because it is too large
Load Diff
52
SVSim.Bootstrap/Importers/StoryDeckImporter.cs
Normal file
52
SVSim.Bootstrap/Importers/StoryDeckImporter.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent upsert of story-deck presentation rows from <c>seeds/story-decks.json</c>.
|
||||
/// Card lists are NOT imported here — they belong to BuildDeckProductEntry (deck_no == product_id),
|
||||
/// so this importer should run AFTER BuildDeckImporter.ImportPackageAsync. Rows missing from the
|
||||
/// seed are left intact.
|
||||
/// </summary>
|
||||
public class StoryDeckImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var seed = SeedLoader.LoadList<StoryDeckSeed>(Path.Combine(seedDir, "story-decks.json"));
|
||||
if (seed.Count == 0)
|
||||
{
|
||||
Console.WriteLine("[StoryDeckImporter] No seed rows; skipping.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var existing = await context.StoryDecks.ToDictionaryAsync(e => e.Id);
|
||||
int created = 0, updated = 0;
|
||||
|
||||
foreach (var s in seed)
|
||||
{
|
||||
if (s.DeckNo == 0) continue;
|
||||
var entry = existing.TryGetValue(s.DeckNo, out var ex) ? ex : new StoryDeckEntry { DeckNo = s.DeckNo };
|
||||
entry.Kind = string.Equals(s.Kind, "trial", StringComparison.OrdinalIgnoreCase)
|
||||
? StoryDeckKind.Trial : StoryDeckKind.Build;
|
||||
entry.ClassId = s.ClassId;
|
||||
entry.DeckName = s.DeckName;
|
||||
entry.SleeveId = s.SleeveId;
|
||||
entry.LeaderSkinId = s.LeaderSkinId;
|
||||
entry.IsRecommend = s.IsRecommend;
|
||||
entry.OrderNum = s.OrderNum;
|
||||
entry.EntryNo = s.EntryNo;
|
||||
entry.DeckFormat = s.DeckFormat;
|
||||
|
||||
if (ex is null) { context.StoryDecks.Add(entry); existing[s.DeckNo] = entry; created++; }
|
||||
else updated++;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[StoryDeckImporter] +{created}/~{updated}");
|
||||
return created + updated;
|
||||
}
|
||||
}
|
||||
17
SVSim.Bootstrap/Models/Seed/StoryDeckSeed.cs
Normal file
17
SVSim.Bootstrap/Models/Seed/StoryDeckSeed.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
public sealed class StoryDeckSeed
|
||||
{
|
||||
[JsonPropertyName("deck_no")] public int DeckNo { get; set; }
|
||||
[JsonPropertyName("kind")] public string Kind { get; set; } = "build";
|
||||
[JsonPropertyName("class_id")] public int ClassId { get; set; }
|
||||
[JsonPropertyName("deck_name")] public string DeckName { get; set; } = "";
|
||||
[JsonPropertyName("sleeve_id")] public int SleeveId { get; set; }
|
||||
[JsonPropertyName("leader_skin_id")] public int LeaderSkinId { get; set; }
|
||||
[JsonPropertyName("is_recommend")] public int IsRecommend { get; set; }
|
||||
[JsonPropertyName("order_num")] public int OrderNum { get; set; }
|
||||
[JsonPropertyName("entry_no")] public int EntryNo { get; set; }
|
||||
[JsonPropertyName("deck_format")] public int? DeckFormat { get; set; }
|
||||
}
|
||||
@@ -124,6 +124,7 @@ public static class Program
|
||||
await buildDeck.ImportSeriesAsync(context, opts.ReferenceDataDir);
|
||||
await buildDeck.ImportCatalogAsync(context, opts.SeedDir);
|
||||
await buildDeck.ImportPackageAsync(context, opts.ReferenceDataDir);
|
||||
await new StoryDeckImporter().ImportAsync(context, opts.SeedDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
11
SVSim.Database/Enums/StoryDeckKind.cs
Normal file
11
SVSim.Database/Enums/StoryDeckKind.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace SVSim.Database.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Which story deck-select group a prebuilt deck belongs to. Build = the named story decks
|
||||
/// (build_deck_list); Trial = archetype trial decks (trial_deck_list). Stored as int.
|
||||
/// </summary>
|
||||
public enum StoryDeckKind
|
||||
{
|
||||
Build = 0,
|
||||
Trial = 1,
|
||||
}
|
||||
3823
SVSim.Database/Migrations/20260529142631_AddStoryDeck.Designer.cs
generated
Normal file
3823
SVSim.Database/Migrations/20260529142631_AddStoryDeck.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
SVSim.Database/Migrations/20260529142631_AddStoryDeck.cs
Normal file
45
SVSim.Database/Migrations/20260529142631_AddStoryDeck.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SVSim.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStoryDeck : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoryDecks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false),
|
||||
DeckNo = table.Column<int>(type: "integer", nullable: false),
|
||||
Kind = table.Column<int>(type: "integer", nullable: false),
|
||||
ClassId = table.Column<int>(type: "integer", nullable: false),
|
||||
DeckName = table.Column<string>(type: "text", nullable: false),
|
||||
SleeveId = table.Column<int>(type: "integer", nullable: false),
|
||||
LeaderSkinId = table.Column<int>(type: "integer", nullable: false),
|
||||
IsRecommend = table.Column<int>(type: "integer", nullable: false),
|
||||
OrderNum = table.Column<int>(type: "integer", nullable: false),
|
||||
EntryNo = table.Column<int>(type: "integer", nullable: false),
|
||||
DeckFormat = table.Column<int>(type: "integer", nullable: true),
|
||||
DateCreated = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DateUpdated = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StoryDecks", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoryDecks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2240,6 +2240,53 @@ namespace SVSim.Database.Migrations
|
||||
b.ToTable("SpotCardExchangeCatalog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SVSim.Database.Models.StoryDeckEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ClassId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime?>("DateUpdated")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("DeckFormat")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DeckName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("DeckNo")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("EntryNo")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("IsRecommend")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("LeaderSkinId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("OrderNum")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SleeveId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("StoryDecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SVSim.Database.Models.UnlimitedRestrictionEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
|
||||
28
SVSim.Database/Models/StoryDeckEntry.cs
Normal file
28
SVSim.Database/Models/StoryDeckEntry.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using SVSim.Database.Common;
|
||||
using SVSim.Database.Enums;
|
||||
|
||||
namespace SVSim.Database.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Presentation metadata for a story-mode prebuilt/trial deck, as surfaced under
|
||||
/// main_story/get_deck_list's build_deck_list / trial_deck_list. PK (DeckNo) equals the deck's
|
||||
/// wire deck_no, which also equals BuildDeckProductEntry.Id — the 40-card list is read from that
|
||||
/// product (single source of truth), NOT stored here. Sourced from
|
||||
/// data_dumps/traffic_prod_trial_decks.ndjson via seeds/story-decks.json.
|
||||
/// </summary>
|
||||
public class StoryDeckEntry : BaseEntity<int>
|
||||
{
|
||||
public int DeckNo { get => Id; set => Id = value; } // == BuildDeckProductEntry.Id
|
||||
|
||||
public StoryDeckKind Kind { get; set; }
|
||||
public int ClassId { get; set; }
|
||||
public string DeckName { get; set; } = string.Empty;
|
||||
public int SleeveId { get; set; }
|
||||
public int LeaderSkinId { get; set; }
|
||||
public int IsRecommend { get; set; }
|
||||
public int OrderNum { get; set; }
|
||||
public int EntryNo { get; set; }
|
||||
|
||||
/// <summary>Trial decks carry a deck_format on the wire; build decks do not (null).</summary>
|
||||
public int? DeckFormat { get; set; }
|
||||
}
|
||||
@@ -64,4 +64,38 @@ public class BuildDeckRepository : IBuildDeckRepository
|
||||
await _db.SaveChangesAsync();
|
||||
return row.PurchaseCount;
|
||||
}
|
||||
|
||||
public async Task<List<StoryDeckView>> GetStoryDecksByClass(int classId)
|
||||
{
|
||||
var decks = await _db.StoryDecks.Where(d => d.ClassId == classId).ToListAsync();
|
||||
if (decks.Count == 0) return new();
|
||||
|
||||
var ids = decks.Select(d => d.DeckNo).ToList();
|
||||
var products = await _db.BuildDeckProducts
|
||||
.Where(p => ids.Contains(p.Id))
|
||||
.Include(p => p.Cards)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
|
||||
// Expand each product's owned card rows by Number into a flat card_id list (spots included —
|
||||
// validated against the prod capture, 112/112 match).
|
||||
var cardsById = products.ToDictionary(
|
||||
p => p.Id,
|
||||
p => p.Cards.SelectMany(c => Enumerable.Repeat(c.CardId, c.Number)).ToList());
|
||||
|
||||
return decks.Select(d => new StoryDeckView
|
||||
{
|
||||
DeckNo = d.DeckNo,
|
||||
Kind = d.Kind,
|
||||
ClassId = d.ClassId,
|
||||
DeckName = d.DeckName,
|
||||
SleeveId = d.SleeveId,
|
||||
LeaderSkinId = d.LeaderSkinId,
|
||||
IsRecommend = d.IsRecommend,
|
||||
OrderNum = d.OrderNum,
|
||||
EntryNo = d.EntryNo,
|
||||
DeckFormat = d.DeckFormat,
|
||||
CardIdArray = cardsById.TryGetValue(d.DeckNo, out var cards) ? cards : new(),
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,11 @@ public interface IBuildDeckRepository
|
||||
/// Returns the new total.
|
||||
/// </summary>
|
||||
Task<int> IncrementPurchaseCount(long viewerId, int productId);
|
||||
|
||||
/// <summary>
|
||||
/// Story deck-select decks for a class: StoryDeckEntry presentation rows joined to the matching
|
||||
/// BuildDeckProductEntry card lists (deck_no == product_id), expanded to a flat card_id array.
|
||||
/// Returns build and trial decks together; the caller splits by Kind.
|
||||
/// </summary>
|
||||
Task<List<StoryDeckView>> GetStoryDecksByClass(int classId);
|
||||
}
|
||||
|
||||
22
SVSim.Database/Repositories/BuildDeck/StoryDeckView.cs
Normal file
22
SVSim.Database/Repositories/BuildDeck/StoryDeckView.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using SVSim.Database.Enums;
|
||||
|
||||
namespace SVSim.Database.Repositories.BuildDeck;
|
||||
|
||||
/// <summary>
|
||||
/// A story-select deck ready for the wire: presentation metadata from StoryDeckEntry plus the
|
||||
/// 40-card list expanded from the matching BuildDeckProductEntry. Plain projection, not an entity.
|
||||
/// </summary>
|
||||
public sealed class StoryDeckView
|
||||
{
|
||||
public int DeckNo { get; init; }
|
||||
public StoryDeckKind Kind { get; init; }
|
||||
public int ClassId { get; init; }
|
||||
public string DeckName { get; init; } = string.Empty;
|
||||
public int SleeveId { get; init; }
|
||||
public int LeaderSkinId { get; init; }
|
||||
public int IsRecommend { get; init; }
|
||||
public int OrderNum { get; init; }
|
||||
public int EntryNo { get; init; }
|
||||
public int? DeckFormat { get; init; }
|
||||
public List<long> CardIdArray { get; init; } = new();
|
||||
}
|
||||
@@ -70,6 +70,7 @@ public class SVSimDbContext : DbContext
|
||||
public DbSet<PackConfigEntry> Packs => Set<PackConfigEntry>();
|
||||
public DbSet<BuildDeckSeriesEntry> BuildDeckSeries => Set<BuildDeckSeriesEntry>();
|
||||
public DbSet<BuildDeckProductEntry> BuildDeckProducts => Set<BuildDeckProductEntry>();
|
||||
public DbSet<StoryDeckEntry> StoryDecks => Set<StoryDeckEntry>();
|
||||
public DbSet<SleeveShopSeriesEntry> SleeveShopSeries => Set<SleeveShopSeriesEntry>();
|
||||
public DbSet<SleeveShopProductEntry> SleeveShopProducts => Set<SleeveShopProductEntry>();
|
||||
public DbSet<ItemPurchaseCatalogEntry> ItemPurchaseCatalog => Set<ItemPurchaseCatalogEntry>();
|
||||
|
||||
27
SVSim.EmulatedEntrypoint/Models/Dtos/Common/TrialDeck.cs
Normal file
27
SVSim.EmulatedEntrypoint/Models/Dtos/Common/TrialDeck.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
|
||||
/// <summary>
|
||||
/// One archetype trial deck under <c>trial_deck_list</c> (DeckAttributeType.TrialDeck). Wire shape
|
||||
/// from the 2026-05-29 main_story/get_deck_list capture. Distinct from build decks: carries
|
||||
/// <c>deck_format</c> and no order_num/leader-skin-list. card_id_array only (no numbered card_id_N).
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class TrialDeck
|
||||
{
|
||||
[JsonPropertyName("deck_no")] [Key("deck_no")] public int DeckNo { get; set; }
|
||||
[JsonPropertyName("class_id")] [Key("class_id")] public int ClassId { get; set; }
|
||||
[JsonPropertyName("sleeve_id")] [Key("sleeve_id")] public int SleeveId { get; set; }
|
||||
[JsonPropertyName("leader_skin_id")] [Key("leader_skin_id")] public int LeaderSkinId { get; set; }
|
||||
[JsonPropertyName("deck_name")] [Key("deck_name")] public string DeckName { get; set; } = string.Empty;
|
||||
[JsonPropertyName("card_id_array")] [Key("card_id_array")] public List<long> CardIdArray { get; set; } = new();
|
||||
[JsonPropertyName("is_complete_deck")] [Key("is_complete_deck")] public int IsCompleteDeck { get; set; } = 1;
|
||||
[JsonPropertyName("restricted_card_exists")] [Key("restricted_card_exists")] public bool RestrictedCardExists { get; set; }
|
||||
[JsonPropertyName("is_available_deck")] [Key("is_available_deck")] public int IsAvailableDeck { get; set; } = 1;
|
||||
[JsonPropertyName("maintenance_card_ids")] [Key("maintenance_card_ids")] public List<long> MaintenanceCardIds { get; set; } = new();
|
||||
[JsonPropertyName("is_include_un_possession_card")] [Key("is_include_un_possession_card")] public bool IsIncludeUnPossessionCard { get; set; }
|
||||
[JsonPropertyName("deck_format")] [Key("deck_format")] public int DeckFormat { get; set; }
|
||||
[JsonPropertyName("is_recommend")] [Key("is_recommend")] public int IsRecommend { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MessagePack;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Deck;
|
||||
@@ -53,11 +54,11 @@ public class DeckListResponse
|
||||
[Key("user_leader_skin_setting_list")] public Dictionary<string, UserLeaderSkinSetting> UserLeaderSkinSettingList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Trial / tutorial-specific decks. Prod emits this on <c>/deck/info</c> (All format) but
|
||||
/// OMITS the key entirely on <c>/deck/my_list</c> (specific-format) — controller mirrors that
|
||||
/// asymmetry by leaving this null on specific-format responses. Empty array in the
|
||||
/// 2026-05-23 prod capture; entry shape TBD.
|
||||
/// Trial / archetype decks. Prod emits this on <c>/deck/info</c> (All format) but OMITS the key
|
||||
/// entirely on <c>/deck/my_list</c> (specific-format) — controller mirrors that asymmetry by
|
||||
/// leaving this null on specific-format responses. Emitted EMPTY on /deck/info (matches the
|
||||
/// 2026-05-23 prod capture); story/get_deck_list is where trial decks are actually populated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("trial_deck_list")]
|
||||
[Key("trial_deck_list")] public List<UserDeck>? TrialDeckList { get; set; }
|
||||
[Key("trial_deck_list")] public List<TrialDeck>? TrialDeckList { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Story;
|
||||
@@ -30,10 +32,36 @@ public class GetDeckListResponse
|
||||
[JsonPropertyName("build_deck_list")]
|
||||
[Key("build_deck_list")]
|
||||
public List<BuildDeck> BuildDeckList { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("trial_deck_list")]
|
||||
[Key("trial_deck_list")]
|
||||
public List<TrialDeck> TrialDeckList { get; set; } = new();
|
||||
|
||||
/// <summary>Global starter decks, keyed by deck_no string (prod ids 91-98, one per class).</summary>
|
||||
[JsonPropertyName("default_deck_list")]
|
||||
[Key("default_deck_list")]
|
||||
public Dictionary<string, DefaultDeck> DefaultDeckList { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One named prebuilt story deck under <c>build_deck_list</c> (DeckAttributeType.BuildDeck). Wire
|
||||
/// shape from the 2026-05-29 capture. Emits card_id_array only — the numbered card_id_1..40 keys
|
||||
/// prod also sends are omitted (default/trial entries omit them and parse fine).
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class BuildDeck
|
||||
{
|
||||
// Placeholder — build decks return [] for v1 per spec.
|
||||
[JsonPropertyName("deck_no")] [Key("deck_no")] public int DeckNo { get; set; }
|
||||
[JsonPropertyName("order_num")] [Key("order_num")] public int OrderNum { get; set; }
|
||||
[JsonPropertyName("class_id")] [Key("class_id")] public int ClassId { get; set; }
|
||||
[JsonPropertyName("sleeve_id")] [Key("sleeve_id")] public int SleeveId { get; set; }
|
||||
[JsonPropertyName("leader_skin_id")] [Key("leader_skin_id")] public int LeaderSkinId { get; set; }
|
||||
[JsonPropertyName("entry_no")] [Key("entry_no")] public int EntryNo { get; set; }
|
||||
[JsonPropertyName("create_deck_time")] [Key("create_deck_time")] public DateTime? CreateDeckTime { get; set; }
|
||||
[JsonPropertyName("deck_name")] [Key("deck_name")] public string DeckName { get; set; } = string.Empty;
|
||||
[JsonPropertyName("card_id_array")] [Key("card_id_array")] public List<long> CardIdArray { get; set; } = new();
|
||||
[JsonPropertyName("is_complete_deck")] [Key("is_complete_deck")] public int IsCompleteDeck { get; set; } = 1;
|
||||
[JsonPropertyName("is_available_deck")] [Key("is_available_deck")] public int IsAvailableDeck { get; set; } = 1;
|
||||
[JsonPropertyName("maintenance_card_ids")] [Key("maintenance_card_ids")] public List<long> MaintenanceCardIds { get; set; } = new();
|
||||
[JsonPropertyName("is_recommend")] [Key("is_recommend")] public int IsRecommend { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Entities.Story;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models.Config;
|
||||
using SVSim.Database.Repositories.Deck;
|
||||
using SVSim.Database.Repositories.BuildDeck;
|
||||
using SVSim.Database.Services;
|
||||
using SVSim.Database.Repositories.Story;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Story;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Services;
|
||||
@@ -19,6 +23,7 @@ public class StoryService : IStoryService
|
||||
private readonly SVSimDbContext _db;
|
||||
private readonly IGameConfigService _configService;
|
||||
private readonly IDeckRepository _deckRepository;
|
||||
private readonly IBuildDeckRepository _buildDecks;
|
||||
private readonly ILogger<StoryService> _logger;
|
||||
|
||||
public StoryService(
|
||||
@@ -28,6 +33,7 @@ public class StoryService : IStoryService
|
||||
SVSimDbContext db,
|
||||
IGameConfigService configService,
|
||||
IDeckRepository deckRepository,
|
||||
IBuildDeckRepository buildDecks,
|
||||
ILogger<StoryService> logger)
|
||||
{
|
||||
_master = master;
|
||||
@@ -36,6 +42,7 @@ public class StoryService : IStoryService
|
||||
_db = db;
|
||||
_configService = configService;
|
||||
_deckRepository = deckRepository;
|
||||
_buildDecks = buildDecks;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -344,16 +351,86 @@ public class StoryService : IStoryService
|
||||
{
|
||||
var byFormat = await _deckRepository.GetDecksByFormats(
|
||||
viewerId, new[] { SVSim.Database.Enums.Format.Rotation, SVSim.Database.Enums.Format.Unlimited });
|
||||
return new GetDeckListResponse
|
||||
|
||||
var resp = new GetDeckListResponse
|
||||
{
|
||||
UserDeckRotation = byFormat[SVSim.Database.Enums.Format.Rotation]
|
||||
.Select(d => new SVSim.EmulatedEntrypoint.Models.Dtos.UserDeck(d)).ToList(),
|
||||
.Select(d => new UserDeck(d)).ToList(),
|
||||
UserDeckUnlimited = byFormat[SVSim.Database.Enums.Format.Unlimited]
|
||||
.Select(d => new SVSim.EmulatedEntrypoint.Models.Dtos.UserDeck(d)).ToList(),
|
||||
BuildDeckList = new List<BuildDeck>(), // v1: empty
|
||||
.Select(d => new UserDeck(d)).ToList(),
|
||||
MaintenanceCardList = new List<long>(),
|
||||
};
|
||||
|
||||
// The chapter's leader (CharaId == class_id 1-8 for standard classes) drives which
|
||||
// prebuilt/trial decks the story deck-select shows. Non-class chapters (custom leaders,
|
||||
// chara_id outside 1-8) get empty build/trial lists, matching prod.
|
||||
var chapter = await _master.GetChapterByIdAsync(storyId);
|
||||
int classId = chapter?.CharaId ?? 0;
|
||||
if (classId is >= 1 and <= 8)
|
||||
{
|
||||
var storyDecks = await _buildDecks.GetStoryDecksByClass(classId);
|
||||
resp.BuildDeckList = storyDecks
|
||||
.Where(d => d.Kind == StoryDeckKind.Build)
|
||||
.Select(ToBuildDeck).ToList();
|
||||
resp.TrialDeckList = storyDecks
|
||||
.Where(d => d.Kind == StoryDeckKind.Trial)
|
||||
.Select(ToTrialDeck).ToList();
|
||||
}
|
||||
|
||||
// default_deck_list — all 8 starter decks, keyed by deck_no string (same shape as /deck/info).
|
||||
var defaults = await _db.DefaultDecks.OrderBy(d => d.Id).ToListAsync();
|
||||
resp.DefaultDeckList = defaults.ToDictionary(
|
||||
d => d.Id.ToString(),
|
||||
d => new DefaultDeck
|
||||
{
|
||||
DeckNo = d.DeckNo,
|
||||
ClassId = d.ClassId,
|
||||
SleeveId = d.SleeveId,
|
||||
LeaderSkinId = d.LeaderSkinId,
|
||||
DeckName = d.DeckName,
|
||||
CardIdArray = JsonSerializer.Deserialize<List<long>>(d.CardIdArray) ?? new(),
|
||||
IsCompleteDeck = 1,
|
||||
IsAvailableDeck = 1,
|
||||
MaintenanceCardIds = new(),
|
||||
});
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static BuildDeck ToBuildDeck(StoryDeckView d) => new()
|
||||
{
|
||||
DeckNo = d.DeckNo,
|
||||
OrderNum = d.OrderNum,
|
||||
ClassId = d.ClassId,
|
||||
SleeveId = d.SleeveId,
|
||||
LeaderSkinId = d.LeaderSkinId,
|
||||
EntryNo = d.EntryNo,
|
||||
CreateDeckTime = null,
|
||||
DeckName = d.DeckName,
|
||||
CardIdArray = d.CardIdArray,
|
||||
IsCompleteDeck = 1,
|
||||
IsAvailableDeck = 1,
|
||||
MaintenanceCardIds = new(),
|
||||
IsRecommend = d.IsRecommend,
|
||||
};
|
||||
|
||||
private static TrialDeck ToTrialDeck(StoryDeckView d) => new()
|
||||
{
|
||||
DeckNo = d.DeckNo,
|
||||
ClassId = d.ClassId,
|
||||
SleeveId = d.SleeveId,
|
||||
LeaderSkinId = d.LeaderSkinId,
|
||||
DeckName = d.DeckName,
|
||||
CardIdArray = d.CardIdArray,
|
||||
IsCompleteDeck = 1,
|
||||
RestrictedCardExists = false,
|
||||
IsAvailableDeck = 1,
|
||||
MaintenanceCardIds = new(),
|
||||
IsIncludeUnPossessionCard = false,
|
||||
DeckFormat = d.DeckFormat ?? 0,
|
||||
IsRecommend = d.IsRecommend,
|
||||
};
|
||||
|
||||
public async Task<StartResponse> StartAsync(StoryApiType apiType, int[] storyIds, long viewerId)
|
||||
{
|
||||
var resp = new StartResponse();
|
||||
|
||||
49
SVSim.UnitTests/Importers/StoryDeckImporterTests.cs
Normal file
49
SVSim.UnitTests/Importers/StoryDeckImporterTests.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SVSim.Bootstrap.Importers;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Importers;
|
||||
|
||||
public class StoryDeckImporterTests
|
||||
{
|
||||
private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds");
|
||||
|
||||
[Test]
|
||||
public async Task Imports_story_decks_from_seed_file()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
await new StoryDeckImporter().ImportAsync(db, SeedDir);
|
||||
|
||||
var decks = await db.StoryDecks.OrderBy(d => d.Id).ToListAsync();
|
||||
Assert.That(decks.Count, Is.EqualTo(112), "53 build + 59 trial");
|
||||
Assert.That(decks.Count(d => d.Kind == StoryDeckKind.Build), Is.EqualTo(53));
|
||||
Assert.That(decks.Count(d => d.Kind == StoryDeckKind.Trial), Is.EqualTo(59));
|
||||
var pureDevotion = decks.Single(d => d.DeckNo == 701);
|
||||
Assert.That(pureDevotion.Kind, Is.EqualTo(StoryDeckKind.Build));
|
||||
Assert.That(pureDevotion.ClassId, Is.EqualTo(1));
|
||||
Assert.That(pureDevotion.DeckName, Is.EqualTo("Pure Devotion"));
|
||||
Assert.That(pureDevotion.DeckFormat, Is.Null);
|
||||
Assert.That(decks.Where(d => d.Kind == StoryDeckKind.Trial).All(d => d.DeckFormat != null), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Is_idempotent_on_rerun()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
await new StoryDeckImporter().ImportAsync(db, SeedDir);
|
||||
int before = await db.StoryDecks.CountAsync();
|
||||
await new StoryDeckImporter().ImportAsync(db, SeedDir);
|
||||
int after = await db.StoryDecks.CountAsync();
|
||||
|
||||
Assert.That(after, Is.EqualTo(before));
|
||||
}
|
||||
}
|
||||
66
SVSim.UnitTests/Repositories/StoryDeckRepositoryTests.cs
Normal file
66
SVSim.UnitTests/Repositories/StoryDeckRepositoryTests.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Repositories.BuildDeck;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Repositories;
|
||||
|
||||
public class StoryDeckRepositoryTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetStoryDecksByClass_returns_decks_with_expanded_card_arrays()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
// FK: BuildDeckProducts requires a parent BuildDeckSeries row.
|
||||
db.BuildDeckSeries.Add(new BuildDeckSeriesEntry { Id = 0 });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Product 701 (class 1 build): 2x card 100, 1x card 200 = 3-card "deck".
|
||||
db.BuildDeckProducts.Add(new BuildDeckProductEntry
|
||||
{
|
||||
Id = 701, SeriesId = 0, LeaderId = 1, DeckCode = "", ProductNameKey = "", IsEnabled = false,
|
||||
Cards = new()
|
||||
{
|
||||
new BuildDeckProductCardEntry { CardId = 100, Number = 2, IsSpot = false },
|
||||
new BuildDeckProductCardEntry { CardId = 200, Number = 1, IsSpot = false },
|
||||
},
|
||||
});
|
||||
db.StoryDecks.Add(new StoryDeckEntry
|
||||
{
|
||||
DeckNo = 701, Kind = StoryDeckKind.Build, ClassId = 1, DeckName = "Pure Devotion",
|
||||
SleeveId = 3000011, LeaderSkinId = 1, IsRecommend = 0, OrderNum = 0, EntryNo = 0, DeckFormat = null,
|
||||
});
|
||||
// A class-2 deck that must NOT be returned for class 1.
|
||||
db.StoryDecks.Add(new StoryDeckEntry { DeckNo = 702, Kind = StoryDeckKind.Build, ClassId = 2, DeckName = "Other" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var repo = new BuildDeckRepository(db);
|
||||
var result = await repo.GetStoryDecksByClass(1);
|
||||
|
||||
Assert.That(result.Count, Is.EqualTo(1));
|
||||
var deck = result[0];
|
||||
Assert.That(deck.DeckNo, Is.EqualTo(701));
|
||||
Assert.That(deck.DeckName, Is.EqualTo("Pure Devotion"));
|
||||
Assert.That(deck.Kind, Is.EqualTo(StoryDeckKind.Build));
|
||||
Assert.That(deck.CardIdArray.OrderBy(x => x), Is.EqualTo(new long[] { 100, 100, 200 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetStoryDecksByClass_returns_empty_for_class_with_no_decks()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
var repo = new BuildDeckRepository(db);
|
||||
var result = await repo.GetStoryDecksByClass(8);
|
||||
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
}
|
||||
85
SVSim.UnitTests/Story/GetDeckListWireShapeTests.cs
Normal file
85
SVSim.UnitTests/Story/GetDeckListWireShapeTests.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Story;
|
||||
|
||||
namespace SVSim.UnitTests.Story;
|
||||
|
||||
public class GetDeckListWireShapeTests
|
||||
{
|
||||
// Mirror Program.cs: keys come from [JsonPropertyName]; null values are dropped.
|
||||
private static readonly JsonSerializerOptions WireOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void BuildDeck_serializes_with_prod_wire_keys()
|
||||
{
|
||||
var deck = new BuildDeck
|
||||
{
|
||||
DeckNo = 701, OrderNum = 0, ClassId = 1, SleeveId = 3000011, LeaderSkinId = 1,
|
||||
EntryNo = 0, CreateDeckTime = null, DeckName = "Pure Devotion",
|
||||
CardIdArray = new() { 115141020, 114141020 },
|
||||
IsCompleteDeck = 1, IsAvailableDeck = 1, MaintenanceCardIds = new(), IsRecommend = 0,
|
||||
};
|
||||
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(deck, WireOptions));
|
||||
var root = doc.RootElement;
|
||||
|
||||
// Every key the client's BuildDeck branch reads must be present with the right name.
|
||||
foreach (var key in new[] { "deck_no", "order_num", "class_id", "sleeve_id", "leader_skin_id",
|
||||
"entry_no", "deck_name", "card_id_array", "is_complete_deck",
|
||||
"is_available_deck", "maintenance_card_ids", "is_recommend" })
|
||||
{
|
||||
Assert.That(root.TryGetProperty(key, out _), Is.True, $"missing wire key: {key}");
|
||||
}
|
||||
Assert.That(root.GetProperty("deck_name").GetString(), Is.EqualTo("Pure Devotion"));
|
||||
Assert.That(root.GetProperty("card_id_array").GetArrayLength(), Is.EqualTo(2));
|
||||
// Numbered card_id_N keys are intentionally omitted.
|
||||
Assert.That(root.TryGetProperty("card_id_1", out _), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TrialDeck_serializes_with_prod_wire_keys_including_deck_format()
|
||||
{
|
||||
var deck = new TrialDeck
|
||||
{
|
||||
DeckNo = 13001, ClassId = 1, SleeveId = 3000011, LeaderSkinId = 0,
|
||||
DeckName = "Tempo Forestcraft", CardIdArray = new() { 130141020 },
|
||||
IsCompleteDeck = 1, RestrictedCardExists = false, IsAvailableDeck = 1,
|
||||
MaintenanceCardIds = new(), IsIncludeUnPossessionCard = false, DeckFormat = 1, IsRecommend = 1,
|
||||
};
|
||||
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(deck, WireOptions));
|
||||
var root = doc.RootElement;
|
||||
|
||||
foreach (var key in new[] { "deck_no", "class_id", "sleeve_id", "leader_skin_id", "deck_name",
|
||||
"card_id_array", "is_complete_deck", "restricted_card_exists",
|
||||
"is_available_deck", "maintenance_card_ids",
|
||||
"is_include_un_possession_card", "deck_format", "is_recommend" })
|
||||
{
|
||||
Assert.That(root.TryGetProperty(key, out _), Is.True, $"missing wire key: {key}");
|
||||
}
|
||||
Assert.That(root.GetProperty("deck_format").GetInt32(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDeckListResponse_default_deck_list_is_a_keyed_object()
|
||||
{
|
||||
var resp = new GetDeckListResponse();
|
||||
resp.DefaultDeckList["91"] = new SVSim.EmulatedEntrypoint.Models.Dtos.DefaultDeck
|
||||
{
|
||||
DeckNo = 91, ClassId = 1, SleeveId = 3000011, LeaderSkinId = 0, DeckName = "Default",
|
||||
CardIdArray = new() { 100111010 },
|
||||
};
|
||||
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(resp, WireOptions));
|
||||
var root = doc.RootElement;
|
||||
|
||||
Assert.That(root.GetProperty("default_deck_list").ValueKind, Is.EqualTo(JsonValueKind.Object));
|
||||
Assert.That(root.GetProperty("default_deck_list").GetProperty("91").GetProperty("class_id").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(root.GetProperty("build_deck_list").ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
Assert.That(root.GetProperty("trial_deck_list").ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
}
|
||||
}
|
||||
74
SVSim.UnitTests/Story/StoryDeckListServiceTests.cs
Normal file
74
SVSim.UnitTests/Story/StoryDeckListServiceTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Entities.Story;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.EmulatedEntrypoint.Services;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Story;
|
||||
|
||||
[TestFixture]
|
||||
public class StoryDeckListServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetDeckList_populates_build_trial_and_default_for_chapter_class()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
// StoryChapter.SectionId has an enforced FK to StorySection; seed the parent row first.
|
||||
db.StorySections.Add(new StorySection { Id = 1, StoryApiType = StoryApiType.Main });
|
||||
// Chapter 14 is a class-1 (Forestcraft) chapter.
|
||||
db.StoryChapters.Add(new StoryChapter { StoryId = 14, SectionId = 1, CharaId = 1, ChapterId = "14" });
|
||||
|
||||
// One class-1 build deck (701) + one class-1 trial deck (13001), each with a 1-card product.
|
||||
// BuildDeckProductEntry has an enforced FK SeriesId -> BuildDeckSeries; seed the parent first.
|
||||
db.BuildDeckSeries.Add(new BuildDeckSeriesEntry { Id = 0 });
|
||||
db.BuildDeckProducts.Add(new BuildDeckProductEntry { Id = 701, Cards = new() { new BuildDeckProductCardEntry { CardId = 100, Number = 1 } } });
|
||||
db.BuildDeckProducts.Add(new BuildDeckProductEntry { Id = 13001, Cards = new() { new BuildDeckProductCardEntry { CardId = 200, Number = 1 } } });
|
||||
db.StoryDecks.Add(new StoryDeckEntry { DeckNo = 701, Kind = StoryDeckKind.Build, ClassId = 1, DeckName = "Pure Devotion", DeckFormat = null });
|
||||
db.StoryDecks.Add(new StoryDeckEntry { DeckNo = 13001, Kind = StoryDeckKind.Trial, ClassId = 1, DeckName = "Tempo Forestcraft", DeckFormat = 1 });
|
||||
|
||||
db.DefaultDecks.Add(new DefaultDeckEntry { Id = 91, ClassId = 1, SleeveId = 3000011, LeaderSkinId = 0, DeckName = "Default", CardIdArray = "[100,100,100]" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var service = scope.ServiceProvider.GetRequiredService<IStoryService>();
|
||||
var resp = await service.GetDeckListAsync(StoryApiType.Main, storyId: 14, viewerId: 1);
|
||||
|
||||
Assert.That(resp.BuildDeckList.Count, Is.EqualTo(1));
|
||||
Assert.That(resp.BuildDeckList[0].DeckNo, Is.EqualTo(701));
|
||||
Assert.That(resp.BuildDeckList[0].DeckName, Is.EqualTo("Pure Devotion"));
|
||||
Assert.That(resp.BuildDeckList[0].CardIdArray, Is.EqualTo(new long[] { 100 }));
|
||||
|
||||
Assert.That(resp.TrialDeckList.Count, Is.EqualTo(1));
|
||||
Assert.That(resp.TrialDeckList[0].DeckNo, Is.EqualTo(13001));
|
||||
Assert.That(resp.TrialDeckList[0].DeckFormat, Is.EqualTo(1));
|
||||
|
||||
Assert.That(resp.DefaultDeckList.ContainsKey("91"), Is.True);
|
||||
Assert.That(resp.DefaultDeckList["91"].CardIdArray, Is.EqualTo(new long[] { 100, 100, 100 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDeckList_returns_empty_build_trial_for_non_class_chapter()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
// StoryChapter.SectionId has an enforced FK to StorySection; seed the parent row first.
|
||||
db.StorySections.Add(new StorySection { Id = 17, StoryApiType = StoryApiType.Main });
|
||||
// chara_id 0 -> custom-leader / non-class chapter.
|
||||
db.StoryChapters.Add(new StoryChapter { StoryId = 500, SectionId = 17, CharaId = 0, ChapterId = "500" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var service = scope.ServiceProvider.GetRequiredService<IStoryService>();
|
||||
var resp = await service.GetDeckListAsync(StoryApiType.Main, storyId: 500, viewerId: 1);
|
||||
|
||||
Assert.That(resp.BuildDeckList, Is.Empty);
|
||||
Assert.That(resp.TrialDeckList, Is.Empty);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public class StoryServiceTests
|
||||
db: db,
|
||||
configService: StoryServiceTestHelpers.NewConfigService(),
|
||||
deckRepository: new Mock<SVSim.Database.Repositories.Deck.IDeckRepository>().Object,
|
||||
buildDecks: new Mock<SVSim.Database.Repositories.BuildDeck.IBuildDeckRepository>().Object,
|
||||
logger: NullLogger<StoryService>.Instance);
|
||||
}
|
||||
|
||||
@@ -72,6 +73,7 @@ public class StoryServiceTests
|
||||
db: db,
|
||||
configService: StoryServiceTestHelpers.NewConfigService(),
|
||||
deckRepository: new Mock<SVSim.Database.Repositories.Deck.IDeckRepository>().Object,
|
||||
buildDecks: new Mock<SVSim.Database.Repositories.BuildDeck.IBuildDeckRepository>().Object,
|
||||
logger: NullLogger<StoryService>.Instance);
|
||||
}
|
||||
|
||||
@@ -404,6 +406,7 @@ public class StoryServiceTests
|
||||
db: db,
|
||||
configService: StoryServiceTestHelpers.NewConfigService(),
|
||||
deckRepository: new Mock<SVSim.Database.Repositories.Deck.IDeckRepository>().Object,
|
||||
buildDecks: new Mock<SVSim.Database.Repositories.BuildDeck.IBuildDeckRepository>().Object,
|
||||
logger: NullLogger<StoryService>.Instance);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user