add TeamsController
continuous-integration/drone/push Build is passing Details

tests
maxime 1 year ago
parent 632ac19238
commit e8bdce981c

@ -1,13 +1,12 @@
using API.Validation; using API.Validation;
using Microsoft.AspNetCore.Mvc;
namespace API; namespace API.Context;
public static class AppHttpContext public class HttpContextAccessor : IContextAccessor
{ {
public static int CurrentUserId(this ControllerBase b) public int CurrentUserId(HttpContext ctx)
{ {
var idClaim = b.HttpContext var idClaim = ctx
.User .User
.Claims .Claims
.First(c => c.Type == IdentityData.IdUserClaimName); .First(c => c.Type == IdentityData.IdUserClaimName);

@ -0,0 +1,6 @@
namespace API.Context;
public interface IContextAccessor
{
public int CurrentUserId(HttpContext ctx);
}

@ -3,10 +3,10 @@ using Microsoft.AspNetCore.Mvc;
using Model; using Model;
using Services; using Services;
namespace API.Controllers; namespace API.Controllers.Admin;
[ApiController] [ApiController]
public class TeamsController(ITeamService service) : ControllerBase public class TeamsAdminController(ITeamService service) : ControllerBase
{ {
public record CountTeamsResponse(int Value); public record CountTeamsResponse(int Value);
@ -14,7 +14,7 @@ public class TeamsController(ITeamService service) : ControllerBase
[HttpGet("/admin/teams/count")] [HttpGet("/admin/teams/count")]
public async Task<CountTeamsResponse> CountTeams() public async Task<CountTeamsResponse> CountTeams()
{ {
return new CountTeamsResponse(await service.CountTeams()); return new CountTeamsResponse(await service.CountTotalTeams());
} }
// [HttpGet("/admin/users/count")] // [HttpGet("/admin/users/count")]

@ -3,13 +3,11 @@ using Microsoft.AspNetCore.Mvc;
using Model; using Model;
using Services; using Services;
namespace API.Controllers; namespace API.Controllers.Admin;
[ApiController] [ApiController]
public class UsersController(IUserService service) : ControllerBase public class UsersAdminController(IUserService service) : ControllerBase
{ {
private const string DefaultProfilePicture =
"https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png";
public record CountUsersResponse(int Value); public record CountUsersResponse(int Value);
@ -54,7 +52,7 @@ public class UsersController(IUserService service) : ControllerBase
[HttpGet("/admin/users/{id:int}")] [HttpGet("/admin/users/{id:int}")]
public async Task<IActionResult> GetUser( public async Task<IActionResult> GetUser(
[Range(0, int.MaxValue, ErrorMessage = "Only positive number allowed")] [Range(1, int.MaxValue, ErrorMessage = "Only positive number allowed")]
int id int id
) )
{ {
@ -79,15 +77,16 @@ public class UsersController(IUserService service) : ControllerBase
[HttpPost("/admin/users")] [HttpPost("/admin/users")]
public Task<User> AddUser([FromBody] AddUserRequest req) public Task<User> AddUser([FromBody] AddUserRequest req)
{ {
return service.CreateUser(req.Username, req.Email, req.Password, DefaultProfilePicture, req.IsAdmin); return service.CreateUser(req.Username, req.Email, req.Password, UsersController.DefaultProfilePicture, req.IsAdmin);
} }
public record RemoveUsersRequest(int[] Identifiers); public record RemoveUsersRequest(int[] Identifiers);
[HttpPost("/admin/users/remove-all")] [HttpPost("/admin/users/remove-all")]
public async void RemoveUsers([FromBody] RemoveUsersRequest req) public async Task<IActionResult> RemoveUsers([FromBody] RemoveUsersRequest req)
{ {
await service.RemoveUsers(req.Identifiers); await service.RemoveUsers(req.Identifiers);
return Ok();
} }
public record UpdateUserRequest( public record UpdateUserRequest(
@ -107,7 +106,7 @@ public class UsersController(IUserService service) : ControllerBase
{ {
try try
{ {
await service.UpdateUser(new User(id, req.Username, req.Email, DefaultProfilePicture, req.IsAdmin)); await service.UpdateUser(new User(id, req.Username, req.Email, UsersController.DefaultProfilePicture, req.IsAdmin));
return Ok(); return Ok();
} }
catch (ServiceException e) catch (ServiceException e)

@ -22,7 +22,8 @@ public class AuthenticationController(IUserService service, IConfiguration confi
[EmailAddress] [EmailAddress]
string Email, string Email,
[MaxLength(256, ErrorMessage = "Password is too wide")] [MaxLength(256, ErrorMessage = "Password is too wide")]
string Password); string Password
);
private record AuthenticationResponse(String Token, DateTime ExpirationDate); private record AuthenticationResponse(String Token, DateTime ExpirationDate);

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using API.Context;
using API.DTO; using API.DTO;
using API.Validation; using API.Validation;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -10,7 +11,7 @@ using Services;
namespace API.Controllers; namespace API.Controllers;
[ApiController] [ApiController]
public class TacticController(ITacticService service) : ControllerBase public class TacticController(ITacticService service, IContextAccessor accessor) : ControllerBase
{ {
public record UpdateNameRequest( public record UpdateNameRequest(
[StringLength(50, MinimumLength = 1)] [StringLength(50, MinimumLength = 1)]
@ -23,7 +24,7 @@ public class TacticController(ITacticService service) : ControllerBase
int tacticId, int tacticId,
[FromBody] UpdateNameRequest req) [FromBody] UpdateNameRequest req)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {
return Unauthorized(); return Unauthorized();
@ -38,7 +39,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> GetTacticInfo(int tacticId) public async Task<IActionResult> GetTacticInfo(int tacticId)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {
return Unauthorized(); return Unauthorized();
@ -54,7 +55,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> GetTacticStepsRoot(int tacticId) public async Task<IActionResult> GetTacticStepsRoot(int tacticId)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {
return Unauthorized(); return Unauthorized();
@ -78,7 +79,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<CreateNewResponse> CreateNew([FromBody] CreateNewRequest req) public async Task<CreateNewResponse> CreateNew([FromBody] CreateNewRequest req)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
var courtType = req.CourtType switch var courtType = req.CourtType switch
{ {
@ -107,7 +108,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> GetStepContent(int tacticId, int stepId) public async Task<IActionResult> GetStepContent(int tacticId, int stepId)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {
@ -122,7 +123,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> RemoveStepContent(int tacticId, int stepId) public async Task<IActionResult> RemoveStepContent(int tacticId, int stepId)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {
@ -140,7 +141,7 @@ public class TacticController(ITacticService service) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> SaveStepContent(int tacticId, int stepId, [FromBody] SaveStepContentRequest req) public async Task<IActionResult> SaveStepContent(int tacticId, int stepId, [FromBody] SaveStepContentRequest req)
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
if (!await service.HasAnyRights(userId, tacticId)) if (!await service.HasAnyRights(userId, tacticId))
{ {

@ -0,0 +1,77 @@
using System.ComponentModel.DataAnnotations;
using API.Context;
using API.Validation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Model;
using Services;
namespace API.Controllers;
[ApiController]
[Authorize]
public class TeamsController(ITeamService service, IContextAccessor accessor) : ControllerBase
{
public record CreateTeamRequest(
[Name] string Name,
[Url] string Picture,
[RegularExpression("^#[0-9A-F]{6}$")] string FirstColor,
[RegularExpression("^#[0-9A-F]{6}$")] string SecondColor
);
[HttpPost("/teams")]
public async Task<IActionResult> CreateTeam([FromBody] CreateTeamRequest req)
{
var userId = accessor.CurrentUserId(HttpContext);
var team = await service.AddTeam(req.Name, req.Picture, req.FirstColor, req.SecondColor);
await service.AddMember(team.Id, userId, MemberRole.Coach);
return Ok(team);
}
[HttpGet("/teams/{teamId:int}/members")]
public IActionResult GetMembersOf(int teamId)
{
return Ok(service.GetMembersOf(teamId));
}
public record AddMemberRequest(
int UserId,
[AllowedValues("PLAYER", "COACH")] string Role
);
[HttpPost("/teams/{teamId:int}/members")]
public async Task<IActionResult> AddMember(int teamId, [FromBody] AddMemberRequest req)
{
if (!Enum.TryParse<MemberRole>(req.Role, true, out var role))
{
throw new Exception($"Unable to convert string input '{req.Role}' to a role enum variant.");
}
return Ok(await service.AddMember(teamId, req.UserId, role));
}
public record UpdateMemberRequest(
[AllowedValues("PLAYER", "COACH")] string Role
);
[HttpPut("/team/{teamId:int}/members/{userId:int}")]
public async Task<IActionResult> UpdateMember(int teamId, int userId, [FromBody] UpdateMemberRequest req)
{
if (!Enum.TryParse<MemberRole>(req.Role, true, out var role))
{
throw new Exception($"Unable to convert string input '{req.Role}' to a role enum variant.");
}
var updated = await service.UpdateMember(new Member(teamId, userId, role));
return updated ? Ok() : NotFound();
}
[HttpDelete("/team/{teamId:int}/members/{userId:int}")]
public async Task<IActionResult> RemoveMember(int teamId, int userId)
{
var removed = await service.RemoveMember(teamId, userId);
return removed ? Ok() : NotFound();
}
}

@ -1,19 +1,26 @@
using System.Runtime.CompilerServices;
using API.Context;
using API.DTO; using API.DTO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Model; using Model;
using Services; using Services;
[assembly: InternalsVisibleTo("UnitTests")]
namespace API.Controllers; namespace API.Controllers;
[ApiController] [ApiController]
public class UserController(IUserService users, ITeamService teams, ITacticService tactics) : ControllerBase public class UsersController(IUserService users, ITeamService teams, ITacticService tactics, IContextAccessor accessor)
: ControllerBase
{ {
public const string DefaultProfilePicture =
"https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png";
[Authorize] [Authorize]
[HttpGet("/user")] [HttpGet("/user")]
public async Task<User> GetUser() public async Task<User> GetUser()
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
return (await users.GetUser(userId))!; return (await users.GetUser(userId))!;
} }
@ -23,7 +30,7 @@ public class UserController(IUserService users, ITeamService teams, ITacticServi
[HttpGet("/user-data")] [HttpGet("/user-data")]
public async Task<GetUserDataResponse> GetUserData() public async Task<GetUserDataResponse> GetUserData()
{ {
var userId = this.CurrentUserId(); var userId = accessor.CurrentUserId(HttpContext);
var userTeams = await teams.ListTeamsOf(userId); var userTeams = await teams.ListTeamsOf(userId);
var userTactics = await tactics.ListTacticsOf(userId); var userTactics = await tactics.ListTacticsOf(userId);
return new GetUserDataResponse(userTeams.ToArray(), userTactics.Select(t => t.ToDto()).ToArray()); return new GetUserDataResponse(userTeams.ToArray(), userTactics.Select(t => t.ToDto()).ToArray());

@ -2,12 +2,14 @@ using System.Globalization;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
using API.Auth; using API.Auth;
using API.Context;
using API.Validation; using API.Validation;
using DbServices; using DbServices;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Services; using Services;
using HttpContextAccessor = API.Context.HttpContextAccessor;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration; var config = builder.Configuration;
@ -17,7 +19,7 @@ var config = builder.Configuration;
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddHttpLogging(o => {}); builder.Services.AddHttpLogging(o => { });
builder.Services.AddControllers() builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options => options.InvalidModelStateResponseFactory = context => .ConfigureApiBehaviorOptions(options => options.InvalidModelStateResponseFactory = context =>
@ -55,6 +57,7 @@ builder.Services.AddDbContext<AppContext.AppContext>();
builder.Services.AddScoped<IUserService, DbUserService>(); builder.Services.AddScoped<IUserService, DbUserService>();
builder.Services.AddScoped<ITeamService, DbTeamService>(); builder.Services.AddScoped<ITeamService, DbTeamService>();
builder.Services.AddScoped<ITacticService, DbTacticService>(); builder.Services.AddScoped<ITacticService, DbTacticService>();
builder.Services.AddScoped<IContextAccessor, HttpContextAccessor>();
var app = builder.Build(); var app = builder.Build();
@ -104,5 +107,4 @@ app.Use((context, next) =>
}); });
app.Run(); app.Run();

@ -14,7 +14,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "http://localhost:5254", "applicationUrl": "http://+:5254",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

@ -1,3 +1,6 @@
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("UnitTests")]
namespace API.Validation; namespace API.Validation;
internal class IdentityData internal class IdentityData

@ -1,3 +1,5 @@
using Model;
namespace AppContext.Entities; namespace AppContext.Entities;
public class MemberEntity public class MemberEntity
@ -9,5 +11,5 @@ public class MemberEntity
public required int UserId { get; set; } public required int UserId { get; set; }
public UserEntity? User { get; set; } public UserEntity? User { get; set; }
public required string Role { get; set; } public required MemberRole Role { get; set; }
} }

@ -12,8 +12,4 @@ public class TacticEntity
public UserEntity? Owner { get; set; } public UserEntity? Owner { get; set; }
public CourtType Type { get; set; } public CourtType Type { get; set; }
public required string JsonContent { get; set; }
public required int RootStepId { get; set; }
public TacticStepEntity RootStep { get; set; }
} }

@ -7,9 +7,9 @@ public class TacticStepEntity
public int Id { get; set; } public int Id { get; set; }
public required int TacticId { get; set; }
public Tactic Tactic { get; set; }
public int TacticId { get; set; }
public TacticEntity Tactic { get; set; }
public required int? ParentId { get; set; } public required int? ParentId { get; set; }

@ -32,4 +32,9 @@ public static class EntitiesToModels
{ {
return new Team(entity.Id, entity.Name, entity.Picture, entity.MainColor, entity.SecondColor); return new Team(entity.Id, entity.Name, entity.Picture, entity.MainColor, entity.SecondColor);
} }
public static Member ToModel(this MemberEntity entity)
{
return new Member(entity.TeamId, entity.UserId, entity.Role);
}
} }

@ -38,16 +38,14 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService
Type = courtType Type = courtType
}; };
await context.Tactics.AddAsync(tacticEntity);
await context.SaveChangesAsync();
var stepEntity = new TacticStepEntity var stepEntity = new TacticStepEntity
{ {
ParentId = null, ParentId = null,
TacticId = tacticEntity.Id, JsonContent = "{\"components\": []}",
JsonContent = "{\"components\": []}" Tactic = tacticEntity
}; };
await context.Tactics.AddAsync(tacticEntity);
await context.TacticSteps.AddAsync(stepEntity); await context.TacticSteps.AddAsync(stepEntity);
await context.SaveChangesAsync(); await context.SaveChangesAsync();

@ -42,7 +42,7 @@ public class DbTeamService(AppContext.AppContext context) : ITeamService
return await context.Teams.CountAsync(t => t.Name.ToLower().Contains(nameNeedle.ToLower())); return await context.Teams.CountAsync(t => t.Name.ToLower().Contains(nameNeedle.ToLower()));
} }
public async Task<int> CountTeams() public async Task<int> CountTotalTeams()
{ {
return await context.Teams.CountAsync(); return await context.Teams.CountAsync();
} }
@ -82,4 +82,43 @@ public class DbTeamService(AppContext.AppContext context) : ITeamService
return await context.SaveChangesAsync() > 0; return await context.SaveChangesAsync() > 0;
} }
public IEnumerable<Member> GetMembersOf(int teamId)
{
return context.Members.Where(m => m.TeamId == teamId)
.AsEnumerable()
.Select(e => e.ToModel());
}
public async Task<Member> AddMember(int teamId, int userId, MemberRole role)
{
await context.Members.AddAsync(new MemberEntity
{
TeamId = teamId,
UserId = userId,
Role = role
});
await context.SaveChangesAsync();
return new Member(teamId, userId, role);
}
public async Task<bool> UpdateMember(Member member)
{
var entity =
await context.Members.FirstOrDefaultAsync(e => e.TeamId == member.TeamId && e.UserId == member.UserId);
if (entity == null)
return false;
entity.Role = member.Role;
return await context.SaveChangesAsync() > 0;
}
public async Task<bool> RemoveMember(int teamId, int userId)
{
await context.Members
.Where(e => e.TeamId == teamId && e.UserId == userId)
.ExecuteDeleteAsync();
return await context.SaveChangesAsync() > 0;
}
} }

@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Model; namespace Model;
public enum CourtType public enum CourtType

@ -0,0 +1,3 @@
namespace Model;
public record Member(int TeamId, int UserId, MemberRole Role);

@ -0,0 +1,7 @@
namespace Model;
public enum MemberRole
{
Player,
Coach
}

@ -8,10 +8,19 @@ public interface ITeamService
public Task<IEnumerable<Team>> ListTeams(); public Task<IEnumerable<Team>> ListTeams();
public Task<int> CountTeams(); public Task<int> CountTotalTeams();
public Task<Team> AddTeam(string name, string picture, string firstColor, string secondColor); public Task<Team> AddTeam(string name, string picture, string firstColor, string secondColor);
public Task RemoveTeams(params int[] teams); public Task RemoveTeams(params int[] teams);
public IEnumerable<Member> GetMembersOf(int teamId);
public Task<Member> AddMember(int teamId, int userId, MemberRole role);
public Task<bool> UpdateMember(Member member);
public Task<bool> RemoveMember(int teamId, int userId);
public Task<bool> UpdateTeam(Team team); public Task<bool> UpdateTeam(Team team);
} }

@ -1,5 +1,6 @@
using AppContext.Entities; using AppContext.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Model;
namespace StubContext; namespace StubContext;
@ -36,5 +37,24 @@ public class StubAppContext(DbContextOptions<AppContext> options) : AppContext(o
builder.Entity<MemberEntity>() builder.Entity<MemberEntity>()
.HasKey("TeamId", "UserId"); .HasKey("TeamId", "UserId");
builder.Entity<TacticEntity>()
.HasData(new TacticEntity()
{
Id = 1,
Name = "New tactic",
Type = CourtType.Plain,
CreationDate = new DateTime(2024, 5, 31),
OwnerId = 1,
});
builder.Entity<TacticStepEntity>()
.HasData(new TacticStepEntity
{
Id = 1,
JsonContent = "{}",
TacticId = 1,
ParentId = null
});
} }
} }

@ -0,0 +1,126 @@
using API.Controllers;
using API.Controllers.Admin;
using DbServices;
using FluentAssertions;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Model;
using StubContext;
namespace UnitTests;
public class AdminUserControllerTest
{
private static UsersAdminController GetUsersController()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var context = new StubAppContext(
new DbContextOptionsBuilder<AppContext.AppContext>()
.UseSqlite(connection)
.Options
);
context.Database.EnsureCreated();
var service = new DbUserService(context);
return new UsersAdminController(service);
}
[Fact]
public async void CountUsersTest()
{
var controller = GetUsersController();
(await controller.CountUsers()).Should().Be(new UsersAdminController.CountUsersResponse(5));
(await controller.CountUsers("a")).Should().BeEquivalentTo(new UsersAdminController.CountUsersResponse(3));
(await controller.CountUsers("")).Should().BeEquivalentTo(new UsersAdminController.CountUsersResponse(5));
(await controller.CountUsers("^ù$*")).Should().BeEquivalentTo(new UsersAdminController.CountUsersResponse(0));
}
[Fact]
public async void ListUsersTest()
{
var controller = GetUsersController();
(await controller.CountUsers()).Should().Be(new UsersAdminController.CountUsersResponse(5));
(await controller.ListUsers(0, 10, null)).Should().BeEquivalentTo(new List<User>
{
new(1, "maxime", "maxime@mail.com",
UsersController.DefaultProfilePicture, true),
new(2, "mael", "mael@mail.com",
UsersController.DefaultProfilePicture, true),
new(3, "yanis", "yanis@mail.com",
UsersController.DefaultProfilePicture, true),
new(4, "simon", "simon@mail.com",
UsersController.DefaultProfilePicture, true),
new(5, "vivien", "vivien@mail.com",
UsersController.DefaultProfilePicture, true),
});
(await controller.ListUsers(0, 10, "")).Should().BeEquivalentTo(new List<User>
{
new(1, "maxime", "maxime@mail.com",
UsersController.DefaultProfilePicture, true),
new(2, "mael", "mael@mail.com",
UsersController.DefaultProfilePicture, true),
new(3, "yanis", "yanis@mail.com",
UsersController.DefaultProfilePicture, true),
new(4, "simon", "simon@mail.com",
UsersController.DefaultProfilePicture, true),
new(5, "vivien", "vivien@mail.com",
UsersController.DefaultProfilePicture, true),
});
(await controller.ListUsers(0, 10, "a")).Should().BeEquivalentTo(new List<User>
{
new(1, "maxime", "maxime@mail.com",
UsersController.DefaultProfilePicture, true),
new(2, "mael", "mael@mail.com",
UsersController.DefaultProfilePicture, true),
new(3, "yanis", "yanis@mail.com",
UsersController.DefaultProfilePicture, true),
});
(await controller.ListUsers(0, 0, "")).Should().BeEquivalentTo(new List<User> { });
(await controller.ListUsers(0, 10, "^ù$*")).Should().BeEquivalentTo(new List<User> { });
}
[Fact]
public async void GetUserTest()
{
var controller = GetUsersController();
var response = await controller.GetUser(0);
response.Should().BeEquivalentTo(controller.NotFound());
response = await controller.GetUser(1);
response.Should().BeEquivalentTo(controller.Ok(new User(1, "maxime", "maxime@mail.com",
UsersController.DefaultProfilePicture, true)));
}
[Fact]
public async void AddUserTest()
{
var controller = GetUsersController();
var user = await controller.AddUser(new("Test", "TestPassword", "test@mail.com", true));
user.Should().BeEquivalentTo(new User(6, "Test", "test@mail.com", UsersController.DefaultProfilePicture, true));
}
[Fact]
public async void RemoveUsersTest()
{
var controller = GetUsersController();
var result = await controller.RemoveUsers(new([1, 4, 3, 5]));
result.Should().BeEquivalentTo(controller.Ok());
var remainingUsers = await controller.ListUsers(0, 10, null);
remainingUsers.Should().BeEquivalentTo(new User[] { new(2, "mael", "mael@mail.com",
UsersController.DefaultProfilePicture, true) });
}
[Fact]
public async void UpdateUserTest()
{
var controller = GetUsersController();
var result = await controller.UpdateUser(1, new("maxou", "maxou@mail.com", false));
result.Should().BeEquivalentTo(controller.Ok());
var userResult = await controller.GetUser(1);
userResult.Should().BeEquivalentTo(controller.Ok(new User(1, "maxou", "maxou@mail.com", UsersController.DefaultProfilePicture, false)));
}
}

@ -0,0 +1 @@
global using Xunit;

@ -0,0 +1,12 @@
using API.Context;
using Microsoft.AspNetCore.Http;
namespace UnitTests;
public class ManualContextAccessor(int userId) : IContextAccessor
{
public int CurrentUserId(HttpContext ctx)
{
return userId;
}
}

@ -0,0 +1,66 @@
using API.Controllers;
using API.DTO;
using AppContext.Entities;
using DbServices;
using FluentAssertions;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Model;
using StubContext;
namespace UnitTests;
public class TacticsControllerTest
{
private static (TacticController, AppContext.AppContext) GetController(int userId)
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var context = new StubAppContext(
new DbContextOptionsBuilder<AppContext.AppContext>()
.UseSqlite(connection)
.Options
);
context.Database.EnsureCreated();
var controller = new TacticController(
new DbTacticService(context),
new ManualContextAccessor(userId)
);
return (controller, context);
}
[Fact]
public async void UpdateName()
{
var (controller, context) = GetController(1);
var result = await controller.UpdateName(1, new("Stade de France"));
result.Should().BeEquivalentTo(controller.Ok());
var tactic = await context.Tactics.FindAsync(1);
tactic.Name.Should().BeEquivalentTo("Stade de France");
result = await controller.UpdateName(-1, new("Stade de France"));
result.Should().BeEquivalentTo(controller.Unauthorized());
}
[Fact]
public async void GetTacticInfoTest()
{
var (controller, context) = GetController(1);
var result = await controller.GetTacticInfo(1);
result.Should().BeEquivalentTo(controller.Ok(new Tactic(1, "New tactic", 1, CourtType.Plain, new DateTime(2024, 5, 31)).ToDto()));
result = await controller.GetTacticInfo(100);
result.Should().BeEquivalentTo(controller.Unauthorized());
}
[Fact]
public async void GetTacticStepsRoot()
{
var (controller, context) = GetController(1);
var result = await controller.GetTacticStepsRoot(1);
result.Should().BeEquivalentTo(controller.Ok(new TacticController.GetTacticStepsTreeResponse(new TacticStep(1, null, [], "{}").ToDto())));
}
}

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\API\API.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\API\API.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,49 @@
using API.Controllers;
using DbServices;
using FluentAssertions;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Model;
using StubContext;
namespace UnitTests;
public class UsersControllerTest
{
private static UsersController GetUserController(int userId)
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var context = new StubAppContext(
new DbContextOptionsBuilder<AppContext.AppContext>()
.UseSqlite(connection)
.Options
);
context.Database.EnsureCreated();
var controller = new UsersController(
new DbUserService(context),
new DbTeamService(context),
new DbTacticService(context),
new ManualContextAccessor(userId)
);
return controller;
}
[Fact]
public async void GetCurrentUserTest()
{
var controller = GetUserController(1);
var result = await controller.GetUser();
result.Should().BeEquivalentTo(new User(1, "maxime", "maxime@mail.com",
UsersController.DefaultProfilePicture, true));
}
[Fact]
public async void GetUserDataTest()
{
var controller = GetUserController(1);
var result = await controller.GetUserData();
result.Should().BeEquivalentTo(new UsersController.GetUserDataResponse([], []));
}
}

@ -4,8 +4,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppContext", "AppContext\AppContext.csproj", "{7E245DA5-0C5A-4933-9AF0-CA447F3BC159}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppContext", "AppContext\AppContext.csproj", "{7E245DA5-0C5A-4933-9AF0-CA447F3BC159}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTO", "DTO\DTO.csproj", "{7BE8B235-EF70-4C94-8938-5ABB5AEB08B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StubContext", "StubContext\StubContext.csproj", "{420D507C-D51C-48D9-A819-72B08AEBD024}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StubContext", "StubContext\StubContext.csproj", "{420D507C-D51C-48D9-A819-72B08AEBD024}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}"
@ -16,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbServices", "DbServices\Db
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Converters", "Converters\Converters.csproj", "{465819A9-7158-4612-AC57-ED2C7A0F243E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Converters", "Converters\Converters.csproj", "{465819A9-7158-4612-AC57-ED2C7A0F243E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{82A100BE-5610-4741-8F23-1CD653E8EFCD}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -58,5 +58,9 @@ Global
{9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Release|Any CPU.Build.0 = Release|Any CPU {9C5EAD2F-FA50-43C2-BB86-1065ED661C52}.Release|Any CPU.Build.0 = Release|Any CPU
{82A100BE-5610-4741-8F23-1CD653E8EFCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82A100BE-5610-4741-8F23-1CD653E8EFCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82A100BE-5610-4741-8F23-1CD653E8EFCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82A100BE-5610-4741-8F23-1CD653E8EFCD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

@ -1,18 +1,11 @@
kind: pipeline kind: pipeline
type: docker type: docker
name: "CI and deploy on iqball.maxou.dev" name: "CI, deploy server on iqball.maxou.dev, deploy doc on codehub"
steps: steps:
- image: mcr.microsoft.com/dotnet/sdk:8.0
name: "CI"
commands:
- dotnet test
- image: plugins/docker - image: plugins/docker
name: "build and push docker image" name: "build and push docker image"
depends_on:
- "CI"
settings: settings:
dockerfile: ci/API.dockerfile dockerfile: ci/API.dockerfile
context: . context: .

@ -0,0 +1,9 @@
{
"stryker-config":
{
"reporters": [
"progress",
"html"
]
}
}
Loading…
Cancel
Save