Files
SVSimServer/SVSim.UnitTests/Controllers/ReplayControllerTests.cs
gamer147 c6259e5a14 feat(replay): add ReplayController for /replay/{info,detail}
/replay/info reads from ReplayHistoryReader, newest-first, capped at 50.
/replay/detail returns 400 with result_code=99 - local cache is the
canonical playback source so this endpoint is cache-miss fallback only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:59:51 -04:00

105 lines
4.1 KiB
C#

using System.Net;
using System.Net.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.Database.Models;
using SVSim.EmulatedEntrypoint.Models.Dtos.Replay;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Controllers;
public class ReplayControllerTests
{
[Test]
public async Task ReplayInfo_returns_empty_list_for_fresh_viewer()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
var client = factory.CreateAuthenticatedClient(viewerId);
var resp = await client.PostAsJsonAsync("/replay/info", new { });
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var body = await resp.Content.ReadFromJsonAsync<ReplayInfoResponseDto>();
Assert.That(body!.ReplayList, Is.Empty);
}
[Test]
public async Task ReplayInfo_returns_recent_rows_newest_first()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
using (var seedScope = factory.Services.CreateScope())
{
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
db.ViewerBattleHistories.AddRange(
NewRow(viewerId, battleId: 1, createTime: new DateTime(2026, 6, 1, 0, 0, 0, DateTimeKind.Utc)),
NewRow(viewerId, battleId: 2, createTime: new DateTime(2026, 6, 2, 0, 0, 0, DateTimeKind.Utc)),
NewRow(viewerId, battleId: 3, createTime: new DateTime(2026, 6, 3, 0, 0, 0, DateTimeKind.Utc)));
await db.SaveChangesAsync();
}
var client = factory.CreateAuthenticatedClient(viewerId);
var body = await client.PostAsJsonAsync("/replay/info", new { })
.ContinueWith(t => t.Result.Content.ReadFromJsonAsync<ReplayInfoResponseDto>())
.Unwrap();
Assert.That(body!.ReplayList.Select(r => r.BattleId).ToList(),
Is.EqualTo(new[] { "3", "2", "1" }));
}
[Test]
public async Task ReplayInfo_does_not_leak_other_viewers_rows()
{
using var factory = new SVSimTestFactory();
long viewerA = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
long viewerB = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_002UL);
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
db.ViewerBattleHistories.Add(NewRow(viewerA, battleId: 100, createTime: DateTime.UtcNow));
db.ViewerBattleHistories.Add(NewRow(viewerB, battleId: 200, createTime: DateTime.UtcNow));
await db.SaveChangesAsync();
}
var client = factory.CreateAuthenticatedClient(viewerA);
var body = await client.PostAsJsonAsync("/replay/info", new { })
.ContinueWith(t => t.Result.Content.ReadFromJsonAsync<ReplayInfoResponseDto>())
.Unwrap();
Assert.That(body!.ReplayList, Has.Count.EqualTo(1));
Assert.That(body.ReplayList[0].BattleId, Is.EqualTo("100"));
}
[Test]
public async Task ReplayDetail_returns_non_success_status()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
var client = factory.CreateAuthenticatedClient(viewerId);
var resp = await client.PostAsJsonAsync("/replay/detail", new
{
viewer_id = viewerId,
battle_id = 234_471_983_876L,
});
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
private static ViewerBattleHistory NewRow(long viewerId, long battleId, DateTime createTime) => new()
{
ViewerId = viewerId,
BattleId = battleId,
SelfRotationId = "0",
OpponentName = "",
OpponentCountryCode = "",
OpponentRotationId = "0",
BattleStartTime = createTime.AddMinutes(-3),
CreateTime = createTime,
};
}