Skip to content

Commit b1a30d8

Browse files
author
Adrian Hall
committed
(#48) Added test service for OpenApi generation.
1 parent cb49a94 commit b1a30d8

File tree

22 files changed

+424
-4
lines changed

22 files changed

+424
-4
lines changed

Datasync.Toolkit.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.S
6868
EndProject
6969
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.MongoDB.Test", "tests\CommunityToolkit.Datasync.Server.MongoDB.Test\CommunityToolkit.Datasync.Server.MongoDB.Test.csproj", "{4FC45D20-0BA9-484B-9040-641687659AF6}"
7070
EndProject
71+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.OpenApi.Test", "tests\CommunityToolkit.Datasync.Server.OpenApi.Test\CommunityToolkit.Datasync.Server.OpenApi.Test.csproj", "{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE}"
72+
EndProject
73+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.OpenApiService", "tests\CommunityToolkit.Datasync.OpenApiService\CommunityToolkit.Datasync.OpenApiService.csproj", "{ED03E99D-58BC-48C1-963B-0F02E28FCEDD}"
74+
EndProject
7175
Global
7276
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7377
Debug|Any CPU = Debug|Any CPU
@@ -166,6 +170,14 @@ Global
166170
{4FC45D20-0BA9-484B-9040-641687659AF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
167171
{4FC45D20-0BA9-484B-9040-641687659AF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
168172
{4FC45D20-0BA9-484B-9040-641687659AF6}.Release|Any CPU.Build.0 = Release|Any CPU
173+
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
174+
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
175+
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
176+
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE}.Release|Any CPU.Build.0 = Release|Any CPU
177+
{ED03E99D-58BC-48C1-963B-0F02E28FCEDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
178+
{ED03E99D-58BC-48C1-963B-0F02E28FCEDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
179+
{ED03E99D-58BC-48C1-963B-0F02E28FCEDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
180+
{ED03E99D-58BC-48C1-963B-0F02E28FCEDD}.Release|Any CPU.Build.0 = Release|Any CPU
169181
EndGlobalSection
170182
GlobalSection(SolutionProperties) = preSolution
171183
HideSolutionNode = FALSE
@@ -194,6 +206,8 @@ Global
194206
{A9967817-2A2C-4C6D-A133-967A6062E9B3} = {75F709FD-8CC2-4558-A802-FE57086167C2}
195207
{DC20ACF9-12E9-41D9-B672-CB5FD85548E9} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
196208
{4FC45D20-0BA9-484B-9040-641687659AF6} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
209+
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
210+
{ED03E99D-58BC-48C1-963B-0F02E28FCEDD} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
197211
EndGlobalSection
198212
GlobalSection(ExtensibilityGlobals) = postSolution
199213
SolutionGuid = {78A935E9-8F14-448A-BEDF-360FB742F14E}

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<PackageVersion Include="LiteDB" Version="5.0.21" />
1212
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
1313
<PackageVersion Include="Microsoft.AspNetCore.OData" Version="9.1.3" />
14+
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
1415
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
1516
<PackageVersion Include="Microsoft.EntityFrameworkCore.Cosmos" Version="9.0.1" />
1617
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.1" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<IsTestProject>false</IsTestProject>
4+
</PropertyGroup>
5+
6+
<ItemGroup>
7+
<ProjectReference Include="..\..\src\CommunityToolkit.Datasync.Server.Abstractions\CommunityToolkit.Datasync.Server.Abstractions.csproj" />
8+
<ProjectReference Include="..\..\src\CommunityToolkit.Datasync.Server.EntityFrameworkCore\CommunityToolkit.Datasync.Server.EntityFrameworkCore.csproj" />
9+
<ProjectReference Include="..\..\src\CommunityToolkit.Datasync.Server\CommunityToolkit.Datasync.Server.csproj" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
14+
<PackageReference Include="Microsoft.EntityFrameworkCore" />
15+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.OpenApiService.Models;
6+
using CommunityToolkit.Datasync.Server.EntityFrameworkCore;
7+
using Microsoft.AspNetCore.Mvc;
8+
9+
namespace CommunityToolkit.Datasync.OpenApiService.Controllers;
10+
11+
[Route("tables/kitchenreader")]
12+
[ExcludeFromCodeCoverage]
13+
public class KitchenReaderController : ReadonlyTableController<KitchenSink>
14+
{
15+
public KitchenReaderController(ServiceDbContext context, ILogger<KitchenReaderController> logger) : base()
16+
{
17+
Repository = new EntityTableRepository<KitchenSink>(context);
18+
Logger = logger;
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.OpenApiService.Models;
6+
using CommunityToolkit.Datasync.Server;
7+
using CommunityToolkit.Datasync.Server.EntityFrameworkCore;
8+
using Microsoft.AspNetCore.Mvc;
9+
10+
namespace CommunityToolkit.Datasync.OpenApiService.Controllers;
11+
12+
[Route("tables/kitchensink")]
13+
[ExcludeFromCodeCoverage]
14+
public class KitchenSinkController : TableController<KitchenSink>
15+
{
16+
public KitchenSinkController(ServiceDbContext context, ILogger<KitchenSinkController> logger) : base()
17+
{
18+
Repository = new EntityTableRepository<KitchenSink>(context);
19+
Logger = logger;
20+
}
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.Server;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
namespace CommunityToolkit.Datasync.OpenApiService.Controllers;
9+
10+
[ExcludeFromCodeCoverage]
11+
public abstract class ReadonlyTableController<TData> : TableController<TData> where TData : class, ITableData
12+
{
13+
[NonAction]
14+
public override Task<IActionResult> CreateAsync(CancellationToken cancellationToken = default)
15+
=> base.CreateAsync(cancellationToken);
16+
17+
[NonAction]
18+
public override Task<IActionResult> DeleteAsync([FromRoute] string id, CancellationToken cancellationToken = default)
19+
=> base.DeleteAsync(id, cancellationToken);
20+
21+
[NonAction]
22+
public override Task<IActionResult> ReplaceAsync([FromRoute] string id, CancellationToken cancellationToken = default)
23+
=> base.ReplaceAsync(id, cancellationToken);
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.OpenApiService.Models;
6+
using CommunityToolkit.Datasync.Server;
7+
using CommunityToolkit.Datasync.Server.EntityFrameworkCore;
8+
using Microsoft.AspNetCore.Mvc;
9+
10+
namespace CommunityToolkit.Datasync.OpenApiService.Controllers;
11+
12+
[Route("tables/[controller]")]
13+
[ExcludeFromCodeCoverage]
14+
public class TodoItemController : TableController<TodoItem>
15+
{
16+
public TodoItemController(ServiceDbContext context) : base()
17+
{
18+
Repository = new EntityTableRepository<TodoItem>(context);
19+
}
20+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Metadata;
7+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8+
using System.Text;
9+
10+
namespace CommunityToolkit.Datasync.OpenApiService.Extensions;
11+
12+
[ExcludeFromCodeCoverage]
13+
public static class SqliteExtensions
14+
{
15+
public static void EnableSqliteExtensions(this DbContext context)
16+
{
17+
foreach (IEntityType table in context.Model.GetEntityTypes())
18+
{
19+
IEnumerable<IProperty> props = table.GetProperties()
20+
.Where(prop => prop.ClrType == typeof(byte[]) && prop.ValueGenerated == ValueGenerated.OnAddOrUpdate);
21+
foreach (IProperty prop in props)
22+
{
23+
context.InstallSqliteUpdateTrigger(table.GetTableName(), prop.Name, "UPDATE");
24+
}
25+
}
26+
}
27+
28+
public static void EnableSqliteExtensions(this ModelBuilder builder)
29+
{
30+
IEnumerable<IMutableProperty> props = builder.Model.GetEntityTypes()
31+
.SelectMany(t => t.GetProperties())
32+
.Where(prop => prop.ClrType == typeof(byte[]) && prop.ValueGenerated == ValueGenerated.OnAddOrUpdate);
33+
foreach (IMutableProperty prop in props)
34+
{
35+
prop.SetValueConverter(new SqliteTimestampConverter());
36+
prop.SetDefaultValueSql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')");
37+
}
38+
}
39+
40+
public static void InstallSqliteUpdateTrigger(this DbContext context, string tableName, string fieldName, string operation)
41+
{
42+
string sql = $@"
43+
CREATE TRIGGER s_{tableName}_{fieldName}_{operation} AFTER {operation} ON {tableName}
44+
BEGIN
45+
UPDATE {tableName}
46+
SET {fieldName} = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')
47+
WHERE rowid = NEW.rowid;
48+
END
49+
";
50+
context.Database.ExecuteSqlRaw(sql);
51+
}
52+
}
53+
54+
[ExcludeFromCodeCoverage]
55+
public class SqliteTimestampConverter : ValueConverter<byte[], string>
56+
{
57+
public SqliteTimestampConverter() : base(v => ToDb(v), v => FromDb(v))
58+
{
59+
}
60+
61+
public static string ToDb(byte[] v) => Encoding.UTF8.GetString(v);
62+
public static byte[] FromDb(string v) => Encoding.UTF8.GetBytes(v);
63+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.Data.Sqlite;
6+
7+
namespace CommunityToolkit.Datasync.OpenApiService.Extensions;
8+
9+
public static class SqliteFactory
10+
{
11+
private static SqliteConnection _connection;
12+
private static readonly object _lock = new();
13+
14+
public static SqliteConnection CreateAndOpenConnection(string connectionString = "Data Source=:memory:")
15+
{
16+
lock (_lock)
17+
{
18+
if (_connection == null)
19+
{
20+
_connection = new SqliteConnection(connectionString);
21+
_connection.Open();
22+
}
23+
}
24+
25+
return _connection;
26+
}
27+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.Server.EntityFrameworkCore;
6+
7+
namespace CommunityToolkit.Datasync.OpenApiService.Models;
8+
9+
public enum KitchenSinkState
10+
{
11+
None,
12+
Completed,
13+
Failed
14+
}
15+
16+
[ExcludeFromCodeCoverage]
17+
public class KitchenSink : EntityTableData
18+
{
19+
public bool BooleanValue { get; set; }
20+
public byte ByteValue { get; set; }
21+
public byte[] ByteArrayValue { get; set; }
22+
public char CharValue { get; set; }
23+
public DateOnly DateOnlyValue { get; set; }
24+
public DateTime DateTimeValue { get; set; }
25+
public DateTimeOffset DateTimeOffsetValue { get; set; }
26+
public decimal DecimalValue { get; set; }
27+
public double DoubleValue { get; set; }
28+
public KitchenSinkState EnumValue { get; set; }
29+
public float FloatValue { get; set; }
30+
public Guid? GuidValue { get; set; }
31+
public int IntValue { get; set; }
32+
public long LongValue { get; set; }
33+
public double? NullableDouble { get; set; }
34+
public KitchenSinkState? NullableEnumValue { get; set; }
35+
public string StringValue { get; set; }
36+
public TimeOnly TimeOnlyValue { get; set; }
37+
}

0 commit comments

Comments
 (0)