Disable place_order without Enova credentials
This commit is contained in:
@@ -14,7 +14,7 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365.
|
|||||||
|
|
||||||
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
|
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
|
||||||
|
|
||||||
Uzupełnij pola `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
|
Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
|
||||||
|
|
||||||
## Budowanie i testy
|
## Budowanie i testy
|
||||||
|
|
||||||
|
|||||||
@@ -48,19 +48,22 @@ public sealed class ServiceSettings
|
|||||||
|
|
||||||
public RuntimeSettings ResolveRuntimeSettings()
|
public RuntimeSettings ResolveRuntimeSettings()
|
||||||
{
|
{
|
||||||
var enovaPassword = Enova.Password
|
|
||||||
?? throw new ConfigurationException("Parametr 'enova.password' jest wymagany.");
|
|
||||||
|
|
||||||
var installationPath = Path.GetFullPath(
|
var installationPath = Path.GetFullPath(
|
||||||
Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
|
Environment.ExpandEnvironmentVariables(Enova.InstallationPath));
|
||||||
|
|
||||||
return new RuntimeSettings(
|
EnovaAdapterConfiguration? enovaConfiguration = null;
|
||||||
installationPath,
|
if (!string.IsNullOrWhiteSpace(Enova.Operator) && Enova.Password is not null)
|
||||||
new EnovaAdapterConfiguration(
|
{
|
||||||
new EnovaConnectionOptions(Enova.Database, Enova.Operator, enovaPassword),
|
enovaConfiguration = new EnovaAdapterConfiguration(
|
||||||
|
new EnovaConnectionOptions(Enova.Database, Enova.Operator, Enova.Password),
|
||||||
Enova.DocumentDefinition,
|
Enova.DocumentDefinition,
|
||||||
Enova.DefaultWarehouseCode,
|
Enova.DefaultWarehouseCode,
|
||||||
Enova.SaveMode),
|
Enova.SaveMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RuntimeSettings(
|
||||||
|
installationPath,
|
||||||
|
enovaConfiguration,
|
||||||
Sql.ConnectionString,
|
Sql.ConnectionString,
|
||||||
Sql.CommandTimeoutSeconds,
|
Sql.CommandTimeoutSeconds,
|
||||||
ResolvePath(Diagnostics.LogDirectory),
|
ResolvePath(Diagnostics.LogDirectory),
|
||||||
@@ -78,8 +81,6 @@ public sealed class ServiceSettings
|
|||||||
}
|
}
|
||||||
Require(Enova.InstallationPath, "enova.installationPath", errors);
|
Require(Enova.InstallationPath, "enova.installationPath", errors);
|
||||||
Require(Enova.Database, "enova.database", errors);
|
Require(Enova.Database, "enova.database", errors);
|
||||||
Require(Enova.Operator, "enova.operator", errors);
|
|
||||||
RequirePresent(Enova.Password, "enova.password", errors);
|
|
||||||
Require(Enova.DocumentDefinition, "enova.documentDefinition", errors);
|
Require(Enova.DocumentDefinition, "enova.documentDefinition", errors);
|
||||||
Require(Sql.ConnectionString, "sql.connectionString", errors);
|
Require(Sql.ConnectionString, "sql.connectionString", errors);
|
||||||
if (Sql.CommandTimeoutSeconds <= 0)
|
if (Sql.CommandTimeoutSeconds <= 0)
|
||||||
@@ -101,14 +102,6 @@ public sealed class ServiceSettings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RequirePresent(string? value, string name, ICollection<string> errors)
|
|
||||||
{
|
|
||||||
if (value is null)
|
|
||||||
{
|
|
||||||
errors.Add($"Parametr '{name}' jest wymagany.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolvePath(string path) =>
|
private static string ResolvePath(string path) =>
|
||||||
Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(path, AppContext.BaseDirectory);
|
Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(path, AppContext.BaseDirectory);
|
||||||
}
|
}
|
||||||
@@ -126,7 +119,7 @@ public sealed class EnovaSettings
|
|||||||
|
|
||||||
public string Database { get; init; } = string.Empty;
|
public string Database { get; init; } = string.Empty;
|
||||||
|
|
||||||
public string Operator { get; init; } = "Administrator";
|
public string? Operator { get; init; }
|
||||||
|
|
||||||
public string? Password { get; init; }
|
public string? Password { get; init; }
|
||||||
|
|
||||||
@@ -153,7 +146,7 @@ public sealed class DiagnosticsSettings
|
|||||||
|
|
||||||
public sealed record RuntimeSettings(
|
public sealed record RuntimeSettings(
|
||||||
string EnovaInstallationPath,
|
string EnovaInstallationPath,
|
||||||
EnovaAdapterConfiguration EnovaConfiguration,
|
EnovaAdapterConfiguration? EnovaConfiguration,
|
||||||
string SqlConnectionString,
|
string SqlConnectionString,
|
||||||
int SqlCommandTimeoutSeconds,
|
int SqlCommandTimeoutSeconds,
|
||||||
string LogDirectory,
|
string LogDirectory,
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ namespace SmartB2B.Enova.Service.Diagnostics;
|
|||||||
|
|
||||||
public sealed partial class DiagnosticsHandler
|
public sealed partial class DiagnosticsHandler
|
||||||
{
|
{
|
||||||
private readonly EnovaAdapterInfo _adapterInfo;
|
private readonly EnovaAdapterInfo? _adapterInfo;
|
||||||
private readonly string _enovaPath;
|
private readonly string _enovaPath;
|
||||||
private readonly string _logDirectory;
|
private readonly string _logDirectory;
|
||||||
private readonly string _logPrefix;
|
private readonly string _logPrefix;
|
||||||
|
|
||||||
public DiagnosticsHandler(
|
public DiagnosticsHandler(
|
||||||
EnovaAdapterInfo adapterInfo,
|
EnovaAdapterInfo? adapterInfo,
|
||||||
string enovaPath,
|
string enovaPath,
|
||||||
string logDirectory,
|
string logDirectory,
|
||||||
string logPrefix)
|
string logPrefix)
|
||||||
@@ -35,16 +35,25 @@ public sealed partial class DiagnosticsHandler
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "unknown";
|
var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "unknown";
|
||||||
var lines = new[]
|
var lines = new List<string>
|
||||||
{
|
{
|
||||||
$"SmartB2B Enova Sync version: {serviceVersion}",
|
$"SmartB2B Enova Sync version: {serviceVersion}",
|
||||||
$".NET version: {Environment.Version}",
|
$".NET version: {Environment.Version}",
|
||||||
$"Adapter version: {_adapterInfo.AdapterVersion}",
|
$"Enova path: {_enovaPath}"
|
||||||
$"Soneta.Business version: {_adapterInfo.SonetaBusinessVersion}",
|
|
||||||
$"Soneta.Handel version: {_adapterInfo.SonetaHandelVersion}",
|
|
||||||
$"Enova path: {_enovaPath}",
|
|
||||||
$"Database: {_adapterInfo.DatabaseName}"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_adapterInfo is null)
|
||||||
|
{
|
||||||
|
lines.Add("Adapter Enovy: nie zainicjalizowano (place_order jest wyłączony)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines.Add($"Adapter version: {_adapterInfo.AdapterVersion}");
|
||||||
|
lines.Add($"Soneta.Business version: {_adapterInfo.SonetaBusinessVersion}");
|
||||||
|
lines.Add($"Soneta.Handel version: {_adapterInfo.SonetaHandelVersion}");
|
||||||
|
lines.Add($"Database: {_adapterInfo.DatabaseName}");
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult<object?>(string.Join(Environment.NewLine, lines));
|
return Task.FromResult<object?>(string.Join(Environment.NewLine, lines));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using SmartB2B.Enova.Contracts;
|
||||||
using SmartB2B.Enova.Service.Configuration;
|
using SmartB2B.Enova.Service.Configuration;
|
||||||
using SmartB2B.Enova.Service.Diagnostics;
|
using SmartB2B.Enova.Service.Diagnostics;
|
||||||
using SmartB2B.Enova.Service.Orders;
|
using SmartB2B.Enova.Service.Orders;
|
||||||
@@ -28,7 +29,21 @@ internal static class Program
|
|||||||
|
|
||||||
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
|
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
|
||||||
var adapter = adapterLoader.Load();
|
var adapter = adapterLoader.Load();
|
||||||
var adapterInfo = adapter.Initialize(runtime.EnovaConfiguration);
|
EnovaAdapterInfo? adapterInfo = null;
|
||||||
|
if (runtime.EnovaConfiguration is not null)
|
||||||
|
{
|
||||||
|
adapterInfo = adapter.Initialize(runtime.EnovaConfiguration);
|
||||||
|
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Załadowano Enovę {adapterInfo.SonetaBusinessVersion} z '{runtime.EnovaInstallationPath}'.");
|
||||||
|
Console.WriteLine($"Baza: {adapterInfo.DatabaseName}; portal WAMP: {settings.Portal}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
"Nie podano enova.operator lub enova.password; endpoint eu.smartb2b.place_order jest wyłączony.");
|
||||||
|
Console.WriteLine($"Portal WAMP: {settings.Portal}.");
|
||||||
|
}
|
||||||
|
|
||||||
_ = new System.Data.Common.DbConnectionStringBuilder
|
_ = new System.Data.Common.DbConnectionStringBuilder
|
||||||
{
|
{
|
||||||
@@ -38,32 +53,32 @@ internal static class Program
|
|||||||
runtime.SqlConnectionString,
|
runtime.SqlConnectionString,
|
||||||
runtime.SqlCommandTimeoutSeconds);
|
runtime.SqlCommandTimeoutSeconds);
|
||||||
|
|
||||||
Console.WriteLine(
|
|
||||||
$"Załadowano Enovę {adapterInfo.SonetaBusinessVersion} z '{runtime.EnovaInstallationPath}'.");
|
|
||||||
Console.WriteLine($"Baza: {adapterInfo.DatabaseName}; portal WAMP: {settings.Portal}.");
|
|
||||||
|
|
||||||
if (commandLine.CheckConfigurationOnly)
|
if (commandLine.CheckConfigurationOnly)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Konfiguracja i adapter Enovy są poprawne.");
|
Console.WriteLine(runtime.EnovaConfiguration is null
|
||||||
|
? "Konfiguracja jest poprawna; endpoint place_order pozostaje wyłączony."
|
||||||
|
: "Konfiguracja i adapter Enovy są poprawne.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var orders = new PlaceOrderHandler(adapter, runtime.EnovaConfiguration);
|
|
||||||
var diagnostics = new DiagnosticsHandler(
|
var diagnostics = new DiagnosticsHandler(
|
||||||
adapterInfo,
|
adapterInfo,
|
||||||
runtime.EnovaInstallationPath,
|
runtime.EnovaInstallationPath,
|
||||||
runtime.LogDirectory,
|
runtime.LogDirectory,
|
||||||
runtime.LogPrefix);
|
runtime.LogPrefix);
|
||||||
|
|
||||||
IReadOnlyList<IWampRpcOperation> operations =
|
var operations = new List<IWampRpcOperation>();
|
||||||
[
|
if (runtime.EnovaConfiguration is not null)
|
||||||
new DelegateRpcOperation("eu.smartb2b.place_order", orders.HandleAsync),
|
{
|
||||||
new DelegateRpcOperation("eu.smartb2b.sql_raw", sql.HandleAsync),
|
var orders = new PlaceOrderHandler(adapter, runtime.EnovaConfiguration);
|
||||||
new DelegateRpcOperation("eu.smartb2b.sync.get_version", diagnostics.GetVersionAsync),
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.place_order", orders.HandleAsync));
|
||||||
new DelegateRpcOperation("eu.smartb2b.sync.get_info", diagnostics.GetInfoAsync),
|
}
|
||||||
new DelegateRpcOperation("eu.smartb2b.sync.get_log", diagnostics.GetLogAsync),
|
|
||||||
new DelegateRpcOperation("eu.smartb2b.sync.get_error_log", diagnostics.GetErrorLogAsync)
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.sql_raw", sql.HandleAsync));
|
||||||
];
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_version", diagnostics.GetVersionAsync));
|
||||||
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_info", diagnostics.GetInfoAsync));
|
||||||
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_log", diagnostics.GetLogAsync));
|
||||||
|
operations.Add(new DelegateRpcOperation("eu.smartb2b.sync.get_error_log", diagnostics.GetErrorLogAsync));
|
||||||
|
|
||||||
var wampClient = new WampServiceClient(
|
var wampClient = new WampServiceClient(
|
||||||
settings.Wamp.ServerUrl,
|
settings.Wamp.ServerUrl,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ var tests = new (string Name, Func<Task> Run)[]
|
|||||||
("Brak waluty zwraca stabilny błąd", MissingCurrency),
|
("Brak waluty zwraca stabilny błąd", MissingCurrency),
|
||||||
("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency),
|
("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency),
|
||||||
("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory),
|
("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory),
|
||||||
("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile)
|
("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile),
|
||||||
|
("Brak danych operatora wyłącza place_order", MissingCredentialsDisablePlaceOrder),
|
||||||
|
("Częściowe dane operatora wyłączają place_order", PartialCredentialsDisablePlaceOrder)
|
||||||
};
|
};
|
||||||
|
|
||||||
var failures = 0;
|
var failures = 0;
|
||||||
@@ -214,7 +216,10 @@ static Task SecretsComeFromConfigurationFile()
|
|||||||
|
|
||||||
var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings();
|
var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings();
|
||||||
|
|
||||||
AssertEqual("haslo-z-pliku", runtime.EnovaConfiguration.Connection.Password, "hasło operatora");
|
AssertEqual(
|
||||||
|
"haslo-z-pliku",
|
||||||
|
runtime.EnovaConfiguration?.Connection.Password,
|
||||||
|
"hasło operatora");
|
||||||
AssertEqual(
|
AssertEqual(
|
||||||
"Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
"Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
||||||
runtime.SqlConnectionString,
|
runtime.SqlConnectionString,
|
||||||
@@ -227,6 +232,47 @@ static Task SecretsComeFromConfigurationFile()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Task MissingCredentialsDisablePlaceOrder()
|
||||||
|
{
|
||||||
|
var runtime = LoadRuntimeSettings(string.Empty);
|
||||||
|
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Task PartialCredentialsDisablePlaceOrder()
|
||||||
|
{
|
||||||
|
var runtime = LoadRuntimeSettings("\"operator\": \"Administrator\",");
|
||||||
|
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RuntimeSettings LoadRuntimeSettings(string credentialsJson)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, $$"""
|
||||||
|
{
|
||||||
|
"portal": "demo",
|
||||||
|
"wamp": { "serverUrl": "ws://localhost:8080/" },
|
||||||
|
"enova": {
|
||||||
|
"installationPath": "C:/Enova",
|
||||||
|
"database": "Firma demo",
|
||||||
|
{{credentialsJson}}
|
||||||
|
"documentDefinition": "ZO"
|
||||||
|
},
|
||||||
|
"sql": { "connectionString": "Server=db;Database=enova;User ID=user;Password=secret" }
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
return ServiceSettings.Load(path).ResolveRuntimeSettings();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static RpcInvocation CreateInvocation() => new(
|
static RpcInvocation CreateInvocation() => new(
|
||||||
[],
|
[],
|
||||||
new Dictionary<string, object?>
|
new Dictionary<string, object?>
|
||||||
|
|||||||
Reference in New Issue
Block a user