Add verbose Enova diagnostics and reliable Sentry reporting

This commit is contained in:
2026-08-31 21:13:46 +00:00
parent ee62a6253d
commit af6466e2dc
20 changed files with 276 additions and 37 deletions

View File

@@ -35,6 +35,20 @@ Kontrola konfiguracji i dynamicznego ładowania, bez zapisu do bazy:
.\src\SmartB2B.Enova.Service\bin\Debug\net8.0\SmartB2B.Enova.Service.exe --config .\src\SmartB2B.Enova.Service\bin\Debug\net8.0\config\enova.json.sample --check-config
```
Szczegółowe logowanie włącza parametr `/debug` albo `/verbose` (obsługiwane są też
warianty `--debug` i `--verbose`). Wyłącza je brak parametru lub jawny parametr
`/nodebug` albo `/noverbose`. Błędy są zapisywane do `stderr` niezależnie od tego
ustawienia. W usłudze Windows parametr można włączyć przez odkomentowanie elementu
`<arguments>/verbose</arguments>` w `daemon/smartb2bsync-enova.xml` i restart usługi.
W trybie szczegółowym zapis ZO raportuje kolejne etapy: walidację żądania, logowanie
do Enovy, wyszukanie definicji, kontrahenta, magazynu i towarów, commit transakcji
oraz `session.Save()`. Sentry zawsze wypisuje błędy własnego transportu, a w trybie
szczegółowym również przebieg kolejkowania i wysyłania zdarzenia. Przy starcie usługa
podaje używany cel Sentry bez ujawniania klucza DSN. Jeżeli lokalny `config/enova.json`
nie zawiera `sentry.dsn`, usługa wypisuje dokładną ścieżkę używanego pliku. Aktualizacja
nie kopiuje tej wartości automatycznie z `enova.json.sample` do istniejącej konfiguracji.
## Publikacja i instalacja
```powershell

View File

@@ -4,6 +4,8 @@
<description>Synchronizuje enova365 z portalem SmartB2B.</description>
<executable>%BASE%\..\SmartB2B.Enova.Service.exe</executable>
<workingdirectory>%BASE%\..</workingdirectory>
<!-- Odkomentuj, aby włączyć logowanie etapów Enovy i diagnostykę Sentry. -->
<!-- <arguments>/verbose</arguments> -->
<stoptimeout>30sec</stoptimeout>
<log mode="roll-by-time">
<pattern>yyyyMMdd</pattern>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,6 +4,8 @@
<description>Synchronizuje enova365 z portalem SmartB2B.</description>
<executable>%BASE%\..\SmartB2B.Enova.Service.exe</executable>
<workingdirectory>%BASE%\..</workingdirectory>
<!-- Odkomentuj, aby włączyć logowanie etapów Enovy i diagnostykę Sentry. -->
<!-- <arguments>/verbose</arguments> -->
<stoptimeout>30sec</stoptimeout>
<log mode="roll-by-time">
<pattern>yyyyMMdd</pattern>

View File

