فهرست منبع

Add loom mapper, add mapper tests, add TenantService

Dalibor Votruba 3 سال پیش
والد
کامیت
1b0792976f

+ 8 - 1
Common/qdr.app.qlbrc.common/Dtos/QlbrcDtoTrackedWithId.cs

@@ -1,5 +1,12 @@
-using Quadarax.Application.QLiberace.Common.Entities.Base;
+using System.Runtime.CompilerServices;
+using Quadarax.Application.QLiberace.Common.Entities.Base;
 
+#if DEBUG
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base.Tests")]
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.common.Tests")]
+#endif
+
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base")]
 namespace Quadarax.Application.QLiberace.Common.Dtos
 {
     public class QlbrcDtoTrackedWithId : QlbrcDtoWithId, ITracked

+ 8 - 2
Common/qdr.app.qlbrc.common/Dtos/QlbrcDtoWithId.cs

@@ -1,10 +1,16 @@
-using Quadarax.Application.QLiberace.Common.Entities.Base;
+using System.Runtime.CompilerServices;
+using Quadarax.Application.QLiberace.Common.Entities.Base;
 using Quadarax.Foundation.Core.Data.Interface.Entity;
 
+#if DEBUG
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base.Tests")]
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.common.Tests")]
+#endif
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base")]
 namespace Quadarax.Application.QLiberace.Common.Dtos
 {
     public class QlbrcDtoWithId : IDto, IIdentifier
     {
-        public long Id { get; }
+        public long Id { get; set; }
     }
 }

+ 6 - 0
Common/qdr.app.qlbrc.common/Entities/Base/QlbrcEntityTracked.cs

@@ -1,5 +1,11 @@
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
+using System.Runtime.CompilerServices;
+
+#if DEBUG
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base.Tests")]
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.common.Tests")]
+#endif
 
 namespace Quadarax.Application.QLiberace.Common.Entities.Base
 {

+ 7 - 1
Common/qdr.app.qlbrc.common/ExceptionLibrary.cs

@@ -18,12 +18,18 @@ namespace Quadarax.Application.QLiberace.Common
         {
             public const string SettingsNotFoundMsg = "Setting '{0}' not found fo module '{1}'";
             public const int SettingsNotFoundCode = 10000;
-            public const string SettingsNotFoundForModuleMsg = "Any setting not found fo module '{0}'";
+            public const string SettingsNotFoundForModuleMsg = "Any setting not found for module '{0}'";
             public const int SettingsNotFoundForModuleCode = 10001;
             public const string SettingsHasNoPropertyMsg = "Setting '{0}' has no defined property in class '{1}'";
             public const int SettingsHasNoPropertyCode = 10002;
             public const string SettingsAlreadyExistsMsg = "Setting '{0}' for module '{1}' already exists.";
             public const int SettingsAlreadyExistsCode = 10003;
+
+            public const string TenantNotFoundMsg = "Tenant with identifier {0}='{1}' not found";
+            public const int TenantNotFoundCode = 10010;
+            public const string TenantAlreadyExistsMsg = "Tenant with code '{0}' already exists";
+            public const int TenantAlreadyExistsCode = 10011;
+            
         }
 
     }

+ 4 - 0
Common/qdr.app.qlbrc.common/qdr.app.qlbrc.common.csproj

@@ -12,4 +12,8 @@
     <ProjectReference Include="..\qdr.fnd.core.data\qdr.fnd.core.data.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Mapper\" />
+  </ItemGroup>
+
 </Project>

+ 1 - 0
Common/qdr.fnd.core.data/Mapper/Mapper.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using Quadarax.Foundation.Core.Data.Interface.Entity;
 using Quadarax.Foundation.Core.Mapper;
 

+ 20 - 1
Modules/qdr.app.qlbrc.base/Dtos/TenantDto.cs

@@ -1,12 +1,31 @@
 using Quadarax.Application.QLiberace.Base.Entities.Interfaces;
+using Quadarax.Application.QLiberace.Common.Dtos;
+using Quadarax.Foundation.Core.Data.Interface.Entity;
+using System.Runtime.CompilerServices;
+#if DEBUG
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base.Tests")]
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.common.Tests")]
+#endif
 
 namespace Quadarax.Application.QLiberace.Base.Dtos
 {
-    public class TenantDto : IStructTenant
+    public class TenantRDto : QlbrcDtoTrackedWithId, IStructTenant
     {
         public string Code { get; set; } = null!;
         public string Name { get; set; } = null!;
         public DateTime? LastAccess { get; set; }
         public bool IsLocked { get; set; }
     }
+
+    public class TenantWDto : QlbrcDtoWithoutId, IStructTenant
+    {
+        public string Code { get; set; } = null!;
+        public string Name { get; set; } = null!;
+        public DateTime? LastAccess { get; set; }
+        public bool IsLocked { get; set; }
+    }
+
+    public class TenantDDto : QlbrcDtoWithId
+    {
+    }
 }

+ 18 - 2
Modules/qdr.app.qlbrc.base/Dtos/UserDto.cs

@@ -1,11 +1,27 @@
-using Quadarax.Application.QLiberace.Base.Entities.Interfaces;
+using System.Runtime.CompilerServices;
+using Quadarax.Application.QLiberace.Base.Entities.Interfaces;
 using Quadarax.Application.QLiberace.Common.Dtos;
 
+#if DEBUG
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.base.Tests")]
+[assembly: InternalsVisibleTo("qdr.app.qlbrc.common.Tests")]
+#endif
+
 namespace Quadarax.Application.QLiberace.Base.Dtos
 {
-    public class UserDto : QlbrcDtoTrackedWithId, IStructUser
+    public class UserRDto : QlbrcDtoTrackedWithId, IStructUser
     {
         public string LoginName { get; set; } = null!;
         public bool IsLocked { get; set; }
     }
+
+    public class UserWDto : QlbrcDtoWithoutId, IStructUser
+    {
+        public string LoginName { get; set; } = null!;
+        public bool IsLocked { get; set; }
+    }
+
+    public class UserDDto : QlbrcDtoWithId
+    {
+    }
 }

+ 126 - 0
Modules/qdr.app.qlbrc.base/Mapper/MapperLong.cs

@@ -0,0 +1,126 @@
+using Quadarax.Application.QLiberace.Base.Dtos;
+using Quadarax.Application.QLiberace.Base.Entities;
+using Quadarax.Foundation.Core.Data.Interface.Entity;
+
+namespace Quadarax.Application.QLiberace.Base.Mapper
+{
+    public class MapperLong : Quadarax.Foundation.Core.Data.Mapper.Mapper<MapperLong>
+    {
+        public MapperLong()
+        {
+            //TODO: make automatic
+            Define<Tenant,TenantRDto>().PublicProperties().Compile();
+            Define<TenantWDto, Tenant>().PublicProperties().Compile();
+            
+            Define<User,UserRDto>().PublicProperties().Compile();
+            Define<UserWDto, User>().PublicProperties().Compile();
+
+            /*
+            DefineBiDirection<User,UserRDto>();
+            DefineBiDirection<User,UserCDto>();
+            DefineBiDirection<User,UserUDto>();
+
+            DefineBiDirection<BillingPlan,BillingPlanRDto>();
+            DefineBiDirection<BillingPlan,BillingPlanCDto>();
+            DefineBiDirection<BillingPlan,BillingPlanUDto>();
+
+
+            DefineBiDirection<MimeType,MimeTypeRDto>();
+            DefineBiDirection<MimeType,MimeTypeCDto>();
+            DefineBiDirection<MimeType,MimeTypeUDto>();
+
+            DefineBiDirection<Statistic,StatisticsDto>();
+
+            Define<Workspace, WorkspaceRDto>(builder =>
+                {
+                    builder.Custom(dest => dest.Structures, src => src.Structures.MapList<Structure, StructureRDto>());
+                    builder.Custom(dest => dest.Audit, src => new BoAuditDto()
+                    {
+                        Created = src.Created,
+                        Changed = src.Modified,
+                        CreatedBy = src.CreatedByUser.Name,
+                        ChangedBy = src.ModifiedByUser != null ? src.ModifiedByUser.Name : string.Empty
+                    });
+                    //calculate on demand  builder.Custom(dest => dest.BillingPlanCode, src => src.WorkspaceBillings.OrderByDescending(x=>x.Created).FirstOrDefault().BillingPlan.Code);
+                });
+
+            Define<WorkspaceCDto, Workspace>();
+            Define<WorkspaceUDto, Workspace>();
+
+            DefineReverse<DocumentCDto, Metadocument>();
+
+            Define<Metadocument, DocumentRDto>();
+            Define<Metadocument, DocumentRDto>(builder =>
+            {
+                builder.Custom(dest => dest.Audit, src => new BoAuditDto()
+                {
+                    Created = src.Created,
+                    Changed = src.Modified,
+                    CreatedBy = src.CreatedByUser.Name,
+                    ChangedBy = src.ModifiedByUser != null ? src.ModifiedByUser.Name : string.Empty
+                });
+                builder.Custom(dest => dest.Artifacts, src => src.Artifacts.MapList<Artifact, ArtifactRDto>());
+                //calculate on demand  builder.Custom(dest => dest.BillingPlanCode, src => src.WorkspaceBillings.OrderByDescending(x=>x.Created).FirstOrDefault().BillingPlan.Code);
+            });
+            
+            //Define<ArtifactCDto, Artifact>();
+            Define<Artifact, ArtifactRDto>(builder =>
+            {
+                builder.Custom(dest => dest.Audit, src => new BoAuditDto()
+                {
+                    Created = src.Created,
+                    Changed = null,
+                    CreatedBy = src.CreatedByUser.Name,
+                    ChangedBy = string.Empty
+                });
+                //calculate on demand  builder.Custom(dest => dest.BillingPlanCode, src => src.WorkspaceBillings.OrderByDescending(x=>x.Created).FirstOrDefault().BillingPlan.Code);
+            });
+
+
+
+
+
+
+
+            Define<Structure, StructureRDto>(builder =>
+            {
+                builder.Custom(dest => dest.DocumentAllCount, src => src.Metadocuments.Count);
+                builder.Custom(dest => dest.DocumentPendingCount, src => src.Metadocuments.Count(x => x.Status == (int)DocumentStatusEnum.Pending));
+                builder.Custom(dest => dest.DocumentWorkingCount, src => src.Metadocuments.Count(x => x.Status == (int)DocumentStatusEnum.Processing));
+                builder.Custom(dest => dest.DocumentDoneCount, src => src.Metadocuments.Count(x => x.Status == (int)DocumentStatusEnum.DoneOk));
+                builder.Custom(dest => dest.DocumentFailCount, src => src.Metadocuments.Count(x => x.Status == (int)DocumentStatusEnum.DoneFail));
+                builder.Custom(dest => dest.Audit, src => new BoAuditDto()
+                {
+                    Created = src.Created,
+                    Changed = src.Modified,
+                    CreatedBy = src.CreatedByUser.Name,
+                    ChangedBy = src.ModifiedByUser != null ? src.ModifiedByUser.Name : string.Empty
+                });
+
+            });
+
+
+            Define<RegistrationCDto, Registration>();
+            Define<RegistrationUDto, Registration>();
+            Define<Registration, RegistrationRDto>(builder =>
+            {
+                builder.Custom(dest => dest.Audit, src => new BoAuditTimestampDto()
+                {
+                    Created = src.Created,
+                    Changed = null,
+                });
+            });
+            */
+
+        }
+        public void DefineBiDirection<TDao, TDto>() 
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            DefineBiDirection<TDao, TDto, long>();
+        }
+
+
+
+    }
+}

