diff --git a/Directory.Packages.props b/Directory.Packages.props index 8c2d31084..c1ea4fa17 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -90,8 +90,8 @@ - - + + diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs index c4981aa71..ac82b7946 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/Todo.cs @@ -1,12 +1,19 @@ using SurrealDb.Net.Models; +using System.ComponentModel.DataAnnotations.Schema; namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; +[Table(Table)] public class Todo : Record { internal const string Table = "todo"; + [Column("title")] public string? Title { get; set; } + + [Column("due_by")] public DateOnly? DueBy { get; set; } = null; + + [Column("is_complete")] public bool IsComplete { get; set; } = false; } \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/TodoFaker.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/TodoFaker.cs new file mode 100644 index 000000000..fda23c38a --- /dev/null +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/TodoFaker.cs @@ -0,0 +1,16 @@ +using Bogus; + +namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; + +/// +/// Faker test class to generate fake objects. +/// +public class TodoFaker : Faker +{ + public TodoFaker() + { + RuleFor(o => o.Title, f => f.Lorem.Sentence()); + RuleFor(o => o.DueBy, f => f.Date.FutureDateOnly()); + RuleFor(o => o.IsComplete, f => f.Random.Bool()); + } +} \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs index a90521be4..20d238b90 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecast.cs @@ -1,10 +1,12 @@ using SurrealDb.Net.Models; +using System.ComponentModel.DataAnnotations.Schema; namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; /// /// Weather forecast model. /// +[Table(Table)] public class WeatherForecast : Record { internal const string Table = "weatherForecast"; @@ -12,25 +14,30 @@ public class WeatherForecast : Record /// /// Date of the weather forecast. /// + [Column("date")] public DateTime Date { get; set; } /// /// Country of the weather forecast. /// + [Column("country")] public string? Country { get; set; } /// /// Temperature in Celsius. /// + [Column("temperature_c")] public int TemperatureC { get; set; } /// /// Temperature in Fahrenheit. /// + [Column("temperature_f")] public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); /// /// Summary of the weather forecast. /// + [Column("summary")] public string? Summary { get; set; } } \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs index 027fbe81e..3aa6fb8f3 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Models/WeatherForecastFaker.cs @@ -3,7 +3,7 @@ namespace CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService.Models; /// -/// Faker test class to generate fake WeatherForecast objects. +/// Faker test class to generate fake objects. /// public class WeatherForecastFaker : Faker { @@ -21,9 +21,6 @@ public class WeatherForecastFaker : Faker "Scorching" }; - /// - /// Constructor - /// public WeatherForecastFaker() { RuleFor(o => o.Date, f => f.Date.Recent()); diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs index 0f13ace2e..80517fdf7 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.ApiService/Program.cs @@ -2,37 +2,51 @@ using SurrealDb.Net; var builder = WebApplication.CreateBuilder(args); +var configuration = builder.Configuration; builder.AddServiceDefaults(); -builder.AddSurrealClient("db", settings => -{ - settings.Options!.NamingPolicy = "CamelCase"; -}); +builder.AddSurrealClient("db"); var app = builder.Build(); app.MapDefaultEndpoints(); app.MapGroup("/api") - .MapSurrealEndpoints( + .MapSurrealEndpoints( "/weatherForecast", new() { EnableMutations = false } ) - .MapSurrealEndpoints("/todo"); + .MapSurrealEndpoints("/todo"); app.MapPost("/init", InitializeDbAsync); app.Run(); -Task InitializeDbAsync(ISurrealDbClient surrealDbClient) +async Task InitializeDbAsync() { const int initialCount = 5; var weatherForecasts = new WeatherForecastFaker().Generate(initialCount); - - var tasks = weatherForecasts.Select(weatherForecast => - surrealDbClient.Create(WeatherForecast.Table, weatherForecast) + var todos = new TodoFaker().Generate(initialCount); + + var surrealDbClient = new SurrealDbClient( + SurrealDbOptions + .Create() + .FromConnectionString(configuration.GetConnectionString("db")!) + .Build() ); - return Task.WhenAll(tasks); + var weatherForecastTasks = weatherForecasts.Select(async weatherForecast => { + await surrealDbClient.Create(WeatherForecast.Table, weatherForecast); + return Task.CompletedTask; + }); + + var todoTasks = todos.Select(async todo => { + await surrealDbClient.Create(Todo.Table, todo); + return Task.CompletedTask; + }); + + await Task.WhenAll(weatherForecastTasks.Concat(todoTasks)); + + await surrealDbClient.DisposeAsync(); } \ No newline at end of file diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.TypeScript/apphost.mts b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.TypeScript/apphost.mts index 0e1d7d777..b50a81e69 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.TypeScript/apphost.mts +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.TypeScript/apphost.mts @@ -3,9 +3,8 @@ import { createBuilder } from "./.aspire/modules/aspire.mjs"; const builder = await createBuilder(); let primary = await builder.addSurrealServer("primary", { - port: 18000, - path: "memory", - strictMode: true, + port: 18000, + path: "memory", }); primary = await primary.withDataVolume({ name: "surreal-primary-data" }); @@ -14,22 +13,22 @@ primary = await primary.withSurrealist({ containerName: "surrealist-primary" }); primary = await primary.withSurrealDbOtlpExporter(); let appNamespace = await primary.addNamespace("appns", { - namespaceName: "polyglotNs", + namespaceName: "polyglotNs", }); appNamespace = await appNamespace.withCreationScript( - "DEFINE NAMESPACE IF NOT EXISTS `polyglotNs`;", + "DEFINE NAMESPACE IF NOT EXISTS `polyglotNs`;", ); let appDatabase = await appNamespace.addDatabase("appdb", { - databaseName: "polyglotDb", + databaseName: "polyglotDb", }); appDatabase = await appDatabase.withCreationScript( - "DEFINE DATABASE IF NOT EXISTS `polyglotDb`;", + "DEFINE DATABASE IF NOT EXISTS `polyglotDb`;", ); let mounted = await builder.addSurrealServer("mounted", { - port: 18001, - path: "memory", + port: 18001, + path: "memory", }); mounted = await mounted.withDataBindMount("./data"); mounted = await mounted.withInitFiles("./seed.surql"); @@ -43,13 +42,13 @@ const _primaryConnectionString = await primary.connectionStringExpression(); const _namespaceParent = await appNamespace.parent(); const _namespaceConnectionString = - await appNamespace.connectionStringExpression(); + await appNamespace.connectionStringExpression(); const _namespaceName = await appNamespace.namespaceName(); const _namespaceParentName = await _namespaceParent.name(); const _databaseParent = await appDatabase.parent(); const _databaseConnectionString = - await appDatabase.connectionStringExpression(); + await appDatabase.connectionStringExpression(); const _databaseName = await appDatabase.databaseName(); const _databaseParentName = await _databaseParent.name(); const _databaseServerName = await (await _databaseParent.parent()).name(); @@ -61,4 +60,3 @@ const _mountedUri = await mounted.uriExpression(); const _mountedConnectionString = await mounted.connectionStringExpression(); await builder.build().run(); - diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj index 13953b7f9..9875cb3e7 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs index 949076eef..517d3b0a6 100644 --- a/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs +++ b/examples/surrealdb/CommunityToolkit.Aspire.Hosting.SurrealDb.AppHost/Program.cs @@ -4,33 +4,33 @@ bool strictMode = false; -var db = builder.AddSurrealServer("surreal", strictMode: strictMode) +var db = builder.AddSurrealServer("surreal") .WithSurrealist() .AddNamespace("ns") .AddDatabase("db"); -if (strictMode) -{ #pragma warning disable CTASPIRE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - db.WithCreationScript( - $""" - DEFINE DATABASE IF NOT EXISTS {nameof(db)}; - USE DATABASE {nameof(db)}; - - DEFINE TABLE todo; - DEFINE FIELD title ON todo TYPE string; - DEFINE FIELD dueBy ON todo TYPE datetime; - DEFINE FIELD isComplete ON todo TYPE bool; - - DEFINE TABLE weatherForecast; - DEFINE FIELD date ON weatherForecast TYPE datetime; - DEFINE FIELD country ON weatherForecast TYPE string; - DEFINE FIELD temperatureC ON weatherForecast TYPE number; - DEFINE FIELD summary ON weatherForecast TYPE string; - """ - ); +string strictSuffix = strictMode ? " STRICT" : string.Empty; +db.WithCreationScript( + $""" + DEFINE DATABASE IF NOT EXISTS {nameof(db)}{strictSuffix}; + USE DATABASE {nameof(db)}; + + DEFINE TABLE todo; + DEFINE FIELD title ON todo TYPE string; + DEFINE FIELD due_by ON todo TYPE datetime; + DEFINE FIELD is_complete ON todo TYPE bool; + + DEFINE TABLE weatherForecast; + DEFINE FIELD date ON weatherForecast TYPE datetime; + DEFINE FIELD country ON weatherForecast TYPE string; + DEFINE FIELD temperature_c ON weatherForecast TYPE number; + DEFINE FIELD temperature_f ON weatherForecast TYPE number; + DEFINE FIELD summary ON weatherForecast TYPE string; + """ +); #pragma warning restore CTASPIRE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. -} + builder.AddProject("apiservice") .WithReference(db) diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs index 6957b5a53..91cc6d23d 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbBuilderExtensions.cs @@ -33,7 +33,6 @@ public static class SurrealDbBuilderExtensions /// The parameter used to provide the administrator password for the SurrealDB resource. If a random password will be generated. /// The host port for the SurrealDB instance. /// Sets the path for storing data. If no argument is given, the default of memory for non-persistent storage in memory is assumed. - /// Whether strict mode is enabled on the server. /// A reference to the . /// /// @@ -58,8 +57,7 @@ public static IResourceBuilder AddSurrealServer( IResourceBuilder? userName = null, IResourceBuilder? password = null, int? port = null, - string path = "memory", - bool strictMode = false + string path = "memory" ) { ArgumentNullException.ThrowIfNull(builder); @@ -70,10 +68,6 @@ public static IResourceBuilder AddSurrealServer( "start", path }; - if (strictMode) - { - args.Add("--strict"); - } // The password must be at least 8 characters long and contain characters from three of the following four sets: Uppercase letters, Lowercase letters, Base 10 digits, and Symbols var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", minLower: 1, minUpper: 1, minNumeric: 1); @@ -97,11 +91,6 @@ public static IResourceBuilder AddSurrealServer( .WithArgs([.. args]) .OnResourceReady(async (_, @event, ct) => { - if (!strictMode) - { - return; - } - var connectionString = await surrealServer.GetConnectionStringAsync(ct).ConfigureAwait(false); if (connectionString is null) { diff --git a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs index b2ec7e980..bcad99984 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SurrealDb/SurrealDbContainerImageTags.cs @@ -9,13 +9,13 @@ internal sealed class SurrealDbContainerImageTags public const string Registry = "docker.io"; /// surrealdb/surrealdb public const string Image = "surrealdb/surrealdb"; - /// v2.4 - public const string Tag = "v2.4"; + /// v3.1 + public const string Tag = "v3.1"; /// docker.io public const string SurrealistRegistry = "docker.io"; /// surrealdb/surrealist public const string SurrealistImage = "surrealdb/surrealist"; - /// 3.6.9 - public const string SurrealistTag = "3.6.9"; + /// 3.8.3 + public const string SurrealistTag = "3.8.3"; } \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs index 02c077197..72127a9d1 100644 --- a/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs +++ b/src/CommunityToolkit.Aspire.SurrealDb/SurrealDbClientSettings.cs @@ -20,9 +20,9 @@ public sealed class SurrealDbClientSettings /// Gets or sets the Service lifetime to register services under. /// /// - /// The default value is . + /// The default value is . /// - public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton; + public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped; /// /// Gets or sets a boolean value that indicates whether the SurrealDB health check is disabled or not. diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs index 29d1dc54d..e8c4308cc 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/AppHostTests.cs @@ -42,11 +42,11 @@ public async Task ApiServiceStartsAndRespondsOk() await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(1)); var httpClient = fixture.CreateHttpClient(resourceName); - var todoResponse = await httpClient.GetAsync("/api/todo"); - Assert.Equal(HttpStatusCode.OK, todoResponse.StatusCode); - var initResponse = await httpClient.PostAsync("/init", null); Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode); + + var todoResponse = await httpClient.GetAsync("/api/todo"); + Assert.Equal(HttpStatusCode.OK, todoResponse.StatusCode); var weatherForecastResponse = await httpClient.GetAsync("/api/weatherForecast"); Assert.Equal(HttpStatusCode.OK, weatherForecastResponse.StatusCode); diff --git a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs index 4f92c4573..6dc0f884e 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SurrealDb.Tests/SurrealDbFunctionalTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Aspire.Components.Common.Tests; using Aspire.Hosting; using Aspire.Hosting.Utils; using Bogus; @@ -12,7 +11,7 @@ using Polly; using SurrealDb.Net; using SurrealDb.Net.Exceptions; -using System.Data; +using System.ComponentModel.DataAnnotations.Schema; using SurrealRecord = SurrealDb.Net.Models.Record; namespace CommunityToolkit.Aspire.Hosting.SurrealDb.Tests; @@ -39,7 +38,7 @@ public async Task VerifySurrealDbResource() { using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); var ct = cts.Token; - using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + await using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var surrealServer = builder.AddSurrealServer("surreal"); @@ -47,15 +46,15 @@ public async Task VerifySurrealDbResource() .AddNamespace("ns") .AddDatabase("db"); - using var app = builder.Build(); + await using var app = builder.Build(); - await app.StartAsync(); + await app.StartAsync(ct); await app.ResourceNotifications.WaitForResourceHealthyAsync(db.Resource.Name, ct); var hb = Host.CreateApplicationBuilder(); - hb.Configuration[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default); + hb.Configuration[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(ct); hb.AddSurrealClient(db.Resource.Name); @@ -63,10 +62,11 @@ public async Task VerifySurrealDbResource() await host.StartAsync(ct); - var surrealDbClient = host.Services.GetRequiredService(); + await using var scope = host.Services.CreateAsyncScope(); + await using var client = scope.ServiceProvider.GetRequiredService(); - await CreateTestData(surrealDbClient, ct); - await AssertTestData(surrealDbClient, ct); + await CreateTestData(client, ct); + await AssertTestData(client, ct); } [Theory] @@ -80,14 +80,17 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) string? volumeName = null; string? bindMountPath = null; + const string databasePath = "surrealkv://data/db.db"; + try { using var builder1 = TestDistributedApplicationBuilder.Create(testOutputHelper); - + var password1 = builder1.AddParameter("surreal-password", secret: true); password1.Resource.Default = new PasswordConstantDefault(); - - var surrealServer1 = builder1.AddSurrealServer("surreal", path: "rocksdb://data/db.db", password: password1); + + var surrealServer1 = + builder1.AddSurrealServer("surreal", path: databasePath, password: password1); var db1 = surrealServer1 .AddNamespace("ns") @@ -96,7 +99,8 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) if (useVolume) { // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails - volumeName = VolumeNameGenerator.Generate(surrealServer1, nameof(WithDataShouldPersistStateBetweenUsages)); + volumeName = VolumeNameGenerator.Generate(surrealServer1, + nameof(WithDataShouldPersistStateBetweenUsages)); // if the volume already exists (because of a crashing previous run), delete it DockerUtils.AttemptDeleteDockerVolume(volumeName, throwOnFailure: true); @@ -109,7 +113,9 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) if (!OperatingSystem.IsWindows()) { - File.SetUnixFileMode(bindMountPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute); + File.SetUnixFileMode(bindMountPath, + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute); } } @@ -124,16 +130,18 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) { var hb = Host.CreateApplicationBuilder(); - hb.Configuration[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(ct); + hb.Configuration[$"ConnectionStrings:{db1.Resource.Name}"] = + await db1.Resource.ConnectionStringExpression.GetValueAsync(ct); hb.AddSurrealClient(db1.Resource.Name); using var host = hb.Build(); await host.StartAsync(ct); - await using var surrealDbClient = host.Services.GetRequiredService(); - await CreateTestData(surrealDbClient, ct); - await AssertTestData(surrealDbClient, ct); + await using var scope1 = host.Services.CreateAsyncScope(); + await using var client = scope1.ServiceProvider.GetRequiredService(); + await CreateTestData(client, ct); + await AssertTestData(client, ct); } finally { @@ -143,11 +151,12 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) } using var builder2 = TestDistributedApplicationBuilder.Create(testOutputHelper); - + var password2 = builder2.AddParameter("surreal-password", secret: true); password2.Resource.Default = new PasswordConstantDefault(); - - var surrealServer2 = builder2.AddSurrealServer("surreal", path: "rocksdb://data/db.db", password: password2); + + var surrealServer2 = + builder2.AddSurrealServer("surreal", path: databasePath, password: password2); var db2 = surrealServer2 .AddNamespace("ns") @@ -173,14 +182,16 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) { var hb = Host.CreateApplicationBuilder(); - hb.Configuration[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(ct); + hb.Configuration[$"ConnectionStrings:{db2.Resource.Name}"] = + await db2.Resource.ConnectionStringExpression.GetValueAsync(ct); hb.AddSurrealClient(db2.Resource.Name); using var host = hb.Build(); await host.StartAsync(ct); - await using var surrealDbClient = host.Services.GetRequiredService(); - await AssertTestData(surrealDbClient, ct); + await using var scope2 = host.Services.CreateAsyncScope(); + await using var client = scope2.ServiceProvider.GetRequiredService(); + await AssertTestData(client, ct); } finally { @@ -188,7 +199,6 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) await app.StopAsync(ct); } } - } finally { @@ -215,7 +225,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) public async Task VerifyWaitForOnSurrealDbBlocksDependentResources() { using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)); - using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + await using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var healthCheckTcs = new TaskCompletionSource(); builder.Services.AddHealthChecks().AddAsyncCheck("blocking_check", () => healthCheckTcs.Task); @@ -226,7 +236,7 @@ public async Task VerifyWaitForOnSurrealDbBlocksDependentResources() var dependentResource = builder.AddSurrealServer("dependentresource") .WaitFor(resource); - using var app = builder.Build(); + await using var app = builder.Build(); var pendingStart = app.StartAsync(cts.Token); @@ -244,7 +254,7 @@ public async Task VerifyWaitForOnSurrealDbBlocksDependentResources() await pendingStart; - await app.StopAsync(); + await app.StopAsync(cts.Token); } [Fact(Skip = "Feature is unstable and blocking the release")] @@ -278,8 +288,8 @@ await File.WriteAllTextAsync( """, cts.Token ); - - using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + + await using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); #pragma warning disable CTASPIRE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var surrealServer = builder @@ -289,8 +299,8 @@ await File.WriteAllTextAsync( var ns = surrealServer.AddNamespace(surrealNsName); var db = ns.AddDatabase(surrealDbName); - - using var app = builder.Build(); + + await using var app = builder.Build(); await app.StartAsync(cts.Token); @@ -316,7 +326,7 @@ await File.WriteAllTextAsync( await pipeline.ExecuteAsync(async token => { - var client = host.Services.GetRequiredService(); + await using var client = host.Services.GetRequiredService(); var cars = (await client.Select(Car.Table, token)).ToList(); var car = Assert.Single(cars); @@ -346,7 +356,7 @@ public async Task AddDatabaseCreatesDatabaseWithCustomScript() var surrealNsName = "ns1"; var surrealDbName = "db1"; - var surreal = builder.AddSurrealServer("surreal", strictMode: true); + var surreal = builder.AddSurrealServer("surreal"); #pragma warning disable CTASPIRE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var db = surreal @@ -358,9 +368,9 @@ public async Task AddDatabaseCreatesDatabaseWithCustomScript() USE DATABASE {surrealDbName}; DEFINE TABLE todo; - DEFINE FIELD Title ON todo TYPE string; - DEFINE FIELD DueBy ON todo TYPE datetime; - DEFINE FIELD IsComplete ON todo TYPE bool; + DEFINE FIELD title ON todo TYPE string; + DEFINE FIELD due_by ON todo TYPE datetime; + DEFINE FIELD is_complete ON todo TYPE bool; """ ); #pragma warning restore CTASPIRE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -381,23 +391,24 @@ public async Task AddDatabaseCreatesDatabaseWithCustomScript() await app.ResourceNotifications.WaitForResourceHealthyAsync(db.Resource.Name, cts.Token); - await using var client = host.Services.GetRequiredService(); + await using var scope = host.Services.CreateAsyncScope(); + await using var client = scope.ServiceProvider.GetRequiredService(); await CreateTestData(client, cts.Token); await AssertTestData(client, cts.Token); } - private static async Task CreateTestData(SurrealDbClient surrealDbClient, CancellationToken ct) + private static async Task CreateTestData(SurrealDbSession client, CancellationToken ct) { - await surrealDbClient.Insert(Todo.Table, _todoList, ct); + await client.Insert(Todo.Table, _todoList, ct); } - private static async Task AssertTestData(SurrealDbClient surrealDbClient, CancellationToken ct) + private static async Task AssertTestData(SurrealDbSession client, CancellationToken ct) { - var records = await surrealDbClient.Select(Todo.Table, ct); + var records = await client.Select(Todo.Table, ct); Assert.Equal(_generatedTodoCount, records.Count()); - var firstRecord = await surrealDbClient.Select((Todo.Table, "1"), ct); + var firstRecord = await client.Select((Todo.Table, "1"), ct); Assert.NotNull(firstRecord); Assert.Equivalent(firstRecord, _todoList[0]); } @@ -406,8 +417,13 @@ private sealed class Todo : SurrealRecord { internal const string Table = "todo"; + [Column("title")] public string? Title { get; set; } + + [Column("due_by")] public DateOnly? DueBy { get; set; } = null; + + [Column("is_complete")] public bool IsComplete { get; set; } = false; } @@ -425,6 +441,7 @@ private sealed class Car : SurrealRecord { internal const string Table = "car"; + [Column("brand")] public string Brand { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs index bbb8c6e2c..eb8548ba1 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/AspireSurrealClientExtensionsTest.cs @@ -97,15 +97,25 @@ public void CanAddMultipleKeyedServices() builder.AddKeyedSurrealClient("surreal2"); builder.AddKeyedSurrealClient("surreal3"); - using var host = builder.Build(); + var host = builder.Build(); - var client1 = host.Services.GetRequiredService(); - var client2 = host.Services.GetRequiredKeyedService("surreal2"); - var client3 = host.Services.GetRequiredKeyedService("surreal3"); + var client1 = host.Services.GetRequiredService(); + var client2 = host.Services.GetRequiredKeyedService("surreal2"); + var client3 = host.Services.GetRequiredKeyedService("surreal3"); Assert.NotSame(client1, client2); Assert.NotSame(client1, client3); Assert.NotSame(client2, client3); + + try + { + host.Dispose(); + } + catch + { + // ignored + // Failed to be disposed on unknown server + } } [Fact] @@ -121,12 +131,22 @@ public void CanAddClientFromEncodedConnectionString() builder.AddSurrealClient("surreal1"); builder.AddKeyedSurrealClient("surreal2"); - using var host = builder.Build(); + var host = builder.Build(); - var client1 = host.Services.GetRequiredService(); - var client2 = host.Services.GetRequiredKeyedService("surreal2"); + var client1 = host.Services.GetRequiredService(); + var client2 = host.Services.GetRequiredKeyedService("surreal2"); Assert.NotSame(client1, client2); + + try + { + host.Dispose(); + } + catch + { + // ignored + // Failed to be disposed on unknown server + } } private static HostApplicationBuilder CreateBuilder(string connectionString) diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs index 162d09f97..97cd6f042 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConfigurationTests.cs @@ -14,6 +14,6 @@ public void OptionsAreNullByDefault() => Assert.Null(new SurrealDbClientSettings().Options); [Fact] - public void LifetimeIsSingletonByDefault() => - Assert.Equal(ServiceLifetime.Singleton, new SurrealDbClientSettings().Lifetime); + public void LifetimeIsScopedByDefault() => + Assert.Equal(ServiceLifetime.Scoped, new SurrealDbClientSettings().Lifetime); } diff --git a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs index 1d170ce32..110103e7e 100644 --- a/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs +++ b/tests/CommunityToolkit.Aspire.SurrealDb.Tests/ConformanceTests.cs @@ -15,12 +15,12 @@ public class ConformanceCollection; [Collection("Conformance")] public class ConformanceTests : - ConformanceTests, + ConformanceTests, IClassFixture { private readonly SurrealDbContainerFixture _containerFixture; - protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; + protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Scoped; protected override string ActivitySourceName => string.Empty; @@ -104,7 +104,7 @@ protected override void SetTracing(SurrealDbClientSettings options, bool enabled throw new NotImplementedException(); } - protected override void TriggerActivity(SurrealDbClient service) + protected override void TriggerActivity(SurrealDbSession service) { using var source = new CancellationTokenSource(100); diff --git a/tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj b/tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj index b0251f810..f48b2b9af 100644 --- a/tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj +++ b/tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj @@ -1,4 +1,4 @@ - + false diff --git a/tests/CommunityToolkit.Aspire.Testing/ConformanceTests.cs b/tests/CommunityToolkit.Aspire.Testing/ConformanceTests.cs index 13e8441a8..bd4446459 100644 --- a/tests/CommunityToolkit.Aspire.Testing/ConformanceTests.cs +++ b/tests/CommunityToolkit.Aspire.Testing/ConformanceTests.cs @@ -176,7 +176,7 @@ public void MetricsRegistersMeterProvider(bool enabled) [Theory] [InlineData(true)] [InlineData(false)] - public void ServiceLifetimeIsAsExpected(bool useKey) + public async Task ServiceLifetimeIsAsExpected(bool useKey) { SkipIfRequiredServerConnectionCanNotBeEstablished(); SkipIfKeyedRegistrationIsNotSupported(useKey); @@ -186,18 +186,31 @@ public void ServiceLifetimeIsAsExpected(bool useKey) using IHost host = CreateHostWithComponent(key: key); - using (IServiceScope scope1 = host.Services.CreateScope()) + if (typeof(IAsyncDisposable).IsAssignableFrom(typeof(TService))) { - serviceFromFirstScope = Resolve(scope1.ServiceProvider, key); + await using (AsyncServiceScope scope1 = host.Services.CreateAsyncScope()) + { + serviceFromFirstScope = Resolve(scope1.ServiceProvider, key); + } + await using (AsyncServiceScope scope2 = host.Services.CreateAsyncScope()) + { + serviceFromSecondScope = Resolve(scope2.ServiceProvider, key); + secondServiceFromSecondScope = Resolve(scope2.ServiceProvider, key); + } } - - using (IServiceScope scope2 = host.Services.CreateScope()) + else { - serviceFromSecondScope = Resolve(scope2.ServiceProvider, key); - - secondServiceFromSecondScope = Resolve(scope2.ServiceProvider, key); + using (IServiceScope scope1 = host.Services.CreateScope()) + { + serviceFromFirstScope = Resolve(scope1.ServiceProvider, key); + } + using (IServiceScope scope2 = host.Services.CreateScope()) + { + serviceFromSecondScope = Resolve(scope2.ServiceProvider, key); + secondServiceFromSecondScope = Resolve(scope2.ServiceProvider, key); + } } - + Assert.NotNull(serviceFromFirstScope); Assert.NotNull(serviceFromSecondScope); Assert.NotNull(secondServiceFromSecondScope); @@ -285,7 +298,7 @@ public void LoggerFactoryIsUsedByRegisteredClient(bool registerAfterLoggerFactor builder.Services.AddSingleton(); } - using IHost host = builder.Build(); + IHost host = builder.Build(); TService service = key is null ? host.Services.GetRequiredService() @@ -307,6 +320,15 @@ public void LoggerFactoryIsUsedByRegisteredClient(bool registerAfterLoggerFactor { Assert.DoesNotContain(logCategory, loggerFactory.Categories); } + + try + { + host.Dispose(); + } + catch + { + // ignored + } } [Theory]