@@ -53,9 +53,14 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
try
{
Verbose(configuration.VerboseLogging, "Enova: przygotowanie stanu sesji.");
var sessionState = SessionState.Create();
using var attachedSessionState = sessionState.Attach();
Verbose(
configuration.VerboseLogging,
$"Enova: logowanie operatora '{configuration.Connection.OperatorName}' do bazy " +
$"'{configuration.Connection.DatabaseName}'.");
using var login = database.Login(
winAuth: false,
user: configuration.Connection.OperatorName,
@@ -64,8 +69,9 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
readOnly: false,
config: false,
name: "SmartB2B Enova Sync");
Verbose(configuration.VerboseLogging, "Enova: utworzono sesję zapisu.");
return CreateOrderInSession(session, request);
return CreateOrderInSession(session, request, configuration.VerboseLogging);
}
catch (EnovaOperationException)
{
@@ -81,8 +87,12 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
}
}
private static OrderResult CreateOrderInSession(Session session, OrderRequest request)
private static OrderResult CreateOrderInSession(
Session session,
OrderRequest request,
bool verboseLogging)
{
Verbose(verboseLogging, "Enova: pobieranie modułów Handel, CRM, Towary i Magazyny.");
var handel = HandelModule.GetInstance(session);
var crm = CRMModule.GetInstance(session);
var towary = TowaryModule.GetInstance(session);
@@ -93,11 +103,13 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
"eu.smartb2b.erp.document_definition_not_found",
$"Nie znaleziono definicji dokumentu '{request.DocumentDefinition}'.",
new Dictionary<string, object?> { ["documentDefinition"] = request.DocumentDefinition });
Verbose(verboseLogging, $"Enova: znaleziono definicję dokumentu '{definition.Symbol}'.");
var customer = crm.Kontrahenci.WgKodu[request.CustomerCode]
?? throw new EnovaOperationException(
"eu.smartb2b.company_not_found",
$"Nie znaleziono kontrahenta o kodzie '{request.CustomerCode}'.",
new Dictionary<string, object?> { ["companyErpId"] = request.CustomerCode });
Verbose(verboseLogging, $"Enova: znaleziono kontrahenta '{customer.Kod}'.");
var warehouse = string.IsNullOrWhiteSpace(request.WarehouseCode)
? warehouses.StandardowyMagazyn
: warehouses.GetGrantedView()
@@ -116,15 +128,18 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
: $"Nie znaleziono magazynu o symbolu '{request.WarehouseCode.Trim()}'.",
new Dictionary<string, object?> { ["warehouseErpId"] = request.WarehouseCode });
}
Verbose(verboseLogging, $"Enova: wybrano magazyn '{warehouse.Symbol}'.");
DokumentHandlowy document;
Verbose(verboseLogging, "Enova: rozpoczęcie transakcji edycyjnej.");
using (var transaction = session.Logout(editMode: true))
{
document = session.AddRow(new DokumentHandlowy());
document.Definicja = definition;
document.Kontrahent = customer;
document.Magazyn = warehouse;
Verbose(verboseLogging, "Enova: dodano nagłówek dokumentu.");
if (!string.IsNullOrWhiteSpace(request.CustomerReference))
{
@@ -137,8 +152,12 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
document.Opis.Add(request.Notes.Trim());
}
foreach (var line in request.Items)
for (var index = 0; index < request.Items.Count; index++)
{
var line = request.Items[index];
Verbose(
verboseLogging,
$"Enova: pozycja {index + 1}/{request.Items.Count}, wyszukiwanie towaru '{line.ProductCode}'.");
var product = towary.Towary.WgKodu[line.ProductCode]
?? throw new EnovaOperationException(
"eu.smartb2b.product_not_found",
@@ -150,6 +169,7 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
position.Ilosc = new Quantity((double)line.Quantity);
position.Cena = new DoubleCy(line.UnitPrice, CurrencySymbol);
position.UstawRabat(new Percent(line.Discount / 100m), ręcznie: true);
Verbose(verboseLogging, $"Enova: dodano pozycję {index + 1}.");
}
document.Stan = request.SaveMode switch
@@ -160,11 +180,17 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
"eu.smartb2b.erp.invalid_order",
$"Nieobsługiwany tryb zapisu: {request.SaveMode}.")
};
Verbose(verboseLogging, $"Enova: ustawiono stan dokumentu na {request.SaveMode}.");
transaction.Commit();
Verbose(verboseLogging, "Enova: zatwierdzono transakcję edycyjną.");
}
Verbose(verboseLogging, "Enova: wykonywanie session.Save().");
session.Save();
Verbose(
verboseLogging,
$"Enova: session.Save() zakończone; numer={document.Numer.Pelny}, ID={document.ID}.");
return new OrderResult(
document.ID,
@@ -271,4 +297,12 @@ public sealed class EnovaOrderAdapter : IEnovaOrderAdapter
private static string GetAssemblyVersion(Assembly assembly) =>
assembly.GetName().Version?.ToString() ?? "unknown";
private static void Verbose(bool enabled, string message)
{
if (enabled)
{
Console.WriteLine($"[verbose] {message}");
}
}
}

View File

