From f9aa02ec1a4c24e80a206857d4f68198bb027bb4 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Mon, 11 Nov 2024 01:12:29 +0800
Subject: HALF WORK: 2024.12.19

Re-organize file structure.
---
 .../Crud/ColumnTypeInfoTest.cs                     |  39 ++++
 .../Crud/CrudIntegratedTest.cs                     | 200 +++++++++++++++++++++
 .../Crud/CrudServiceTest.cs                        |  77 ++++++++
 .../Crud/SqlCompareHelper.cs                       |  85 +++++++++
 .../CrupestApi.Commons.Tests/Crud/TableInfoTest.cs |  35 ++++
 .../CrupestApi.Commons.Tests/Crud/TestEntity.cs    |  23 +++
 6 files changed, 459 insertions(+)
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/ColumnTypeInfoTest.cs
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/SqlCompareHelper.cs
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs
 create mode 100644 dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs

(limited to 'dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud')

diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/ColumnTypeInfoTest.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/ColumnTypeInfoTest.cs
new file mode 100644
index 0000000..b9ec03e
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/ColumnTypeInfoTest.cs
@@ -0,0 +1,39 @@
+using System.Data;
+
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class ColumnTypeInfoTest
+{
+    private ColumnTypeProvider _provider = new ColumnTypeProvider();
+
+    [Theory]
+    [InlineData(typeof(int), DbType.Int32, 123)]
+    [InlineData(typeof(long), DbType.Int64, 456)]
+    [InlineData(typeof(sbyte), DbType.SByte, 789)]
+    [InlineData(typeof(short), DbType.Int16, 101)]
+    [InlineData(typeof(float), DbType.Single, 1.0f)]
+    [InlineData(typeof(double), DbType.Double, 1.0)]
+    [InlineData(typeof(string), DbType.String, "Hello world!")]
+    [InlineData(typeof(byte[]), DbType.Binary, new byte[] { 1, 2, 3 })]
+    public void BasicColumnTypeTest(Type type, DbType dbType, object? value)
+    {
+        var typeInfo = _provider.Get(type);
+        Assert.True(typeInfo.IsSimple);
+        Assert.Equal(dbType, typeInfo.DbType);
+        Assert.Equal(value, typeInfo.ConvertFromDatabase(value));
+        Assert.Equal(value, typeInfo.ConvertToDatabase(value));
+    }
+
+    [Fact]
+    public void DateTimeColumnTypeTest()
+    {
+        var dateTimeColumnTypeInfo = _provider.Get(typeof(DateTime));
+        Assert.Equal(typeof(DateTime), dateTimeColumnTypeInfo.ClrType);
+        Assert.Equal(typeof(string), dateTimeColumnTypeInfo.DatabaseClrType);
+
+        var dateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+        var dateTimeString = "2000-01-01T00:00:00Z";
+        Assert.Equal(dateTimeString, dateTimeColumnTypeInfo.ConvertToDatabase(dateTime));
+        Assert.Equal(dateTime, dateTimeColumnTypeInfo.ConvertFromDatabase(dateTimeString));
+    }
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
new file mode 100644
index 0000000..bd07c70
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudIntegratedTest.cs
@@ -0,0 +1,200 @@
+using System.Net;
+using System.Net.Http.Headers;
+using CrupestApi.Commons.Secrets;
+using Microsoft.AspNetCore.TestHost;
+
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class CrudIntegratedTest : IAsyncLifetime
+{
+    private readonly WebApplication _app;
+    private HttpClient _httpClient = default!;
+    private HttpClient _authorizedHttpClient = default!;
+    private string _token = default!;
+
+    public CrudIntegratedTest()
+    {
+        var builder = WebApplication.CreateBuilder();
+        builder.Logging.ClearProviders();
+        builder.Services.AddSingleton<IDbConnectionFactory, SqliteMemoryConnectionFactory>();
+        builder.Services.AddCrud<TestEntity>();
+        builder.WebHost.UseTestServer();
+        _app = builder.Build();
+        _app.UseCrudCore();
+        _app.MapCrud<TestEntity>("/test", "test-perm");
+    }
+
+    public async Task InitializeAsync()
+    {
+        await _app.StartAsync();
+        _httpClient = _app.GetTestClient();
+
+        using (var scope = _app.Services.CreateScope())
+        {
+            var secretService = (SecretService)scope.ServiceProvider.GetRequiredService<ISecretService>();
+            var key = secretService.Create(new SecretInfo
+            {
+                Key = "test-perm"
+            });
+            _token = secretService.GetByKey(key).Secret;
+        }
+
+        _authorizedHttpClient = _app.GetTestClient();
+        _authorizedHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
+    }
+
+    public async Task DisposeAsync()
+    {
+        await _app.StopAsync();
+    }
+
+
+    [Fact]
+    public async Task EmptyTest()
+    {
+        using var response = await _authorizedHttpClient.GetAsync("/test");
+        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+        var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
+        Assert.NotNull(body);
+        Assert.Empty(body);
+    }
+
+    [Fact]
+    public async Task CrudTest()
+    {
+        {
+            using var response = await _authorizedHttpClient.PostAsJsonAsync("/test", new TestEntity
+            {
+                Name = "test",
+                Age = 22
+            });
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+            Assert.NotNull(body);
+            Assert.Equal("test", body.Name);
+            Assert.Equal(22, body.Age);
+            Assert.Null(body.Height);
+            Assert.NotEmpty(body.Secret);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.GetAsync("/test");
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
+            Assert.NotNull(body);
+            var entity = Assert.Single(body);
+            Assert.Equal("test", entity.Name);
+            Assert.Equal(22, entity.Age);
+            Assert.Null(entity.Height);
+            Assert.NotEmpty(entity.Secret);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.GetAsync("/test/test");
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+            Assert.NotNull(body);
+            Assert.Equal("test", body.Name);
+            Assert.Equal(22, body.Age);
+            Assert.Null(body.Height);
+            Assert.NotEmpty(body.Secret);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.PatchAsJsonAsync("/test/test", new TestEntity
+            {
+                Name = "test-2",
+                Age = 23,
+                Height = 188.0f
+            });
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+            Assert.NotNull(body);
+            Assert.Equal("test-2", body.Name);
+            Assert.Equal(23, body.Age);
+            Assert.Equal(188.0f, body.Height);
+            Assert.NotEmpty(body.Secret);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.GetAsync("/test/test-2");
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<TestEntity>();
+            Assert.NotNull(body);
+            Assert.Equal("test-2", body.Name);
+            Assert.Equal(23, body.Age);
+            Assert.Equal(188.0f, body.Height);
+            Assert.NotEmpty(body.Secret);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.DeleteAsync("/test/test-2");
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.GetAsync("/test");
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+            var body = await response.Content.ReadFromJsonAsync<List<TestEntity>>();
+            Assert.NotNull(body);
+            Assert.Empty(body);
+        }
+    }
+
+    [Fact]
+    public async Task UnauthorizedTest()
+    {
+        {
+            using var response = await _httpClient.GetAsync("/test");
+            Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+        }
+
+        {
+            using var response = await _httpClient.GetAsync("/test/test");
+            Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+        }
+
+        {
+            using var response = await _httpClient.PostAsJsonAsync("/test", new TestEntity
+            {
+                Name = "test",
+                Age = 22
+            });
+            Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+        }
+
+        {
+            using var response = await _httpClient.PatchAsJsonAsync("/test/test", new TestEntity
+            {
+                Name = "test-2",
+                Age = 23,
+                Height = 188.0f
+            });
+            Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+        }
+
+        {
+            using var response = await _httpClient.DeleteAsync("/test/test");
+            Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+        }
+    }
+
+    [Fact]
+    public async Task NotFoundTest()
+    {
+        {
+            using var response = await _authorizedHttpClient.GetAsync("/test/test");
+            Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+        }
+
+        {
+            using var response = await _authorizedHttpClient.PatchAsJsonAsync("/test/test", new TestEntity
+            {
+                Name = "test-2",
+                Age = 23,
+                Height = 188.0f
+            });
+            Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+        }
+    }
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs
new file mode 100644
index 0000000..ad0d34c
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/CrudServiceTest.cs
@@ -0,0 +1,77 @@
+using CrupestApi.Commons.Crud.Migrations;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class CrudServiceTest
+{
+    private readonly SqliteMemoryConnectionFactory _memoryConnectionFactory = new SqliteMemoryConnectionFactory();
+
+    private readonly CrudService<TestEntity> _crudService;
+
+    public CrudServiceTest()
+    {
+        var columnTypeProvider = new ColumnTypeProvider();
+        var tableInfoFactory = new TableInfoFactory(columnTypeProvider, NullLoggerFactory.Instance);
+        var dbConnectionFactory = new SqliteMemoryConnectionFactory();
+
+        _crudService = new CrudService<TestEntity>(
+            tableInfoFactory, dbConnectionFactory, new SqliteDatabaseMigrator(), NullLoggerFactory.Instance);
+    }
+
+    [Fact]
+    public void CrudTest()
+    {
+        var key = _crudService.Create(new TestEntity()
+        {
+            Name = "crupest",
+            Age = 18,
+        });
+
+        Assert.Equal("crupest", key);
+
+        var entity = _crudService.GetByKey(key);
+        Assert.Equal("crupest", entity.Name);
+        Assert.Equal(18, entity.Age);
+        Assert.Null(entity.Height);
+        Assert.NotEmpty(entity.Secret);
+
+        var list = _crudService.GetAll();
+        entity = Assert.Single(list);
+        Assert.Equal("crupest", entity.Name);
+        Assert.Equal(18, entity.Age);
+        Assert.Null(entity.Height);
+        Assert.NotEmpty(entity.Secret);
+
+        var count = _crudService.GetCount();
+        Assert.Equal(1, count);
+
+        _crudService.UpdateByKey(key, new TestEntity()
+        {
+            Name = "crupest2.0",
+            Age = 22,
+            Height = 180,
+        });
+
+        entity = _crudService.GetByKey("crupest2.0");
+        Assert.Equal("crupest2.0", entity.Name);
+        Assert.Equal(22, entity.Age);
+        Assert.Equal(180, entity.Height);
+        Assert.NotEmpty(entity.Secret);
+
+        _crudService.DeleteByKey("crupest2.0");
+
+        count = _crudService.GetCount();
+        Assert.Equal(0, count);
+    }
+
+    [Fact]
+    public void EntityNotExistTest()
+    {
+        Assert.Throws<EntityNotExistException>(() => _crudService.GetByKey("KeyNotExist"));
+        Assert.Throws<EntityNotExistException>(() => _crudService.UpdateByKey("KeyNotExist", new TestEntity
+        {
+            Name = "crupest"
+        }));
+    }
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/SqlCompareHelper.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/SqlCompareHelper.cs
new file mode 100644
index 0000000..72b6218
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/SqlCompareHelper.cs
@@ -0,0 +1,85 @@
+using System.Text;
+
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class SqlCompareHelper
+{
+    private static List<char> SymbolTokens = new List<char>() { '(', ')', ';' };
+
+    public static List<string> SqlExtractWords(string? sql, bool toLower = true)
+    {
+        var result = new List<string>();
+
+        if (string.IsNullOrEmpty(sql))
+        {
+            return result;
+        }
+
+        var current = 0;
+
+        StringBuilder? wordBuilder = null;
+
+        while (current < sql.Length)
+        {
+            if (char.IsWhiteSpace(sql[current]))
+            {
+                if (wordBuilder is not null)
+                {
+                    result.Add(wordBuilder.ToString());
+                    wordBuilder = null;
+                }
+            }
+            else if (SymbolTokens.Contains(sql[current]))
+            {
+                if (wordBuilder is not null)
+                {
+                    result.Add(wordBuilder.ToString());
+                    wordBuilder = null;
+                }
+                result.Add(sql[current].ToString());
+            }
+            else
+            {
+                if (wordBuilder is not null)
+                {
+                    wordBuilder.Append(sql[current]);
+                }
+                else
+                {
+                    wordBuilder = new StringBuilder();
+                    wordBuilder.Append(sql[current]);
+                }
+            }
+            current++;
+        }
+
+        if (wordBuilder is not null)
+        {
+            result.Add(wordBuilder.ToString());
+        }
+
+        if (toLower)
+        {
+            for (int i = 0; i < result.Count; i++)
+            {
+                result[i] = result[i].ToLower();
+            }
+        }
+
+        return result;
+    }
+
+    public static bool SqlEqual(string left, string right)
+    {
+        return SqlExtractWords(left) == SqlExtractWords(right);
+    }
+
+    [Fact]
+    public void TestSqlExtractWords()
+    {
+        var sql = "SELECT * FROM TableName WHERE (id = @abcd);";
+        var words = SqlExtractWords(sql);
+
+        Assert.Equal(new List<string> { "select", "*", "from", "tablename", "where", "(", "id", "=", "@abcd", ")", ";" }, words);
+    }
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs
new file mode 100644
index 0000000..b0aa702
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TableInfoTest.cs
@@ -0,0 +1,35 @@
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class TableInfoTest
+{
+    private static TableInfoFactory TableInfoFactory = new TableInfoFactory(new ColumnTypeProvider(), NullLoggerFactory.Instance);
+
+    private TableInfo _tableInfo;
+
+    public TableInfoTest()
+    {
+        _tableInfo = TableInfoFactory.Get(typeof(TestEntity));
+    }
+
+    [Fact]
+    public void TestColumnCount()
+    {
+        Assert.Equal(5, _tableInfo.Columns.Count);
+        Assert.Equal(4, _tableInfo.PropertyColumns.Count);
+        Assert.Equal(4, _tableInfo.ColumnProperties.Count);
+        Assert.Equal(1, _tableInfo.NonColumnProperties.Count);
+    }
+
+    [Fact]
+    public void GenerateSelectSqlTest()
+    {
+        var (sql, parameters) = _tableInfo.GenerateSelectSql(null, WhereClause.Create().Eq("Name", "Hello"));
+        var parameterName = parameters.First().Name;
+
+        // TODO: Is there a way to auto detect parameters?
+        SqlCompareHelper.SqlEqual($"SELECT * FROM TestEntity WHERE (Name = @{parameterName})", sql);
+        Assert.Equal("Hello", parameters.Get<string>(parameterName));
+    }
+}
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs
new file mode 100644
index 0000000..c15334c
--- /dev/null
+++ b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Crud/TestEntity.cs
@@ -0,0 +1,23 @@
+namespace CrupestApi.Commons.Crud.Tests;
+
+public class TestEntity
+{
+    [Column(ActAsKey = true, NotNull = true)]
+    public string Name { get; set; } = default!;
+
+    [Column(NotNull = true)]
+    public int Age { get; set; }
+
+    [Column]
+    public float? Height { get; set; }
+
+    [Column(OnlyGenerated = true, NotNull = true, NoUpdate = true)]
+    public string Secret { get; set; } = default!;
+
+    public static string SecretDefaultValueGenerator()
+    {
+        return "secret";
+    }
+
+    public string NonColumn { get; set; } = "Not A Column";
+}
-- 
cgit v1.2.3