424 lines
20 KiB
C#
424 lines
20 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using SVSim.Database;
|
|
using SVSim.Database.Entities.Story;
|
|
using SVSim.Database.Models;
|
|
using SVSim.Database.Repositories.Story;
|
|
using SVSim.Database.Services;
|
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Story;
|
|
using SVSim.EmulatedEntrypoint.Services;
|
|
using SVSim.UnitTests.Infrastructure;
|
|
|
|
namespace SVSim.UnitTests.Story;
|
|
|
|
[TestFixture]
|
|
public class StoryServiceTests
|
|
{
|
|
private Mock<IStoryMasterRepository> _master = null!;
|
|
private Mock<IViewerStoryProgressRepository> _viewer = null!;
|
|
private StoryService _service = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_master = new Mock<IStoryMasterRepository>();
|
|
_viewer = new Mock<IViewerStoryProgressRepository>();
|
|
// Non-reward tests never exercise the DB/reward path; use a stub InMemory context.
|
|
var db = StoryServiceTestHelpers.NewInMemoryDb(nameof(SetUp));
|
|
var rewards = new RewardGrantService(db);
|
|
_service = new StoryService(
|
|
_master.Object, _viewer.Object,
|
|
rewards: rewards,
|
|
db: db,
|
|
configService: StoryServiceTestHelpers.NewConfigService(),
|
|
deckRepository: new Mock<SVSim.Database.Repositories.Deck.IDeckRepository>().Object,
|
|
logger: NullLogger<StoryService>.Instance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="StoryService"/> backed by a real <see cref="SVSimDbContext"/> from
|
|
/// <paramref name="factory"/>, seeds a viewer with RedEther reset to 0, and returns the
|
|
/// service + viewer's actual ID.
|
|
/// The caller owns the factory lifetime; keep it alive for post-call assertions.
|
|
/// </summary>
|
|
private StoryService NewServiceWithSeededViewer(
|
|
SVSimTestFactory factory,
|
|
out IServiceScope scope,
|
|
out long viewerId)
|
|
{
|
|
viewerId = factory.SeedViewerAsync().GetAwaiter().GetResult();
|
|
|
|
// Reset RedEther to 0 so tests can assert literal post-state totals (spec requirement).
|
|
var seedId = viewerId;
|
|
using (var resetScope = factory.Services.CreateScope())
|
|
{
|
|
var resetDb = resetScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var v = resetDb.Viewers.First(x => x.Id == seedId);
|
|
v.Currency.RedEther = 0;
|
|
resetDb.SaveChanges();
|
|
}
|
|
|
|
scope = factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var rewards = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
|
|
|
return new StoryService(
|
|
_master.Object,
|
|
_viewer.Object,
|
|
rewards: rewards,
|
|
db: db,
|
|
configService: StoryServiceTestHelpers.NewConfigService(),
|
|
deckRepository: new Mock<SVSim.Database.Repositories.Deck.IDeckRepository>().Object,
|
|
logger: NullLogger<StoryService>.Instance);
|
|
}
|
|
|
|
private static StoryChapter Ch(int storyId, int section, int chara, string chapId, string nextId,
|
|
bool battle = true, int? sbsId = null) =>
|
|
new() { StoryId = storyId, SectionId = section, CharaId = chara,
|
|
ChapterId = chapId, NextChapterId = nextId, BattleExists = battle,
|
|
SpecialBattleSettingId = sbsId, IsSkipEnabled = true };
|
|
|
|
[Test]
|
|
public async Task GetInfoAsync_chapter1_always_released_chapter2_locked_when_no_progress()
|
|
{
|
|
var chapters = new List<StoryChapter> {
|
|
Ch(100, 1, 2, "1", "2"),
|
|
Ch(101, 1, 2, "2", "3"),
|
|
};
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, 2)).ReturnsAsync(chapters);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
|
_viewer.Setup(v => v.GetBranchUnlockedStoryIdsAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new HashSet<int>());
|
|
|
|
var resp = await _service.GetInfoAsync(StoryApiType.Main, 1, 2, viewerId: 7L);
|
|
|
|
var ch1 = resp.StoryMasterList.Single(c => c.ChapterId == "1");
|
|
var ch2 = resp.StoryMasterList.Single(c => c.ChapterId == "2");
|
|
Assert.That(ch1.IsReleased, Is.True);
|
|
Assert.That(ch2.IsReleased, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetInfoAsync_chapter2_released_after_chapter1_finished()
|
|
{
|
|
var chapters = new List<StoryChapter> {
|
|
Ch(100, 1, 2, "1", "2"),
|
|
Ch(101, 1, 2, "2", "3"),
|
|
};
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, 2)).ReturnsAsync(chapters);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 100, new ViewerStoryProgress { ViewerId = 7, StoryId = 100, IsFinish = true } } });
|
|
_viewer.Setup(v => v.GetBranchUnlockedStoryIdsAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new HashSet<int>());
|
|
|
|
var resp = await _service.GetInfoAsync(StoryApiType.Main, 1, 2, viewerId: 7L);
|
|
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "2").IsReleased, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetInfoAsync_chapter2_released_after_skip_clear_too()
|
|
{
|
|
var chapters = new List<StoryChapter> {
|
|
Ch(100, 1, 2, "1", "2"), Ch(101, 1, 2, "2", "3"),
|
|
};
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, 2)).ReturnsAsync(chapters);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 100, new ViewerStoryProgress { StoryId = 100, IsSkipped = true } } });
|
|
_viewer.Setup(v => v.GetBranchUnlockedStoryIdsAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new HashSet<int>());
|
|
|
|
var resp = await _service.GetInfoAsync(StoryApiType.Main, 1, 2, viewerId: 7L);
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "2").IsReleased, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetInfoAsync_branch_children_locked_without_explicit_unlock()
|
|
{
|
|
var chapters = new List<StoryChapter> {
|
|
Ch(200, 17, 500901, "2", "3a 3b 3c"),
|
|
Ch(201, 17, 500901, "3a", "4a"),
|
|
Ch(202, 17, 500901, "3b", "4b"),
|
|
};
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(17, 500901)).ReturnsAsync(chapters);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 200, new ViewerStoryProgress { StoryId = 200, IsFinish = true } } });
|
|
_viewer.Setup(v => v.GetBranchUnlockedStoryIdsAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new HashSet<int>());
|
|
|
|
var resp = await _service.GetInfoAsync(StoryApiType.Main, 17, 500901, viewerId: 7L);
|
|
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "3a").IsReleased, Is.False);
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "3b").IsReleased, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetInfoAsync_branch_child_released_when_unlock_exists()
|
|
{
|
|
var chapters = new List<StoryChapter> {
|
|
Ch(200, 17, 500901, "2", "3a 3b 3c"),
|
|
Ch(201, 17, 500901, "3a", "4a"),
|
|
Ch(202, 17, 500901, "3b", "4b"),
|
|
};
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(17, 500901)).ReturnsAsync(chapters);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 200, new ViewerStoryProgress { StoryId = 200, IsFinish = true } } });
|
|
_viewer.Setup(v => v.GetBranchUnlockedStoryIdsAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new HashSet<int> { 201 }); // only 3a unlocked
|
|
|
|
var resp = await _service.GetInfoAsync(StoryApiType.Main, 17, 500901, viewerId: 7L);
|
|
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "3a").IsReleased, Is.True);
|
|
Assert.That(resp.StoryMasterList.Single(c => c.ChapterId == "3b").IsReleased, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetLeaderSelectAsync_untouched_chara_has_current_chapter_1()
|
|
{
|
|
_master.Setup(m => m.GetSectionsByFamilyAsync(StoryApiType.Main))
|
|
.ReturnsAsync(new List<StorySection> { new() { Id = 1, IsLeaderSelect = true } });
|
|
foreach (int chara in new[] { 1, 2, 3, 4, 5, 6, 7, 8 })
|
|
{
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, chara))
|
|
.ReturnsAsync(new List<StoryChapter> { Ch(100 + chara, 1, chara, "1", "2") });
|
|
}
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
|
|
|
var resp = await _service.GetLeaderSelectAsync(StoryApiType.Main, 1, viewerId: 7L);
|
|
|
|
Assert.That(resp.LeaderList, Has.Count.EqualTo(8));
|
|
Assert.That(resp.LeaderList.All(l => l.CurrentChapter == 1));
|
|
Assert.That(resp.LeaderCount, Is.EqualTo(8));
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetLeaderSelectAsync_after_clearing_chapter5_current_chapter_is_6()
|
|
{
|
|
_master.Setup(m => m.GetSectionsByFamilyAsync(StoryApiType.Main))
|
|
.ReturnsAsync(new List<StorySection> { new() { Id = 1, IsLeaderSelect = true } });
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, 2)).ReturnsAsync(new List<StoryChapter> {
|
|
Ch(101, 1, 2, "1", "2"), Ch(102, 1, 2, "2", "3"), Ch(103, 1, 2, "3", "4"),
|
|
Ch(104, 1, 2, "4", "5"), Ch(105, 1, 2, "5", "6"), Ch(106, 1, 2, "6", "0"),
|
|
});
|
|
foreach (int chara in new[] { 1, 3, 4, 5, 6, 7, 8 })
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(1, chara)).ReturnsAsync(new List<StoryChapter>());
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 101, new ViewerStoryProgress { StoryId = 101, IsFinish = true } },
|
|
{ 102, new ViewerStoryProgress { StoryId = 102, IsFinish = true } },
|
|
{ 103, new ViewerStoryProgress { StoryId = 103, IsFinish = true } },
|
|
{ 104, new ViewerStoryProgress { StoryId = 104, IsFinish = true } },
|
|
{ 105, new ViewerStoryProgress { StoryId = 105, IsFinish = true } },
|
|
});
|
|
|
|
var resp = await _service.GetLeaderSelectAsync(StoryApiType.Main, 1, viewerId: 7L);
|
|
|
|
var chara2 = resp.LeaderList.Single(l => l.CharaId == 2);
|
|
Assert.That(chara2.CurrentChapter, Is.EqualTo(6));
|
|
Assert.That(chara2.IsFinished, Is.False); // chapter 6 not done yet
|
|
}
|
|
|
|
[Test]
|
|
public async Task StartAsync_returns_sbs_payload_for_chapter_with_sbs_id()
|
|
{
|
|
var chapter = Ch(101, 1, 2, "2", "3", sbsId: 8);
|
|
var sbs = new SpecialBattleSetting { Id = 8, PlayerStartLife = 20, EnemyStartLife = 10,
|
|
Note = "Disaster Tree ch2&3" };
|
|
_master.Setup(m => m.GetChapterByIdAsync(101)).ReturnsAsync(chapter);
|
|
_master.Setup(m => m.GetSbsByIdAsync(8)).ReturnsAsync(sbs);
|
|
|
|
var resp = await _service.StartAsync(StoryApiType.Main, new[] { 101 }, viewerId: 7L);
|
|
|
|
Assert.That(resp.ContainsKey("0"), Is.True);
|
|
var slot = (StartSlotWithSbs)resp["0"];
|
|
Assert.That(slot.SpecialBattleSetting.Id, Is.EqualTo("8"));
|
|
Assert.That(slot.SpecialBattleSetting.EnemyStartLife, Is.EqualTo("10"));
|
|
Assert.That(resp.ContainsKey("mission_parameter"), Is.True);
|
|
Assert.That(((Array)resp["mission_parameter"]).Length, Is.EqualTo(0));
|
|
}
|
|
|
|
[Test]
|
|
public async Task StartAsync_returns_empty_slot_for_chapter_without_sbs_id()
|
|
{
|
|
var chapter = Ch(100, 1, 2, "1", "2", sbsId: null);
|
|
_master.Setup(m => m.GetChapterByIdAsync(100)).ReturnsAsync(chapter);
|
|
|
|
var resp = await _service.StartAsync(StoryApiType.Main, new[] { 100 }, viewerId: 7L);
|
|
|
|
Assert.That(resp["0"], Is.InstanceOf<Array>());
|
|
Assert.That(((Array)resp["0"]).Length, Is.EqualTo(0));
|
|
}
|
|
|
|
[Test]
|
|
public async Task FinishAsync_skip_shape_sets_isSkipped_and_grants_nothing()
|
|
{
|
|
var chapter = Ch(100, 1, 2, "1", "2");
|
|
chapter.Rewards.Add(new StoryChapterReward { RewardType = 1, RewardNumber = 20 });
|
|
_master.Setup(m => m.GetChapterByIdAsync(100)).ReturnsAsync(chapter);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
|
|
|
var req = new FinishRequest {
|
|
StoryId = 100, IsFinish = 1,
|
|
SelectionChapterId = null,
|
|
IsSelectAnotherEnd = false,
|
|
ClassId = null, // play-shape absence → skip
|
|
};
|
|
|
|
var resp = await _service.FinishAsync(StoryApiType.Main, req, viewerId: 7L);
|
|
|
|
Assert.That(resp.RewardList, Is.Empty);
|
|
Assert.That(resp.StoryRewardList, Is.Empty);
|
|
Assert.That(resp.GetClassExperience, Is.EqualTo("0"));
|
|
_viewer.Verify(v => v.UpsertProgressAsync(7L, 100, null, true), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public async Task FinishAsync_skip_shape_with_selection_unlocks_branch()
|
|
{
|
|
var parent = Ch(200, 17, 500901, "2", "3a 3b 3c");
|
|
var branch3b = Ch(202, 17, 500901, "3b", "4b");
|
|
_master.Setup(m => m.GetChapterByIdAsync(200)).ReturnsAsync(parent);
|
|
_master.Setup(m => m.GetChaptersBySectionCharaAsync(17, 500901))
|
|
.ReturnsAsync(new List<StoryChapter> { parent, branch3b });
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(7L, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
|
|
|
var req = new FinishRequest {
|
|
StoryId = 200, IsFinish = 1,
|
|
SelectionChapterId = "3b",
|
|
IsSelectAnotherEnd = false,
|
|
};
|
|
|
|
await _service.FinishAsync(StoryApiType.Main, req, viewerId: 7L);
|
|
|
|
_viewer.Verify(v => v.UpsertBranchUnlockAsync(7L, 202), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public async Task FinishAsync_play_shape_first_clear_grants_rewards_and_xp()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var svc = NewServiceWithSeededViewer(factory, out var scope, out var viewerId);
|
|
using (scope)
|
|
{
|
|
var chapter = Ch(100, 1, 2, "1", "2");
|
|
chapter.Rewards.Add(new StoryChapterReward { RewardType = 1, RewardDetailId = 0, RewardNumber = 100 });
|
|
_master.Setup(m => m.GetChapterByIdAsync(100)).ReturnsAsync(chapter);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(viewerId, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
|
|
|
var req = new FinishRequest {
|
|
StoryId = 100, IsFinish = 1, ClassId = 2,
|
|
EvolveCount = 0, TotalTurn = 5, DeckNo = 1,
|
|
UseBuildDeck = 0, DeckFormat = 1, Mission = new(),
|
|
RecoveryData = null,
|
|
};
|
|
|
|
var resp = await svc.FinishAsync(StoryApiType.Main, req, viewerId: viewerId);
|
|
|
|
// Viewer started at RedEther=0; grant of 100 → post-state total = 100.
|
|
Assert.That(resp.RewardList, Has.Count.EqualTo(1));
|
|
Assert.That(resp.RewardList[0].RewardNum, Is.EqualTo("100"));
|
|
Assert.That(resp.GetClassExperience, Is.EqualTo("200"));
|
|
_viewer.Verify(v => v.UpsertProgressAsync(viewerId, 100, true, null), Times.Once);
|
|
|
|
// Confirm currency persisted: fetch fresh viewer from a new scope.
|
|
using var verifyScope = factory.Services.CreateScope();
|
|
var db2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var freshViewer = await db2.Viewers.FirstAsync(v => v.Id == viewerId);
|
|
Assert.That(freshViewer.Currency.RedEther, Is.EqualTo(100UL));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task FinishAsync_play_shape_replay_does_not_double_grant()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var svc = NewServiceWithSeededViewer(factory, out var scope, out var viewerId);
|
|
using (scope)
|
|
{
|
|
var chapter = Ch(100, 1, 2, "1", "2");
|
|
chapter.Rewards.Add(new StoryChapterReward { RewardType = 1, RewardDetailId = 0, RewardNumber = 100 });
|
|
_master.Setup(m => m.GetChapterByIdAsync(100)).ReturnsAsync(chapter);
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(viewerId, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 100, new ViewerStoryProgress { StoryId = 100, IsFinish = true } } });
|
|
|
|
var req = new FinishRequest { StoryId = 100, IsFinish = 1, ClassId = 2 };
|
|
var resp = await svc.FinishAsync(StoryApiType.Main, req, viewerId: viewerId);
|
|
|
|
Assert.That(resp.RewardList, Is.Empty);
|
|
|
|
// Currency must not have changed from its seed value of 0.
|
|
using var verifyScope = factory.Services.CreateScope();
|
|
var db2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var freshViewer = await db2.Viewers.FirstAsync(v => v.Id == viewerId);
|
|
Assert.That(freshViewer.Currency.RedEther, Is.EqualTo(0UL));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task FinishAsync_battle_after_skip_still_grants_rewards()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var svc = NewServiceWithSeededViewer(factory, out var scope, out var viewerId);
|
|
using (scope)
|
|
{
|
|
var chapter = Ch(100, 1, 2, "1", "2");
|
|
chapter.Rewards.Add(new StoryChapterReward { RewardType = 1, RewardDetailId = 0, RewardNumber = 100 });
|
|
_master.Setup(m => m.GetChapterByIdAsync(100)).ReturnsAsync(chapter);
|
|
// Previously skipped but never finished — should be treated as first clear.
|
|
_viewer.Setup(v => v.GetProgressForChaptersAsync(viewerId, It.IsAny<IEnumerable<int>>()))
|
|
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress> {
|
|
{ 100, new ViewerStoryProgress { StoryId = 100, IsFinish = false, IsSkipped = true } } });
|
|
|
|
var req = new FinishRequest { StoryId = 100, IsFinish = 1, ClassId = 2 };
|
|
var resp = await svc.FinishAsync(StoryApiType.Main, req, viewerId: viewerId);
|
|
|
|
Assert.That(resp.RewardList, Has.Count.EqualTo(1));
|
|
Assert.That(resp.RewardList[0].RewardNum, Is.EqualTo("100"));
|
|
|
|
using var verifyScope = factory.Services.CreateScope();
|
|
var db2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var freshViewer = await db2.Viewers.FirstAsync(v => v.Id == viewerId);
|
|
Assert.That(freshViewer.Currency.RedEther, Is.EqualTo(100UL));
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static class StoryServiceTestHelpers
|
|
{
|
|
public static SVSim.Database.Services.IGameConfigService NewConfigService()
|
|
{
|
|
var mock = new Mock<SVSim.Database.Services.IGameConfigService>();
|
|
mock.Setup(s => s.Get<SVSim.Database.Models.Config.StoryConfig>())
|
|
.Returns(new SVSim.Database.Models.Config.StoryConfig { ClassXpPerClear = 200 });
|
|
return mock.Object;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a minimal <see cref="SVSimDbContext"/> backed by the EF InMemory provider.
|
|
/// Safe for non-reward tests that never actually query the DB.
|
|
/// Each call should use a unique <paramref name="dbName"/> to prevent test bleed-through.
|
|
/// </summary>
|
|
public static SVSimDbContext NewInMemoryDb(string dbName)
|
|
{
|
|
var options = new Microsoft.EntityFrameworkCore.DbContextOptionsBuilder<SVSimDbContext>()
|
|
.UseInMemoryDatabase(dbName)
|
|
.Options;
|
|
return new SVSimDbContext(
|
|
Microsoft.Extensions.Logging.Abstractions.NullLogger<SVSimDbContext>.Instance,
|
|
options);
|
|
}
|
|
}
|