@@ -16,7 +16,8 @@ public sealed record EnovaAdapterConfiguration(
EnovaConnectionOptions Connection,
string DocumentDefinition,
string? DefaultWarehouseCode,
OrderSaveMode SaveMode);
OrderSaveMode SaveMode,
bool VerboseLogging = false);
public sealed class OrderRequest
{

View File

@@ -48,7 +48,7 @@ public sealed class ServiceSettings
return settings;
}
public RuntimeSettings ResolveRuntimeSettings()
public RuntimeSettings ResolveRuntimeSettings(bool verboseLogging = false)
{
var installationPath = Path.GetFullPath(
Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
@@ -64,7 +64,8 @@ public sealed class ServiceSettings
Sql.ConnectionString),
Enova.DocumentDefinition,
Enova.DefaultWarehouseCode,
Enova.SaveMode);
Enova.SaveMode,
verboseLogging);
}
return new RuntimeSettings(

View File

@@ -0,0 +1,28 @@
namespace SmartB2B.Enova.Service.Diagnostics;
internal static class ConsoleLogger
{
public static bool VerboseEnabled { get; private set; }
public static void Configure(bool verboseEnabled)
{
VerboseEnabled = verboseEnabled;
}
public static void Verbose(string message)
{
if (VerboseEnabled)
{
Console.WriteLine($"[verbose] {message}");
}
}
public static void Error(string message, Exception? exception = null)
{
Console.Error.WriteLine(message);
if (VerboseEnabled && exception is not null)
{
Console.Error.WriteLine(exception);
}
}
}

View File

@@ -0,0 +1,28 @@
using Sentry;
namespace SmartB2B.Enova.Service.Diagnostics;
internal static class SentryReporter
{
private static readonly TimeSpan FlushTimeout = TimeSpan.FromSeconds(5);
public static async Task CaptureAsync(Exception exception, Action<Scope>? configureScope = null)
{
try
{
var eventId = configureScope is null
? SentrySdk.CaptureException(exception)
: SentrySdk.CaptureException(exception, configureScope);
ConsoleLogger.Verbose($"Sentry: zakolejkowano zdarzenie {eventId}.");
await SentrySdk.FlushAsync(FlushTimeout).ConfigureAwait(false);
ConsoleLogger.Verbose("Sentry: zakończono oczekiwanie na opróżnienie kolejki.");
}
catch (Exception sentryException)
{
ConsoleLogger.Error(
$"Błąd raportowania do Sentry: {sentryException.Message}",
sentryException);
}
}
}

View File

@@ -1,6 +1,6 @@
using Newtonsoft.Json;
using Sentry;
using SmartB2B.Enova.Contracts;
using SmartB2B.Enova.Service.Diagnostics;
using SmartB2B.Enova.Service.Rpc;
namespace SmartB2B.Enova.Service.Orders;
@@ -60,6 +60,10 @@ public sealed class PlaceOrderHandler
new Dictionary<string, object?> { ["errors"] = errors });
}
ConsoleLogger.Verbose(
$"ZO: żądanie zweryfikowane; definicja={request.DocumentDefinition}, " +
$"magazyn={request.WarehouseCode ?? "(standardowy)"}, tryb={request.SaveMode}.");
ConsoleLogger.Verbose("ZO: oczekiwanie na wyłączną sekcję operacji Enovy.");
await _orderLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
@@ -69,20 +73,28 @@ public sealed class PlaceOrderHandler
OrderResult result;
try
{
ConsoleLogger.Verbose("ZO: przekazanie żądania do adaptera Enovy.");
result = await Task.Run(() => _adapter.CreateOrder(request), cancellationToken)
.ConfigureAwait(false);
}
catch (EnovaOperationException exception)
{
SentrySdk.CaptureException(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.place_order");
scope.SetTag("wamp.error_uri", exception.ErrorUri);
});
ConsoleLogger.Error(
$"Błąd tworzenia ZO [{exception.ErrorUri}]: {exception.Message}",
exception);
await SentryReporter.CaptureAsync(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.place_order");
scope.SetTag("wamp.error_uri", exception.ErrorUri);
})
.ConfigureAwait(false);
throw WampErrorFactory.FromEnova(exception);
}
Console.WriteLine($"Utworzono ZO {result.Number} (ID: {result.Id}).");
ConsoleLogger.Verbose(
$"ZO: zapis zakończony; stan={result.SaveMode}, magazyn={result.WarehouseCode}, " +
$"netto={result.Net}, VAT={result.Vat}, brutto={result.Gross}.");
return new KeywordResult(new Dictionary<string, object?>
{
["order_erp_id"] = result.Id,
@@ -95,6 +107,7 @@ public sealed class PlaceOrderHandler
finally
{
_orderLock.Release();
ConsoleLogger.Verbose("ZO: zwolniono wyłączną sekcję operacji Enovy.");
}
}
}

View File

