diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..9b68d7f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,39 @@
+image: mcr.microsoft.com/dotnet/sdk:9.0
+
+stages:
+ - build
+ - test
+ - publish
+
+before_script:
+ - echo "Setting up environment"
+ - 'dotnet --version'
+
+build:
+ stage: build
+ script:
+ - dotnet restore
+ - dotnet build --configuration Release --no-restore
+ artifacts:
+ paths:
+ - "**/bin/Release"
+ expire_in: 10 minutes
+
+test:
+ stage: test
+ script:
+ - dotnet test --verbosity normal
+ dependencies:
+ - build
+
+publish:
+ stage: publish
+ script:
+ - export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
+ - dotnet pack -c Release -o . /p:Version=$VERSION
+ - for nupkg in *.nupkg; do dotnet nuget push $nupkg -k ${NUGET_API_KEY} -s https://api.nuget.org/v3/index.json; done
+ only:
+ - tags
+ dependencies:
+ - build
+ - test
diff --git a/.idea/.idea.HopFrame/.idea/workspace.xml b/.idea/.idea.HopFrame/.idea/workspace.xml
index 0705f65..66cd2af 100644
--- a/.idea/.idea.HopFrame/.idea/workspace.xml
+++ b/.idea/.idea.HopFrame/.idea/workspace.xml
@@ -9,17 +9,25 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
+
+
+
@@ -39,7 +47,7 @@
@@ -67,18 +75,12 @@
+
-
-
-
-
-
-
-
-
+
{
@@ -97,9 +99,11 @@
".NET Launch Settings Profile.HopFrame.Testing.executor": "Run",
".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run",
".NET Project.HopFrame.Testing.executor": "Run",
+ "72b118b0-a6fc-4561-acdf-74f0b454dbb8.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
- "git-widget-placeholder": "!17 on feature/documentation",
+ "dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
+ "git-widget-placeholder": "!18 on feature/unit-tests",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
@@ -166,7 +170,8 @@
-
+
+
@@ -288,7 +293,15 @@
1737203441319
-
+
+
+ 1737208088933
+
+
+
+ 1737208088933
+
+
@@ -313,6 +326,7 @@
-
+
+
\ No newline at end of file
diff --git a/HopFrame.sln b/HopFrame.sln
index d622867..10bd3c7 100644
--- a/HopFrame.sln
+++ b/HopFrame.sln
@@ -10,6 +10,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{9EB7
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Testing", "testing\HopFrame.Testing\HopFrame.Testing.csproj", "{58490069-51DF-454C-8B54-7FB7D4BDFF81}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{141928CB-5977-4285-A986-5BD785F2883C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Core.Tests", "tests\HopFrame.Core.Tests\HopFrame.Core.Tests.csproj", "{2E2D29E0-53FA-462D-B4D2-4678CD106E29}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -19,6 +23,7 @@ Global
{4BFE21C2-EAAC-4662-8B97-500836651B2A} = {7E4AAFB3-9762-4F42-86DF-5A3194FDC243}
{8E59F398-184A-47C9-AAA2-3E0FFD775ABF} = {7E4AAFB3-9762-4F42-86DF-5A3194FDC243}
{58490069-51DF-454C-8B54-7FB7D4BDFF81} = {9EB7FDBD-49C2-4872-9666-6F7AEBA541B2}
+ {2E2D29E0-53FA-462D-B4D2-4678CD106E29} = {141928CB-5977-4285-A986-5BD785F2883C}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4BFE21C2-EAAC-4662-8B97-500836651B2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -33,5 +38,9 @@ Global
{58490069-51DF-454C-8B54-7FB7D4BDFF81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58490069-51DF-454C-8B54-7FB7D4BDFF81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58490069-51DF-454C-8B54-7FB7D4BDFF81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E2D29E0-53FA-462D-B4D2-4678CD106E29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E2D29E0-53FA-462D-B4D2-4678CD106E29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E2D29E0-53FA-462D-B4D2-4678CD106E29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E2D29E0-53FA-462D-B4D2-4678CD106E29}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/HopFrame.Core/Config/DbContextConfigurator.cs b/src/HopFrame.Core/Config/DbContextConfig.cs
similarity index 100%
rename from src/HopFrame.Core/Config/DbContextConfigurator.cs
rename to src/HopFrame.Core/Config/DbContextConfig.cs
diff --git a/src/HopFrame.Core/Config/HopFrameConfig.cs b/src/HopFrame.Core/Config/HopFrameConfig.cs
index 1bf8227..732d367 100644
--- a/src/HopFrame.Core/Config/HopFrameConfig.cs
+++ b/src/HopFrame.Core/Config/HopFrameConfig.cs
@@ -1,5 +1,4 @@
-using HopFrame.Core.Services;
-using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Config;
diff --git a/src/HopFrame.Core/Config/PropertyConfigurator.cs b/src/HopFrame.Core/Config/PropertyConfig.cs
similarity index 99%
rename from src/HopFrame.Core/Config/PropertyConfigurator.cs
rename to src/HopFrame.Core/Config/PropertyConfig.cs
index 9058c45..1860c8a 100644
--- a/src/HopFrame.Core/Config/PropertyConfigurator.cs
+++ b/src/HopFrame.Core/Config/PropertyConfig.cs
@@ -1,5 +1,4 @@
-using System.Collections;
-using System.Linq.Expressions;
+using System.Linq.Expressions;
using System.Reflection;
namespace HopFrame.Core.Config;
diff --git a/src/HopFrame.Core/Config/TableConfigurator.cs b/src/HopFrame.Core/Config/TableConfig.cs
similarity index 100%
rename from src/HopFrame.Core/Config/TableConfigurator.cs
rename to src/HopFrame.Core/Config/TableConfig.cs
diff --git a/src/HopFrame.Core/HopFrame.Core.csproj b/src/HopFrame.Core/HopFrame.Core.csproj
index ae907a6..d29affa 100644
--- a/src/HopFrame.Core/HopFrame.Core.csproj
+++ b/src/HopFrame.Core/HopFrame.Core.csproj
@@ -10,4 +10,10 @@
+
+
+ <_Parameter1>HopFrame.Core.Tests
+
+
+
diff --git a/tests/HopFrame.Core.Tests/Config/DbContextConfiguratorTests.cs b/tests/HopFrame.Core.Tests/Config/DbContextConfiguratorTests.cs
new file mode 100644
index 0000000..568ec13
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Config/DbContextConfiguratorTests.cs
@@ -0,0 +1,34 @@
+using HopFrame.Core.Config;
+using HopFrame.Core.Tests.Models;
+using Moq;
+
+namespace HopFrame.Core.Tests.Config;
+
+public class DbContextConfiguratorTests {
+ [Fact]
+ public void Table_WithConfigurator_InvokesConfigurator() {
+ // Arrange
+ var dbContextConfig = new DbContextConfig(typeof(MockDbContext));
+ var configurator = new DbContextConfigurator(dbContextConfig);
+ var mockConfigurator = new Mock>>();
+
+ // Act
+ configurator.Table(mockConfigurator.Object);
+
+ // Assert
+ mockConfigurator.Verify(c => c.Invoke(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public void Table_ReturnsCorrectTableConfigurator() {
+ // Arrange
+ var dbContextConfig = new DbContextConfig(typeof(MockDbContext));
+ var configurator = new DbContextConfigurator(dbContextConfig);
+
+ // Act
+ var tableConfigurator = configurator.Table();
+
+ // Assert
+ Assert.IsType>(tableConfigurator);
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs b/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs
new file mode 100644
index 0000000..d41a9f0
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs
@@ -0,0 +1,62 @@
+using HopFrame.Core.Config;
+using HopFrame.Core.Tests.Models;
+
+namespace HopFrame.Core.Tests.Config;
+
+public class HopFrameConfiguratorTests {
+ [Fact]
+ public void AddDbContext_AddsDbContextToInnerConfig() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var configurator = new HopFrameConfigurator(config);
+
+ // Act
+ var dbContextConfigurator = configurator.AddDbContext();
+
+ // Assert
+ Assert.Single(config.Contexts);
+ Assert.IsType(config.Contexts[0]);
+ Assert.IsType>(dbContextConfigurator);
+ }
+
+ [Fact]
+ public void DisplayUserInfo_SetsDisplayUserInfoProperty() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var configurator = new HopFrameConfigurator(config);
+
+ // Act
+ configurator.DisplayUserInfo(false);
+
+ // Assert
+ Assert.False(config.DisplayUserInfo);
+ }
+
+ [Fact]
+ public void SetBasePolicy_SetsBasePolicyProperty() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var configurator = new HopFrameConfigurator(config);
+ var basePolicy = "Admin";
+
+ // Act
+ configurator.SetBasePolicy(basePolicy);
+
+ // Assert
+ Assert.Equal(basePolicy, config.BasePolicy);
+ }
+
+ [Fact]
+ public void SetLoginPage_SetsLoginPageRewriteProperty() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var configurator = new HopFrameConfigurator(config);
+ var loginPageUrl = "/login";
+
+ // Act
+ configurator.SetLoginPage(loginPageUrl);
+
+ // Assert
+ Assert.Equal(loginPageUrl, config.LoginPageRewrite);
+ }
+}
diff --git a/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs b/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs
new file mode 100644
index 0000000..c0a8188
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs
@@ -0,0 +1,212 @@
+using System.Linq.Expressions;
+using HopFrame.Core.Config;
+using HopFrame.Core.Tests.Models;
+
+namespace HopFrame.Core.Tests.Config;
+
+public class PropertyConfiguratorTests {
+ [Fact]
+ public void SetDisplayName_SetsNameProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ var displayName = "ID";
+
+ // Act
+ configurator.SetDisplayName(displayName);
+
+ // Assert
+ Assert.Equal(displayName, propertyConfig.Name);
+ }
+
+ [Fact]
+ public void List_SetsListAndSearchableProperties() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.List(true);
+
+ // Assert
+ Assert.True(propertyConfig.List);
+ Assert.False(propertyConfig.Searchable);
+ }
+
+ [Fact]
+ public void IsSortable_SetsSortableProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.IsSortable(true);
+
+ // Assert
+ Assert.True(propertyConfig.Sortable);
+ }
+
+ [Fact]
+ public void IsSearchable_SetsSearchableProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.IsSearchable(true);
+
+ // Assert
+ Assert.True(propertyConfig.Searchable);
+ }
+
+ [Fact]
+ public void SetDisplayedProperty_SetsDisplayedProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ Expression> propertyExpression = model => model.Id;
+
+ // Act
+ configurator.SetDisplayedProperty(propertyExpression);
+
+ // Assert
+ Assert.NotNull(propertyConfig.DisplayedProperty);
+ Assert.Equal("Id", propertyConfig.DisplayedProperty?.Name);
+ }
+
+ [Fact]
+ public void Format_SetsFormatter() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ Func formatter = (val, _) => val.ToString();
+
+ // Act
+ configurator.Format(formatter);
+
+ // Assert
+ Assert.NotNull(propertyConfig.Formatter);
+ }
+
+ [Fact]
+ public void SetParser_SetsParser() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ Func parser = (str, _) => int.Parse(str);
+
+ // Act
+ configurator.SetParser(parser);
+
+ // Assert
+ Assert.NotNull(propertyConfig.Parser);
+ }
+
+ [Fact]
+ public void SetEditable_SetsEditableProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.SetEditable(true);
+
+ // Assert
+ Assert.True(propertyConfig.Editable);
+ }
+
+ [Fact]
+ public void SetCreatable_SetsCreatableProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.SetCreatable(true);
+
+ // Assert
+ Assert.True(propertyConfig.Creatable);
+ }
+
+ [Fact]
+ public void DisplayValue_SetsDisplayValueProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.DisplayValue(true);
+
+ // Assert
+ Assert.True(propertyConfig.DisplayValue);
+ }
+
+ [Fact]
+ public void IsTextArea_SetsTextAreaProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+
+ // Act
+ configurator.IsTextArea(true);
+
+ // Assert
+ Assert.True(propertyConfig.TextArea);
+ }
+
+ [Fact]
+ public void SetTextAreaRows_SetsTextAreaRowsProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ var rows = 10;
+
+ // Act
+ configurator.SetTextAreaRows(rows);
+
+ // Assert
+ Assert.Equal(rows, propertyConfig.TextAreaRows);
+ }
+
+ [Fact]
+ public void SetValidator_SetsValidator() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ Func> validator = (_, _) => new List();
+
+ // Act
+ configurator.SetValidator(validator);
+
+ // Assert
+ Assert.NotNull(propertyConfig.Validator);
+ }
+
+ [Fact]
+ public void SetOrderIndex_SetsOrderProperty() {
+ // Arrange
+ var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!,
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0), 0);
+ var configurator = new PropertyConfigurator(propertyConfig);
+ var orderIndex = 1;
+
+ // Act
+ configurator.SetOrderIndex(orderIndex);
+
+ // Assert
+ Assert.Equal(orderIndex, propertyConfig.Order);
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs b/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs
new file mode 100644
index 0000000..8a27f4d
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs
@@ -0,0 +1,151 @@
+using System.Linq.Expressions;
+using HopFrame.Core.Config;
+using HopFrame.Core.Tests.Models;
+
+namespace HopFrame.Core.Tests.Config;
+
+public class TableConfiguratorTests {
+ [Fact]
+ public void Ignore_SetsIgnoredProperty() {
+ // Arrange
+ var tableConfig =
+ new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+
+ // Act
+ configurator.Ignore(true);
+
+ // Assert
+ Assert.True(tableConfig.Ignored);
+ }
+
+ [Fact]
+ public void Property_ReturnsCorrectPropertyConfigurator() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ Expression> propertyExpression = model => model.Id;
+
+ // Act
+ var propertyConfigurator = configurator.Property(propertyExpression);
+
+ // Assert
+ Assert.IsType>(propertyConfigurator);
+ }
+
+ [Fact]
+ public void AddVirtualProperty_AddsVirtualPropertyToConfig() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ Func template = (model, _) => model.Name!;
+
+ // Act
+ var propertyConfigurator = configurator.AddVirtualProperty("VirtualName", template);
+
+ // Assert
+ var virtualProperty = tableConfig.Properties.SingleOrDefault(p => p.Name == "VirtualName");
+ Assert.NotNull(virtualProperty);
+ Assert.NotNull(propertyConfigurator);
+ Assert.True(virtualProperty.IsListingProperty);
+ Assert.Equal("VirtualName", virtualProperty.Name);
+ }
+
+ [Fact]
+ public void SetDisplayName_SetsDisplayNameProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var displayName = "Mock Model Display Name";
+
+ // Act
+ configurator.SetDisplayName(displayName);
+
+ // Assert
+ Assert.Equal(displayName, tableConfig.DisplayName);
+ }
+
+ [Fact]
+ public void SetDescription_SetsDescriptionProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var description = "Mock Model Description";
+
+ // Act
+ configurator.SetDescription(description);
+
+ // Assert
+ Assert.Equal(description, tableConfig.Description);
+ }
+
+ [Fact]
+ public void SetOrderIndex_SetsOrderIndexProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var orderIndex = 1;
+
+ // Act
+ configurator.SetOrderIndex(orderIndex);
+
+ // Assert
+ Assert.Equal(orderIndex, tableConfig.Order);
+ }
+
+ [Fact]
+ public void SetViewPolicy_SetsViewPolicyProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var policy = "ViewPolicy";
+
+ // Act
+ configurator.SetViewPolicy(policy);
+
+ // Assert
+ Assert.Equal(policy, tableConfig.ViewPolicy);
+ }
+
+ [Fact]
+ public void SetUpdatePolicy_SetsUpdatePolicyProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var policy = "UpdatePolicy";
+
+ // Act
+ configurator.SetUpdatePolicy(policy);
+
+ // Assert
+ Assert.Equal(policy, tableConfig.UpdatePolicy);
+ }
+
+ [Fact]
+ public void SetCreatePolicy_SetsCreatePolicyProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var policy = "CreatePolicy";
+
+ // Act
+ configurator.SetCreatePolicy(policy);
+
+ // Assert
+ Assert.Equal(policy, tableConfig.CreatePolicy);
+ }
+
+ [Fact]
+ public void SetDeletePolicy_SetsDeletePolicyProperty() {
+ // Arrange
+ var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
+ var configurator = new TableConfigurator(tableConfig);
+ var policy = "DeletePolicy";
+
+ // Act
+ configurator.SetDeletePolicy(policy);
+
+ // Assert
+ Assert.Equal(policy, tableConfig.DeletePolicy);
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/HopFrame.Core.Tests.csproj b/tests/HopFrame.Core.Tests/HopFrame.Core.Tests.csproj
new file mode 100644
index 0000000..023d3aa
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/HopFrame.Core.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/HopFrame.Core.Tests/Models/MockDbContext.cs b/tests/HopFrame.Core.Tests/Models/MockDbContext.cs
new file mode 100644
index 0000000..4666454
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Models/MockDbContext.cs
@@ -0,0 +1,13 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace HopFrame.Core.Tests.Models;
+
+// A mock DbContext for testing purposes
+public class MockDbContext : DbContext {
+ public DbSet Models { get; set; }
+ public DbSet Models2 { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
+ optionsBuilder.UseInMemoryDatabase(nameof(MockDbContext));
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Models/MockModel.cs b/tests/HopFrame.Core.Tests/Models/MockModel.cs
new file mode 100644
index 0000000..c6153b7
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Models/MockModel.cs
@@ -0,0 +1,7 @@
+namespace HopFrame.Core.Tests.Models;
+
+// A mock model for testing purposes
+public class MockModel {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Models/MockModel2.cs b/tests/HopFrame.Core.Tests/Models/MockModel2.cs
new file mode 100644
index 0000000..ba3b540
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Models/MockModel2.cs
@@ -0,0 +1,5 @@
+namespace HopFrame.Core.Tests.Models;
+
+public class MockModel2 {
+ public string Id { get; set; }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Models/QueryProvider.cs b/tests/HopFrame.Core.Tests/Models/QueryProvider.cs
new file mode 100644
index 0000000..65b0127
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Models/QueryProvider.cs
@@ -0,0 +1,129 @@
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+
+namespace HopFrame.Core.Tests.Models;
+
+// A mock implementation for async query provider
+internal class TestAsyncQueryProvider : IAsyncQueryProvider {
+ private readonly IQueryProvider _inner;
+
+ internal TestAsyncQueryProvider(IQueryProvider inner) {
+ _inner = inner;
+ }
+
+ public IQueryable CreateQuery(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+
+ public IQueryable CreateQuery(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+
+ public object Execute(Expression expression) {
+ return _inner.Execute(expression);
+ }
+
+ public TResult Execute(Expression expression) {
+ return _inner.Execute(expression);
+ }
+
+ public TResult ExecuteAsync(Expression expression,
+ CancellationToken cancellationToken = new CancellationToken()) {
+ return _inner.Execute(expression);
+ }
+
+ public IAsyncEnumerable ExecuteAsync(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+}
+
+internal class TestAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable {
+ public TestAsyncEnumerable(IEnumerable enumerable) : base(enumerable) { }
+
+ public TestAsyncEnumerable(Expression expression) : base(expression) { }
+
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) {
+ return new TestAsyncEnumerator(this.AsEnumerable().GetEnumerator());
+ }
+
+ IQueryProvider IQueryable.Provider => new TestAsyncQueryProvider(this);
+}
+
+internal class TestAsyncEnumerator : IAsyncEnumerator {
+ private readonly IEnumerator _inner;
+
+ public TestAsyncEnumerator(IEnumerator inner) {
+ _inner = inner ?? throw new ArgumentNullException(nameof(inner));
+ }
+
+ public T Current => _inner.Current;
+
+ public ValueTask DisposeAsync() {
+ _inner.Dispose();
+ return new ValueTask();
+ }
+
+ public ValueTask MoveNextAsync() {
+ return new ValueTask(_inner.MoveNext());
+ }
+}
+
+/*// A mock implementation for async query provider
+internal class TestAsyncQueryProvider : IAsyncQueryProvider {
+ private readonly IQueryProvider _inner;
+
+ internal TestAsyncQueryProvider(IQueryProvider inner) {
+ _inner = inner;
+ }
+
+ public IQueryable CreateQuery(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+
+ public IQueryable CreateQuery(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+
+ public object? Execute(Expression expression) {
+ return _inner.Execute(expression);
+ }
+
+ public TResult Execute(Expression expression) {
+ return _inner.Execute(expression);
+ }
+
+ public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken) {
+ return _inner.Execute(expression);
+ }
+
+ public IAsyncEnumerable ExecuteAsync(Expression expression) {
+ return new TestAsyncEnumerable(expression);
+ }
+}
+
+internal class TestAsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable {
+ public TestAsyncEnumerable(IEnumerable enumerable) : base(enumerable) { }
+
+ public TestAsyncEnumerable(Expression expression) : base(expression) { }
+
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) {
+ return new TestAsyncEnumerator(this.AsEnumerable().GetEnumerator());
+ }
+
+ IQueryProvider IQueryable.Provider => new TestAsyncQueryProvider(this);
+}
+
+internal class TestAsyncEnumerator(IEnumerator inner) : IAsyncEnumerator {
+ private readonly IEnumerator _inner = inner ?? throw new ArgumentNullException(nameof(inner));
+
+ public T Current => _inner.Current;
+
+ public ValueTask DisposeAsync() {
+ _inner.Dispose();
+ return new ValueTask();
+ }
+
+ public ValueTask MoveNextAsync() {
+ return new ValueTask(_inner.MoveNext());
+ }
+}*/
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.cs b/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.cs
new file mode 100644
index 0000000..1f368e4
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.cs
@@ -0,0 +1,132 @@
+using HopFrame.Core.Config;
+using HopFrame.Core.Services.Implementations;
+using HopFrame.Core.Tests.Models;
+using Microsoft.Extensions.Logging;
+using Moq;
+
+namespace HopFrame.Core.Tests.Services;
+
+public class ContextExplorerTests {
+ [Fact]
+ public void GetTables_ReturnsNonIgnoredTables() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig1 = contextConfig.Tables[0];
+ var tableConfig2 = contextConfig.Tables[1];
+ config.Contexts.Add(contextConfig);
+ tableConfig2.Ignored = true;
+
+ var provider = new Mock();
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ var tables = contextExplorer.GetTables().ToList();
+
+ // Assert
+ Assert.Single(tables);
+ Assert.Contains(tableConfig1, tables);
+ Assert.DoesNotContain(tableConfig2, tables);
+ }
+
+ [Fact]
+ public void GetTable_ByDisplayName_ReturnsCorrectTable() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig = contextConfig.Tables[0];
+ config.Contexts.Add(contextConfig);
+ tableConfig.DisplayName = "TestTable";
+
+ var provider = new Mock();
+ provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ var result = contextExplorer.GetTable("TestTable");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(tableConfig, result);
+ }
+
+ [Fact]
+ public void GetTable_ByType_ReturnsCorrectTable() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig = contextConfig.Tables[0];
+ config.Contexts.Add(contextConfig);
+
+ var provider = new Mock();
+ provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ var result = contextExplorer.GetTable(typeof(MockModel));
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(tableConfig, result);
+ }
+
+ [Fact]
+ public void GetTableManager_ReturnsCorrectTableManager() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig = new TableConfig(contextConfig, typeof(MockModel), "Models", 0);
+ contextConfig.Tables.Add(tableConfig);
+ config.Contexts.Add(contextConfig);
+
+ var dbContext = new MockDbContext();
+ var provider = new Mock();
+ provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(dbContext);
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ var tableManager = contextExplorer.GetTableManager("Models");
+
+ // Assert
+ Assert.NotNull(tableManager);
+ Assert.IsType>(tableManager);
+ }
+
+ [Fact]
+ public void GetTableManager_ReturnsNullIfDbContextNotFound() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig = new TableConfig(contextConfig, typeof(MockModel), "MockModels", 0);
+ contextConfig.Tables.Add(tableConfig);
+ config.Contexts.Add(contextConfig);
+
+ var provider = new Mock();
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ var tableManager = contextExplorer.GetTableManager("Models");
+
+ // Assert
+ Assert.Null(tableManager);
+ }
+
+ [Fact]
+ public void SeedTableData_SetsTableSeededFlag() {
+ // Arrange
+ var config = new HopFrameConfig();
+ var contextConfig = new DbContextConfig(typeof(MockDbContext));
+ var tableConfig = contextConfig.Tables[0];
+ config.Contexts.Add(contextConfig);
+
+ var provider = new Mock();
+ provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
+ var contextExplorer = new ContextExplorer(config, provider.Object, new Logger(new LoggerFactory()));
+
+ // Act
+ contextExplorer.GetTable("Models");
+
+ // Assert
+ Assert.True(tableConfig.Seeded);
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Services/DefaultAuthHandlerTests.cs b/tests/HopFrame.Core.Tests/Services/DefaultAuthHandlerTests.cs
new file mode 100644
index 0000000..f93d58b
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Services/DefaultAuthHandlerTests.cs
@@ -0,0 +1,41 @@
+using HopFrame.Core.Services.Implementations;
+
+namespace HopFrame.Core.Tests.Services;
+
+public class DefaultAuthHandlerTests {
+ [Fact]
+ public async Task IsAuthenticatedAsync_ReturnsTrue() {
+ // Arrange
+ var authHandler = new DefaultAuthHandler();
+
+ // Act
+ var result = await authHandler.IsAuthenticatedAsync(null);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public async Task IsAuthenticatedAsync_WithPolicy_ReturnsTrue() {
+ // Arrange
+ var authHandler = new DefaultAuthHandler();
+
+ // Act
+ var result = await authHandler.IsAuthenticatedAsync("TestPolicy");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public async Task GetCurrentUserDisplayNameAsync_ReturnsEmptyString() {
+ // Arrange
+ var authHandler = new DefaultAuthHandler();
+
+ // Act
+ var result = await authHandler.GetCurrentUserDisplayNameAsync();
+
+ // Assert
+ Assert.Equal(string.Empty, result);
+ }
+}
\ No newline at end of file
diff --git a/tests/HopFrame.Core.Tests/Services/TableManagerTests.cs b/tests/HopFrame.Core.Tests/Services/TableManagerTests.cs
new file mode 100644
index 0000000..e2d85bb
--- /dev/null
+++ b/tests/HopFrame.Core.Tests/Services/TableManagerTests.cs
@@ -0,0 +1,192 @@
+// ReSharper disable GenericEnumeratorNotDisposed
+
+using HopFrame.Core.Config;
+using HopFrame.Core.Services;
+using HopFrame.Core.Services.Implementations;
+using HopFrame.Core.Tests.Models;
+using Microsoft.EntityFrameworkCore;
+using Moq;
+
+namespace HopFrame.Core.Tests.Services;
+
+public class TableManagerTests {
+ private Mock CreateMockDbContext(List data) where TModel : class {
+ var dbContext = new Mock();
+ var dbSet = CreateMockDbSet(data);
+
+ dbContext.Setup(m => m.Set()).Returns(dbSet.Object);
+ dbContext.Setup(m => m.Entry(It.IsAny())).Returns(entry => new MockDbContext().Entry(entry));
+ return dbContext;
+ }
+
+ private Mock> CreateMockDbSet(List data) where TModel : class {
+ var queryableData = data.AsQueryable();
+ var dbSet = new Mock>();
+
+ dbSet.As>().Setup(m => m.Provider)
+ .Returns(new TestAsyncQueryProvider(queryableData.Provider));
+ dbSet.As>().Setup(m => m.Expression).Returns(queryableData.Expression);
+ dbSet.As>().Setup(m => m.ElementType).Returns(queryableData.ElementType);
+ dbSet.As>().Setup(m => m.GetEnumerator()).Returns(queryableData.GetEnumerator());
+
+ dbSet.As>().Setup(m => m.GetAsyncEnumerator(It.IsAny()))
+ .Returns(new TestAsyncEnumerator(queryableData.GetEnumerator()));
+ dbSet.As>().Setup(m => m.Provider)
+ .Returns(new TestAsyncQueryProvider(queryableData.Provider));
+ dbSet.Setup(m => m.FindAsync(It.IsAny