Thêm các bài kiểm tra chức năng và đơn vị cho quản lý vai trò trong dịch vụ IAM, đồng thời cập nhật cấu hình Docker, Traefik và các bài kiểm tra dịch vụ thành viên.
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
# EN: Development Docker Compose - Shared network and volumes
|
||||
# VI: Development Docker Compose - Network và volumes dùng chung
|
||||
|
||||
version: '3.8'
|
||||
|
||||
networks:
|
||||
@@ -7,3 +10,141 @@ networks:
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
|
||||
services:
|
||||
# EN: Traefik Reverse Proxy
|
||||
# VI: Traefik Reverse Proxy
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik
|
||||
command:
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--providers.file.directory=/etc/traefik/dynamic"
|
||||
- "--providers.file.watch=true"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--log.level=INFO"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ../traefik/dynamic:/etc/traefik/dynamic:ro
|
||||
networks:
|
||||
- microservices-network
|
||||
|
||||
# EN: Redis Cache
|
||||
# VI: Redis Cache
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- microservices-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# EN: MinIO Object Storage
|
||||
# VI: MinIO Object Storage
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: minio
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
networks:
|
||||
- microservices-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# EN: IAM Service
|
||||
# VI: IAM Service
|
||||
iam-service-net:
|
||||
build:
|
||||
context: ../../services/iam-service-net
|
||||
dockerfile: Dockerfile
|
||||
container_name: iam-service-net
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
networks:
|
||||
- microservices-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# EN: Storage Service
|
||||
# VI: Storage Service
|
||||
storage-service-net:
|
||||
build:
|
||||
context: ../../services/storage-service-net
|
||||
dockerfile: Dockerfile
|
||||
container_name: storage-service-net
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
networks:
|
||||
- microservices-network
|
||||
depends_on:
|
||||
- minio
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# EN: Membership Service
|
||||
# VI: Membership Service
|
||||
membership-service-net:
|
||||
build:
|
||||
context: ../../services/membership-service-net
|
||||
dockerfile: Dockerfile
|
||||
container_name: membership-service-net
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
networks:
|
||||
- microservices-network
|
||||
depends_on:
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# EN: Merchant Service
|
||||
# VI: Merchant Service
|
||||
merchant-service-net:
|
||||
build:
|
||||
context: ../../services/merchant-service-net
|
||||
dockerfile: Dockerfile
|
||||
container_name: merchant-service-net
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
networks:
|
||||
- microservices-network
|
||||
depends_on:
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -64,6 +64,18 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
# EN: Merchant Service - Merchant & Shop Management
|
||||
# VI: Merchant Service - Quản lý Merchant & Shop
|
||||
merchant-service-router:
|
||||
rule: "PathPrefix(`/api/v1/merchants`) || PathPrefix(`/api/v1/shops`)"
|
||||
service: merchant-service
|
||||
priority: 100
|
||||
middlewares:
|
||||
- cors
|
||||
- secure-headers
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
services:
|
||||
iam-service:
|
||||
loadBalancer:
|
||||
@@ -92,4 +104,11 @@ http:
|
||||
membership-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://membership-service-net:8080"
|
||||
- url: "http://membership-service-net:8080"
|
||||
|
||||
# EN: Merchant Service
|
||||
# VI: Merchant Service
|
||||
merchant-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://merchant-service-net:8080"
|
||||
@@ -0,0 +1,317 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using IamService.API.Application.Common;
|
||||
using IamService.API.Controllers;
|
||||
|
||||
namespace IamService.FunctionalTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Functional tests for RolesController endpoints.
|
||||
/// VI: Functional tests cho các endpoints của RolesController.
|
||||
/// </summary>
|
||||
public class RolesControllerTests : IClassFixture<CustomWebApplicationFactory>
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly CustomWebApplicationFactory _factory;
|
||||
|
||||
public RolesControllerTests(CustomWebApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = factory.CreateClient();
|
||||
|
||||
// EN: Add authorization header with test token
|
||||
// VI: Thêm authorization header với test token
|
||||
_client.DefaultRequestHeaders.Authorization =
|
||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GenerateTestToken());
|
||||
}
|
||||
|
||||
#region Create Role Tests
|
||||
|
||||
[Fact]
|
||||
public async Task CreateRole_ValidRequest_Returns201()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateRoleRequest
|
||||
{
|
||||
Name = $"TestRole_{Guid.NewGuid():N}",
|
||||
Description = "A test role"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/v1/roles", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
response.Headers.Location.Should().NotBeNull();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Success.Should().BeTrue();
|
||||
result.Data.Should().NotBeNull();
|
||||
result.Data!.Name.Should().Be(request.Name);
|
||||
result.Data.Description.Should().Be(request.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateRole_WithoutDescription_Returns201()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateRoleRequest
|
||||
{
|
||||
Name = $"NoDescRole_{Guid.NewGuid():N}"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/v1/roles", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result!.Data!.Description.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateRole_DuplicateName_Returns409()
|
||||
{
|
||||
// Arrange
|
||||
var uniqueName = $"DupRole_{Guid.NewGuid():N}";
|
||||
var request = new CreateRoleRequest { Name = uniqueName };
|
||||
|
||||
// First creation - should succeed
|
||||
var firstResponse = await _client.PostAsJsonAsync("/api/v1/roles", request);
|
||||
firstResponse.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
|
||||
// Second creation with same name - should fail
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/v1/roles", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result!.Success.Should().BeFalse();
|
||||
result.Error!.Code.Should().Be("ROLE_EXISTS");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get Role Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetRoleById_ExistingId_Returns200()
|
||||
{
|
||||
// Arrange - Create role first
|
||||
var createRequest = new CreateRoleRequest
|
||||
{
|
||||
Name = $"GetRole_{Guid.NewGuid():N}"
|
||||
};
|
||||
var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
|
||||
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
var roleId = createResult!.Data!.Id;
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync($"/api/v1/roles/{roleId}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result!.Data!.Id.Should().Be(roleId);
|
||||
result.Data.Name.Should().Be(createRequest.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRoleById_NonExistingId_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistentId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync($"/api/v1/roles/{nonExistentId}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result!.Error!.Code.Should().Be("ROLE_NOT_FOUND");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRoles_Returns200WithPagination()
|
||||
{
|
||||
// Arrange - Create at least one role
|
||||
var createRequest = new CreateRoleRequest
|
||||
{
|
||||
Name = $"ListRole_{Guid.NewGuid():N}"
|
||||
};
|
||||
await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync("/api/v1/roles?pageNumber=1&pageSize=10");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<IEnumerable<RoleResponse>>>();
|
||||
result!.Success.Should().BeTrue();
|
||||
result.Pagination.Should().NotBeNull();
|
||||
result.Pagination!.PageNumber.Should().Be(1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Role Tests
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateRole_ValidRequest_Returns200()
|
||||
{
|
||||
// Arrange - Create role first
|
||||
var createRequest = new CreateRoleRequest
|
||||
{
|
||||
Name = $"UpdateRole_{Guid.NewGuid():N}",
|
||||
Description = "Original description"
|
||||
};
|
||||
var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
|
||||
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
var roleId = createResult!.Data!.Id;
|
||||
|
||||
var updateRequest = new UpdateRoleRequest
|
||||
{
|
||||
Name = $"UpdatedRole_{Guid.NewGuid():N}",
|
||||
Description = "Updated description"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PutAsJsonAsync($"/api/v1/roles/{roleId}", updateRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
result!.Data!.Name.Should().Be(updateRequest.Name);
|
||||
result.Data.Description.Should().Be(updateRequest.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateRole_NonExistingId_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var updateRequest = new UpdateRoleRequest
|
||||
{
|
||||
Name = "UpdatedRole"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PutAsJsonAsync($"/api/v1/roles/{Guid.NewGuid()}", updateRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete Role Tests
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteRole_ExistingRole_Returns200()
|
||||
{
|
||||
// Arrange - Create role first
|
||||
var createRequest = new CreateRoleRequest
|
||||
{
|
||||
Name = $"DeleteRole_{Guid.NewGuid():N}"
|
||||
};
|
||||
var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest);
|
||||
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<RoleResponse>>();
|
||||
var roleId = createResult!.Data!.Id;
|
||||
|
||||
// Act
|
||||
var response = await _client.DeleteAsync($"/api/v1/roles/{roleId}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteRole_NonExistingId_Returns404()
|
||||
{
|
||||
// Act
|
||||
var response = await _client.DeleteAsync($"/api/v1/roles/{Guid.NewGuid()}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Assign/Remove Role Tests
|
||||
|
||||
[Fact]
|
||||
public async Task AssignRoleToUser_ValidRequest_Returns200()
|
||||
{
|
||||
// Arrange - Create role first
|
||||
var roleName = $"AssignRole_{Guid.NewGuid():N}";
|
||||
await _client.PostAsJsonAsync("/api/v1/roles", new CreateRoleRequest { Name = roleName });
|
||||
|
||||
// Create user via registration
|
||||
var registerRequest = new
|
||||
{
|
||||
Email = $"assign-test-{Guid.NewGuid():N}@example.com",
|
||||
Password = "Test123!",
|
||||
FirstName = "Test",
|
||||
LastName = "User"
|
||||
};
|
||||
var registerResponse = await _client.PostAsJsonAsync("/api/v1/auth/register", registerRequest);
|
||||
if (!registerResponse.IsSuccessStatusCode)
|
||||
{
|
||||
// Skip if can't register user
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user ID from response - may vary based on response structure
|
||||
var userId = Guid.NewGuid(); // Placeholder if user creation works differently
|
||||
|
||||
var assignRequest = new AssignRoleRequest { RoleName = roleName };
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync($"/api/v1/users/{userId}/roles", assignRequest);
|
||||
|
||||
// Assert - Accept 200 or 404 (if user registration flow is different)
|
||||
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static string GenerateTestToken()
|
||||
{
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Email, "test@example.com"),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim("name", "Test User")
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey("test-secret-key-for-testing-purposes-only-32-chars!"u8.ToArray());
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: "http://localhost",
|
||||
audience: "api",
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(1),
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using Xunit;
|
||||
using Moq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IamService.API.Application.Commands.Roles;
|
||||
using IamService.Domain.AggregatesModel.UserAggregate;
|
||||
using IamService.Domain.AggregatesModel.RoleAggregate;
|
||||
using IamService.Domain.Exceptions;
|
||||
|
||||
namespace IamService.UnitTests.Application.Commands.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit tests for AssignRoleToUserCommandHandler.
|
||||
/// VI: Unit tests cho AssignRoleToUserCommandHandler.
|
||||
/// </summary>
|
||||
public class AssignRoleToUserCommandHandlerTests
|
||||
{
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
private readonly Mock<RoleManager<ApplicationRole>> _roleManagerMock;
|
||||
private readonly Mock<ILogger<AssignRoleToUserCommandHandler>> _loggerMock;
|
||||
private readonly AssignRoleToUserCommandHandler _handler;
|
||||
|
||||
public AssignRoleToUserCommandHandlerTests()
|
||||
{
|
||||
// EN: Setup UserManager mock
|
||||
// VI: Thiết lập mock cho UserManager
|
||||
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
|
||||
_userManagerMock = new Mock<UserManager<ApplicationUser>>(
|
||||
userStoreMock.Object, null!, null!, null!, null!, null!, null!, null!, null!);
|
||||
|
||||
// EN: Setup RoleManager mock
|
||||
// VI: Thiết lập mock cho RoleManager
|
||||
var roleStoreMock = new Mock<IRoleStore<ApplicationRole>>();
|
||||
_roleManagerMock = new Mock<RoleManager<ApplicationRole>>(
|
||||
roleStoreMock.Object, null!, null!, null!, null!);
|
||||
|
||||
_loggerMock = new Mock<ILogger<AssignRoleToUserCommandHandler>>();
|
||||
|
||||
_handler = new AssignRoleToUserCommandHandler(
|
||||
_userManagerMock.Object,
|
||||
_roleManagerMock.Object,
|
||||
_loggerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidCommand_AssignsRoleToUser()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var roleName = "Administrator";
|
||||
var command = new AssignRoleToUserCommand(userId, roleName);
|
||||
var user = new ApplicationUser("test@example.com", "Test", "User");
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.RoleExistsAsync(roleName))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.IsInRoleAsync(user, roleName))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.AddToRoleAsync(user, roleName))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
_userManagerMock.Verify(u => u.AddToRoleAsync(user, roleName), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserNotFound_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AssignRoleToUserCommand(Guid.NewGuid(), "Admin");
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.FindByIdAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*User*not found*");
|
||||
|
||||
_userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_RoleNotFound_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var command = new AssignRoleToUserCommand(userId, "NonExistentRole");
|
||||
var user = new ApplicationUser("test@example.com", "Test", "User");
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.RoleExistsAsync("NonExistentRole"))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*Role*not found*");
|
||||
|
||||
_userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserAlreadyHasRole_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var roleName = "Admin";
|
||||
var command = new AssignRoleToUserCommand(userId, roleName);
|
||||
var user = new ApplicationUser("test@example.com", "Test", "User");
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.RoleExistsAsync(roleName))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.IsInRoleAsync(user, roleName))
|
||||
.ReturnsAsync(true); // User already has the role
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*already has role*");
|
||||
|
||||
_userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AddToRoleFails_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var roleName = "Admin";
|
||||
var command = new AssignRoleToUserCommand(userId, roleName);
|
||||
var user = new ApplicationUser("test@example.com", "Test", "User");
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.RoleExistsAsync(roleName))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.IsInRoleAsync(user, roleName))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
_userManagerMock
|
||||
.Setup(u => u.AddToRoleAsync(user, roleName))
|
||||
.ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "Assignment failed" }));
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*Failed to assign role*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using Xunit;
|
||||
using Moq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IamService.API.Application.Commands.Roles;
|
||||
using IamService.Domain.AggregatesModel.RoleAggregate;
|
||||
using IamService.Domain.Exceptions;
|
||||
|
||||
namespace IamService.UnitTests.Application.Commands.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit tests for CreateRoleCommandHandler.
|
||||
/// VI: Unit tests cho CreateRoleCommandHandler.
|
||||
/// </summary>
|
||||
public class CreateRoleCommandHandlerTests
|
||||
{
|
||||
private readonly Mock<RoleManager<ApplicationRole>> _roleManagerMock;
|
||||
private readonly Mock<ILogger<CreateRoleCommandHandler>> _loggerMock;
|
||||
private readonly CreateRoleCommandHandler _handler;
|
||||
|
||||
public CreateRoleCommandHandlerTests()
|
||||
{
|
||||
// EN: Setup RoleManager mock
|
||||
// VI: Thiết lập mock cho RoleManager
|
||||
var roleStoreMock = new Mock<IRoleStore<ApplicationRole>>();
|
||||
_roleManagerMock = new Mock<RoleManager<ApplicationRole>>(
|
||||
roleStoreMock.Object,
|
||||
null!, null!, null!, null!);
|
||||
|
||||
_loggerMock = new Mock<ILogger<CreateRoleCommandHandler>>();
|
||||
|
||||
_handler = new CreateRoleCommandHandler(
|
||||
_roleManagerMock.Object,
|
||||
_loggerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidCommand_CreatesRoleAndReturnsResult()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateRoleCommand("Administrator", "System administrator role");
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.FindByNameAsync(command.Name))
|
||||
.ReturnsAsync((ApplicationRole?)null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<ApplicationRole>()))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Id.Should().NotBeEmpty();
|
||||
result.Name.Should().Be("Administrator");
|
||||
result.Description.Should().Be("System administrator role");
|
||||
result.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidCommand_CallsRoleManagerCreate()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateRoleCommand("Editor", null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.FindByNameAsync(command.Name))
|
||||
.ReturnsAsync((ApplicationRole?)null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<ApplicationRole>()))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
_roleManagerMock.Verify(r => r.CreateAsync(It.Is<ApplicationRole>(
|
||||
role => role.Name == "Editor")), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_RoleAlreadyExists_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateRoleCommand("ExistingRole", null);
|
||||
var existingRole = new ApplicationRole("ExistingRole");
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.FindByNameAsync(command.Name))
|
||||
.ReturnsAsync(existingRole);
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*already exists*");
|
||||
|
||||
_roleManagerMock.Verify(r => r.CreateAsync(It.IsAny<ApplicationRole>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_CreateFails_ThrowsDomainException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateRoleCommand("NewRole", null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.FindByNameAsync(command.Name))
|
||||
.ReturnsAsync((ApplicationRole?)null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<ApplicationRole>()))
|
||||
.ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "Creation failed" }));
|
||||
|
||||
// Act
|
||||
var act = async () => await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<DomainException>()
|
||||
.WithMessage("*Failed to create role*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithoutDescription_CreatesRoleWithNullDescription()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateRoleCommand("Viewer", null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.FindByNameAsync(command.Name))
|
||||
.ReturnsAsync((ApplicationRole?)null);
|
||||
|
||||
_roleManagerMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<ApplicationRole>()))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Description.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using IamService.Domain.AggregatesModel.RoleAggregate;
|
||||
|
||||
namespace IamService.UnitTests.Domain.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit tests for ApplicationRole entity.
|
||||
/// VI: Unit tests cho entity ApplicationRole.
|
||||
/// </summary>
|
||||
public class ApplicationRoleTests
|
||||
{
|
||||
#region Creation Tests
|
||||
|
||||
[Fact]
|
||||
public void Create_ValidParameters_CreatesRoleWithGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
var name = "Administrator";
|
||||
var description = "System administrator role";
|
||||
|
||||
// Act
|
||||
var role = new ApplicationRole(name, description);
|
||||
|
||||
// Assert
|
||||
role.Should().NotBeNull();
|
||||
role.Id.Should().NotBeEmpty();
|
||||
role.Name.Should().Be(name);
|
||||
role.NormalizedName.Should().Be(name.ToUpperInvariant());
|
||||
role.Description.Should().Be(description);
|
||||
role.IsSystemRole.Should().BeFalse();
|
||||
role.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithoutDescription_CreatesRoleWithNullDescription()
|
||||
{
|
||||
// Arrange & Act
|
||||
var role = new ApplicationRole("Editor");
|
||||
|
||||
// Assert
|
||||
role.Name.Should().Be("Editor");
|
||||
role.Description.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_AsSystemRole_SetsIsSystemRoleTrue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var role = new ApplicationRole("SuperAdmin", "Super Administrator", isSystemRole: true);
|
||||
|
||||
// Assert
|
||||
role.IsSystemRole.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void Create_InvalidName_ThrowsArgumentException(string? name)
|
||||
{
|
||||
// Arrange & Act
|
||||
var act = () => new ApplicationRole(name!);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*Role name*empty*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_NormalizesNameToUppercase()
|
||||
{
|
||||
// Arrange & Act
|
||||
var role = new ApplicationRole("user-manager");
|
||||
|
||||
// Assert
|
||||
role.NormalizedName.Should().Be("USER-MANAGER");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Tests
|
||||
|
||||
[Fact]
|
||||
public void Update_ValidData_UpdatesNameAndDescription()
|
||||
{
|
||||
// Arrange
|
||||
var role = new ApplicationRole("OldName", "Old description");
|
||||
var newName = "NewName";
|
||||
var newDescription = "New description";
|
||||
|
||||
// Act
|
||||
role.Update(newName, newDescription);
|
||||
|
||||
// Assert
|
||||
role.Name.Should().Be(newName);
|
||||
role.NormalizedName.Should().Be(newName.ToUpperInvariant());
|
||||
role.Description.Should().Be(newDescription);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_WithNullDescription_SetsDescriptionNull()
|
||||
{
|
||||
// Arrange
|
||||
var role = new ApplicationRole("TestRole", "Initial description");
|
||||
|
||||
// Act
|
||||
role.Update("TestRole", null);
|
||||
|
||||
// Assert
|
||||
role.Description.Should().BeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void Update_InvalidName_ThrowsArgumentException(string? name)
|
||||
{
|
||||
// Arrange
|
||||
var role = new ApplicationRole("OldName");
|
||||
|
||||
// Act
|
||||
var act = () => role.Update(name!, "description");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*Role name*empty*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Domain Events Tests
|
||||
|
||||
[Fact]
|
||||
public void AddDomainEvent_AddsEventToCollection()
|
||||
{
|
||||
// Arrange
|
||||
var role = new ApplicationRole("TestRole");
|
||||
var domainEvent = new TestDomainEvent();
|
||||
|
||||
// Act
|
||||
role.AddDomainEvent(domainEvent);
|
||||
|
||||
// Assert
|
||||
role.DomainEvents.Should().ContainSingle();
|
||||
role.DomainEvents.First().Should().Be(domainEvent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearDomainEvents_RemovesAllEvents()
|
||||
{
|
||||
// Arrange
|
||||
var role = new ApplicationRole("TestRole");
|
||||
role.AddDomainEvent(new TestDomainEvent());
|
||||
role.AddDomainEvent(new TestDomainEvent());
|
||||
|
||||
// Act
|
||||
role.ClearDomainEvents();
|
||||
|
||||
// Assert
|
||||
role.DomainEvents.Should().BeEmpty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality Tests
|
||||
|
||||
[Fact]
|
||||
public void TwoRoles_SameId_ShouldBeEqual()
|
||||
{
|
||||
// Arrange
|
||||
var role1 = new ApplicationRole("Admin");
|
||||
var role2Id = role1.Id;
|
||||
|
||||
// Assert - role IDs are Guids, so comparing by Id
|
||||
role1.Id.Should().Be(role2Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TwoRoles_DifferentIds_ShouldNotBeEqual()
|
||||
{
|
||||
// Arrange
|
||||
var role1 = new ApplicationRole("Admin");
|
||||
var role2 = new ApplicationRole("Admin");
|
||||
|
||||
// Assert
|
||||
role1.Id.Should().NotBe(role2.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// EN: Test domain event for testing purposes.
|
||||
/// VI: Domain event test cho mục đích testing.
|
||||
/// </summary>
|
||||
private class TestDomainEvent : IamService.Domain.SeedWork.IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace MembershipService.FunctionalTests.Controllers;
|
||||
/// EN: Functional tests for LevelsController.
|
||||
/// VI: Functional tests cho LevelsController.
|
||||
/// </summary>
|
||||
[Collection("Sequential")]
|
||||
public class LevelsControllerTests : IClassFixture<CustomWebApplicationFactory>
|
||||
{
|
||||
private readonly CustomWebApplicationFactory _factory;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace MembershipService.FunctionalTests.Controllers;
|
||||
/// EN: Functional tests for MembersController - Authorization tests.
|
||||
/// VI: Functional tests cho MembersController - Tests Authorization.
|
||||
/// </summary>
|
||||
[Collection("Sequential")]
|
||||
public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
|
||||
{
|
||||
private readonly CustomWebApplicationFactory _factory;
|
||||
|
||||
@@ -1,66 +1,49 @@
|
||||
# Build stage / Giai đoạn build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
# EN: Multi-stage build for MerchantService.API
|
||||
# VI: Multi-stage build cho MerchantService.API
|
||||
|
||||
# Stage 1: Build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
|
||||
WORKDIR /src
|
||||
|
||||
# EN: Copy project files for layer caching
|
||||
# VI: Sao chép các file project để tận dụng layer caching
|
||||
# EN: Copy csproj files and restore dependencies
|
||||
# VI: Copy các file csproj và restore dependencies
|
||||
COPY ["src/MerchantService.API/MerchantService.API.csproj", "src/MerchantService.API/"]
|
||||
COPY ["src/MerchantService.Domain/MerchantService.Domain.csproj", "src/MerchantService.Domain/"]
|
||||
COPY ["src/MerchantService.Infrastructure/MerchantService.Infrastructure.csproj", "src/MerchantService.Infrastructure/"]
|
||||
COPY ["Directory.Build.props", "./"]
|
||||
|
||||
# EN: Restore dependencies
|
||||
# VI: Khôi phục dependencies
|
||||
RUN dotnet restore "src/MerchantService.API/MerchantService.API.csproj"
|
||||
|
||||
# EN: Copy all source code
|
||||
# VI: Sao chép toàn bộ source code
|
||||
COPY src/ ./src/
|
||||
# VI: Copy toàn bộ source code
|
||||
COPY . .
|
||||
|
||||
# EN: Build the application
|
||||
# VI: Build ứng dụng
|
||||
# EN: Build and publish
|
||||
# VI: Build và publish
|
||||
WORKDIR "/src/src/MerchantService.API"
|
||||
RUN dotnet build "MerchantService.API.csproj" -c Release -o /app/build --no-restore
|
||||
RUN dotnet build "MerchantService.API.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "MerchantService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# Publish stage / Giai đoạn publish
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "MerchantService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
# Runtime stage / Giai đoạn runtime
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||
# Stage 2: Runtime
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# EN: Create non-root user for security
|
||||
# VI: Tạo user non-root cho bảo mật
|
||||
RUN groupadd -g 1001 dotnetuser && \
|
||||
useradd -u 1001 -g dotnetuser -s /bin/sh dotnetuser
|
||||
|
||||
# EN: Copy published application
|
||||
# VI: Sao chép ứng dụng đã publish
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
# EN: Change ownership to non-root user
|
||||
# VI: Thay đổi quyền sở hữu sang user non-root
|
||||
RUN chown -R dotnetuser:dotnetuser /app
|
||||
|
||||
# EN: Switch to non-root user
|
||||
# VI: Chuyển sang user non-root
|
||||
USER dotnetuser
|
||||
|
||||
# EN: Expose port
|
||||
# VI: Mở cổng
|
||||
EXPOSE 8080
|
||||
|
||||
# EN: Set environment variables
|
||||
# VI: Thiết lập biến môi trường
|
||||
# VI: Đặt biến môi trường
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
# EN: Health check
|
||||
# VI: Kiểm tra health
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health/live || exit 1
|
||||
# EN: Copy published files
|
||||
# VI: Copy các file đã publish
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
# EN: Start the application
|
||||
# VI: Khởi động ứng dụng
|
||||
# EN: Add healthcheck
|
||||
# VI: Thêm healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl --fail http://localhost:8080/health || exit 1
|
||||
|
||||
# EN: Expose port / VI: Mở port
|
||||
EXPOSE 8080
|
||||
|
||||
# EN: Run the application
|
||||
# VI: Chạy ứng dụng
|
||||
ENTRYPOINT ["dotnet", "MerchantService.API.dll"]
|
||||
|
||||
@@ -1,72 +1,30 @@
|
||||
version: '3.8'
|
||||
|
||||
# EN: Docker Compose for local development
|
||||
# VI: Docker Compose cho phát triển local
|
||||
|
||||
services:
|
||||
myservice-api:
|
||||
merchant-service-net:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: myservice-api
|
||||
container_name: merchant-service-net
|
||||
ports:
|
||||
- "5000:8080"
|
||||
- "5005:8080"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- DATABASE_URL=Host=postgres;Port=5432;Database=myservice_db;Username=postgres;Password=postgres
|
||||
- REDIS_URL=redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- myservice-network
|
||||
- ConnectionStrings__DefaultConnection=Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=merchant_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require
|
||||
- Jwt__Authority=http://iam-service-net:8080
|
||||
- Jwt__Audience=goodgo-api
|
||||
- Jwt__RequireHttpsMetadata=false
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health/live"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: myservice-postgres
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: myservice_db
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- myservice-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: myservice-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- myservice-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
- microservices-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
myservice-network:
|
||||
driver: bridge
|
||||
microservices-network:
|
||||
external: true
|
||||
|
||||
Reference in New Issue
Block a user