diff --git a/Presentation/Nop.Web/App_Data/appsettings.json b/Presentation/Nop.Web/App_Data/appsettings.json index 3fe7c34..3081cc3 100644 --- a/Presentation/Nop.Web/App_Data/appsettings.json +++ b/Presentation/Nop.Web/App_Data/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "ConnectionString": "Data Source=195.26.231.218;Initial Catalog=FruitBank_DEV;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True", + "ConnectionString": "Data Source=195.26.231.218;Initial Catalog=FruitBank_PROD;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True", "DataProvider": "sqlserver", "SQLCommandTimeout": null, "WithNoLock": false diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientSandbox.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientSandbox.cs index 7b33e03..03e2970 100644 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientSandbox.cs +++ b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientSandbox.cs @@ -1,6 +1,5 @@ using AyCode.Core.Loggers; using AyCode.Services.SignalRs; -using Mango.Sandbox.EndPoints; namespace Mango.Sandbox.EndPoints.Tests; @@ -15,12 +14,15 @@ public class SignalRClientSandbox : AcSignalRClientBase { } - protected override SignalResponseJsonMessage DeserializeResponseMsgPack(byte[] messageBytes) - { - var responseJsonMessage = base.DeserializeResponseMsgPack(messageBytes); - Console.WriteLine(responseJsonMessage.ResponseDataJson); - return responseJsonMessage; - } + //protected override ISignalResponseMessage DeserializeResponseMessage(byte[] messageBytes) + //{ + // var responseMessage = base.DeserializeResponseMessage(messageBytes); + // if (responseMessage is SignalResponseJsonMessage jsonMessage) + // { + // Console.WriteLine(jsonMessage.ResponseData); + // } + // return responseMessage; + //} protected override Task MessageReceived(int messageTag, byte[] messageBytes) { diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientToEndpointTest.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientToEndpointTest.cs index 0ba373e..73f4b86 100644 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientToEndpointTest.cs +++ b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalRClientToEndpointTest.cs @@ -454,4 +454,49 @@ public class SignalRClientToEndpointTest } #endregion + + #region Binary Serialization Tesztek - AcBinaryDeserializationException reprodukálása + + /// + /// Reprodukálja a GetMeasuringUsers hibát: AcBinaryDeserializationException: Invalid interned string index + /// A hiba akkor jelentkezik, amikor a szerver Binary formátumban küldi a választ (List), + /// de a kliens deszerializálásnál az interned string index hibás. + /// + [TestMethod] + public async Task GetAllAsync_CustomerDtoList_BinaryDeserialization_ThrowsInternedStringIndexError() + { + // Act - GetAllAsync>(tag) + // Ez a hívás a GetMeasuringUsers-höz hasonló forgatókönyvet reprodukál + var result = await _client.GetAllAsync>(TestSignalRTags.GetCustomerDtoList); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Count > 0, "Should return at least one CustomerDto"); + + // EllenÅ‘rizzük, hogy minden CustomerDto megfelelÅ‘en deszerializálódott + foreach (var dto in result) + { + Assert.IsTrue(dto.Id > 0, $"CustomerDto.Id should be > 0, got {dto.Id}"); + Assert.IsFalse(string.IsNullOrEmpty(dto.Username), "CustomerDto.Username should not be empty"); + Assert.IsFalse(string.IsNullOrEmpty(dto.Email), "CustomerDto.Email should not be empty"); + Console.WriteLine($"[GetAllAsync_CustomerDtoList] Id={dto.Id}, Username={dto.Username}, Email={dto.Email}, FullName={dto.FullName}"); + } + } + + /// + /// Nagyobb CustomerDto lista tesztelése - több interned string a szerializációban + /// + [TestMethod] + public async Task GetAllAsync_LargeCustomerDtoList_BinaryDeserialization_Success() + { + // Act + var result = await _client.GetAllAsync>(TestSignalRTags.GetLargeCustomerDtoList); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Count >= 10, $"Should return at least 10 CustomerDtos, got {result.Count}"); + Console.WriteLine($"[GetAllAsync_LargeCustomerDtoList] Received {result.Count} items"); + } + + #endregion } \ No newline at end of file diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointSimpleTests.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointSimpleTests.cs deleted file mode 100644 index 1520649..0000000 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointSimpleTests.cs +++ /dev/null @@ -1,210 +0,0 @@ -using AyCode.Core.Extensions; -using AyCode.Services.SignalRs; -using FruitBank.Common.SignalRs; -using Mango.Sandbox.EndPoints; -using MessagePack; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Text; -using MessagePack.Resolvers; - -namespace Mango.Sandbox.EndPoints.Tests; - -/// -/// Alacsony szintû SignalR tesztek - közvetlen HubConnection használatával. -/// FONTOS: A SANDBOX-ot manuálisan kell elindítani a tesztek futtatása elõtt! -/// -[TestClass] -public class SignalREndpointSimpleTests -{ - private static readonly string SandboxUrl = "http://localhost:59579"; - private static readonly string HubUrl = $"{SandboxUrl}/fbHub"; - - [ClassInitialize] - public static async Task ClassInitialize(TestContext context) - { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; - try - { - var response = await httpClient.GetAsync($"{SandboxUrl}/health"); - Assert.IsTrue(response.IsSuccessStatusCode, - "SANDBOX not running! Start: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579"); - } - catch (Exception ex) - { - Assert.Fail($"SANDBOX not running! {ex.Message}"); - } - } - - #region HTTP Endpoint Tests - - [TestMethod] - public async Task HealthEndpoint_ReturnsSuccess() - { - using var httpClient = new HttpClient(); - var response = await httpClient.GetAsync($"{SandboxUrl}/health"); - Assert.IsTrue(response.IsSuccessStatusCode); - } - - [TestMethod] - public async Task RootEndpoint_ReturnsSandboxIsRunning() - { - using var httpClient = new HttpClient(); - var response = await httpClient.GetStringAsync(SandboxUrl); - Assert.AreEqual("SANDBOX is running!", response); - } - - #endregion - - #region SignalR Connection Tests - - [TestMethod] - public async Task SignalR_Negotiate_ReturnsSuccess() - { - using var httpClient = new HttpClient(); - var response = await httpClient.PostAsync($"{HubUrl}/negotiate?negotiateVersion=1", null); - Assert.IsTrue(response.IsSuccessStatusCode); - } - - [TestMethod] - public async Task SignalR_Connect_Succeeds() - { - var connection = new HubConnectionBuilder() - .WithUrl(HubUrl) - .Build(); - - try - { - await connection.StartAsync(); - Assert.AreEqual(HubConnectionState.Connected, connection.State); - } - finally - { - await connection.StopAsync(); - } - } - - #endregion - - #region Low-Level SignalR Tests - - [TestMethod] - public async Task SignalR_Ping_ReturnsResponse() - { - await SignalRClientHelper(TestSignalRTags.PingTag, "Hello SignalR!", "Ping", response => - { - Assert.IsNotNull(response); - var pingResponse = response.JsonTo(); - Assert.IsNotNull(pingResponse); - Console.WriteLine($"[Ping] Message: {pingResponse.Message}"); - }); - } - - [TestMethod] - public async Task SignalR_Echo_ReturnsEchoedData() - { - var request = new TestEchoRequest { Id = 42, Name = "TestName" }; - await SignalRClientHelper(TestSignalRTags.EchoTag, request, "Echo", response => - { - Assert.IsNotNull(response); - var echoResponse = response.JsonTo(); - Assert.IsNotNull(echoResponse); - Assert.AreEqual(42, echoResponse.Id); - }); - } - - [TestMethod] - public async Task SignalR_GetTestItems_ReturnsItemList() - { - await SignalRClientHelper(TestSignalRTags.GetTestItems, null, "GetTestItems", response => - { - Assert.IsNotNull(response); - var items = response.JsonTo>(); - Assert.IsNotNull(items); - Assert.IsTrue(items.Count > 0); - }); - } - - #endregion - - #region Helper Methods - - private async Task SignalRClientHelper(int tag, object? parameter, string endpointName, Action? validateResponse = null) - { - var connection = new HubConnectionBuilder() - .WithUrl(HubUrl) - .Build(); - - string? receivedJson = null; - var responseReceived = new TaskCompletionSource(); - - connection.On("OnReceiveMessage", (responseTag, data, requestId) => - { - if (data != null && data.Length > 0) - { - try - { - var options = MessagePack.Resolvers.ContractlessStandardResolver.Options; - var response = MessagePackSerializer.Deserialize(data, options); - receivedJson = response?.ResponseData; - } - catch - { - receivedJson = Encoding.UTF8.GetString(data); - } - } - responseReceived.TrySetResult(true); - }); - - try - { - await connection.StartAsync(); - Assert.AreEqual(HubConnectionState.Connected, connection.State); - - byte[]? requestData = CreateRequestData(parameter); - await connection.InvokeAsync("OnReceiveMessage", tag, requestData, (int?)null); - - var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(5000)); - if (completed == responseReceived.Task) - { - validateResponse?.Invoke(receivedJson); - } - else - { - Assert.Fail($"[{endpointName}] Timeout"); - } - } - finally - { - if (connection.State == HubConnectionState.Connected) - await connection.StopAsync(); - } - } - - private static byte[]? CreateRequestData(object? parameter) - { - if (parameter == null) return null; - - var isPrimitive = parameter is string or int or long or double or float or decimal or bool or DateTime; - - if (isPrimitive) - { - var idMessage = new IdMessage(parameter); - return new SignalPostJsonDataMessage(idMessage).ToMessagePack(ContractlessStandardResolver.Options); - } - else - { - return new SignalPostJsonDataMessage(parameter).ToMessagePack(ContractlessStandardResolver.Options); - } - } - - #endregion -} - -/// -/// Wrapper a Task eredményekhez - a szerver Task-ot ad vissza {"Result":...} formátumban -/// -public class TaskResultWrapper -{ - public T? Result { get; set; } -} diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointWithNopEnvTests.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointWithNopEnvTests.cs deleted file mode 100644 index 9c11f96..0000000 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints.Tests/SignalREndpointWithNopEnvTests.cs +++ /dev/null @@ -1,218 +0,0 @@ -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Diagnostics; -using System.Text; -using System.Text.Json; - -namespace Mango.Sandbox.EndPoints.Tests; - -/// -/// SignalR Endpoint tesztek. -/// FONTOS: A SANDBOX-ot manuálisan kell elindítani a tesztek futtatása elõtt! -// Indítás: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579 -/// -[TestClass] -public class SignalREndpointWithNopEnvTests -{ - private static readonly string SandboxUrl = "http://localhost:59579"; - private static readonly string HubUrl = $"{SandboxUrl}/fbHub"; - - // SignalR Tags from FruitBank.Common.SignalRs.SignalRTags - private const int GetMeasuringUsersTag = 70; - private const int GetStockQuantityHistoryDtosTag = 40; - private const int GetStockQuantityHistoryDtosByProductIdTag = 41; - private const int GetShippingDocumentsByShippingIdTag = 60; - private const int GetOrderDtoByIdTag = 21; - private const int GetStockTakingItemsByIdTag = 81; - - //[ClassInitialize] - //public static async Task ClassInitialize(TestContext context) - //{ - // using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; - // try - // { - // var response = await httpClient.GetAsync($"{SandboxUrl}/health"); - // Assert.IsTrue(response.IsSuccessStatusCode, - // "SANDBOX is not running! Start it manually: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579"); - // } - // catch (Exception ex) - // { - // Assert.Fail($"SANDBOX is not running! Error: {ex.Message}\n" + - // "Start it manually: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579"); - // } - //} - - //#region HTTP Endpoint Tests - - //[TestMethod] - //public async Task HealthEndpoint_ReturnsSuccess() - //{ - // using var httpClient = new HttpClient(); - // var response = await httpClient.GetAsync($"{SandboxUrl}/health"); - // Assert.IsTrue(response.IsSuccessStatusCode, $"Health endpoint returned {response.StatusCode}"); - //} - - //[TestMethod] - //public async Task RootEndpoint_ReturnsSandboxIsRunning() - //{ - // using var httpClient = new HttpClient(); - // var response = await httpClient.GetStringAsync(SandboxUrl); - // Assert.AreEqual("SANDBOX is running!", response); - //} - - //#endregion - - //#region SignalR Connection Tests - - //[TestMethod] - //public async Task SignalR_Negotiate_ReturnsSuccess() - //{ - // using var httpClient = new HttpClient(); - // var response = await httpClient.PostAsync($"{HubUrl}/negotiate?negotiateVersion=1", null); - // Assert.IsTrue(response.IsSuccessStatusCode, $"SignalR negotiate returned {response.StatusCode}"); - //} - - //[TestMethod] - //public async Task SignalR_Connect_Succeeds() - //{ - // var connection = new HubConnectionBuilder() - // .WithUrl(HubUrl) - // .Build(); - - // try - // { - // await connection.StartAsync(); - // Assert.AreEqual(HubConnectionState.Connected, connection.State); - // } - // finally - // { - // await connection.StopAsync(); - // } - //} - - //#endregion - - //#region SignalR Business Endpoint Tests - - //[TestMethod] - //public async Task SignalR_GetMeasuringUsers_ReturnsJson() - //{ - // await TestSignalREndpoint(GetMeasuringUsersTag, null, "GetMeasuringUsers"); - //} - - //[TestMethod] - //public async Task SignalR_GetStockQuantityHistoryDtos_ReturnsJson() - //{ - // await TestSignalREndpoint(GetStockQuantityHistoryDtosTag, null, "GetStockQuantityHistoryDtos"); - //} - - //[TestMethod] - //public async Task SignalR_GetStockQuantityHistoryDtosByProductId_ReturnsJson() - //{ - // // ProductId = 10 - // await TestSignalREndpoint(GetStockQuantityHistoryDtosByProductIdTag, 10, "GetStockQuantityHistoryDtosByProductId"); - //} - - //[TestMethod] - //public async Task SignalR_GetShippingDocumentsByShippingId_ReturnsJson() - //{ - // // ShippingId = 5 - // await TestSignalREndpoint(GetShippingDocumentsByShippingIdTag, 5, "GetShippingDocumentsByShippingId"); - //} - - //[TestMethod] - //public async Task SignalR_GetOrderDtoById_ReturnsJson() - //{ - // // OrderId = 15 - // await TestSignalREndpoint(GetOrderDtoByIdTag, 15, "GetOrderDtoById"); - //} - - //[TestMethod] - //public async Task SignalR_GetStockTakingItemsById_ReturnsJson() - //{ - // // StockTakingItemId = 200 - // await TestSignalREndpoint(GetStockTakingItemsByIdTag, 200, "GetStockTakingItemsById"); - //} - - //#endregion - - //#region Helper Methods - - //private async Task TestSignalREndpoint(int tag, object? parameter, string endpointName) - //{ - // var connection = new HubConnectionBuilder() - // .WithUrl(HubUrl) - // .Build(); - - // string? receivedJson = null; - // int receivedTag = -1; - // var responseReceived = new TaskCompletionSource(); - - // connection.On("ReceiveMessage", (responseTag, data) => - // { - // receivedTag = responseTag; - // if (data != null && data.Length > 0) - // { - // receivedJson = Encoding.UTF8.GetString(data); - // } - // responseReceived.TrySetResult(true); - // }); - - // try - // { - // await connection.StartAsync(); - // Assert.AreEqual(HubConnectionState.Connected, connection.State, $"Failed to connect to SignalR hub for {endpointName}"); - - // // Készítsük el a request data-t - // byte[] requestData = parameter != null - // ? Encoding.UTF8.GetBytes(JsonSerializer.Serialize(parameter)) - // : Array.Empty(); - - // await connection.InvokeAsync("ReceiveMessage", tag, requestData); - - // var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(15000)); - - // if (completed == responseReceived.Task) - // { - // Console.WriteLine($"[{endpointName}] Response tag: {receivedTag}"); - // Console.WriteLine($"[{endpointName}] Response JSON: {receivedJson?.Substring(0, Math.Min(500, receivedJson?.Length ?? 0))}..."); - - // // Ellenõrizzük, hogy valid JSON-e (ha van adat) - // if (!string.IsNullOrEmpty(receivedJson)) - // { - // try - // { - // var jsonDoc = JsonDocument.Parse(receivedJson); - // Assert.IsTrue( - // jsonDoc.RootElement.ValueKind == JsonValueKind.Array || - // jsonDoc.RootElement.ValueKind == JsonValueKind.Object || - // jsonDoc.RootElement.ValueKind == JsonValueKind.Null, - // $"[{endpointName}] Response is not a valid JSON"); - // } - // catch (JsonException ex) - // { - // Assert.Fail($"[{endpointName}] Invalid JSON response: {ex.Message}"); - // } - // } - // } - // else - // { - // Assert.AreEqual(HubConnectionState.Connected, connection.State, - // $"[{endpointName}] Connection was closed - check SANDBOX logs for DI errors"); - // } - // } - // catch (Exception ex) - // { - // Assert.Fail($"[{endpointName}] SignalR error: {ex.Message}. Check SANDBOX logs for missing DI registrations."); - // } - // finally - // { - // if (connection.State == HubConnectionState.Connected) - // { - // await connection.StopAsync(); - // } - // } - //} - // - //#endregion -} diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/DevAdminSignalRHubSandbox.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/DevAdminSignalRHubSandbox.cs index 97c4ab5..00adf64 100644 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/DevAdminSignalRHubSandbox.cs +++ b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/DevAdminSignalRHubSandbox.cs @@ -1,4 +1,5 @@ using AyCode.Core.Enums; +using AyCode.Core.Extensions; using AyCode.Core.Loggers; using AyCode.Models.Server.DynamicMethods; using AyCode.Services.Server.SignalRs; @@ -23,16 +24,16 @@ public class DevAdminSignalRHubSandbox : AcWebSignalRHubWithSessionBase logWriters) : base(configuration, new Logger(logWriters.ToArray())) { + SerializerOptions = new AcBinarySerializerOptions(); DynamicMethodCallModels.Add(new AcDynamicMethodCallModel(testSignalREndpoint)); - } - protected override Task SendMessageToClient(IAcSignalRHubItemServer sendTo, int messageTag, ISignalRMessage message, int? requestId = null) - { - Console.WriteLine(((SignalResponseJsonMessage)message).ResponseDataJson); + //protected override Task SendMessageToClient(IAcSignalRHubItemServer sendTo, int messageTag, ISignalRMessage message, int? requestId = null) + //{ + // Console.WriteLine(((SignalResponseJsonMessage)message).ResponseDataJson); - return base.SendMessageToClient(sendTo, messageTag, message, requestId); - } + // return base.SendMessageToClient(sendTo, messageTag, message, requestId); + //} } // =========================================== diff --git a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/TestSignalREndpoint.cs b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/TestSignalREndpoint.cs index 80fea18..3659e92 100644 --- a/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/TestSignalREndpoint.cs +++ b/Tests/Mango.Sandbox/Mango.Sandbox.EndPoints/TestSignalREndpoint.cs @@ -396,6 +396,75 @@ public class TestSignalREndpoint : ITestSignalREndpointServer } #endregion + + #region Binary Serialization Tesztek - CustomerDto reprodukálás + + /// + /// CustomerDto lista visszaadása - reprodukálja a GetMeasuringUsers forgatókönyvet + /// + [SignalR(TestSignalRTags.GetCustomerDtoList)] + public Task> GetCustomerDtoList() + { + var customers = new List + { + new() + { + Id = 1, + Username = "user1", + Email = "user1@test.com", + FirstName = "First1", + LastName = "Last1", + RegisteredInStoreId = 1, + Deleted = false + }, + new() + { + Id = 2, + Username = "user2", + Email = "user2@test.com", + FirstName = "First2", + LastName = "Last2", + RegisteredInStoreId = 1, + Deleted = false + }, + new() + { + Id = 3, + Username = "user3", + Email = "user3@test.com", + FirstName = "First3", + LastName = "Last3", + RegisteredInStoreId = 2, + Deleted = true + } + }; + return Task.FromResult(customers); + } + + /// + /// Nagyobb CustomerDto lista - több interned string a szerializációban + /// + [SignalR(TestSignalRTags.GetLargeCustomerDtoList)] + public Task> GetLargeCustomerDtoList() + { + var customers = new List(); + for (int i = 1; i <= 30; i++) + { + customers.Add(new TestCustomerDto + { + Id = i, + Username = $"user{i}", + Email = $"user{i}@test.com", + FirstName = $"FirstName{i}", + LastName = $"LastName{i}", + RegisteredInStoreId = (i % 3) + 1, + Deleted = i % 5 == 0 + }); + } + return Task.FromResult(customers); + } + + #endregion } /// @@ -449,6 +518,10 @@ public static class TestSignalRTags public const int DtoAndListParam = 9061; public const int ThreeComplexParams = 9062; public const int FiveParams = 9063; + + // Binary serialization tesztek - CustomerDto reprodukálás + public const int GetCustomerDtoList = 9070; + public const int GetLargeCustomerDtoList = 9071; } /// @@ -500,6 +573,10 @@ public interface ITestSignalREndpointServer Task HandleDtoAndList(TestOrderItem item, List numbers); Task HandleThreeComplexParams(TestOrderItem item, List tags, SharedTag sharedTag); Task HandleFiveParams(int a, string b, bool c, Guid d, decimal e); + + // Binary serialization tesztek - CustomerDto reprodukálás + Task> GetCustomerDtoList(); + Task> GetLargeCustomerDtoList(); } #region DTOs @@ -584,4 +661,19 @@ public class SharedTag public bool IsActive { get; set; } } +/// +/// Teszt CustomerDto a felhasználók teszteléséhez +/// +public class TestCustomerDto +{ + public int Id { get; set; } + public string Username { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string FullName => $"{LastName} {FirstName}"; + public int RegisteredInStoreId { get; set; } + public bool Deleted { get; set; } +} + #endregion