mirror of
https://github.com/BillyCool/MariesWonderland.git
synced 2026-05-06 12:53:38 +02:00
Fix up ExploreService APIs and add unit tests
This commit is contained in:
42
tests/Helpers/StaminaHelperTests.cs
Normal file
42
tests/Helpers/StaminaHelperTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using MariesWonderland.Data;
|
||||
using MariesWonderland.Helpers;
|
||||
using MariesWonderland.Models.Entities;
|
||||
|
||||
namespace MariesWonderland.Tests.Helpers;
|
||||
|
||||
public class StaminaHelperTests
|
||||
{
|
||||
private static GameConfig ConfigWithCap(int cap) => new() { StaminaMaxCount = cap };
|
||||
|
||||
[Fact]
|
||||
public void AddStamina_BelowCap_AddsFullAmount()
|
||||
{
|
||||
var status = new EntityIUserStatus { StaminaMilliValue = 0 };
|
||||
|
||||
StaminaHelper.AddStamina(status, 50, ConfigWithCap(999), nowMs: 1000L);
|
||||
|
||||
Assert.Equal(50_000, status.StaminaMilliValue);
|
||||
Assert.Equal(1000L, status.StaminaUpdateDatetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStamina_ExceedsCap_ClampsToMax()
|
||||
{
|
||||
// Start near cap (990 stamina), add 50 → should clamp to 999
|
||||
var status = new EntityIUserStatus { StaminaMilliValue = 990_000 };
|
||||
|
||||
StaminaHelper.AddStamina(status, 50, ConfigWithCap(999), nowMs: 2000L);
|
||||
|
||||
Assert.Equal(999_000, status.StaminaMilliValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStamina_AlreadyAtCap_DoesNotExceed()
|
||||
{
|
||||
var status = new EntityIUserStatus { StaminaMilliValue = 999_000 };
|
||||
|
||||
StaminaHelper.AddStamina(status, 50, ConfigWithCap(999), nowMs: 3000L);
|
||||
|
||||
Assert.Equal(999_000, status.StaminaMilliValue);
|
||||
}
|
||||
}
|
||||
400
tests/Services/ExploreServiceTests.cs
Normal file
400
tests/Services/ExploreServiceTests.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
using Grpc.Core;
|
||||
using MariesWonderland.Data;
|
||||
using MariesWonderland.Models.Entities;
|
||||
using MariesWonderland.Models.Type;
|
||||
using MariesWonderland.Proto.Explore;
|
||||
using MariesWonderland.Tests.Infrastructure;
|
||||
using ExploreService = MariesWonderland.Services.ExploreService;
|
||||
|
||||
namespace MariesWonderland.Tests.Services;
|
||||
|
||||
public class ExploreServiceTests(MasterDatabaseFixture fixture) : ServiceTestBase(fixture), IClassFixture<MasterDatabaseFixture>
|
||||
{
|
||||
private const long UserId = 1L;
|
||||
private const int ExploreId = 1; // Normal explore, requires quest 31 cleared
|
||||
private const int UnlockQuestId = 31;
|
||||
private const int TicketItemId = 2001; // ConsumableItemIdForExploreTicket
|
||||
|
||||
private ExploreService CreateService(UserDataStore store) => new(store, MasterDb, GameConfig);
|
||||
|
||||
private static void AddQuestCleared(DarkUserMemoryDatabase userDb, int questId = UnlockQuestId)
|
||||
=> userDb.EntityIUserQuest.Add(new EntityIUserQuest
|
||||
{
|
||||
UserId = UserId,
|
||||
QuestId = questId,
|
||||
QuestStateType = (int)QuestStateType.CLEARED
|
||||
});
|
||||
|
||||
private static EntityIUserExplore AddUserExplore(DarkUserMemoryDatabase userDb,
|
||||
int playingExploreId = 0, bool isUsingTicket = false)
|
||||
{
|
||||
var entity = new EntityIUserExplore
|
||||
{
|
||||
UserId = UserId,
|
||||
PlayingExploreId = playingExploreId,
|
||||
IsUseExploreTicket = isUsingTicket
|
||||
};
|
||||
userDb.EntityIUserExplore.Add(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static EntityIUserStatus AddUserStatus(DarkUserMemoryDatabase userDb,
|
||||
int staminaMilliValue = 5000, int level = 0, int exp = 0)
|
||||
{
|
||||
var entity = new EntityIUserStatus { UserId = UserId, StaminaMilliValue = staminaMilliValue, Level = level, Exp = exp };
|
||||
userDb.EntityIUserStatus.Add(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
#region StartExplore
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_FreePlay_SetsPlayingStateWithoutDeductingItem()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddQuestCleared(userDb);
|
||||
var userExplore = AddUserExplore(userDb);
|
||||
userDb.EntityIUserConsumableItem.Add(new EntityIUserConsumableItem
|
||||
{
|
||||
UserId = UserId, ConsumableItemId = TicketItemId, Count = 3
|
||||
});
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = ExploreId, UseConsumableItemId = 0 },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(ExploreId, userExplore.PlayingExploreId);
|
||||
Assert.False(userExplore.IsUseExploreTicket);
|
||||
Assert.True(userExplore.LatestPlayDatetime > 0);
|
||||
Assert.Equal(3, userDb.EntityIUserConsumableItem[0].Count); // not deducted
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_WithTicket_DeductsTicketAndSetsFlag()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddQuestCleared(userDb);
|
||||
var userExplore = AddUserExplore(userDb);
|
||||
userDb.EntityIUserConsumableItem.Add(new EntityIUserConsumableItem
|
||||
{
|
||||
UserId = UserId, ConsumableItemId = TicketItemId, Count = 3
|
||||
});
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = ExploreId, UseConsumableItemId = TicketItemId },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(ExploreId, userExplore.PlayingExploreId);
|
||||
Assert.True(userExplore.IsUseExploreTicket);
|
||||
Assert.Equal(2, userDb.EntityIUserConsumableItem[0].Count); // 3 - 1 = 2
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_InvalidExploreId_ThrowsInvalidArgument()
|
||||
{
|
||||
var store = CreateStore(UserId, CreateUserDb(), MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = 9999 },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_UnlockConditionNotMet_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb); // quest 31 NOT cleared
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_TicketNotFound_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddQuestCleared(userDb);
|
||||
AddUserExplore(userDb); // no consumable item record
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = ExploreId, UseConsumableItemId = TicketItemId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartExplore_InsufficientTickets_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddQuestCleared(userDb);
|
||||
AddUserExplore(userDb);
|
||||
userDb.EntityIUserConsumableItem.Add(new EntityIUserConsumableItem
|
||||
{
|
||||
UserId = UserId, ConsumableItemId = TicketItemId, Count = 0
|
||||
});
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.StartExplore(
|
||||
new StartExploreRequest { ExploreId = ExploreId, UseConsumableItemId = TicketItemId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RetireExplore
|
||||
|
||||
[Fact]
|
||||
public async Task RetireExplore_WithActiveExplore_ClearsState()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
var userExplore = AddUserExplore(userDb, playingExploreId: ExploreId, isUsingTicket: true);
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.RetireExplore(
|
||||
new RetireExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(0, userExplore.PlayingExploreId);
|
||||
Assert.False(userExplore.IsUseExploreTicket);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetireExplore_NoMatchingActiveExplore_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: 0);
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.RetireExplore(
|
||||
new RetireExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetireExplore_UserExploreNotFound_ThrowsFailedPrecondition()
|
||||
{
|
||||
var store = CreateStore(UserId, CreateUserDb(), MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.RetireExplore(
|
||||
new RetireExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FinishExplore
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_NewHighScore_GrantsRewardsAndClearsState()
|
||||
{
|
||||
// Score 50000 hits the 50000 threshold → ExploreGradeId 102 → AssetGradeIconId 17000
|
||||
const int score = 50000;
|
||||
const int expectedGradeIconId = 17000;
|
||||
|
||||
var userDb = CreateUserDb();
|
||||
var userExplore = AddUserExplore(userDb, playingExploreId: ExploreId, isUsingTicket: true);
|
||||
var status = AddUserStatus(userDb, staminaMilliValue: 5000, level: 229);
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var response = await service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId, Score = score },
|
||||
ContextFor(UserId));
|
||||
|
||||
// Score record created
|
||||
Assert.Single(userDb.EntityIUserExploreScore);
|
||||
Assert.Equal(score, userDb.EntityIUserExploreScore[0].MaxScore);
|
||||
|
||||
// Active state cleared
|
||||
Assert.Equal(0, userExplore.PlayingExploreId);
|
||||
Assert.False(userExplore.IsUseExploreTicket);
|
||||
|
||||
// Stamina recovered (50 base × RewardLotteryCount 1 = 50 stamina = 50000 milli)
|
||||
Assert.Equal(55000, status.StaminaMilliValue);
|
||||
|
||||
// EXP granted: 0.5% of level 229 requirement (884,516) × 1 = 4,422
|
||||
Assert.Equal(4422, status.Exp);
|
||||
|
||||
// Material granted: 1 draw × 8 per draw (grade 102) = 8 of material 100001
|
||||
Assert.Single(userDb.EntityIUserMaterial);
|
||||
Assert.Equal(8, userDb.EntityIUserMaterial[0].Count);
|
||||
|
||||
// Response populated
|
||||
Assert.Equal(50, response.AcquireStaminaCount);
|
||||
Assert.Equal(expectedGradeIconId, response.AssetGradeIconId);
|
||||
Assert.Single(response.ExploreReward);
|
||||
Assert.Equal(8, response.ExploreReward[0].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_ImprovedScore_UpdatesMaxScore()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: ExploreId);
|
||||
AddUserStatus(userDb);
|
||||
userDb.EntityIUserExploreScore.Add(new EntityIUserExploreScore
|
||||
{
|
||||
UserId = UserId, ExploreId = ExploreId, MaxScore = 50000
|
||||
});
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId, Score = 80000 },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(80000, userDb.EntityIUserExploreScore[0].MaxScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_LowerScore_DoesNotUpdateMaxScore()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: ExploreId);
|
||||
AddUserStatus(userDb);
|
||||
userDb.EntityIUserExploreScore.Add(new EntityIUserExploreScore
|
||||
{
|
||||
UserId = UserId, ExploreId = ExploreId, MaxScore = 80000
|
||||
});
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId, Score = 50000 },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(80000, userDb.EntityIUserExploreScore[0].MaxScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_InvalidExploreId_ThrowsInvalidArgument()
|
||||
{
|
||||
var store = CreateStore(UserId, CreateUserDb(), MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = 9999 },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_NoMatchingActiveExplore_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: 0); // nothing active
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_UserExploreNotFound_ThrowsFailedPrecondition()
|
||||
{
|
||||
var store = CreateStore(UserId, CreateUserDb(), MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_UserStatusNotFound_ThrowsFailedPrecondition()
|
||||
{
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: ExploreId); // no status record
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() =>
|
||||
service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId },
|
||||
ContextFor(UserId)));
|
||||
|
||||
Assert.Equal(StatusCode.FailedPrecondition, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_ExpGrantCrossesThreshold_AdvancesLevel()
|
||||
{
|
||||
// Level 229 → 230 threshold is 87,191,772. Start 100 EXP below so the grant (4,422) pushes us over.
|
||||
const int level230Threshold = 87_191_772;
|
||||
const int startExp = level230Threshold - 100;
|
||||
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: ExploreId);
|
||||
var status = AddUserStatus(userDb, staminaMilliValue: 0, level: 229, exp: startExp);
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
await service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId, Score = 0 },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Equal(230, status.Level);
|
||||
Assert.True(status.Exp >= level230Threshold);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinishExplore_WorstGrade_GrantsNoMaterials()
|
||||
{
|
||||
// Score 0 → grade 106 (worst) → stackPerDraw = 0 → no ExploreReward entries
|
||||
var userDb = CreateUserDb();
|
||||
AddUserExplore(userDb, playingExploreId: ExploreId);
|
||||
AddUserStatus(userDb, level: 229);
|
||||
var store = CreateStore(UserId, userDb, MasterDb);
|
||||
var service = CreateService(store);
|
||||
|
||||
var response = await service.FinishExplore(
|
||||
new FinishExploreRequest { ExploreId = ExploreId, Score = 0 },
|
||||
ContextFor(UserId));
|
||||
|
||||
Assert.Empty(userDb.EntityIUserMaterial);
|
||||
Assert.Empty(response.ExploreReward);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user