+ 75 - 0
Modules/qdr.app.qlbrc.base/Mapper/MapperLongExt.cs

@@ -0,0 +1,75 @@
+using Quadarax.Foundation.Core.Data.Interface.Entity;
+
+namespace Quadarax.Application.QLiberace.Base.Mapper
+{
+     public static class MapperLongExt
+    {
+        public static TDto? Map<TDao, TDto>(this TDao? dao)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dao == null) return null;
+            return MapperLong.Instance.Map<TDao, TDto>(dao);
+        }
+
+        public static TDao? MapDto<TDto, TDao>(this TDto? dto)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dto == null) return null;
+            return MapperLong.Instance.Map<TDto, TDao>(dto);
+        }
+
+        public static void CopyToDto<TDto, TDao>(this TDto? @from, TDao? to)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (@from == null) return;
+            if (to == null) return;
+            MapperLong.Instance.Update(@from,to);
+        }
+
+        public static void CopyToDto<TDto, TDao>(this TDao? @from, TDto? to)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (to == null) return;
+            if (@from == null) return;
+            MapperLong.Instance.Update(@from,to);
+        }
+
+
+        public static List<TDto> MapList<TDao, TDto>(this IQueryable<TDao>? dao)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dao == null) return new List<TDto>();
+            return MapperLong.Instance.MapList<TDao, TDto>(dao);
+        }
+
+        public static List<TDto> MapList<TDao, TDto>(this IEnumerable<TDao>? dao)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dao == null) return new List<TDto>();
+            return MapperLong.Instance.MapList<TDao, TDto>(dao);
+        }
+
+        public static List<TDto> MapList<TDao, TDto>(this IList<TDao>? dao)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dao == null) return new List<TDto>();
+            return MapperLong.Instance.MapList<TDao, TDto>(dao);
+        }
+
+        public static IQueryable<TDao> MapListDto<TDto,TDao>(this IEnumerable<TDto>? dto)
+            where TDao : class, IDao<long>, new()
+            where TDto : class, IDto, new()
+        {
+            if (dto == null) return new List<TDao>().AsQueryable();
+            return MapperLong.Instance.MapList<TDto, TDao>(dto.AsQueryable()).AsQueryable();
+        }
+
+    }
+}

