Skip to content

Commit

Permalink
Implement available opponent controller
Browse files Browse the repository at this point in the history
  • Loading branch information
Atralupus committed Dec 26, 2024
1 parent b550292 commit 97832d3
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 10 deletions.
103 changes: 103 additions & 0 deletions ArenaService.Tests/Controllers/AvailableOpponentControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Security.Claims;
using ArenaService.Controllers;
using ArenaService.Dtos;
using ArenaService.Models;
using ArenaService.Repositories;
using ArenaService.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Moq;

namespace ArenaService.Tests.Controllers;

public class AvailableOpponentControllerTests
{
private readonly AvailableOpponentController _controller;
private Mock<IAvailableOpponentRepository> _availableOpponentRepositoryMock;
private AvailableOpponentService _availableOpponentService;
private Mock<IParticipantRepository> _participantRepositoryMock;
private ParticipantService _participantService;

public AvailableOpponentControllerTests()
{
var availableOpponentRepositoryMock = new Mock<IAvailableOpponentRepository>();
_availableOpponentRepositoryMock = availableOpponentRepositoryMock;
_availableOpponentService = new AvailableOpponentService(
_availableOpponentRepositoryMock.Object
);
var participantRepositoryMock = new Mock<IParticipantRepository>();
_participantRepositoryMock = participantRepositoryMock;
_participantService = new ParticipantService(_participantRepositoryMock.Object);
_controller = new AvailableOpponentController(
_availableOpponentService,
_participantService
);
}

[Fact]
public async Task GetAvailableOpponents_WithValidHeader_ReturnsOk()
{
var avatarAddress = "DDF1472fD5a79B8F46C28e7643eDEF045e36BD3d";

_participantRepositoryMock
.Setup(repo => repo.GetParticipantByAvatarAddressAsync(1, avatarAddress))
.ReturnsAsync(
new Participant
{
Id = 1,
AvatarAddress = avatarAddress,
NameWithHash = "test",
PortraitId = 1
}
);

_availableOpponentRepositoryMock
.Setup(repo => repo.GetAvailableOpponents(1))
.ReturnsAsync(
[
new AvailableOpponent
{
Id = 1,
ParticipantId = 1,
OpponentId = 2,
Opponent = new Participant
{
Id = 2,
AvatarAddress = "test",
NameWithHash = "opponent1",
PortraitId = 1
},
RefillBlockIndex = 1
},
new AvailableOpponent
{
Id = 2,
ParticipantId = 1,
OpponentId = 3,
Opponent = new Participant
{
Id = 3,
AvatarAddress = "test",
NameWithHash = "opponent2",
PortraitId = 1
},
RefillBlockIndex = 1
}
]
);

_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
};
_controller.ControllerContext.HttpContext.User = new ClaimsPrincipal(
new ClaimsIdentity([new Claim("avatar", avatarAddress)])
);

var result = await _controller.GetAvailableOpponents(1);

var okResult = Assert.IsType<Ok<AvailableOpponentsResponse>>(result.Result);
Assert.Equal(2, okResult.Value?.AvailableOpponents.Count);
}
}
6 changes: 3 additions & 3 deletions ArenaService.Tests/Controllers/ParticipantControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task Join_ActivatedSeasonsExist_ReturnsOk()
_seasonRepositoryMock.Setup(repo => repo.GetSeasonAsync(season.Id)).ReturnsAsync(season);
_participantRepositoryMock
.Setup(repo =>
repo.InsertParticipantToSpecificSeason(
repo.InsertParticipantToSpecificSeasonAsync(
season.Id,
participant.AvatarAddress,
participant.NameWithHash,
Expand Down Expand Up @@ -99,7 +99,7 @@ public async Task Join_ActivatedSeasonsNotExist_ReturnsNotFound()
_seasonRepositoryMock.Setup(repo => repo.GetActivatedSeasonsAsync()).ReturnsAsync([season]);
_participantRepositoryMock
.Setup(repo =>
repo.InsertParticipantToSpecificSeason(
repo.InsertParticipantToSpecificSeasonAsync(
season.Id,
participant.AvatarAddress,
participant.NameWithHash,
Expand Down Expand Up @@ -146,7 +146,7 @@ public async Task Join_SeasonsNotExist_ReturnsNotFound()
_seasonRepositoryMock.Setup(repo => repo.GetActivatedSeasonsAsync()).ReturnsAsync([season]);
_participantRepositoryMock
.Setup(repo =>
repo.InsertParticipantToSpecificSeason(
repo.InsertParticipantToSpecificSeasonAsync(
season.Id,
participant.AvatarAddress,
participant.NameWithHash,
Expand Down
80 changes: 80 additions & 0 deletions ArenaService/Controllers/AvailableOpponentController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace ArenaService.Controllers;

using System.Security.Claims;
using ArenaService.Dtos;
using ArenaService.Extensions;
using ArenaService.Services;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

[Route("seasons/{seasonId}/available-opponents")]
[ApiController]
public class AvailableOpponentController : ControllerBase
{
private readonly AvailableOpponentService _availableOpponentService;
private readonly ParticipantService _participantService;

public AvailableOpponentController(
AvailableOpponentService availableOpponentService,
ParticipantService participantService
)
{
_availableOpponentService = availableOpponentService;
_participantService = participantService;
}

private string? ExtractAvatarAddress()
{
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
var claim = identity.FindFirst("avatar");
return claim?.Value;
}
return null;
}

[HttpGet]
public async Task<
Results<UnauthorizedHttpResult, NotFound<string>, Ok<AvailableOpponentsResponse>>
> GetAvailableOpponents(int seasonId)
{
var avatarAddress = ExtractAvatarAddress();

if (avatarAddress is null)
{
return TypedResults.Unauthorized();
}

var participant = await _participantService.GetParticipantByAvatarAddressAsync(
seasonId,
avatarAddress
);

if (participant is null)
{
return TypedResults.NotFound("Not participant user.");
}

var opponents = await _availableOpponentService.GetAvailableOpponents(participant.Id);

return TypedResults.Ok(
new AvailableOpponentsResponse { AvailableOpponents = opponents.ToResponse() }
);
}

[HttpGet]
public async Task<Results<UnauthorizedHttpResult, NotFound<string>, Created>> ResetOpponents(

Check warning on line 66 in ArenaService/Controllers/AvailableOpponentController.cs

View workflow job for this annotation

GitHub Actions / Test (ArenaService.Tests)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
int seasonId
)
{
var avatarAddress = ExtractAvatarAddress();

if (avatarAddress is null)
{
return TypedResults.Unauthorized();
}

// Dummy implementation
return TypedResults.Created();
}
}
2 changes: 1 addition & 1 deletion ArenaService/Controllers/ParticipantController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ArenaService.Controllers;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

[Route("api/seasons/{seasonId}/participants")]
[Route("seasons/{seasonId}/participants")]
[ApiController]
public class ParticipantController : ControllerBase
{
Expand Down
6 changes: 6 additions & 0 deletions ArenaService/Dtos/AvailableOpponentsResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ArenaService.Dtos;

public class AvailableOpponentsResponse
{
public required List<ParticipantResponse> AvailableOpponents { get; set; }
}
12 changes: 12 additions & 0 deletions ArenaService/Extensions/ParticipantExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,16 @@ public static ParticipantResponse ToResponse(this Participant participant)
PortraitId = participant.PortraitId,
};
}

public static List<ParticipantResponse> ToResponse(this List<Participant> participants)
{
return participants
.Select(p => new ParticipantResponse
{
AvatarAddress = p.AvatarAddress,
NameWithHash = p.NameWithHash,
PortraitId = p.PortraitId,
})
.ToList();
}
}
6 changes: 3 additions & 3 deletions ArenaService/Models/AvailableOpponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ public class AvailableOpponent

[Required]
public int ParticipantId { get; set; }
public required Participant Participant { get; set; }
public Participant Participant { get; set; } = null!;

[Required]
public int OpponentId { get; set; }
public required Participant Opponent { get; set; }
public Participant Opponent { get; set; } = null!;

[Required]
public long RefillBlockIndex { get; set; }

public bool IsBattled { get; set; }
public bool IsBattled { get; set; } = false;
}
27 changes: 27 additions & 0 deletions ArenaService/SeasonRepositories/AvailableOpponentRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace ArenaService.Repositories;

using ArenaService.Data;
using ArenaService.Models;
using Microsoft.EntityFrameworkCore;

public interface IAvailableOpponentRepository
{
Task<List<AvailableOpponent>> GetAvailableOpponents(int participantId);
}

public class AvailableOpponentRepository : IAvailableOpponentRepository
{
private readonly ArenaDbContext _context;

public AvailableOpponentRepository(ArenaDbContext context)
{
_context = context;
}

public async Task<List<AvailableOpponent>> GetAvailableOpponents(int participantId)
{
return await _context
.AvailableOpponents.Where(ao => ao.ParticipantId == participantId)
.ToListAsync();
}
}
16 changes: 14 additions & 2 deletions ArenaService/SeasonRepositories/ParticipantRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ namespace ArenaService.Repositories;

using ArenaService.Data;
using ArenaService.Models;
using Microsoft.EntityFrameworkCore;

public interface IParticipantRepository
{
Task<Participant> InsertParticipantToSpecificSeason(
Task<Participant> InsertParticipantToSpecificSeasonAsync(
int seasonId,
string avatarAddress,
string nameWithHash,
int portraitId
);
Task<Participant?> GetParticipantByAvatarAddressAsync(int seasonId, string avatarAddress);
}

public class ParticipantRepository : IParticipantRepository
Expand All @@ -22,7 +24,7 @@ public ParticipantRepository(ArenaDbContext context)
_context = context;
}

public async Task<Participant> InsertParticipantToSpecificSeason(
public async Task<Participant> InsertParticipantToSpecificSeasonAsync(
int seasonId,
string avatarAddress,
string nameWithHash,
Expand All @@ -41,4 +43,14 @@ int portraitId
_context.SaveChanges();
return participant.Entity;
}

public async Task<Participant?> GetParticipantByAvatarAddressAsync(
int seasonId,
string avatarAddress
)
{
return await _context.Participants.FirstOrDefaultAsync(p =>
p.SeasonId == seasonId && p.AvatarAddress == avatarAddress
);
}
}
25 changes: 25 additions & 0 deletions ArenaService/Services/AvailableOpponentService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace ArenaService.Services;

using ArenaService.Dtos;
using ArenaService.Extensions;
using ArenaService.Models;
using ArenaService.Repositories;

public class AvailableOpponentService
{
private readonly IAvailableOpponentRepository _availableOpponentRepository;

public AvailableOpponentService(IAvailableOpponentRepository availableOpponentRepository)
{
_availableOpponentRepository = availableOpponentRepository;
}

public async Task<List<Participant>> GetAvailableOpponents(int participantId)
{
var availableOpponents = await _availableOpponentRepository.GetAvailableOpponents(
participantId
);
var opponents = availableOpponents.Select(ao => ao.Opponent).ToList();
return opponents;
}
}
15 changes: 14 additions & 1 deletion ArenaService/Services/ParticipaintService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace ArenaService.Services;

using ArenaService.Dtos;
using ArenaService.Extensions;
using ArenaService.Models;
using ArenaService.Repositories;

public class ParticipantService
Expand All @@ -18,12 +19,24 @@ public async Task<ParticipantResponse> AddParticipantAsync(
JoinRequest joinRequest
)
{
var participant = await _participantRepository.InsertParticipantToSpecificSeason(
var participant = await _participantRepository.InsertParticipantToSpecificSeasonAsync(
seasonId,
joinRequest.AvatarAddress,
joinRequest.NameWithHash,
joinRequest.PortraitId
);
return participant.ToResponse();
}

public async Task<Participant?> GetParticipantByAvatarAddressAsync(
int seasonId,
string avatarAddress
)
{
var participant = await _participantRepository.GetParticipantByAvatarAddressAsync(
seasonId,
avatarAddress
);
return participant;
}
}

0 comments on commit 97832d3

Please sign in to comment.