Add AD auth and B1 currency fields

This commit is contained in:
2026-04-29 11:07:35 +02:00
parent 3ac03a4782
commit 4a1561d85f
29 changed files with 1016 additions and 31 deletions
@@ -0,0 +1,116 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using TrafagSalesExporter.Data;
using TrafagSalesExporter.Models;
using TrafagSalesExporter.Services;
namespace TrafagSalesExporter.Tests;
public class CentralSalesRecordServiceTests : IDisposable
{
private readonly SqliteConnection _connection;
private readonly TestDbContextFactory _dbFactory;
public CentralSalesRecordServiceTests()
{
_connection = new SqliteConnection("DataSource=:memory:");
_connection.Open();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(_connection)
.Options;
using var db = new AppDbContext(options);
db.Database.EnsureCreated();
_dbFactory = new TestDbContextFactory(options);
}
public void Dispose()
{
_connection.Dispose();
}
[Fact]
public async Task ReplaceForSiteAsync_Persists_B1_Document_Currency_Fields()
{
var site = new Site
{
Id = 1,
Schema = "SBODEMO",
TSC = "TRCH",
Land = "Schweiz",
SourceSystem = "BI1",
IsActive = true
};
await using (var db = await _dbFactory.CreateDbContextAsync())
{
db.Sites.Add(site);
await db.SaveChangesAsync();
}
var service = new CentralSalesRecordService(_dbFactory, new NullAppEventLogService());
await service.ReplaceForSiteAsync(site, [
new SalesRecord
{
ExtractionDate = new DateTime(2026, 4, 29),
Tsc = "TRCH",
InvoiceNumber = "1001",
PositionOnInvoice = 1,
Material = "MAT",
Name = "Article",
ProductGroup = "PG",
Quantity = 2m,
StandardCost = 10m,
StandardCostCurrency = "CHF",
SalesPriceValue = 25m,
SalesCurrency = "EUR",
DocumentCurrency = "EUR",
DocumentTotalForeignCurrency = 100m,
DocumentTotalLocalCurrency = 95m,
VatSumForeignCurrency = 8m,
VatSumLocalCurrency = 7.6m,
DocumentRate = 0.95m,
CompanyCurrency = "CHF",
Land = "Schweiz",
DocumentType = "INV"
}
]);
var rows = await service.GetAllAsync();
var row = Assert.Single(rows);
Assert.Equal("EUR", row.DocumentCurrency);
Assert.Equal(100m, row.DocumentTotalForeignCurrency);
Assert.Equal(95m, row.DocumentTotalLocalCurrency);
Assert.Equal(8m, row.VatSumForeignCurrency);
Assert.Equal(7.6m, row.VatSumLocalCurrency);
Assert.Equal(0.95m, row.DocumentRate);
Assert.Equal("CHF", row.CompanyCurrency);
}
private sealed class NullAppEventLogService : IAppEventLogService
{
public Task WriteAsync(string category, string message, string level = "Info", int? siteId = null, string? land = null, string? details = null)
=> Task.CompletedTask;
public Task WriteDebugAsync(string category, string message, int? siteId = null, string? land = null, string? details = null)
=> Task.CompletedTask;
}
private sealed class TestDbContextFactory : IDbContextFactory<AppDbContext>
{
private readonly DbContextOptions<AppDbContext> _options;
public TestDbContextFactory(DbContextOptions<AppDbContext> options)
{
_options = options;
}
public AppDbContext CreateDbContext() => new(_options);
public Task<AppDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
=> Task.FromResult(new AppDbContext(_options));
}
}
@@ -38,12 +38,19 @@ public class ManualExcelImportServiceTests
ws.Cell(2, 18).Value = "PO-1";
ws.Cell(2, 19).Value = 21.40m;
ws.Cell(2, 20).Value = "EUR";
ws.Cell(2, 21).Value = "DAP";
ws.Cell(2, 22).Value = "Alice";
ws.Cell(2, 23).Value = "14.04.2026";
ws.Cell(2, 24).Value = "10.04.2026";
ws.Cell(2, 25).Value = "Deutschland";
ws.Cell(2, 26).Value = "Invoice";
ws.Cell(2, 21).Value = "EUR";
ws.Cell(2, 22).Value = 120.50m;
ws.Cell(2, 23).Value = 110.25m;
ws.Cell(2, 24).Value = 8.10m;
ws.Cell(2, 25).Value = 7.45m;
ws.Cell(2, 26).Value = 1.0925m;
ws.Cell(2, 27).Value = "CHF";
ws.Cell(2, 28).Value = "DAP";
ws.Cell(2, 29).Value = "Alice";
ws.Cell(2, 30).Value = "14.04.2026";
ws.Cell(2, 31).Value = "10.04.2026";
ws.Cell(2, 32).Value = "Deutschland";
ws.Cell(2, 33).Value = "Invoice";
});
try
@@ -60,6 +67,13 @@ public class ManualExcelImportServiceTests
Assert.Equal(2.5m, row.Quantity);
Assert.Equal(10.25m, row.StandardCost);
Assert.Equal(21.40m, row.SalesPriceValue);
Assert.Equal("EUR", row.DocumentCurrency);
Assert.Equal(120.50m, row.DocumentTotalForeignCurrency);
Assert.Equal(110.25m, row.DocumentTotalLocalCurrency);
Assert.Equal(8.10m, row.VatSumForeignCurrency);
Assert.Equal(7.45m, row.VatSumLocalCurrency);
Assert.Equal(1.0925m, row.DocumentRate);
Assert.Equal("CHF", row.CompanyCurrency);
Assert.Equal("Deutschland", row.Land);
Assert.Equal("Invoice", row.DocumentType);
Assert.Equal(new DateTime(2026, 4, 14), row.InvoiceDate);
@@ -205,6 +219,13 @@ public class ManualExcelImportServiceTests
"Purchase Order number",
"Sales Price/Value",
"Sales Currency",
"Document Currency",
"Document Total FC",
"Document Total LC",
"VAT Sum FC",
"VAT Sum LC",
"Document Rate",
"Company Currency",
"Incoterms 2020",
"Sales responsible employee",
"invoice date",
@@ -0,0 +1,122 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using TrafagSalesExporter.Security;
namespace TrafagSalesExporter.Tests;
public class SecurityPolicyFactoryTests
{
[Fact]
public async Task AccessPolicy_Allows_User_In_Configured_Access_Group()
{
var policy = SecurityPolicyFactory.BuildAccessPolicy(new SecurityOptions
{
AccessGroups = ["TRAFAG\\TrafagSalesExporter-Users"]
}, useDevelopmentAuthentication: false);
var result = await AuthorizeAsync(policy, CreateUser(roles: ["TRAFAG\\TrafagSalesExporter-Users"]));
Assert.True(result.Succeeded);
}
[Fact]
public async Task AccessPolicy_Denies_User_Outside_Configured_Access_Group()
{
var policy = SecurityPolicyFactory.BuildAccessPolicy(new SecurityOptions
{
AccessGroups = ["TRAFAG\\TrafagSalesExporter-Users"]
}, useDevelopmentAuthentication: false);
var result = await AuthorizeAsync(policy, CreateUser(roles: ["TRAFAG\\OtherGroup"]));
Assert.False(result.Succeeded);
}
[Fact]
public async Task AccessPolicy_Allows_Authenticated_User_When_Development_Authentication_Is_Active()
{
var policy = SecurityPolicyFactory.BuildAccessPolicy(new SecurityOptions
{
AccessGroups = ["TRAFAG\\TrafagSalesExporter-Users"]
}, useDevelopmentAuthentication: true);
var result = await AuthorizeAsync(policy, CreateUser());
Assert.True(result.Succeeded);
}
[Fact]
public async Task AdminPolicy_Allows_User_In_Admin_Group()
{
var policy = SecurityPolicyFactory.BuildAdminPolicy(new SecurityOptions
{
AdminGroups = ["TRAFAG\\TrafagSalesExporter-Admins"]
}, useDevelopmentAuthentication: false);
var result = await AuthorizeAsync(policy, CreateUser(roles: ["TRAFAG\\TrafagSalesExporter-Admins"]));
Assert.True(result.Succeeded);
}
[Fact]
public async Task AdminPolicy_Denies_Normal_Access_User()
{
var policy = SecurityPolicyFactory.BuildAdminPolicy(new SecurityOptions
{
AdminGroups = ["TRAFAG\\TrafagSalesExporter-Admins"]
}, useDevelopmentAuthentication: false);
var result = await AuthorizeAsync(policy, CreateUser(roles: ["TRAFAG\\TrafagSalesExporter-Users"]));
Assert.False(result.Succeeded);
}
[Fact]
public async Task AdminPolicy_Allows_Development_Admin_Claim()
{
var policy = SecurityPolicyFactory.BuildAdminPolicy(new SecurityOptions(), useDevelopmentAuthentication: true);
var result = await AuthorizeAsync(policy, CreateUser(claims:
[
new Claim(DevelopmentAuthenticationHandler.AdminClaimType, "true")
]));
Assert.True(result.Succeeded);
}
[Fact]
public async Task AdminPolicy_Denies_Development_User_Without_Admin_Claim()
{
var policy = SecurityPolicyFactory.BuildAdminPolicy(new SecurityOptions(), useDevelopmentAuthentication: true);
var result = await AuthorizeAsync(policy, CreateUser());
Assert.False(result.Succeeded);
}
private static async Task<AuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, ClaimsPrincipal user)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddAuthorization();
var provider = services.BuildServiceProvider();
var service = provider.GetRequiredService<IAuthorizationService>();
return await service.AuthorizeAsync(user, resource: null, policy);
}
private static ClaimsPrincipal CreateUser(IEnumerable<string>? roles = null, IEnumerable<Claim>? claims = null)
{
var allClaims = new List<Claim>
{
new(ClaimTypes.Name, "TRAFAG\\tester")
};
allClaims.AddRange((roles ?? []).Select(role => new Claim(ClaimTypes.Role, role)));
allClaims.AddRange(claims ?? []);
return new ClaimsPrincipal(new ClaimsIdentity(allClaims, "Test"));
}
}