+ 7 - 0
Modules/qdr.app.qlbrc.base/Repositories/RepoTenant.cs

@@ -1,4 +1,5 @@
 using Quadarax.Application.QLiberace.Base.Entities;
+using Quadarax.Foundation.Core.Data;
 using Quadarax.Foundation.Core.Data.Repository;
 using Quadarax.Foundation.Core.Data.Domain;
 using Quadarax.Foundation.Core.Data.Interface.Domain;
@@ -10,5 +11,11 @@ namespace Quadarax.Application.QLiberace.Base.Repositories
         public RepoTenant(IUnitOfWork<IDataDomain> unitOfWork) : base(unitOfWork)
         {
         }
+
+        public Tenant? GetByCode(string tenantCode)
+        {
+            if (string.IsNullOrEmpty(tenantCode)) throw new ArgumentNullException(nameof(tenantCode));
+            return Query(Paging.NoPaging()).FirstOrDefault(x => Equals(x.Code, tenantCode));
+        }
     }
 }

+ 132 - 0
Modules/qdr.app.qlbrc.base/Services/TenantService.cs

@@ -0,0 +1,132 @@
+using Quadarax.Application.QLiberace.Base.Repositories;
+using Quadarax.Foundation.Core.Business;
+using Microsoft.Extensions.Logging;
+using Quadarax.Application.QLiberace.Base.Dtos;
+using Quadarax.Application.QLiberace.Base.Entities;
+using Quadarax.Application.QLiberace.Base.Mapper;
+using Quadarax.Foundation.Core.Data.Interface.Domain;
+using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
+using Quadarax.Application.QLiberace.Common;
+
+namespace Quadarax.Application.QLiberace.Base.Services
+{
+    public class TenantService : AbstractRepositoryService<RepoTenant>
+    {
+        #region *** Constructor ***
+        public TenantService(RepoTenant repository, IContext currentContext, ILoggerFactory logger) : base(repository, currentContext, logger)
+        {
+        }
+        #endregion
+        #region *** Public Operations ***
+        public ResultBoolDto ExistsTenant(string tenantCode)
+        {
+            if (string.IsNullOrEmpty(tenantCode)) throw new ArgumentNullException(nameof(tenantCode));
+
+            return TryCatchBlock(() =>
+            {
+                var result = Repository.GetByCode(tenantCode);
+                if (result !=null) UpdateAccess(result);  
+                return new ResultBoolDto(result!=null);
+            });
+        }
+
+        public ResultDto Lock(string tenantCode, bool isLocked)
+        {
+            if (string.IsNullOrEmpty(tenantCode)) throw new ArgumentNullException(nameof(tenantCode));
+
+            return TryCatchBlock(() =>
+            {
+                var result = Repository.GetByCode(tenantCode);
+                if (result == null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantNotFoundCode, "code", tenantCode);
+                result.IsLocked = isLocked;
+                Repository.Set(result);
+                UpdateAccess(result);
+                return new ResultPlain();
+            });
+        }
+
+        public ResultValueDto<TenantRDto?> GetTenant(string tenantCode)
+        {
+            if (string.IsNullOrEmpty(tenantCode)) throw new ArgumentNullException(nameof(tenantCode));
+
+            return TryCatchBlock(() =>
+            {
+                var result = Repository.GetByCode(tenantCode);
+                if (result == null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantNotFoundCode, "code", tenantCode);
+
+                UpdateAccess(result);
+                return new ResultValueDto<TenantRDto?>(result.Map<Tenant, TenantRDto>());
+            });
+        }
+        public ResultValueDto<TenantRDto?> GetTenant(long tenantId)
+        {
+            return TryCatchBlock(() =>
+            {
+                var result = Repository.Get(tenantId);
+                if (result == null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantNotFoundCode, "id", tenantId.ToString());
+
+                UpdateAccess(result);
+                return new ResultValueDto<TenantRDto?>(result.Map<Tenant, TenantRDto>());
+            });
+        }
+
+        public ResultValueDto<TenantRDto?> CreateTenant(TenantWDto tenant)
+        {
+            if (tenant==null) throw new ArgumentNullException(nameof(tenant));
+
+            return TryCatchBlock(() =>
+            {
+                var exists = Repository.GetByCode(tenant.Code);
+                if (exists != null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantAlreadyExistsCode, tenant.Code);
+
+                var tenantDao = Repository.New();
+                tenant.CopyToDto(tenantDao);
+                return new ResultValueDto<TenantRDto?>(tenantDao.Map<Tenant, TenantRDto>());
+            });
+        }
+
+        public ResultDto UpdateTenant(TenantWDto tenant)
+        {
+            if (tenant==null) throw new ArgumentNullException(nameof(tenant));
+
+            return TryCatchBlock(() =>
+            {
+                var exists = Repository.GetByCode(tenant.Code);
+                if (exists == null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantNotFoundCode, "code", tenant.Code);
+
+                tenant.CopyToDto(exists);
+                Repository.Set(exists);
+                UpdateAccess(exists);
+                return new ResultPlain();
+            });
+        }
+
+        public ResultDto DeleteTenant(long tenantId)
+        {
+            return TryCatchBlock(() =>
+            {
+                var result = Repository.Get(tenantId);
+                if (result == null) throw 
+                    ExceptionFactory.CreateException(ExceptionLibrary.ExceptionsCodes.TenantNotFoundCode, "id", tenantId.ToString());
+
+                Repository.Remove(tenantId);
+                return new ResultPlain();
+            });
+        }
+        
+        #endregion
+        #region *** Private Operations ***
+
+        private void UpdateAccess(Tenant tenant)
+        {
+            tenant.LastAccess = DateTime.Now;
+            Repository.Set(tenant);
+        }
+        #endregion
+    }
+}

