Files
maraphon-app/tests/Marathon.Application.Tests/UseCases/UpdatePlacedBetUseCaseTests.cs
T
alexei.dolgolyov 1092e2a2c5 feat(journal): edit a placed bet
Adds an edit flow to the bet journal — an Edit button per row repurposes the inline
entry form (edit mode), saving via a new UpdatePlacedBetUseCase that preserves the
original placed-at and re-grades the outcome against the result (so a changed
selection/event re-settles). A cancel affordance + an editing banner make the mode
obvious; the submit button switches to "Save changes".

- UpdatePlacedBetUseCase (loads existing for PlacedAt, validates the event, re-grades)
  + IBetJournalService.UpdateAsync + service impl; Journal page edit-mode state +
  per-row Edit button; en/ru resx.
- 3 tests: unknown-bet, unknown-event, and preserve-PlacedAt + re-grade.
2026-05-29 13:38:38 +03:00

86 lines
3.7 KiB
C#

using FluentAssertions;
using Marathon.Application.Abstractions;
using Marathon.Application.UseCases;
using Marathon.Domain.Entities;
using Marathon.Domain.Enums;
using Marathon.Domain.ValueObjects;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
namespace Marathon.Application.Tests.UseCases;
public sealed class UpdatePlacedBetUseCaseTests
{
private static readonly TimeSpan Msk = TimeSpan.FromHours(3);
private static readonly DateTimeOffset PlacedAt = new(2026, 5, 16, 12, 0, 0, Msk);
private readonly IPlacedBetRepository _bets = Substitute.For<IPlacedBetRepository>();
private readonly IEventRepository _events = Substitute.For<IEventRepository>();
private readonly IResultRepository _results = Substitute.For<IResultRepository>();
private UpdatePlacedBetUseCase CreateSut() =>
new(_bets, _events, _results, NullLogger<UpdatePlacedBetUseCase>.Instance);
private static Bet Selection(Side side = Side.Side1, decimal rate = 2.0m) =>
new(MatchScope.Instance, BetType.Win, side, null, new OddsRate(rate));
private static PlacedBet Existing(Guid id, EventId eventId) =>
new(id, eventId, Selection(rate: 1.5m), 50m, PlacedAt, BetOutcome.Pending, "old note");
private static Event Ev(EventId id) =>
new(id, new SportCode(11), "England", "L", "C", PlacedAt.AddDays(1), "Home", "Away");
[Fact]
public async Task Throws_When_BetUnknown()
{
var id = Guid.NewGuid();
_bets.GetAsync(id, Arg.Any<CancellationToken>()).Returns((PlacedBet?)null);
var act = async () => await CreateSut().ExecuteAsync(id, new EventId("e"), Selection(), 100m, null);
await act.Should().ThrowAsync<InvalidOperationException>().WithMessage("*unknown bet*");
await _bets.DidNotReceive().UpdateAsync(Arg.Any<PlacedBet>(), Arg.Any<CancellationToken>());
}
[Fact]
public async Task Throws_When_EventUnknown()
{
var id = Guid.NewGuid();
var eid = new EventId("missing");
_bets.GetAsync(id, Arg.Any<CancellationToken>()).Returns(Existing(id, eid));
_events.GetAsync(eid, Arg.Any<CancellationToken>()).Returns((Event?)null);
var act = async () => await CreateSut().ExecuteAsync(id, eid, Selection(), 100m, null);
await act.Should().ThrowAsync<InvalidOperationException>().WithMessage("*unknown event*");
await _bets.DidNotReceive().UpdateAsync(Arg.Any<PlacedBet>(), Arg.Any<CancellationToken>());
}
[Fact]
public async Task Updates_PreservesPlacedAt_AppliesChanges_AndRegrades()
{
var id = Guid.NewGuid();
var eid = new EventId("evt-1");
_bets.GetAsync(id, Arg.Any<CancellationToken>()).Returns(Existing(id, eid));
_events.GetAsync(eid, Arg.Any<CancellationToken>()).Returns(Ev(eid));
// Side1 wins → the (new) Side1 selection re-grades to Won.
_results.GetAsync(eid, Arg.Any<CancellationToken>())
.Returns(new EventResult(eid, 2, 0, Side.Side1, PlacedAt.AddHours(3)));
var updated = await CreateSut().ExecuteAsync(
id, eid, Selection(side: Side.Side1, rate: 2.5m), stake: 120m, notes: "new note");
updated.Id.Should().Be(id);
updated.PlacedAt.Should().Be(PlacedAt); // original entry time preserved
updated.Stake.Should().Be(120m);
updated.Selection.Rate.Value.Should().Be(2.5m);
updated.Notes.Should().Be("new note");
updated.Outcome.Should().Be(BetOutcome.Won); // re-graded against the result
await _bets.Received(1).UpdateAsync(
Arg.Is<PlacedBet>(b => b.Id == id && b.Outcome == BetOutcome.Won && b.Stake == 120m),
Arg.Any<CancellationToken>());
await _bets.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}