unittests
This commit is contained in:
@@ -134,12 +134,12 @@ public class ManualExcelImportService : IManualExcelImportService
|
|||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
return 0m;
|
return 0m;
|
||||||
|
|
||||||
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out decimalValue))
|
|
||||||
return decimalValue;
|
|
||||||
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.GetCultureInfo("de-CH"), out decimalValue))
|
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.GetCultureInfo("de-CH"), out decimalValue))
|
||||||
return decimalValue;
|
return decimalValue;
|
||||||
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.GetCultureInfo("de-DE"), out decimalValue))
|
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.GetCultureInfo("de-DE"), out decimalValue))
|
||||||
return decimalValue;
|
return decimalValue;
|
||||||
|
if (decimal.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out decimalValue))
|
||||||
|
return decimalValue;
|
||||||
|
|
||||||
return 0m;
|
return 0m;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Text.Json;
|
||||||
|
using TrafagSalesExporter.Data;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class ConfigTransferServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly SqliteConnection _connection;
|
||||||
|
private readonly TestDbContextFactory _dbFactory;
|
||||||
|
private readonly ConfigTransferService _service;
|
||||||
|
|
||||||
|
public ConfigTransferServiceTests()
|
||||||
|
{
|
||||||
|
_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);
|
||||||
|
_service = new ConfigTransferService(_dbFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_connection.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExportJsonAsync_Excludes_Secrets_When_Requested()
|
||||||
|
{
|
||||||
|
await SeedExportConfigurationAsync();
|
||||||
|
|
||||||
|
var json = await _service.ExportJsonAsync(includeSecrets: false);
|
||||||
|
var package = JsonSerializer.Deserialize<ConfigTransferPackage>(json)
|
||||||
|
?? throw new InvalidOperationException("Package missing.");
|
||||||
|
|
||||||
|
Assert.False(package.IncludesSecrets);
|
||||||
|
Assert.NotNull(package.ExportSettings);
|
||||||
|
Assert.Null(package.ExportSettings.SapUsername);
|
||||||
|
Assert.Null(package.ExportSettings.SapPassword);
|
||||||
|
Assert.NotNull(package.SharePointConfig);
|
||||||
|
Assert.Null(package.SharePointConfig.ClientSecret);
|
||||||
|
|
||||||
|
var server = Assert.Single(package.HanaServers);
|
||||||
|
Assert.Null(server.Username);
|
||||||
|
Assert.Null(server.Password);
|
||||||
|
|
||||||
|
var site = Assert.Single(package.Sites);
|
||||||
|
Assert.Null(site.UsernameOverride);
|
||||||
|
Assert.Null(site.PasswordOverride);
|
||||||
|
Assert.Equal("C:\\imports\\manual.xlsx", site.ManualImportFilePath);
|
||||||
|
|
||||||
|
var rule = Assert.Single(package.FieldTransformationRules);
|
||||||
|
Assert.Equal("Record", rule.RuleScope);
|
||||||
|
Assert.Equal("FirstNonEmpty", rule.TransformationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ImportJsonAsync_Preserves_Existing_Secrets_When_Import_Excludes_Secrets()
|
||||||
|
{
|
||||||
|
await SeedExistingSecretsAsync();
|
||||||
|
|
||||||
|
var package = new ConfigTransferPackage
|
||||||
|
{
|
||||||
|
IncludesSecrets = false,
|
||||||
|
SharePointConfig = new ConfigTransferSharePoint
|
||||||
|
{
|
||||||
|
SiteUrl = "https://new.sharepoint.local",
|
||||||
|
ExportFolder = "/new",
|
||||||
|
TenantId = "new-tenant",
|
||||||
|
ClientId = "new-client",
|
||||||
|
ClientSecret = null
|
||||||
|
},
|
||||||
|
ExportSettings = new ConfigTransferExportSettings
|
||||||
|
{
|
||||||
|
DateFilter = "2026-01-01",
|
||||||
|
TimerHour = 5,
|
||||||
|
TimerMinute = 30,
|
||||||
|
TimerEnabled = false,
|
||||||
|
DebugLoggingEnabled = true,
|
||||||
|
LocalSiteExportFolder = "D:\\site",
|
||||||
|
LocalConsolidatedExportFolder = "D:\\consolidated",
|
||||||
|
SapUsername = null,
|
||||||
|
SapPassword = null,
|
||||||
|
Bi1Username = null,
|
||||||
|
Bi1Password = null,
|
||||||
|
SageUsername = null,
|
||||||
|
SagePassword = null
|
||||||
|
},
|
||||||
|
HanaServers =
|
||||||
|
[
|
||||||
|
new ConfigTransferHanaServer
|
||||||
|
{
|
||||||
|
Key = "server-1",
|
||||||
|
Name = "Server A",
|
||||||
|
Host = "hana-a",
|
||||||
|
Port = 30015,
|
||||||
|
Username = null,
|
||||||
|
Password = null,
|
||||||
|
DatabaseName = "DB1",
|
||||||
|
UseSsl = true,
|
||||||
|
ValidateCertificate = false,
|
||||||
|
AdditionalParams = "x=y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Sites =
|
||||||
|
[
|
||||||
|
new ConfigTransferSite
|
||||||
|
{
|
||||||
|
Key = "site-1",
|
||||||
|
HanaServerKey = "server-1",
|
||||||
|
Schema = "schema_a",
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz",
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
UsernameOverride = null,
|
||||||
|
PasswordOverride = null,
|
||||||
|
ManualImportFilePath = "D:\\manual\\trch.xlsx",
|
||||||
|
ManualImportLastUploadedAtUtc = new DateTime(2026, 4, 16, 8, 0, 0, DateTimeKind.Utc),
|
||||||
|
IsActive = true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
FieldTransformationRules =
|
||||||
|
[
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
SourceField = "",
|
||||||
|
TargetField = nameof(SalesRecord.CustomerName),
|
||||||
|
TransformationType = "FirstNonEmpty",
|
||||||
|
RuleScope = "Record",
|
||||||
|
Argument = "CustomerName|SupplierName|Name",
|
||||||
|
SortOrder = 10,
|
||||||
|
IsActive = true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await _service.ImportJsonAsync(JsonSerializer.Serialize(package));
|
||||||
|
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var settings = await db.ExportSettings.SingleAsync();
|
||||||
|
var sharePoint = await db.SharePointConfigs.SingleAsync();
|
||||||
|
var server = await db.HanaServers.SingleAsync();
|
||||||
|
var site = await db.Sites.SingleAsync();
|
||||||
|
var rule = await db.FieldTransformationRules.SingleAsync();
|
||||||
|
|
||||||
|
Assert.Equal("preserved-sap-user", settings.SapUsername);
|
||||||
|
Assert.Equal("preserved-sap-password", settings.SapPassword);
|
||||||
|
Assert.Equal("preserved-bi1-user", settings.Bi1Username);
|
||||||
|
Assert.Equal("preserved-sage-password", settings.SagePassword);
|
||||||
|
|
||||||
|
Assert.Equal("preserved-sharepoint-secret", sharePoint.ClientSecret);
|
||||||
|
Assert.Equal("new-tenant", sharePoint.TenantId);
|
||||||
|
|
||||||
|
Assert.Equal("preserved-server-user", server.Username);
|
||||||
|
Assert.Equal("preserved-server-password", server.Password);
|
||||||
|
Assert.True(server.UseSsl);
|
||||||
|
|
||||||
|
Assert.Equal("preserved-site-user", site.UsernameOverride);
|
||||||
|
Assert.Equal("preserved-site-password", site.PasswordOverride);
|
||||||
|
Assert.Equal("D:\\manual\\trch.xlsx", site.ManualImportFilePath);
|
||||||
|
Assert.Equal("MANUAL_EXCEL", site.SourceSystem);
|
||||||
|
|
||||||
|
Assert.Equal("Record", rule.RuleScope);
|
||||||
|
Assert.Equal("FirstNonEmpty", rule.TransformationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedExportConfigurationAsync()
|
||||||
|
{
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
db.SharePointConfigs.Add(new SharePointConfig
|
||||||
|
{
|
||||||
|
SiteUrl = "https://sharepoint.local",
|
||||||
|
ExportFolder = "/exports",
|
||||||
|
TenantId = "tenant",
|
||||||
|
ClientId = "client",
|
||||||
|
ClientSecret = "secret"
|
||||||
|
});
|
||||||
|
db.ExportSettings.Add(new ExportSettings
|
||||||
|
{
|
||||||
|
SapUsername = "sap-user",
|
||||||
|
SapPassword = "sap-password",
|
||||||
|
Bi1Username = "bi1-user",
|
||||||
|
Bi1Password = "bi1-password",
|
||||||
|
SageUsername = "sage-user",
|
||||||
|
SagePassword = "sage-password"
|
||||||
|
});
|
||||||
|
db.HanaServers.Add(new HanaServer
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Server A",
|
||||||
|
Host = "hana-a",
|
||||||
|
Port = 30015,
|
||||||
|
Username = "server-user",
|
||||||
|
Password = "server-password",
|
||||||
|
DatabaseName = "DB1"
|
||||||
|
});
|
||||||
|
db.Sites.Add(new Site
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
HanaServerId = 1,
|
||||||
|
Schema = "schema_a",
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz",
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
UsernameOverride = "site-user",
|
||||||
|
PasswordOverride = "site-password",
|
||||||
|
ManualImportFilePath = "C:\\imports\\manual.xlsx",
|
||||||
|
ManualImportLastUploadedAtUtc = new DateTime(2026, 4, 16, 7, 0, 0, DateTimeKind.Utc),
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
db.FieldTransformationRules.Add(new FieldTransformationRule
|
||||||
|
{
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
SourceField = "",
|
||||||
|
TargetField = nameof(SalesRecord.CustomerName),
|
||||||
|
TransformationType = "FirstNonEmpty",
|
||||||
|
RuleScope = "Record",
|
||||||
|
Argument = "CustomerName|SupplierName|Name",
|
||||||
|
SortOrder = 10,
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedExistingSecretsAsync()
|
||||||
|
{
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
db.SharePointConfigs.Add(new SharePointConfig
|
||||||
|
{
|
||||||
|
SiteUrl = "https://old.sharepoint.local",
|
||||||
|
ExportFolder = "/old",
|
||||||
|
TenantId = "old-tenant",
|
||||||
|
ClientId = "old-client",
|
||||||
|
ClientSecret = "preserved-sharepoint-secret"
|
||||||
|
});
|
||||||
|
db.ExportSettings.Add(new ExportSettings
|
||||||
|
{
|
||||||
|
SapUsername = "preserved-sap-user",
|
||||||
|
SapPassword = "preserved-sap-password",
|
||||||
|
Bi1Username = "preserved-bi1-user",
|
||||||
|
Bi1Password = "preserved-bi1-password",
|
||||||
|
SageUsername = "preserved-sage-user",
|
||||||
|
SagePassword = "preserved-sage-password"
|
||||||
|
});
|
||||||
|
db.HanaServers.Add(new HanaServer
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Server A",
|
||||||
|
Host = "hana-a",
|
||||||
|
Port = 30015,
|
||||||
|
Username = "preserved-server-user",
|
||||||
|
Password = "preserved-server-password",
|
||||||
|
DatabaseName = "DB1"
|
||||||
|
});
|
||||||
|
db.Sites.Add(new Site
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
HanaServerId = 1,
|
||||||
|
Schema = "schema_a",
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz",
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
UsernameOverride = "preserved-site-user",
|
||||||
|
PasswordOverride = "preserved-site-password",
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using TrafagSalesExporter.Data;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class ManagementCockpitServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly SqliteConnection _connection;
|
||||||
|
private readonly TestDbContextFactory _dbFactory;
|
||||||
|
private readonly ManagementCockpitService _service;
|
||||||
|
|
||||||
|
public ManagementCockpitServiceTests()
|
||||||
|
{
|
||||||
|
_connection = new SqliteConnection("DataSource=:memory:");
|
||||||
|
_connection.Open();
|
||||||
|
|
||||||
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||||
|
.UseSqlite(_connection)
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
using (var db = new AppDbContext(options))
|
||||||
|
{
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
if (!db.Sites.Any())
|
||||||
|
{
|
||||||
|
db.Sites.Add(new Site
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
HanaServerId = null,
|
||||||
|
Schema = "test",
|
||||||
|
TSC = "TEST",
|
||||||
|
Land = "Testland",
|
||||||
|
SourceSystem = "SAP",
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbFactory = new TestDbContextFactory(options);
|
||||||
|
_service = new ManagementCockpitService(_dbFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_connection.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAvailableCentralYearsAsync_Returns_Distinct_Ordered_Years()
|
||||||
|
{
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow("SAP", "CH", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10)),
|
||||||
|
CreateRow("SAP", "CH", "TRCH", "INV-2", "CHF", 200m, new DateTime(2026, 2, 10)),
|
||||||
|
CreateRow("SAP", "CH", "TRCH", "INV-3", "CHF", 300m, null, new DateTime(2026, 3, 5)));
|
||||||
|
|
||||||
|
var years = await _service.GetAvailableCentralYearsAsync();
|
||||||
|
|
||||||
|
Assert.Equal([2025, 2026], years);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzeCentralAsync_Uses_InvoiceDate_Or_ExtractionDate_And_Builds_Monthly_Daily_And_Source_Totals()
|
||||||
|
{
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow("SAP", "Schweiz", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10)),
|
||||||
|
CreateRow("MANUAL_EXCEL", "Deutschland", "TRDE", "INV-2", "EUR", 50m, new DateTime(2025, 1, 11)),
|
||||||
|
CreateRow("SAP", "Deutschland", "TRDE", "INV-3", "EUR", 25m, null, new DateTime(2025, 1, 12)),
|
||||||
|
CreateRow("SAP", "Schweiz", "TRCH", "INV-4", "CHF", 70m, new DateTime(2026, 2, 5)));
|
||||||
|
|
||||||
|
var result = await _service.AnalyzeCentralAsync(2025, 1);
|
||||||
|
|
||||||
|
Assert.Equal(2025, result.Filter.Year);
|
||||||
|
Assert.Equal(1, result.Filter.Month);
|
||||||
|
Assert.Equal(3, result.Summary.RowCount);
|
||||||
|
Assert.Equal(3, result.Summary.InvoiceCount);
|
||||||
|
Assert.Equal(2, result.Summary.SiteCount);
|
||||||
|
Assert.Equal(2, result.Summary.CountryCount);
|
||||||
|
Assert.Equal(2, result.Summary.CurrencyCount);
|
||||||
|
Assert.Equal(new DateTime(2025, 1, 10), result.Summary.PeriodStart);
|
||||||
|
Assert.Equal(new DateTime(2025, 1, 12), result.Summary.PeriodEnd);
|
||||||
|
|
||||||
|
var yearly2025Chf = Assert.Single(result.YearlyTotals, x => x.Year == 2025 && x.Currency == "CHF");
|
||||||
|
Assert.Equal(100m, yearly2025Chf.SalesValue);
|
||||||
|
|
||||||
|
var yearly2025Eur = Assert.Single(result.YearlyTotals, x => x.Year == 2025 && x.Currency == "EUR");
|
||||||
|
Assert.Equal(75m, yearly2025Eur.SalesValue);
|
||||||
|
|
||||||
|
var januaryChf = Assert.Single(result.MonthlyTotals, x => x.Label == "2025-01" && x.Currency == "CHF");
|
||||||
|
Assert.Equal(100m, januaryChf.SalesValue);
|
||||||
|
|
||||||
|
var januaryEur = Assert.Single(result.MonthlyTotals, x => x.Label == "2025-01" && x.Currency == "EUR");
|
||||||
|
Assert.Equal(75m, januaryEur.SalesValue);
|
||||||
|
|
||||||
|
Assert.Equal(3, result.DailyTotals.Count);
|
||||||
|
Assert.Contains(result.DailyTotals, x => x.Label == "2025-01-12" && x.Currency == "EUR" && x.SalesValue == 25m);
|
||||||
|
|
||||||
|
var sapTotal = Assert.Single(result.SourceSystemTotals, x => x.Label == "SAP" && x.Currency == "CHF");
|
||||||
|
Assert.Equal(100m, sapTotal.SalesValue);
|
||||||
|
|
||||||
|
var manualTotal = Assert.Single(result.SourceSystemTotals, x => x.Label == "MANUAL_EXCEL" && x.Currency == "EUR");
|
||||||
|
Assert.Equal(50m, manualTotal.SalesValue);
|
||||||
|
|
||||||
|
var germanyEur = Assert.Single(result.CountryTotals, x => x.Label == "Deutschland" && x.Currency == "EUR");
|
||||||
|
Assert.Equal(75m, germanyEur.SalesValue);
|
||||||
|
Assert.Equal(2, germanyEur.InvoiceCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzeCentralAsync_With_Year_Only_Does_Not_Build_DailyTotals()
|
||||||
|
{
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow("SAP", "Schweiz", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10)),
|
||||||
|
CreateRow("SAP", "Schweiz", "TRCH", "INV-2", "CHF", 150m, new DateTime(2025, 2, 10)));
|
||||||
|
|
||||||
|
var result = await _service.AnalyzeCentralAsync(2025, null);
|
||||||
|
|
||||||
|
Assert.Empty(result.DailyTotals);
|
||||||
|
Assert.Equal(2, result.MonthlyTotals.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzeCentralAsync_Throws_When_No_Rows_Exist_For_Selected_Period()
|
||||||
|
{
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow("SAP", "Schweiz", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10)));
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _service.AnalyzeCentralAsync(2026, 1));
|
||||||
|
|
||||||
|
Assert.Contains("gewählten Zeitraum", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedCentralRowsAsync(params CentralSalesRecord[] rows)
|
||||||
|
{
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
db.CentralSalesRecords.RemoveRange(db.CentralSalesRecords);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
db.CentralSalesRecords.AddRange(rows);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CentralSalesRecord CreateRow(string sourceSystem, string land, string tsc, string invoiceNumber, string currency, decimal salesValue, DateTime? invoiceDate, DateTime? extractionDate = null)
|
||||||
|
{
|
||||||
|
return new CentralSalesRecord
|
||||||
|
{
|
||||||
|
SiteId = 1,
|
||||||
|
StoredAtUtc = DateTime.UtcNow,
|
||||||
|
SourceSystem = sourceSystem,
|
||||||
|
ExtractionDate = extractionDate ?? invoiceDate ?? DateTime.UtcNow.Date,
|
||||||
|
Tsc = tsc,
|
||||||
|
InvoiceNumber = invoiceNumber,
|
||||||
|
PositionOnInvoice = 1,
|
||||||
|
Material = "MAT",
|
||||||
|
Name = "Article",
|
||||||
|
ProductGroup = "PG",
|
||||||
|
Quantity = 1m,
|
||||||
|
SupplierNumber = "SUP",
|
||||||
|
SupplierName = "Supplier",
|
||||||
|
SupplierCountry = "CH",
|
||||||
|
CustomerNumber = "CUS",
|
||||||
|
CustomerName = "Customer",
|
||||||
|
CustomerCountry = "CH",
|
||||||
|
CustomerIndustry = "Industry",
|
||||||
|
StandardCost = 1m,
|
||||||
|
StandardCostCurrency = currency,
|
||||||
|
PurchaseOrderNumber = "PO",
|
||||||
|
SalesPriceValue = salesValue,
|
||||||
|
SalesCurrency = currency,
|
||||||
|
Incoterms2020 = "DAP",
|
||||||
|
SalesResponsibleEmployee = "Alice",
|
||||||
|
InvoiceDate = invoiceDate,
|
||||||
|
OrderDate = invoiceDate?.AddDays(-2),
|
||||||
|
Land = land,
|
||||||
|
DocumentType = "Invoice"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class ManualExcelImportServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadSalesRecordsAsync_Reads_Expected_Columns_From_Exporter_Format()
|
||||||
|
{
|
||||||
|
var site = new Site
|
||||||
|
{
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz"
|
||||||
|
};
|
||||||
|
var filePath = CreateWorkbook(workbook =>
|
||||||
|
{
|
||||||
|
var ws = workbook.Worksheets.Add("Sales");
|
||||||
|
WriteHeaders(ws);
|
||||||
|
ws.Cell(2, 1).Value = "15.04.2026 13:45:00";
|
||||||
|
ws.Cell(2, 2).Value = "TRDE";
|
||||||
|
ws.Cell(2, 3).Value = "INV-100";
|
||||||
|
ws.Cell(2, 4).Value = 7;
|
||||||
|
ws.Cell(2, 5).Value = "MAT-1";
|
||||||
|
ws.Cell(2, 6).Value = "Pressure Sensor";
|
||||||
|
ws.Cell(2, 7).Value = "PG-A";
|
||||||
|
ws.Cell(2, 8).Value = 2.5m;
|
||||||
|
ws.Cell(2, 9).Value = "SUP-1";
|
||||||
|
ws.Cell(2, 10).Value = "Supplier";
|
||||||
|
ws.Cell(2, 11).Value = "DE";
|
||||||
|
ws.Cell(2, 12).Value = "CUST-1";
|
||||||
|
ws.Cell(2, 13).Value = "Customer";
|
||||||
|
ws.Cell(2, 14).Value = "CH";
|
||||||
|
ws.Cell(2, 15).Value = "Industry";
|
||||||
|
ws.Cell(2, 16).Value = 10.25m;
|
||||||
|
ws.Cell(2, 17).Value = "EUR";
|
||||||
|
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";
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = new ManualExcelImportService();
|
||||||
|
|
||||||
|
var rows = await service.ReadSalesRecordsAsync(filePath, site);
|
||||||
|
|
||||||
|
var row = Assert.Single(rows);
|
||||||
|
Assert.Equal("TRDE", row.Tsc);
|
||||||
|
Assert.Equal("INV-100", row.InvoiceNumber);
|
||||||
|
Assert.Equal(7, row.PositionOnInvoice);
|
||||||
|
Assert.Equal("MAT-1", row.Material);
|
||||||
|
Assert.Equal(2.5m, row.Quantity);
|
||||||
|
Assert.Equal(10.25m, row.StandardCost);
|
||||||
|
Assert.Equal(21.40m, row.SalesPriceValue);
|
||||||
|
Assert.Equal("Deutschland", row.Land);
|
||||||
|
Assert.Equal("Invoice", row.DocumentType);
|
||||||
|
Assert.Equal(new DateTime(2026, 4, 14), row.InvoiceDate);
|
||||||
|
Assert.Equal(new DateTime(2026, 4, 10), row.OrderDate);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadSalesRecordsAsync_Uses_Site_Fallbacks_When_Tsc_And_Land_Are_Missing()
|
||||||
|
{
|
||||||
|
var site = new Site
|
||||||
|
{
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz"
|
||||||
|
};
|
||||||
|
var filePath = CreateWorkbook(workbook =>
|
||||||
|
{
|
||||||
|
var ws = workbook.Worksheets.Add("Sales");
|
||||||
|
WriteHeaders(ws);
|
||||||
|
ws.Cell(2, 3).Value = "INV-200";
|
||||||
|
ws.Cell(2, 5).Value = "MAT-2";
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = new ManualExcelImportService();
|
||||||
|
|
||||||
|
var rows = await service.ReadSalesRecordsAsync(filePath, site);
|
||||||
|
|
||||||
|
var row = Assert.Single(rows);
|
||||||
|
Assert.Equal("TRCH", row.Tsc);
|
||||||
|
Assert.Equal("Schweiz", row.Land);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadSalesRecordsAsync_Parses_German_Decimal_Text_And_Skips_Empty_Rows()
|
||||||
|
{
|
||||||
|
var site = new Site
|
||||||
|
{
|
||||||
|
TSC = "TRAT",
|
||||||
|
Land = "Oesterreich"
|
||||||
|
};
|
||||||
|
var filePath = CreateWorkbook(workbook =>
|
||||||
|
{
|
||||||
|
var ws = workbook.Worksheets.Add("Sales");
|
||||||
|
WriteHeaders(ws);
|
||||||
|
ws.Cell(2, 3).Value = "INV-300";
|
||||||
|
ws.Cell(2, 8).Value = "1,50";
|
||||||
|
ws.Cell(2, 16).Value = "3,25";
|
||||||
|
ws.Cell(2, 19).Value = "7,90";
|
||||||
|
ws.Cell(3, 1).Value = "";
|
||||||
|
ws.Cell(3, 2).Value = "";
|
||||||
|
ws.Cell(3, 3).Value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = new ManualExcelImportService();
|
||||||
|
|
||||||
|
var rows = await service.ReadSalesRecordsAsync(filePath, site);
|
||||||
|
|
||||||
|
var row = Assert.Single(rows);
|
||||||
|
Assert.Equal(1.50m, row.Quantity);
|
||||||
|
Assert.Equal(3.25m, row.StandardCost);
|
||||||
|
Assert.Equal(7.90m, row.SalesPriceValue);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadSalesRecordsAsync_Throws_When_InvoiceNumber_Header_Is_Missing()
|
||||||
|
{
|
||||||
|
var site = new Site
|
||||||
|
{
|
||||||
|
TSC = "TRCH",
|
||||||
|
Land = "Schweiz"
|
||||||
|
};
|
||||||
|
var filePath = CreateWorkbook(workbook =>
|
||||||
|
{
|
||||||
|
var ws = workbook.Worksheets.Add("Sales");
|
||||||
|
ws.Cell(1, 1).Value = "TSC";
|
||||||
|
ws.Cell(1, 2).Value = "Material";
|
||||||
|
ws.Cell(2, 1).Value = "TRCH";
|
||||||
|
ws.Cell(2, 2).Value = "MAT-3";
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = new ManualExcelImportService();
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.ReadSalesRecordsAsync(filePath, site));
|
||||||
|
|
||||||
|
Assert.Contains("Invoice Number", ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateWorkbook(Action<XLWorkbook> fillWorkbook)
|
||||||
|
{
|
||||||
|
var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.xlsx");
|
||||||
|
using var workbook = new XLWorkbook();
|
||||||
|
fillWorkbook(workbook);
|
||||||
|
workbook.SaveAs(filePath);
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteHeaders(IXLWorksheet ws)
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
"extraction date",
|
||||||
|
"TSC",
|
||||||
|
"Invoice Number",
|
||||||
|
"Position on invoice",
|
||||||
|
"Material",
|
||||||
|
"Name",
|
||||||
|
"Product Group",
|
||||||
|
"Quantity",
|
||||||
|
"Supplier number",
|
||||||
|
"Supplier name",
|
||||||
|
"Supplier country",
|
||||||
|
"Customer number",
|
||||||
|
"Customer name",
|
||||||
|
"Customer country",
|
||||||
|
"Customer Industry",
|
||||||
|
"Standard cost",
|
||||||
|
"Standard Cost Currency",
|
||||||
|
"Purchase Order number",
|
||||||
|
"Sales Price/Value",
|
||||||
|
"Sales Currency",
|
||||||
|
"Incoterms 2020",
|
||||||
|
"Sales responsible employee",
|
||||||
|
"invoice date",
|
||||||
|
"order date",
|
||||||
|
"Land",
|
||||||
|
"Document Type"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < headers.Length; i++)
|
||||||
|
{
|
||||||
|
ws.Cell(1, i + 1).Value = headers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class RecordTransformationServiceTests
|
||||||
|
{
|
||||||
|
private readonly RecordTransformationService _service;
|
||||||
|
|
||||||
|
public RecordTransformationServiceTests()
|
||||||
|
{
|
||||||
|
ITransformationStrategy[] valueStrategies =
|
||||||
|
[
|
||||||
|
new CopyTransformationStrategy(),
|
||||||
|
new UppercaseTransformationStrategy(),
|
||||||
|
new LowercaseTransformationStrategy(),
|
||||||
|
new PrefixTransformationStrategy(),
|
||||||
|
new SuffixTransformationStrategy(),
|
||||||
|
new ReplaceTransformationStrategy(),
|
||||||
|
new ConstantTransformationStrategy()
|
||||||
|
];
|
||||||
|
IRecordTransformationStrategy[] recordStrategies =
|
||||||
|
[
|
||||||
|
new FirstNonEmptyRecordTransformationStrategy()
|
||||||
|
];
|
||||||
|
|
||||||
|
_service = new RecordTransformationService(valueStrategies, recordStrategies);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_Ignores_Inactive_Rules()
|
||||||
|
{
|
||||||
|
var records = new List<SalesRecord>
|
||||||
|
{
|
||||||
|
new() { Material = "abc" }
|
||||||
|
};
|
||||||
|
var rules = new[]
|
||||||
|
{
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
IsActive = false,
|
||||||
|
RuleScope = "Value",
|
||||||
|
SourceField = nameof(SalesRecord.Material),
|
||||||
|
TargetField = nameof(SalesRecord.Material),
|
||||||
|
TransformationType = "Uppercase",
|
||||||
|
SortOrder = 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.Apply(records, rules);
|
||||||
|
|
||||||
|
Assert.Equal("abc", records[0].Material);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_Uses_SortOrder_For_Multiple_Value_Rules()
|
||||||
|
{
|
||||||
|
var records = new List<SalesRecord>
|
||||||
|
{
|
||||||
|
new() { Material = "abc" }
|
||||||
|
};
|
||||||
|
var rules = new[]
|
||||||
|
{
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
IsActive = true,
|
||||||
|
RuleScope = "Value",
|
||||||
|
SourceField = nameof(SalesRecord.Material),
|
||||||
|
TargetField = nameof(SalesRecord.Material),
|
||||||
|
TransformationType = "Uppercase",
|
||||||
|
SortOrder = 20
|
||||||
|
},
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
IsActive = true,
|
||||||
|
RuleScope = "Value",
|
||||||
|
SourceField = nameof(SalesRecord.Material),
|
||||||
|
TargetField = nameof(SalesRecord.Material),
|
||||||
|
TransformationType = "Prefix",
|
||||||
|
Argument = "X-",
|
||||||
|
SortOrder = 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.Apply(records, rules);
|
||||||
|
|
||||||
|
Assert.Equal("X-ABC", records[0].Material);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_Uses_Record_Strategy_When_RuleScope_Is_Record()
|
||||||
|
{
|
||||||
|
var records = new List<SalesRecord>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
CustomerName = "",
|
||||||
|
SupplierName = "Supplier A",
|
||||||
|
Name = "Name A"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var rules = new[]
|
||||||
|
{
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
IsActive = true,
|
||||||
|
RuleScope = "Record",
|
||||||
|
TargetField = nameof(SalesRecord.CustomerName),
|
||||||
|
TransformationType = "FirstNonEmpty",
|
||||||
|
Argument = $"{nameof(SalesRecord.CustomerName)}|{nameof(SalesRecord.SupplierName)}|{nameof(SalesRecord.Name)}",
|
||||||
|
SortOrder = 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.Apply(records, rules);
|
||||||
|
|
||||||
|
Assert.Equal("Supplier A", records[0].CustomerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_Converts_Value_To_Target_Type()
|
||||||
|
{
|
||||||
|
var records = new List<SalesRecord>
|
||||||
|
{
|
||||||
|
new() { Material = "42" }
|
||||||
|
};
|
||||||
|
var rules = new[]
|
||||||
|
{
|
||||||
|
new FieldTransformationRule
|
||||||
|
{
|
||||||
|
IsActive = true,
|
||||||
|
RuleScope = "Value",
|
||||||
|
SourceField = nameof(SalesRecord.Material),
|
||||||
|
TargetField = nameof(SalesRecord.PositionOnInvoice),
|
||||||
|
TransformationType = "Copy",
|
||||||
|
SortOrder = 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.Apply(records, rules);
|
||||||
|
|
||||||
|
Assert.Equal(42, records[0].PositionOnInvoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TrafagSalesExporter.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class TransformationCatalogTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Catalog_Returns_Value_And_Record_Strategies()
|
||||||
|
{
|
||||||
|
ITransformationStrategy[] valueStrategies =
|
||||||
|
[
|
||||||
|
new CopyTransformationStrategy(),
|
||||||
|
new ConstantTransformationStrategy()
|
||||||
|
];
|
||||||
|
IRecordTransformationStrategy[] recordStrategies =
|
||||||
|
[
|
||||||
|
new FirstNonEmptyRecordTransformationStrategy()
|
||||||
|
];
|
||||||
|
|
||||||
|
var catalog = new TransformationCatalog(valueStrategies, recordStrategies);
|
||||||
|
|
||||||
|
var all = catalog.GetAll();
|
||||||
|
|
||||||
|
Assert.Contains(all, x => x.RuleScope == "Value" && x.Key == "Copy");
|
||||||
|
Assert.Contains(all, x => x.RuleScope == "Value" && x.Key == "Constant");
|
||||||
|
Assert.Contains(all, x => x.RuleScope == "Record" && x.Key == "FirstNonEmpty");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Tests;
|
||||||
|
|
||||||
|
public class TransformationStrategiesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ReplaceStrategy_Replaces_Text_Using_Argument_Syntax()
|
||||||
|
{
|
||||||
|
var strategy = new ReplaceTransformationStrategy();
|
||||||
|
|
||||||
|
var result = strategy.Transform("Intercompany Kunde", "Intercompany=>Extern");
|
||||||
|
|
||||||
|
Assert.Equal("Extern Kunde", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ConstantStrategy_Returns_Argument_Ignoring_SourceValue()
|
||||||
|
{
|
||||||
|
var strategy = new ConstantTransformationStrategy();
|
||||||
|
|
||||||
|
var result = strategy.Transform("ignored", "CHF");
|
||||||
|
|
||||||
|
Assert.Equal("CHF", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FirstNonEmptyRecordStrategy_Uses_First_Non_Empty_Field_From_Argument_List()
|
||||||
|
{
|
||||||
|
var strategy = new FirstNonEmptyRecordTransformationStrategy();
|
||||||
|
var record = new SalesRecord
|
||||||
|
{
|
||||||
|
CustomerName = "",
|
||||||
|
SupplierName = "Fallback Supplier",
|
||||||
|
Name = "Article Name"
|
||||||
|
};
|
||||||
|
var rule = new FieldTransformationRule
|
||||||
|
{
|
||||||
|
RuleScope = "Record",
|
||||||
|
TargetField = nameof(SalesRecord.CustomerName),
|
||||||
|
TransformationType = "FirstNonEmpty",
|
||||||
|
Argument = $"{nameof(SalesRecord.CustomerName)}|{nameof(SalesRecord.SupplierName)}|{nameof(SalesRecord.Name)}"
|
||||||
|
};
|
||||||
|
|
||||||
|
strategy.Transform(record, rule);
|
||||||
|
|
||||||
|
Assert.Equal("Fallback Supplier", record.CustomerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,13 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="TrafagSalesExporter.Tests\**\*.cs" />
|
||||||
|
<Content Remove="TrafagSalesExporter.Tests\**\*" />
|
||||||
|
<EmbeddedResource Remove="TrafagSalesExporter.Tests\**\*" />
|
||||||
|
<None Remove="TrafagSalesExporter.Tests\**\*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="CheckHanaClient" BeforeTargets="ResolveAssemblyReferences">
|
<Target Name="CheckHanaClient" BeforeTargets="ResolveAssemblyReferences">
|
||||||
<Warning Condition="!Exists('$(HanaClientDll)')"
|
<Warning Condition="!Exists('$(HanaClientDll)')"
|
||||||
Text="SAP HANA Client DLL nicht gefunden: $(HanaClientDll). Bitte SAP HANA Client installieren (https://tools.hana.ondemand.com) oder MSBuild-Property 'HanaClientDll' setzen." />
|
Text="SAP HANA Client DLL nicht gefunden: $(HanaClientDll). Bitte SAP HANA Client installieren (https://tools.hana.ondemand.com) oder MSBuild-Property 'HanaClientDll' setzen." />
|
||||||
|
|||||||
@@ -1,20 +1,46 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.37012.4 d17.14
|
VisualStudioVersion = 17.14.37012.4
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafagSalesExporter", "TrafagSalesExporter.csproj", "{49B56D6D-731C-6482-4A5C-82EAEEBCE593}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafagSalesExporter", "TrafagSalesExporter.csproj", "{49B56D6D-731C-6482-4A5C-82EAEEBCE593}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafagSalesExporter.Tests", "TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj", "{A72D1AFB-E49E-4920-B783-EFFE1132435D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|Any CPU.Build.0 = Release|Any CPU
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{49B56D6D-731C-6482-4A5C-82EAEEBCE593}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A72D1AFB-E49E-4920-B783-EFFE1132435D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user