@@ -6,6 +6,7 @@ using SmartB2B.Enova.Service.Rpc;
using SmartB2B.Enova.Service.Runtime;
using SmartB2B.Enova.Service.Sql;
using Sentry;
using Sentry.Infrastructure;
using WampSharp.V2.Rpc;
namespace SmartB2B.Enova.Service;
@@ -26,9 +27,13 @@ internal static class Program
try
{
var commandLine = CommandLineOptions.Parse(args);
ConsoleLogger.Configure(commandLine.VerboseLogging);
ConsoleLogger.Verbose(
$"Włączono szczegółowe logowanie; konfiguracja: '{commandLine.ConfigurationPath}'.");
var settings = ServiceSettings.Load(commandLine.ConfigurationPath);
sentry = InitializeSentry(settings);
var runtime = settings.ResolveRuntimeSettings();
ConsoleLogger.Verbose("Wczytano i zweryfikowano konfigurację usługi.");
sentry = InitializeSentry(settings, commandLine.ConfigurationPath, commandLine.VerboseLogging);
var runtime = settings.ResolveRuntimeSettings(commandLine.VerboseLogging);
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
var adapter = adapterLoader.Load();
@@ -101,8 +106,7 @@ internal static class Program
catch (Exception exception)
{
Console.Error.WriteLine($"Błąd krytyczny: {exception}");
SentrySdk.CaptureException(exception);
await SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
await SentryReporter.CaptureAsync(exception).ConfigureAwait(false);
return 1;
}
finally
@@ -111,11 +115,15 @@ internal static class Program
}
}
private static IDisposable? InitializeSentry(ServiceSettings settings)
private static IDisposable? InitializeSentry(
ServiceSettings settings,
string configurationPath,
bool verboseLogging)
{
if (string.IsNullOrWhiteSpace(settings.Sentry.Dsn))
{
Console.WriteLine("Sentry wyłączone (brak parametru sentry.dsn).");
Console.WriteLine(
$"Sentry wyłączone: brak parametru sentry.dsn w używanym pliku '{configurationPath}'.");
return null;
}
@@ -124,20 +132,41 @@ internal static class Program
options.Dsn = settings.Sentry.Dsn;
options.IsGlobalModeEnabled = true;
options.Release = typeof(Program).Assembly.GetName().Version?.ToString();
// Diagnostyka na poziomie Error pozostaje aktywna także bez /verbose,
// aby problemy transportu Sentry nie znikały bez śladu w usłudze Windows.
options.DiagnosticLevel = verboseLogging ? SentryLevel.Debug : SentryLevel.Error;
options.DiagnosticLogger = new ConsoleDiagnosticLogger(options.DiagnosticLevel);
options.ShutdownTimeout = TimeSpan.FromSeconds(5);
});
SentrySdk.ConfigureScope(scope => scope.SetTag("portal", settings.Portal));
Console.WriteLine("Raportowanie błędów do Sentry włączone.");
Console.WriteLine($"Raportowanie błędów do Sentry włączone; cel: {DescribeSentryTarget(settings.Sentry.Dsn)}.");
return sentry;
}
private static string DescribeSentryTarget(string dsn)
{
if (!Uri.TryCreate(dsn, UriKind.Absolute, out var uri))
{
return "niepoprawny DSN";
}
var port = uri.IsDefaultPort ? string.Empty : $":{uri.Port}";
var project = uri.AbsolutePath.Trim('/').Split('/').LastOrDefault() ?? "?";
return $"{uri.Scheme}://{uri.Host}{port}, projekt {project}";
}
}
internal sealed record CommandLineOptions(string ConfigurationPath, bool CheckConfigurationOnly)
public sealed record CommandLineOptions(
string ConfigurationPath,
bool CheckConfigurationOnly,
bool VerboseLogging)
{
public static CommandLineOptions Parse(string[] args)
{
var path = Path.Combine(AppContext.BaseDirectory, "config", "enova.json");
var checkOnly = false;
var verbose = false;
for (var index = 0; index < args.Length; index++)
{
@@ -154,11 +183,23 @@ internal sealed record CommandLineOptions(string ConfigurationPath, bool CheckCo
case "--check-config":
checkOnly = true;
break;
case "/debug":
case "/verbose":
case "--debug":
case "--verbose":
verbose = true;
break;
case "/nodebug":
case "/noverbose":
case "--no-debug":
case "--no-verbose":
verbose = false;
break;
default:
throw new ConfigurationException($"Nieznany argument: {args[index]}");
}
}
return new CommandLineOptions(path, checkOnly);
return new CommandLineOptions(path, checkOnly, verbose);
}
}

