336 lines
13 KiB
C#
336 lines
13 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using line_gestao_api.Data;
|
|
using line_gestao_api.Dtos;
|
|
using line_gestao_api.Models;
|
|
using line_gestao_api.Services;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Xunit;
|
|
|
|
namespace line_gestao_api.Tests;
|
|
|
|
public class SystemTenantIntegrationTests
|
|
{
|
|
private static readonly Guid TenantAId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
|
private static readonly Guid TenantBId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
|
|
|
|
private const string TenantAClientName = "CLIENTE-ALFA LTDA";
|
|
private const string TenantBClientName = "CLIENTE-BETA S/A";
|
|
|
|
[Fact]
|
|
public async Task CommonUser_OnlySeesOwnTenantData()
|
|
{
|
|
using var factory = new ApiFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
await SeedTenantsAndLinesAsync(factory.Services);
|
|
await UpsertUserAsync(factory.Services, TenantAId, "tenanta.user@test.local", "TenantA123!", "cliente");
|
|
|
|
var token = await LoginAndGetTokenAsync(client, "tenanta.user@test.local", "TenantA123!", TenantAId);
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
|
|
var response = await client.GetAsync("/api/lines/clients");
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var clients = await response.Content.ReadFromJsonAsync<List<string>>();
|
|
Assert.NotNull(clients);
|
|
Assert.Contains(TenantAClientName, clients!);
|
|
Assert.DoesNotContain(TenantBClientName, clients);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CommonUser_CannotAccessSystemEndpoints()
|
|
{
|
|
using var factory = new ApiFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
await SeedTenantsAndLinesAsync(factory.Services);
|
|
await UpsertUserAsync(factory.Services, TenantAId, "tenanta.block@test.local", "TenantA123!", "cliente");
|
|
|
|
var token = await LoginAndGetTokenAsync(client, "tenanta.block@test.local", "TenantA123!", TenantAId);
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
|
|
var response = await client.GetAsync("/api/system/tenants?source=MobileLines.Cliente&active=true");
|
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SysAdmin_CanListClientTenants()
|
|
{
|
|
using var factory = new ApiFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
await SeedTenantsAndLinesAsync(factory.Services);
|
|
|
|
var token = await LoginAndGetTokenAsync(
|
|
client,
|
|
"admin.master@test.local",
|
|
"AdminMaster123!",
|
|
SystemTenantConstants.SystemTenantId);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
var response = await client.GetAsync("/api/system/tenants?source=MobileLines.Cliente&active=true");
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var tenants = await response.Content.ReadFromJsonAsync<List<SystemTenantListItemDto>>();
|
|
Assert.NotNull(tenants);
|
|
Assert.Contains(tenants!, t => t.TenantId == TenantAId && t.NomeOficial == TenantAClientName);
|
|
Assert.Contains(tenants, t => t.TenantId == TenantBId && t.NomeOficial == TenantBClientName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SysAdmin_CreatesTenantUser_AndNewUserSeesOnlyOwnTenant()
|
|
{
|
|
using var factory = new ApiFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
await SeedTenantsAndLinesAsync(factory.Services);
|
|
|
|
var adminToken = await LoginAndGetTokenAsync(
|
|
client,
|
|
"admin.master@test.local",
|
|
"AdminMaster123!",
|
|
SystemTenantConstants.SystemTenantId);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
|
|
|
var request = new CreateSystemTenantUserRequest
|
|
{
|
|
Name = "Usuário Cliente A",
|
|
Email = "novo.clientea@test.local",
|
|
Password = "ClienteA123!",
|
|
Roles = new List<string> { "cliente" }
|
|
};
|
|
|
|
var createResponse = await client.PostAsJsonAsync($"/api/system/tenants/{TenantAId}/users", request);
|
|
createResponse.EnsureSuccessStatusCode();
|
|
|
|
var created = await createResponse.Content.ReadFromJsonAsync<SystemTenantUserCreatedDto>();
|
|
Assert.NotNull(created);
|
|
Assert.Equal(TenantAId, created!.TenantId);
|
|
Assert.Equal("novo.clientea@test.local", created.Email);
|
|
Assert.Contains("cliente", created.Roles);
|
|
|
|
var userToken = await LoginAndGetTokenAsync(client, "novo.clientea@test.local", "ClienteA123!", TenantAId);
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", userToken);
|
|
|
|
var visibleClientsResponse = await client.GetAsync("/api/lines/clients");
|
|
visibleClientsResponse.EnsureSuccessStatusCode();
|
|
var clients = await visibleClientsResponse.Content.ReadFromJsonAsync<List<string>>();
|
|
|
|
Assert.NotNull(clients);
|
|
Assert.Contains(TenantAClientName, clients!);
|
|
Assert.DoesNotContain(TenantBClientName, clients);
|
|
|
|
await using var scope = factory.Services.CreateAsyncScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var systemAudit = await db.AuditLogs
|
|
.IgnoreQueryFilters()
|
|
.OrderByDescending(x => x.OccurredAtUtc)
|
|
.FirstOrDefaultAsync(x => x.Action == SystemAuditActions.CreateTenantUser);
|
|
|
|
Assert.NotNull(systemAudit);
|
|
Assert.Equal(SystemTenantConstants.SystemTenantId, systemAudit!.ActorTenantId);
|
|
Assert.Equal(TenantAId, systemAudit.TargetTenantId);
|
|
Assert.DoesNotContain("ClienteA123!", systemAudit.MetadataJson);
|
|
}
|
|
|
|
private static async Task SeedTenantsAndLinesAsync(IServiceProvider services)
|
|
{
|
|
await using var scope = services.CreateAsyncScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
|
|
var tenantA = await db.Tenants.FirstOrDefaultAsync(t => t.Id == TenantAId);
|
|
if (tenantA == null)
|
|
{
|
|
db.Tenants.Add(new Tenant
|
|
{
|
|
Id = TenantAId,
|
|
NomeOficial = TenantAClientName,
|
|
IsSystem = false,
|
|
Ativo = true,
|
|
SourceType = SystemTenantConstants.MobileLinesClienteSourceType,
|
|
SourceKey = TenantAClientName,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
var tenantB = await db.Tenants.FirstOrDefaultAsync(t => t.Id == TenantBId);
|
|
if (tenantB == null)
|
|
{
|
|
db.Tenants.Add(new Tenant
|
|
{
|
|
Id = TenantBId,
|
|
NomeOficial = TenantBClientName,
|
|
IsSystem = false,
|
|
Ativo = true,
|
|
SourceType = SystemTenantConstants.MobileLinesClienteSourceType,
|
|
SourceKey = TenantBClientName,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
var currentLines = await db.MobileLines.IgnoreQueryFilters().ToListAsync();
|
|
if (currentLines.Count > 0)
|
|
{
|
|
db.MobileLines.RemoveRange(currentLines);
|
|
}
|
|
|
|
db.MobileLines.AddRange(
|
|
new MobileLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = 1,
|
|
Linha = "5511999990001",
|
|
Cliente = TenantAClientName,
|
|
TenantId = TenantAId
|
|
},
|
|
new MobileLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Item = 2,
|
|
Linha = "5511888880002",
|
|
Cliente = TenantBClientName,
|
|
TenantId = TenantBId
|
|
});
|
|
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
private static async Task UpsertUserAsync(
|
|
IServiceProvider services,
|
|
Guid tenantId,
|
|
string email,
|
|
string password,
|
|
params string[] roles)
|
|
{
|
|
await using var scope = services.CreateAsyncScope();
|
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
|
var tenantProvider = scope.ServiceProvider.GetRequiredService<ITenantProvider>();
|
|
|
|
var previousTenant = tenantProvider.ActorTenantId;
|
|
tenantProvider.SetTenantId(tenantId);
|
|
|
|
try
|
|
{
|
|
var normalizedEmail = userManager.NormalizeEmail(email);
|
|
var user = await userManager.Users
|
|
.IgnoreQueryFilters()
|
|
.FirstOrDefaultAsync(u => u.TenantId == tenantId && u.NormalizedEmail == normalizedEmail);
|
|
|
|
if (user == null)
|
|
{
|
|
user = new ApplicationUser
|
|
{
|
|
Name = email,
|
|
Email = email,
|
|
UserName = email,
|
|
TenantId = tenantId,
|
|
EmailConfirmed = true,
|
|
IsActive = true,
|
|
LockoutEnabled = true
|
|
};
|
|
|
|
var createResult = await userManager.CreateAsync(user, password);
|
|
Assert.True(createResult.Succeeded, string.Join("; ", createResult.Errors.Select(e => e.Description)));
|
|
}
|
|
else
|
|
{
|
|
var resetToken = await userManager.GeneratePasswordResetTokenAsync(user);
|
|
var reset = await userManager.ResetPasswordAsync(user, resetToken, password);
|
|
Assert.True(reset.Succeeded, string.Join("; ", reset.Errors.Select(e => e.Description)));
|
|
}
|
|
|
|
var existingRoles = await userManager.GetRolesAsync(user);
|
|
if (existingRoles.Count > 0)
|
|
{
|
|
var removeRolesResult = await userManager.RemoveFromRolesAsync(user, existingRoles);
|
|
Assert.True(removeRolesResult.Succeeded, string.Join("; ", removeRolesResult.Errors.Select(e => e.Description)));
|
|
}
|
|
|
|
var addRolesResult = await userManager.AddToRolesAsync(user, roles);
|
|
Assert.True(addRolesResult.Succeeded, string.Join("; ", addRolesResult.Errors.Select(e => e.Description)));
|
|
}
|
|
finally
|
|
{
|
|
tenantProvider.SetTenantId(previousTenant);
|
|
}
|
|
}
|
|
|
|
private static async Task<string> LoginAndGetTokenAsync(HttpClient client, string email, string password, Guid tenantId)
|
|
{
|
|
var previousAuth = client.DefaultRequestHeaders.Authorization;
|
|
client.DefaultRequestHeaders.Authorization = null;
|
|
|
|
try
|
|
{
|
|
var response = await client.PostAsJsonAsync("/auth/login", new
|
|
{
|
|
email,
|
|
password,
|
|
tenantId
|
|
});
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
var auth = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
|
Assert.NotNull(auth);
|
|
Assert.False(string.IsNullOrWhiteSpace(auth!.Token));
|
|
return auth.Token;
|
|
}
|
|
finally
|
|
{
|
|
client.DefaultRequestHeaders.Authorization = previousAuth;
|
|
}
|
|
}
|
|
|
|
private sealed class ApiFactory : WebApplicationFactory<Program>
|
|
{
|
|
private readonly string _databaseName = $"line-gestao-tests-{Guid.NewGuid()}";
|
|
|
|
protected override void ConfigureWebHost(Microsoft.AspNetCore.Hosting.IWebHostBuilder builder)
|
|
{
|
|
builder.UseEnvironment("Testing");
|
|
|
|
builder.ConfigureAppConfiguration((_, config) =>
|
|
{
|
|
config.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["App:UseHttpsRedirection"] = "false",
|
|
["Seed:Enabled"] = "true",
|
|
["Seed:AdminMasterName"] = "Admin Master",
|
|
["Seed:AdminMasterEmail"] = "admin.master@test.local",
|
|
["Seed:AdminMasterPassword"] = "AdminMaster123!",
|
|
["Seed:ReapplyAdminCredentialsOnStartup"] = "true"
|
|
});
|
|
});
|
|
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
var notificationHostedService = services
|
|
.Where(d => d.ServiceType == typeof(IHostedService) &&
|
|
d.ImplementationType == typeof(VigenciaNotificationBackgroundService))
|
|
.ToList();
|
|
foreach (var descriptor in notificationHostedService)
|
|
{
|
|
services.Remove(descriptor);
|
|
}
|
|
|
|
services.RemoveAll<AppDbContext>();
|
|
services.RemoveAll<DbContextOptions<AppDbContext>>();
|
|
|
|
services.AddDbContext<AppDbContext>(options =>
|
|
{
|
|
options.UseInMemoryDatabase(_databaseName);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|