+ 1 - 0
Modules/qdr.app.qlbrc.base/qdr.app.qlbrc.base.csproj

@@ -20,6 +20,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Folder Include="Mapper\" />
     <Folder Include="Services\Result\" />
   </ItemGroup>
 

+ 101 - 0
Tests/qdr.app.qlbrc.base.Tests/Mapper/BaseMapperDtoTest.cs

@@ -0,0 +1,101 @@
+using qdr.fnd.core.test;
+using Quadarax.Application.QLiberace.Base.Dtos;
+using Quadarax.Application.QLiberace.Base.Entities;
+using Quadarax.Application.QLiberace.Base.Mapper;
+using Quadarax.Application.QLiberace.Common.Dtos;
+using Quadarax.Application.QLiberace.Common.Entities.Base;
+
+namespace Quadarax.Application.QLiberace.Base.Tests.Mapper
+{
+    [TestFixture(Category = "Mapper")]
+    public class BaseMapperDtoTest : BaseTest
+    {
+        #region *** Contants ***
+
+        public const string EntityName = "TestName";
+        public const string EntityCode = "TestCode";
+        public const string EntityDescription = "TestDescription";
+        public const string EntityModifier = "TestModifier";
+        public const long EntityId = -123;
+        #endregion
+
+        
+        [Test]
+        public void Tenant2TenantDto()
+        {
+            var dao = new Tenant();
+            dao.Code = EntityCode;
+            dao.Name = EntityName;
+            dao.LastAccess = DateTime.Now;
+            dao.IsLocked = true;
+            SetTrackedAttributes(dao);
+
+            // dao -> dto
+            var dtoR = dao.Map<Tenant, TenantRDto>();
+            Assert.IsNotNull(dtoR);
+            Assert.That(dao.Id,Is.EqualTo(dtoR.Id), $"Dao2Dto Mapping problem '{nameof(dtoR.Id)}'");
+            Assert.That(dao.Name, Is.EqualTo(dtoR.Name), $"Dao2Dto Mapping problem '{nameof(dtoR.Name)}'");
+            Assert.That(dao.LastAccess, Is.EqualTo(dtoR.LastAccess), $"Dao2Dto Mapping problem '{nameof(dtoR.LastAccess)}'");
+            Assert.That(dao.IsLocked, Is.EqualTo(dtoR.IsLocked), $"Dao2Dto Mapping problem '{nameof(dtoR.IsLocked)}'");
+
+
+            var dtoW = new TenantWDto();
+            dtoW.Code = EntityCode + "X";
+            dtoW.Name = EntityName + "X";
+            dtoW.LastAccess = DateTime.Now.AddHours(2);
+            dtoW.IsLocked = !dtoR.IsLocked;
+
+            // dto -> dao
+            dtoW.CopyToDto(dao);
+            Assert.IsNotNull(dao);
+            Assert.IsNull(dtoW.GetType().GetInterface(nameof(ITracked)), $"Write possible base for {dtoW.GetType().Name} must not be ITracked");
+            Assert.That(dtoW.Name, Is.EqualTo(dao?.Name), $"Dto2Dao Mapping problem '{nameof(dtoW.Name)}'");
+            Assert.That(dtoW.LastAccess, Is.EqualTo(dao?.LastAccess), $"Dto2Dao Mapping problem '{nameof(dtoW.LastAccess)}'");
+            Assert.That(dtoW.IsLocked, Is.EqualTo(dao?.IsLocked), $"Dto2Dao Mapping problem '{nameof(dtoW.IsLocked)}'");
+        }
+
+        [Test]
+        public void User2UserDto()
+        {
+            var dao = new User();
+            dao.LoginName = EntityCode;
+            dao.IsLocked = true;
+            SetTrackedAttributes(dao);
+
+            // dao -> dto
+            var dtoR = dao.Map<User, UserRDto>();
+            Assert.IsNotNull(dtoR);
+            Assert.That(dao.Id,Is.EqualTo(dtoR.Id), $"Dao2Dto Mapping problem '{nameof(dtoR.Id)}'");
+            Assert.That(dao.LoginName, Is.EqualTo(dtoR.LoginName), $"Dao2Dto Mapping problem '{nameof(dtoR.LoginName)}'");
+            Assert.That(dao.IsLocked, Is.EqualTo(dtoR.IsLocked), $"Dao2Dto Mapping problem '{nameof(dtoR.IsLocked)}'");
+
+            var dtoW = new UserWDto();
+            dtoW.LoginName = EntityCode + "X";
+            dtoW.IsLocked = !dtoR.IsLocked;
+
+            // dto -> dao
+            dtoW.CopyToDto(dao);
+            Assert.IsNotNull(dao);
+            Assert.IsNull(dtoW.GetType().GetInterface(nameof(ITracked)), $"Write possible base for {dtoW.GetType().Name} must not be ITracked");
+            Assert.That(dtoW.LoginName, Is.EqualTo(dao?.LoginName), $"Dto2Dao Mapping problem '{nameof(dtoW.LoginName)}'");
+            Assert.That(dtoW.IsLocked, Is.EqualTo(dao?.IsLocked), $"Dto2Dao Mapping problem '{nameof(dtoW.IsLocked)}'");
+        }
+
+        private void SetTrackedAttributes(QlbrcEntityTracked entity)
+        {
+            entity.Id = EntityId;
+            entity.Modified = DateTime.Now;
+            entity.Created = DateTime.Now.AddHours(1);
+            entity.Modifier = EntityModifier;
+        }
+
+
+        protected override void OnSetup()
+        {
+        }
+
+        protected override void OnTearDown()
+        {
+        }
+    }
+}