diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..70df1f38bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,83 @@ +name: Run Integration Tests + +on: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - project: tests/Aspire.Hosting.Elasticsearch.Tests/Aspire.Hosting.Elasticsearch.Tests.csproj + name: Elasticsearch + - project: tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj + name: PostgreSQL + - project: tests/Aspire.Hosting.Oracle.Tests/Aspire.Hosting.Oracle.Tests.csproj + name: Oracle + - project: tests/Aspire.Hosting.Kafka.Tests/Aspire.Hosting.Kafka.Tests.csproj + name: Kafka + - project: tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj + name: Hosting + - project: tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj + name: Redis + - project: tests/Aspire.Hosting.Azure.Tests/Aspire.Hosting.Azure.Tests.csproj + name: Azure + - project: tests/Aspire.Playground.Tests/Aspire.Playground.Tests.csproj + name: Playground + # Add more projects as needed + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.x + 9.x + + - name: Trust HTTPS development certificate + run: dotnet dev-certs https --trust + + - name: Verify Docker is running + run: docker info + + - name: Install Azure Functions Core Tools + run: | + sudo apt-get update + sudo apt-get install -y azure-functions-core-tools-4 + + - name: Restore dependencies + run: dotnet restore ${{ matrix.project }} + + - name: Build projects + run: dotnet build ${{ matrix.project }} /p:CI=false --no-restore + + - name: Run tests + id: run-tests + run: | + dotnet test ${{ matrix.project }} \ + --logger "console;verbosity=normal" \ + --logger "trx" \ + --logger html \ + --blame \ + --blame-hang-timeout 7m \ + --results-directory testresults \ + --no-restore \ + --no-build \ + /p:CI=false + + - name: Compress test results + if: always() + run: zip -r testresults.zip testresults + + - name: Upload test results artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: testresults-${{ matrix.name }} + path: testresults.zip diff --git a/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs b/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs index 02b884a500..55fddd967d 100644 --- a/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs +++ b/src/Aspire.Hosting.Elasticsearch/ElasticsearchBuilderExtensions.cs @@ -145,6 +145,9 @@ public static IResourceBuilder WithDataBindMount(this IRe ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(source); - return builder.WithBindMount(source, "/usr/share/elasticsearch/data"); + return builder.WithBindMount(source, + "/usr/share/elasticsearch/data", + isReadOnly: false, + fileMode: ElasticsearchResource.s_defaultUnixFileMode); } } diff --git a/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs b/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs index bac9b49d20..0517f92276 100644 --- a/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs +++ b/src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs @@ -22,6 +22,11 @@ public class ElasticsearchResource : ContainerResource, IResourceWithConnectionS //For things like cluster updates, master elections, nodes joining/leaving, shard allocation internal const string InternalEndpointName = "internal"; + internal static UnixFileMode s_defaultUnixFileMode = + UnixFileMode.GroupExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | + UnixFileMode.OtherExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | + UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite; + /// The name of the resource. /// A parameter that contains the Elasticsearch superuser password. public ElasticsearchResource(string name, ParameterResource password) : base(name) diff --git a/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs b/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs index b48577b85c..55cd51822f 100644 --- a/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs +++ b/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs @@ -13,6 +13,9 @@ namespace Aspire.Hosting.Postgres; /// The name of the container resource. public sealed class PgAdminContainerResource(string name) : ContainerResource(ThrowIfNull(name)) { + internal static UnixFileMode? s_defaultBindMountFileMode = + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; + private static string ThrowIfNull([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) => argument ?? throw new ArgumentNullException(paramName); } diff --git a/src/Aspire.Hosting.PostgreSQL/PgWebContainerResource.cs b/src/Aspire.Hosting.PostgreSQL/PgWebContainerResource.cs index 3e2e61f46d..e610233aea 100644 --- a/src/Aspire.Hosting.PostgreSQL/PgWebContainerResource.cs +++ b/src/Aspire.Hosting.PostgreSQL/PgWebContainerResource.cs @@ -13,6 +13,11 @@ public sealed class PgWebContainerResource(string name) : ContainerResource(name { internal const string PrimaryEndpointName = "http"; + internal static UnixFileMode s_defaultBindMountFileMode = + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute; + private EndpointReference? _primaryEndpoint; /// diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index ab8f12c48e..01c7fa288d 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -151,7 +151,7 @@ public static IResourceBuilder WithPgAdmin(this IResourceBuilder builde .WithImageRegistry(PostgresContainerImageTags.PgAdminRegistry) .WithHttpEndpoint(targetPort: 80, name: "http") .WithEnvironment(SetPgAdminEnvironmentVariables) - .WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json") + .WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json", isReadOnly: true, fileMode: PgAdminContainerResource.s_defaultBindMountFileMode) .WithHttpHealthCheck("/browser") .ExcludeFromManifest(); @@ -164,11 +164,6 @@ public static IResourceBuilder WithPgAdmin(this IResourceBuilder builde using var stream = new FileStream(serverFileMount.Source!, FileMode.Create); using var writer = new Utf8JsonWriter(stream); - // Need to grant read access to the config file on unix like systems. - if (!OperatingSystem.IsWindows()) - { - File.SetUnixFileMode(serverFileMount.Source!, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead); - } var serverIndex = 1; @@ -284,7 +279,7 @@ public static IResourceBuilder WithPgWeb(this IResourceB .WithImage(PostgresContainerImageTags.PgWebImage, PostgresContainerImageTags.PgWebTag) .WithImageRegistry(PostgresContainerImageTags.PgWebRegistry) .WithHttpEndpoint(targetPort: 8081, name: "http") - .WithBindMount(dir, "/.pgweb/bookmarks") + .WithBindMount(dir, "/.pgweb/bookmarks", isReadOnly: false, fileMode: PgWebContainerResource.s_defaultBindMountFileMode) .WithArgs("--bookmarks-dir=/.pgweb/bookmarks") .WithArgs("--sessions") .ExcludeFromManifest(); @@ -293,17 +288,14 @@ public static IResourceBuilder WithPgWeb(this IResourceB pgwebContainerBuilder.WithRelationship(builder.Resource, "PgWeb"); + pgwebContainerBuilder.WithHttpHealthCheck(); + builder.ApplicationBuilder.Eventing.Subscribe(async (e, ct) => { var adminResource = builder.ApplicationBuilder.Resources.OfType().Single(); var serverFileMount = adminResource.Annotations.OfType().Single(v => v.Target == "/.pgweb/bookmarks"); var postgresInstances = builder.ApplicationBuilder.Resources.OfType(); - if (!Directory.Exists(serverFileMount.Source!)) - { - Directory.CreateDirectory(serverFileMount.Source!); - } - foreach (var postgresDatabase in postgresInstances) { var user = postgresDatabase.Parent.UserNameParameter?.Value ?? "postgres"; diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index ac6f0f48e1..18bcb851d9 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -305,7 +305,7 @@ await pipeline.ExecuteAsync(async (ctx) => { resourceLogger.LogError("Could not import Redis databases into RedisInsight. Reason: {reason}", ex.Message); } - }; + } } } @@ -466,6 +466,6 @@ public static IResourceBuilder WithDataBindMount(this IRes ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(source); - return builder.WithBindMount(source, "/data"); + return builder.WithBindMount(source, "/data", isReadOnly: false, fileMode: RedisInsightResource.s_defaultUnixFileMode); } } diff --git a/src/Aspire.Hosting.Redis/RedisInsightResource.cs b/src/Aspire.Hosting.Redis/RedisInsightResource.cs index 95074b0e6e..1dad5e5f58 100644 --- a/src/Aspire.Hosting.Redis/RedisInsightResource.cs +++ b/src/Aspire.Hosting.Redis/RedisInsightResource.cs @@ -13,6 +13,11 @@ public class RedisInsightResource(string name) : ContainerResource(name) { internal const string PrimaryEndpointName = "http"; + internal static UnixFileMode s_defaultUnixFileMode = + UnixFileMode.GroupExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | + UnixFileMode.OtherExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | + UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite; + private EndpointReference? _primaryEndpoint; /// diff --git a/src/Aspire.Hosting/ApplicationModel/ContainerMountAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ContainerMountAnnotation.cs index 835b9e90cb..ab4aceb9b9 100644 --- a/src/Aspire.Hosting/ApplicationModel/ContainerMountAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/ContainerMountAnnotation.cs @@ -50,6 +50,11 @@ public ContainerMountAnnotation(string? source, string target, ContainerMountTyp /// public string? Source { get; } + /// + /// Gets or sets the Unix file mode for the mount. + /// + public UnixFileMode? UnixFileMode { get; init; } + /// /// Gets the target of the mount. /// diff --git a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs index aa8441ba47..33dd3e7720 100644 --- a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs @@ -90,13 +90,32 @@ public static IResourceBuilder WithVolume(this IResourceBuilder builder /// The target path where the file or directory is mounted in the container. /// A flag that indicates if this is a read-only mount. /// The . - public static IResourceBuilder WithBindMount(this IResourceBuilder builder, string source, string target, bool isReadOnly = false) where T : ContainerResource +#pragma warning disable RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads + public static IResourceBuilder WithBindMount(this IResourceBuilder builder, string source, string target, bool isReadOnly = false) where T : ContainerResource => + builder.WithBindMount(source, target, isReadOnly, fileMode: null); +#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads + + /// + /// Adds a bind mount to a container resource. + /// + /// The resource type. + /// The resource builder. + /// The source path of the mount. This is the path to the file or directory on the host. + /// The target path where the file or directory is mounted in the container. + /// A flag that indicates if this is a read-only mount. + /// The permissions to set on the file or directory on the host. + /// The . + public static IResourceBuilder WithBindMount(this IResourceBuilder builder, string source, string target, bool isReadOnly, UnixFileMode? fileMode) where T : ContainerResource { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(target); - var annotation = new ContainerMountAnnotation(Path.GetFullPath(source, builder.ApplicationBuilder.AppHostDirectory), target, ContainerMountType.BindMount, isReadOnly); + var annotation = new ContainerMountAnnotation(Path.GetFullPath(source, builder.ApplicationBuilder.AppHostDirectory), target, ContainerMountType.BindMount, isReadOnly) + { + UnixFileMode = fileMode + }; + return builder.WithAnnotation(annotation); } diff --git a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs index 63f99ca2da..580430a3ef 100644 --- a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs +++ b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs @@ -1497,6 +1497,23 @@ private void PrepareContainers() foreach (var mount in containerMounts) { + if (mount.Type == ContainerMountType.BindMount && + mount.UnixFileMode is not null && + !OperatingSystem.IsWindows()) + { + // REVIEW: Should we know if it's a file or directory? + var source = mount.Source!; + + if (File.Exists(source) || Directory.Exists(source)) + { + File.SetUnixFileMode(source, mount.UnixFileMode.Value); + } + else if (!Directory.Exists(source)) + { + Directory.CreateDirectory(source, mount.UnixFileMode.Value); + } + } + var volumeSpec = new VolumeMount { Source = mount.Source, @@ -1728,7 +1745,7 @@ private async Task CreateContainerAsync(AppResource cr, ILogger resourceLogger, resourceLogger.LogCritical(ex, "Failed to apply container runtime argument '{ConfigKey}'. A dependency may have failed to start.", arg); _logger.LogDebug(ex, "Failed to apply container runtime argument '{ConfigKey}' to '{ResourceName}'. A dependency may have failed to start.", arg, modelContainerResource.Name); failedToApplyConfiguration = true; - } + } } } diff --git a/src/Aspire.Hosting/PublicAPI.Unshipped.txt b/src/Aspire.Hosting/PublicAPI.Unshipped.txt index 991f39aa17..73907c7ffd 100644 --- a/src/Aspire.Hosting/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting/PublicAPI.Unshipped.txt @@ -1,5 +1,7 @@ #nullable enable Aspire.Hosting.ApplicationModel.ContainerLifetime.Session = 0 -> Aspire.Hosting.ApplicationModel.ContainerLifetime +Aspire.Hosting.ApplicationModel.ContainerMountAnnotation.UnixFileMode.get -> System.IO.UnixFileMode? +Aspire.Hosting.ApplicationModel.ContainerMountAnnotation.UnixFileMode.init -> void Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.Relationships.get -> System.Collections.Immutable.ImmutableArray Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.Relationships.init -> void @@ -232,6 +234,7 @@ Aspire.Hosting.ApplicationModel.ResourceNotificationService.WaitForResourceAsync static Aspire.Hosting.ApplicationModel.ResourceExtensions.HasAnnotationIncludingAncestorsOfType(this Aspire.Hosting.ApplicationModel.IResource! resource) -> bool static Aspire.Hosting.ApplicationModel.ResourceExtensions.HasAnnotationOfType(this Aspire.Hosting.ApplicationModel.IResource! resource) -> bool static Aspire.Hosting.ApplicationModel.ResourceExtensions.TryGetAnnotationsIncludingAncestorsOfType(this Aspire.Hosting.ApplicationModel.IResource! resource, out System.Collections.Generic.IEnumerable? result) -> bool +static Aspire.Hosting.ContainerResourceBuilderExtensions.WithBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, string! target, bool isReadOnly, System.IO.UnixFileMode? fileMode) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ContainerResourceBuilderExtensions.WithBuildArg(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, object? value) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ContainerResourceBuilderExtensions.WithContainerName(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ContainerResourceBuilderExtensions.WithImageRegistry(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? registry) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs index 983c0eeb90..e0882f21bd 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs @@ -162,9 +162,13 @@ public async Task VerifyWithPgWeb() using var app = builder.Build(); + var resourceNotificationService = app.Services.GetRequiredService(); + await app.StartAsync(); - await app.WaitForTextAsync("Starting server...", resourceName: pgWebBuilder.Resource.Name); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + var evt = await resourceNotificationService.WaitForResourceHealthyAsync(pgWebBuilder.Resource.Name, cts.Token); var client = app.CreateHttpClient(pgWebBuilder.Resource.Name, "http"); @@ -177,6 +181,9 @@ public async Task VerifyWithPgWeb() var response = await client.PostAsync("/api/connect", httpContent); var d = await response.Content.ReadAsStringAsync(); + + testOutputHelper.WriteLine("RESPONSE: \r\n" + d); + response.EnsureSuccessStatusCode(); } diff --git a/tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs b/tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs index 5958ceef30..3243273677 100644 --- a/tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs +++ b/tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs @@ -565,7 +565,8 @@ public async Task RedisInsightWithDataShouldPersistStateBetweenUsages(bool useVo } else { - bindMountPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + bindMountPath = Directory.CreateTempSubdirectory().FullName; + redisInsightBuilder1.WithDataBindMount(bindMountPath); } diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs index 37bda3de61..965e0f9e5c 100644 --- a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs +++ b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; +using System.Text.Json; using System.Text.RegularExpressions; using Aspire.Components.Common.Tests; using Aspire.Hosting.Dcp; @@ -487,7 +488,7 @@ public async Task VerifyDockerWithEntrypointWorks() var suffix = app.Services.GetRequiredService>().Value.ResourceNameSuffix; var redisContainer = await KubernetesHelper.GetResourceByNameMatchAsync(s, $"redis-cli-{ReplicaIdRegex}-{suffix}", - r => r.Status?.State == ContainerState.FailedToStart && (r.Status?.Message.Contains("bob") ?? false)).DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout); + r => r.Status?.State == ContainerState.FailedToStart).DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout); Assert.NotNull(redisContainer); Assert.Equal("redis:latest", redisContainer.Spec.Image); @@ -734,30 +735,28 @@ public async Task ProxylessAndProxiedEndpointBothWorkOnSameResource() e.Port = 1543; }, createIfNotExists: true); + testProgram.ServiceABuilder.WithHttpHealthCheck(); + testProgram.ServiceABuilder.WithHttpsHealthCheck(); + testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(_testOutputHelper)); await using var app = testProgram.Build(); - await app.StartAsync().DefaultTimeout(TestConstants.DefaultOrchestratorTestTimeout); + var rns = app.Services.GetRequiredService(); + + await app.StartAsync(); using var cts = AsyncTestHelpers.CreateDefaultTimeoutTokenSource(TestConstants.LongTimeoutDuration); var token = cts.Token; - var urls = string.Empty; + await rns.WaitForResourceHealthyAsync(testProgram.ServiceABuilder.Resource.Name, cts.Token); + var httpEndPoint = app.GetEndpoint(testProgram.ServiceABuilder.Resource.Name, endpointName: "http"); - while (true) - { - try - { - using var client = new HttpClient(); - urls = await client.GetStringAsync($"{httpEndPoint}urls", token); - break; - } - catch - { - await Task.Delay(100, token); - } - } + using var client = new HttpClient(); + var urls = await client.GetStringAsync($"{httpEndPoint}urls", token); + + var urlsData = JsonSerializer.Deserialize(urls)!; + Assert.Equal(2, urlsData.Length); Assert.Contains(httpEndPoint.ToString().Trim('/'), urls); @@ -765,20 +764,8 @@ public async Task ProxylessAndProxiedEndpointBothWorkOnSameResource() var httpsEndpoint = app.GetEndpoint(testProgram.ServiceABuilder.Resource.Name, endpointName: "https"); Assert.DoesNotContain(httpsEndpoint.ToString().Trim('/'), urls); - while (true) - { - try - { - using var client = new HttpClient(); - var value = await client.GetStringAsync($"{httpsEndpoint}urls", token).DefaultTimeout(); - Assert.Equal(urls, value); - break; - } - catch (Exception ex) when (ex is not EqualException) - { - await Task.Delay(100, token); - } - } + var value = await client.GetStringAsync($"{httpsEndpoint}urls", token).DefaultTimeout(); + Assert.Equal(urls, value); } [Fact] @@ -822,7 +809,7 @@ public async Task ProxylessContainerCanBeReferenced() Assert.Equal("localhost:1234", env.Value); var list = await s.ListAsync().DefaultTimeout(); - var redisContainer = Assert.Single(list.Where(c => Regex.IsMatch(c.Name(),$"redis-{ReplicaIdRegex}-{suffix}"))) ; + var redisContainer = Assert.Single(list.Where(c => Regex.IsMatch(c.Name(), $"redis-{ReplicaIdRegex}-{suffix}"))); Assert.Equal(1234, Assert.Single(redisContainer.Spec.Ports!).HostPort); var otherRedisEnv = Assert.Single(service.Spec.Env!.Where(e => e.Name == "ConnectionStrings__redisNoPort")); diff --git a/tests/testproject/TestProject.ServiceA/Program.cs b/tests/testproject/TestProject.ServiceA/Program.cs index faaccbd718..4cdf2d1339 100644 --- a/tests/testproject/TestProject.ServiceA/Program.cs +++ b/tests/testproject/TestProject.ServiceA/Program.cs @@ -10,6 +10,6 @@ app.MapGet("/", () => "Hello World!"); app.MapGet("/pid", () => Environment.ProcessId); -app.MapGet("/urls", (IServiceProvider sp) => sp.GetService()?.Features?.Get()?.Addresses); +app.MapGet("/urls", (IServer server) => server.Features?.Get()?.Addresses ?? []); app.Run();