View File

@@ -1,7 +1,7 @@
using WampSharp.Core.Serialization;
using WampSharp.V2.Core.Contracts;
using WampSharp.V2.Rpc;
using Sentry;
using SmartB2B.Enova.Service.Diagnostics;
namespace SmartB2B.Enova.Service.Rpc;
@@ -87,6 +87,12 @@ public sealed class DelegateRpcOperation : IWampRpcOperation
}
catch (WampException exception)
{
var message = exception.ArgumentsKeywords.TryGetValue("message", out var value)
? Convert.ToString(value)
: exception.Message;
ConsoleLogger.Error(
$"Błąd procedury {Procedure} [{exception.ErrorUri}]: {message}",
exception);
var details = formatter.Serialize(exception.Details);
var errorArguments = exception.Arguments.Select(formatter.Serialize).ToArray();
var keywordArguments = formatter.Serialize(exception.ArgumentsKeywords);
@@ -94,15 +100,19 @@ public sealed class DelegateRpcOperation : IWampRpcOperation
}
catch (OperationCanceledException)
{
ConsoleLogger.Error($"Anulowano wykonanie procedury {Procedure}.");
var details = formatter.Serialize(new Dictionary<string, object>());
caller.Error(formatter, details, "wamp.error.canceled");
}
catch (Exception exception)
{
Console.Error.WriteLine($"Nieobsłużony błąd procedury {Procedure}: {exception}");
SentrySdk.CaptureException(
exception,
scope => scope.SetTag("rpc.procedure", Procedure));
ConsoleLogger.Error(
$"Nieobsłużony błąd procedury {Procedure}: {exception.Message}",
exception);
await SentryReporter.CaptureAsync(
exception,
scope => scope.SetTag("rpc.procedure", Procedure))
.ConfigureAwait(false);
var details = formatter.Serialize(new Dictionary<string, object>());
var keywordArguments = formatter.Serialize(new Dictionary<string, object?>
{

View File

@@ -2,6 +2,7 @@ using WampSharp.V2;
using WampSharp.V2.Client;
using WampSharp.V2.Core.Contracts;
using WampSharp.V2.Rpc;
using SmartB2B.Enova.Service.Diagnostics;
namespace SmartB2B.Enova.Service.Rpc;
@@ -45,6 +46,7 @@ public sealed class WampServiceClient
foreach (var operation in _operations)
{
ConsoleLogger.Verbose($"WAMP: rejestracja procedury {operation.Procedure}.");
var registration = await channel.RealmProxy.RpcCatalog.Register(
operation,
new RegisterOptions { Invoke = "last" })
@@ -56,7 +58,7 @@ public sealed class WampServiceClient
Console.WriteLine(
$"Połączono z WAMP; zarejestrowano {_operations.Count} procedur.");
await disconnected.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
Console.Error.WriteLine("Połączenie WAMP zostało przerwane.");
ConsoleLogger.Error("Połączenie WAMP zostało przerwane.");
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@@ -64,7 +66,7 @@ public sealed class WampServiceClient
}
catch (Exception exception)
{
Console.Error.WriteLine($"Błąd połączenia WAMP: {exception.Message}");
ConsoleLogger.Error($"Błąd połączenia WAMP: {exception.Message}", exception);
}
finally
{

View File

@@ -1,7 +1,7 @@
using System.Data.Common;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json;
using Sentry;
using SmartB2B.Enova.Service.Diagnostics;
using SmartB2B.Enova.Service.Rpc;
namespace SmartB2B.Enova.Service.Sql;
@@ -97,12 +97,13 @@ public sealed class SqlRawHandler
{
var code = exception.GetType().GetProperty("Number")?.GetValue(exception)
?? exception.ErrorCode;
Console.Error.WriteLine($"Błąd SQL {code}: {exception.Message}");
SentrySdk.CaptureException(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.sql_raw");
scope.SetExtra("sql.error_code", code);
});
ConsoleLogger.Error($"Błąd SQL {code}: {exception.Message}", exception);
await SentryReporter.CaptureAsync(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.sql_raw");
scope.SetExtra("sql.error_code", code);
})
.ConfigureAwait(false);
throw WampErrorFactory.Create(
"eu.smartb2b.sql_error",
exception.Message,

View File

@@ -1,6 +1,7 @@
using System.Data.Common;
using System.Reflection;
using SmartB2B.Enova.Contracts;
using SmartB2B.Enova.Service;
using SmartB2B.Enova.Service.Configuration;
using SmartB2B.Enova.Service.Orders;
using SmartB2B.Enova.Service.Rpc;
@@ -27,10 +28,14 @@ var tests = new (string Name, Func<Task> Run)[]
("Warstwa WAMP wysyła wynik jako kwargs", WampKeywordWireResult),
("Brak waluty zwraca stabilny błąd", MissingCurrency),
("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency),
("Błąd adaptera jest wypisywany na stderr", AdapterErrorIsWrittenToStderr),
("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory),
("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile),
("Brak danych operatora wyłącza place_order", MissingCredentialsDisablePlaceOrder),
("Częściowe dane operatora wyłączają place_order", PartialCredentialsDisablePlaceOrder),
("Parametr /debug włącza szczegółowe logowanie", DebugSwitchEnablesVerboseLogging),
("Parametr /verbose można wyłączyć", VerboseSwitchCanBeDisabled),
("Tryb szczegółowy jest przekazywany do adaptera", VerboseModeReachesAdapter),
("Provider SQL działa na bieżącej platformie", SqlProviderIsSupported)
};
@@ -195,6 +200,29 @@ static async Task UnsupportedCurrency()
"eu.smartb2b.erp.currency_not_supported");
}
static async Task AdapterErrorIsWrittenToStderr()
{
var previousError = Console.Error;
using var error = new StringWriter();
try
{
Console.SetError(error);
var handler = new PlaceOrderHandler(new FailingAdapter(), CreateConfiguration());
await AssertWampError(
handler.HandleAsync(CreateInvocation(), CancellationToken.None),
"eu.smartb2b.company_not_found");
}
finally
{
Console.SetError(previousError);
}
if (!error.ToString().Contains("Nie znaleziono kontrahenta", StringComparison.Ordinal))
{
throw new InvalidOperationException($"Brak komunikatu błędu na stderr: {error}");
}
}
static Task MissingEnovaDirectory()
{
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
@@ -281,7 +309,30 @@ static Task PartialCredentialsDisablePlaceOrder()
return Task.CompletedTask;
}
static RuntimeSettings LoadRuntimeSettings(string credentialsJson)
static Task DebugSwitchEnablesVerboseLogging()
{
var options = CommandLineOptions.Parse(["/debug"]);
AssertEqual(true, options.VerboseLogging, "tryb szczegółowy");
return Task.CompletedTask;
}
static Task VerboseSwitchCanBeDisabled()
{
var options = CommandLineOptions.Parse(["/verbose", "/noverbose"]);
AssertEqual(false, options.VerboseLogging, "wyłączony tryb szczegółowy");
return Task.CompletedTask;
}
static Task VerboseModeReachesAdapter()
{
var runtime = LoadRuntimeSettings(
"\"operator\": \"Administrator\", \"password\": \"haslo\",",
verboseLogging: true);
AssertEqual(true, runtime.EnovaConfiguration?.VerboseLogging, "tryb adaptera");
return Task.CompletedTask;
}
static RuntimeSettings LoadRuntimeSettings(string credentialsJson, bool verboseLogging = false)
{
var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json");
try
@@ -300,7 +351,7 @@ static RuntimeSettings LoadRuntimeSettings(string credentialsJson)
}
""");
return ServiceSettings.Load(path).ResolveRuntimeSettings();
return ServiceSettings.Load(path).ResolveRuntimeSettings(verboseLogging);
}
finally
{
@@ -423,6 +474,17 @@ file sealed class FakeAdapter : IEnovaOrderAdapter
}
}
file sealed class FailingAdapter : IEnovaOrderAdapter
{
public EnovaAdapterInfo Initialize(EnovaAdapterConfiguration configuration) =>
new("1.0.0", "2604.4.4.0", "2604.4.4.0", configuration.Connection.DatabaseName);
public OrderResult CreateOrder(OrderRequest request) =>
throw new EnovaOperationException(
"eu.smartb2b.company_not_found",
$"Nie znaleziono kontrahenta o kodzie '{request.CustomerCode}'.");
}
file sealed class FakeRouterCallback : IWampRawRpcOperationRouterCallback
{
public long RequestId => 1;