diff --git a/Aspire.sln b/Aspire.sln index 0a0d557c07..2a2b351806 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -512,6 +512,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Redis.Tests", "tests\Aspire.Hosting.Redis.Tests\Aspire.Hosting.Redis.Tests.csproj", "{1BC02557-B78B-48CE-9D3C-488A6B7672F4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Garnet.Tests", "tests\Aspire.Hosting.Garnet.Tests\Aspire.Hosting.Garnet.Tests.csproj", "{CAA4A93F-6BEB-42EB-8680-C1CF72928023}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.PostgreSQL.Tests", "tests\Aspire.Hosting.PostgreSQL.Tests\Aspire.Hosting.PostgreSQL.Tests.csproj", "{7425E5B2-BC47-4521-AC40-B8CECA329E08}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Qdrant.Tests", "tests\Aspire.Hosting.Qdrant.Tests\Aspire.Hosting.Qdrant.Tests.csproj", "{8E2AA85E-C351-47B4-AF91-58557FAD5840}" @@ -543,6 +544,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Keycloak.Authentication.Tests", "tests\Aspire.Keycloak.Authentication.Tests\Aspire.Keycloak.Authentication.Tests.csproj", "{48FF09E9-7D33-4A3F-9FF2-4C43A219C7B7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Nats.Tests", "tests\Aspire.Hosting.Nats.Tests\Aspire.Hosting.Nats.Tests.csproj", "{F492357C-682E-4CBB-A374-1A124B3976A3}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Azure.Tests", "tests\Aspire.Hosting.Azure.Tests\Aspire.Hosting.Azure.Tests.csproj", "{8691F993-7B19-496E-B8E1-EF1199ACF2E1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestShop.AppHost", "playground\TestShop\TestShop.AppHost\TestShop.AppHost.csproj", "{DB3E1AD8-87F6-414D-B46F-A0DC334AECCD}" @@ -555,6 +557,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalR.AppHost", "playgrou EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPubSub.AppHost", "playground\webpubsub\WebPubSub.AppHost\WebPubSub.AppHost.csproj", "{1419BDCB-47EB-43EB-9149-C935B7208A72}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.NodeJs.Tests", "tests\Aspire.Hosting.NodeJs.Tests\Aspire.Hosting.NodeJs.Tests.csproj", "{50450FBB-CD10-4281-B22C-7FF86CEE9D9F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1377,14 +1381,6 @@ Global {CAA4A93F-6BEB-42EB-8680-C1CF72928023}.Debug|Any CPU.Build.0 = Debug|Any CPU {CAA4A93F-6BEB-42EB-8680-C1CF72928023}.Release|Any CPU.ActiveCfg = Release|Any CPU {CAA4A93F-6BEB-42EB-8680-C1CF72928023}.Release|Any CPU.Build.0 = Release|Any CPU - {8E2AA85E-C351-47B4-AF91-58557FAD5840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E2AA85E-C351-47B4-AF91-58557FAD5840}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.Build.0 = Release|Any CPU - {986886B7-0E38-4890-92C3-5B46DE322DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {986886B7-0E38-4890-92C3-5B46DE322DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {986886B7-0E38-4890-92C3-5B46DE322DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {986886B7-0E38-4890-92C3-5B46DE322DAF}.Release|Any CPU.Build.0 = Release|Any CPU {7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.Build.0 = Debug|Any CPU {7425E5B2-BC47-4521-AC40-B8CECA329E08}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1469,6 +1465,10 @@ Global {1419BDCB-47EB-43EB-9149-C935B7208A72}.Debug|Any CPU.Build.0 = Debug|Any CPU {1419BDCB-47EB-43EB-9149-C935B7208A72}.Release|Any CPU.ActiveCfg = Release|Any CPU {1419BDCB-47EB-43EB-9149-C935B7208A72}.Release|Any CPU.Build.0 = Release|Any CPU + {50450FBB-CD10-4281-B22C-7FF86CEE9D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50450FBB-CD10-4281-B22C-7FF86CEE9D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50450FBB-CD10-4281-B22C-7FF86CEE9D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50450FBB-CD10-4281-B22C-7FF86CEE9D9F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1698,9 +1698,6 @@ Global {9FAE1602-2C69-4D24-8655-A164489441E8} = {C424395C-1235-41A4-BF55-07880A04368C} {DF00FDA3-D3EC-4E07-B4EC-0EBB57A813A4} = {77CFE74A-32EE-400C-8930-5025E8555256} {5CB63205-24F4-4388-A41B-BAF3BEA59866} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} - {588CD2D7-EE70-43C1-8233-330854BDF53C} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} - {588CD2D7-EE70-43C1-8233-330854BDF53C} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} - {E6BE41D3-872C-47D2-B5B1-78C37AFAEAF9} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} {9357EC71-823B-433A-9993-B7CB2FA082D1} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} {3F7B206E-5457-458F-AA81-9449FA3C1B5C} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} {6C71A90C-30AE-45D7-9347-D66F9B257CBE} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} @@ -1717,8 +1714,6 @@ Global {830A89EC-4029-4753-B25A-068BAE37DEC7} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} {1BC02557-B78B-48CE-9D3C-488A6B7672F4} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {CAA4A93F-6BEB-42EB-8680-C1CF72928023} = {830A89EC-4029-4753-B25A-068BAE37DEC7} - {8E2AA85E-C351-47B4-AF91-58557FAD5840} = {830A89EC-4029-4753-B25A-068BAE37DEC7} - {986886B7-0E38-4890-92C3-5B46DE322DAF} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {7425E5B2-BC47-4521-AC40-B8CECA329E08} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {8E2AA85E-C351-47B4-AF91-58557FAD5840} = {830A89EC-4029-4753-B25A-068BAE37DEC7} {986886B7-0E38-4890-92C3-5B46DE322DAF} = {830A89EC-4029-4753-B25A-068BAE37DEC7} @@ -1741,6 +1736,7 @@ Global {355F724F-D24F-45C6-8914-574385F6FC89} = {8BAF2119-8370-4E9E-A887-D92506F8C727} {F1D00709-50F2-4533-B38F-3517C0EDEAEE} = {E6985EED-47E3-4EAC-8222-074E5410CEDC} {1419BDCB-47EB-43EB-9149-C935B7208A72} = {90A70EFA-F26A-49E0-A375-DB461E4E0E25} + {50450FBB-CD10-4281-B22C-7FF86CEE9D9F} = {830A89EC-4029-4753-B25A-068BAE37DEC7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C} diff --git a/tests/Aspire.Components.Common.Tests/FileUtil.cs b/tests/Aspire.Components.Common.Tests/FileUtil.cs index f9db30cdb3..de2052565f 100644 --- a/tests/Aspire.Components.Common.Tests/FileUtil.cs +++ b/tests/Aspire.Components.Common.Tests/FileUtil.cs @@ -9,15 +9,36 @@ internal static class FileUtil { public static string? FindFullPathFromPath(string command) => FindFullPathFromPath(command, Environment.GetEnvironmentVariable("PATH"), Path.PathSeparator, File.Exists); - internal static string? FindFullPathFromPath(string command, string? pathVariable, char pathSeparator, Func fileExists) + private static string? FindFullPathFromPath(string command, string? pathVariable, char pathSeparator, Func fileExists) { Debug.Assert(!string.IsNullOrWhiteSpace(command)); - if (OperatingSystem.IsWindows() && !command.EndsWith(".exe")) + var fullPath = FindFullPath(command, pathVariable, pathSeparator, fileExists); + if (fullPath is not null) { - command += ".exe"; + return fullPath; } + if (OperatingSystem.IsWindows()) + { + // On Windows, we need to check for the command with all possible extensions. + foreach (var extension in Environment.GetEnvironmentVariable("PATHEXT")?.Split(';') ?? Array.Empty()) + { + var fileName = command.EndsWith(extension, StringComparison.OrdinalIgnoreCase) ? command : command + extension; + + fullPath = FindFullPath(fileName, pathVariable, pathSeparator, fileExists); + if (fullPath is not null) + { + return fullPath; + } + } + } + + return null; + } + + private static string? FindFullPath(string command, string? pathVariable, char pathSeparator, Func fileExists) + { foreach (var directory in (pathVariable ?? string.Empty).Split(pathSeparator)) { var fullPath = Path.Combine(directory, command); diff --git a/tests/Aspire.Hosting.NodeJs.Tests/AddNodeAppTests.cs b/tests/Aspire.Hosting.NodeJs.Tests/AddNodeAppTests.cs new file mode 100644 index 0000000000..8f9721f39f --- /dev/null +++ b/tests/Aspire.Hosting.NodeJs.Tests/AddNodeAppTests.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.Utils; +using Xunit; + +namespace Aspire.Hosting.NodeJs.Tests; + +public class AddNodeAppTests +{ + [Fact] + public async Task NodeAppIsExecutableResource() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var nodeApp = builder.AddNodeApp("nodeapp", "..\\foo\\app.js") + .WithHttpEndpoint(port: 5031, env: "PORT"); + var manifest = await ManifestUtils.GetManifest(nodeApp.Resource); + + var expectedManifest = $$""" + { + "type": "executable.v0", + "workingDirectory": "../../../../../tests/foo", + "command": "node", + "args": [ + "..\\foo\\app.js" + ], + "env": { + "NODE_ENV": "development", + "PORT": "{nodeapp.bindings.http.targetPort}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "port": 5031, + "targetPort": 8000 + } + } + } + """; + Assert.Equal(expectedManifest, manifest.ToString()); + + var npmApp = builder.AddNpmApp("npmapp", "..\\foo") + .WithHttpEndpoint(port: 5032, env: "PORT"); + manifest = await ManifestUtils.GetManifest(npmApp.Resource); + + expectedManifest = $$""" + { + "type": "executable.v0", + "workingDirectory": "../../../../../tests/foo", + "command": "npm", + "args": [ + "run", + "start" + ], + "env": { + "NODE_ENV": "development", + "PORT": "{npmapp.bindings.http.targetPort}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "port": 5032, + "targetPort": 8000 + } + } + } + """; + Assert.Equal(expectedManifest, manifest.ToString()); + } +} diff --git a/tests/Aspire.Hosting.NodeJs.Tests/Aspire.Hosting.NodeJs.Tests.csproj b/tests/Aspire.Hosting.NodeJs.Tests/Aspire.Hosting.NodeJs.Tests.csproj new file mode 100644 index 0000000000..982a3fdda9 --- /dev/null +++ b/tests/Aspire.Hosting.NodeJs.Tests/Aspire.Hosting.NodeJs.Tests.csproj @@ -0,0 +1,13 @@ + + + + $(NetCurrent) + + + + + + + + + diff --git a/tests/Aspire.Hosting.NodeJs.Tests/NodeAppFixture.cs b/tests/Aspire.Hosting.NodeJs.Tests/NodeAppFixture.cs new file mode 100644 index 0000000000..35034081bd --- /dev/null +++ b/tests/Aspire.Hosting.NodeJs.Tests/NodeAppFixture.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Testing; +using Aspire.Hosting.Utils; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Aspire.Hosting.NodeJs.Tests; + +/// +/// TestProgram with node and npm apps. +/// +public class NodeAppFixture(IMessageSink diagnosticMessageSink) : IAsyncLifetime +{ + private DistributedApplication? _app; + private string? _nodeAppPath; + + public DistributedApplication App => _app ?? throw new InvalidOperationException("DistributedApplication is not initialized."); + + public IResourceBuilder? NodeAppBuilder { get; private set; } + public IResourceBuilder? NpmAppBuilder { get; private set; } + + public async Task InitializeAsync() + { + var builder = TestDistributedApplicationBuilder.Create(); + builder.Services.AddXunitLogging(new TestOutputWrapper(diagnosticMessageSink)); + + _nodeAppPath = CreateNodeApp(); + var scriptPath = Path.Combine(_nodeAppPath, "app.js"); + + NodeAppBuilder = builder.AddNodeApp("nodeapp", scriptPath) + .WithHttpEndpoint(port: 5031, env: "PORT"); + + NpmAppBuilder = builder.AddNpmApp("npmapp", _nodeAppPath) + .WithHttpEndpoint(port: 5032, env: "PORT"); + + _app = builder.Build(); + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + + await _app.StartAsync(cts.Token); + + await WaitReadyStateAsync(cts.Token); + } + + public async Task DisposeAsync() + { + if (_app is not null) + { + await _app.StopAsync(); + await _app.DisposeAsync(); + } + + if (_nodeAppPath is not null) + { + Directory.Delete(_nodeAppPath, recursive: true); + } + } + + private static string CreateNodeApp() + { + var tempDir = Directory.CreateTempSubdirectory("aspire-nodejs-tests").FullName; + + File.WriteAllText(Path.Combine(tempDir, "app.js"), + """ + const http = require('http'); + const port = process.env.PORT ?? 3000; + + const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + if (process.env.npm_lifecycle_event === undefined) { + res.end('Hello from node!'); + } else { + res.end('Hello from npm!'); + } + }); + + server.listen(port, () => { + console.log('Web server running on on %s', port); + }); + """); + + File.WriteAllText(Path.Combine(tempDir, "package.json"), + """ + { + "scripts": { + "start": "node app.js" + } + } + """); + + return tempDir; + } + + private async Task WaitReadyStateAsync(CancellationToken cancellationToken = default) + { + using var client = App.CreateHttpClient(NodeAppBuilder!.Resource.Name, endpointName: "http"); + await client.GetStringAsync("/", cancellationToken); + } + + private sealed class TestOutputWrapper(IMessageSink messageSink) : ITestOutputHelper + { + public void WriteLine(string message) + { + messageSink.OnMessage(new DiagnosticMessage(message)); + } + + public void WriteLine(string format, params object[] args) + { + messageSink.OnMessage(new DiagnosticMessage(string.Format(CultureInfo.CurrentCulture, format, args))); + } + } +} diff --git a/tests/Aspire.Hosting.Tests/Node/NodeFunctionalTests.cs b/tests/Aspire.Hosting.NodeJs.Tests/NodeFunctionalTests.cs similarity index 60% rename from tests/Aspire.Hosting.Tests/Node/NodeFunctionalTests.cs rename to tests/Aspire.Hosting.NodeJs.Tests/NodeFunctionalTests.cs index 473666c99a..53dd4612af 100644 --- a/tests/Aspire.Hosting.Tests/Node/NodeFunctionalTests.cs +++ b/tests/Aspire.Hosting.NodeJs.Tests/NodeFunctionalTests.cs @@ -5,10 +5,9 @@ using Aspire.Hosting.Testing; using Xunit; -namespace Aspire.Hosting.Tests.Node; +namespace Aspire.Hosting.NodeJs.Tests; -[Collection("NodeApp")] -public class NodeFunctionalTests +public class NodeFunctionalTests : IClassFixture { private readonly NodeAppFixture _nodeJsFixture; @@ -22,13 +21,11 @@ public NodeFunctionalTests(NodeAppFixture nodeJsFixture) [ActiveIssue("https://github.com/dotnet/aspire/issues/4508", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningOnCI))] public async Task VerifyNodeAppWorks() { - var testProgram = _nodeJsFixture.TestProgram; - using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - using var nodeClient = testProgram.App!.CreateHttpClient(testProgram.NodeAppBuilder!.Resource.Name, "http"); - var response0 = await nodeClient.GetStringAsync("/", cts.Token); + using var nodeClient = _nodeJsFixture.App.CreateHttpClient(_nodeJsFixture.NodeAppBuilder!.Resource.Name, "http"); + var response = await nodeClient.GetStringAsync("/", cts.Token); - Assert.Equal("Hello from node!", response0); + Assert.Equal("Hello from node!", response); } [Fact] @@ -36,12 +33,10 @@ public async Task VerifyNodeAppWorks() [ActiveIssue("https://github.com/dotnet/aspire/issues/4508", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningOnCI))] public async Task VerifyNpmAppWorks() { - var testProgram = _nodeJsFixture.TestProgram; - using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - using var npmClient = testProgram.App!.CreateHttpClient(testProgram.NpmAppBuilder!.Resource.Name, "http"); - var response0 = await npmClient.GetStringAsync("/", cts.Token); + using var npmClient = _nodeJsFixture.App.CreateHttpClient(_nodeJsFixture.NpmAppBuilder!.Resource.Name, "http"); + var response = await npmClient.GetStringAsync("/", cts.Token); - Assert.Equal("Hello from npm!", response0); + Assert.Equal("Hello from npm!", response); } } diff --git a/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj b/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj index 80fee8b0b3..d30a926fa3 100644 --- a/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj +++ b/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs index c8590ab659..764c668341 100644 --- a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs +++ b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs @@ -4,12 +4,14 @@ using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; +using Aspire.Components.Common.Tests; using Aspire.Hosting.Dcp; using Aspire.Hosting.Dcp.Model; using Aspire.Hosting.Lifecycle; -using Aspire.Hosting.Utils; using Aspire.Hosting.Testing; +using Aspire.Hosting.Testing.Tests; using Aspire.Hosting.Tests.Helpers; +using Aspire.Hosting.Utils; using k8s.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -18,8 +20,6 @@ using Xunit; using Xunit.Abstractions; using Xunit.Sdk; -using Aspire.Components.Common.Tests; -using Aspire.Hosting.Testing.Tests; namespace Aspire.Hosting.Tests; @@ -261,7 +261,7 @@ public async Task VerifyDockerAppWorks() [ActiveIssue("https://github.com/dotnet/aspire/issues/4651", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningOnCI))] public async Task SpecifyingEnvPortInEndpointFlowsToEnv() { - using var testProgram = CreateTestProgram(includeNodeApp: true, randomizePorts: false); + using var testProgram = CreateTestProgram(randomizePorts: false); testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(_testOutputHelper)); @@ -271,6 +271,9 @@ public async Task SpecifyingEnvPortInEndpointFlowsToEnv() testProgram.AppBuilder.AddContainer("redis0", "redis") .WithEndpoint(targetPort: 6379, name: "tcp", env: "REDIS_PORT"); + testProgram.AppBuilder.AddNodeApp("nodeapp", "fakePath") + .WithHttpEndpoint(port: 5031, env: "PORT"); + await using var app = testProgram.Build(); var kubernetes = app.Services.GetRequiredService(); @@ -532,7 +535,7 @@ public async Task VerifyDockerWithVolumeWorksWithName() [ActiveIssue("https://github.com/dotnet/aspire/issues/4651", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningOnCI))] public async Task KubernetesHasResourceNameForContainersAndExes() { - using var testProgram = CreateTestProgram(includeIntegrationServices: true, includeNodeApp: true); + using var testProgram = CreateTestProgram(includeIntegrationServices: true); testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(_testOutputHelper)); await using var app = testProgram.Build(); @@ -550,8 +553,6 @@ public async Task KubernetesHasResourceNameForContainersAndExes() "serviceb", "servicec", "workera", - "nodeapp", - "npmapp", "integrationservicea" }; @@ -845,13 +846,11 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appMode private static TestProgram CreateTestProgram( string[]? args = null, bool includeIntegrationServices = false, - bool includeNodeApp = false, bool disableDashboard = true, bool randomizePorts = true) => TestProgram.Create( args, includeIntegrationServices: includeIntegrationServices, - includeNodeApp: includeNodeApp, disableDashboard: disableDashboard, randomizePorts: randomizePorts); } diff --git a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs index 8b31b7c462..34274cdd5a 100644 --- a/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs +++ b/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs @@ -111,9 +111,13 @@ public async Task WithContainerRegistryUpdatesContainerImageAnnotationsDuringPub [Fact] public void EnsureExecutablesWithDockerfileProduceDockerfilev0Manifest() { - using var program = CreateTestProgramJsonDocumentManifestPublisher(includeNodeApp: true); - program.NodeAppBuilder!.WithHttpsEndpoint(targetPort: 3000, env: "HTTPS_PORT") + using var program = CreateTestProgramJsonDocumentManifestPublisher(); + program.AppBuilder + .AddNodeApp("nodeapp", "fakePath") + .WithHttpsEndpoint(targetPort: 3000, env: "HTTPS_PORT") .PublishAsDockerFile(); + program.AppBuilder + .AddNpmApp("npmapp", "fakeDirectory"); // Build AppHost so that publisher can be resolved. program.Build(); @@ -303,50 +307,6 @@ public void EnsureAllRabbitMQManifestTypesHaveVersion0Suffix() Assert.Equal("container.v0", server.GetProperty("type").GetString()); } - [Fact] - public void NodeAppIsExecutableResource() - { - using var program = CreateTestProgramJsonDocumentManifestPublisher(); - - program.AppBuilder.AddNodeApp("nodeapp", "..\\foo\\app.js") - .WithHttpEndpoint(port: 5031, env: "PORT"); - program.AppBuilder.AddNpmApp("npmapp", "..\\foo") - .WithHttpEndpoint(port: 5032, env: "PORT"); - - // Build AppHost so that publisher can be resolved. - program.Build(); - var publisher = program.GetManifestPublisher(); - - program.Run(); - - var resources = publisher.ManifestDocument.RootElement.GetProperty("resources"); - - var nodeApp = resources.GetProperty("nodeapp"); - var npmApp = resources.GetProperty("npmapp"); - - static void AssertNodeResource(TestProgram program, string resourceName, JsonElement jsonElement, string expectedCommand, string[] expectedArgs) - { - var s = jsonElement.ToString(); - Assert.Equal("executable.v0", jsonElement.GetProperty("type").GetString()); - - var bindings = jsonElement.GetProperty("bindings"); - var httpBinding = bindings.GetProperty("http"); - - Assert.Equal("http", httpBinding.GetProperty("scheme").GetString()); - - var env = jsonElement.GetProperty("env"); - Assert.Equal($$"""{{{resourceName}}.bindings.http.targetPort}""", env.GetProperty("PORT").GetString()); - Assert.Equal(program.AppBuilder.Environment.EnvironmentName.ToLowerInvariant(), env.GetProperty("NODE_ENV").GetString()); - - var command = jsonElement.GetProperty("command"); - Assert.Equal(expectedCommand, command.GetString()); - Assert.Equal(expectedArgs, jsonElement.GetProperty("args").EnumerateArray().Select(e => e.GetString()).ToArray()); - } - - AssertNodeResource(program, "nodeapp", nodeApp, "node", ["..\\foo\\app.js"]); - AssertNodeResource(program, "npmapp", npmApp, "npm", ["run", "start"]); - } - [Fact] public void MetadataPropertyNotEmittedWhenMetadataNotAdded() { diff --git a/tests/Aspire.Hosting.Tests/TestProgramFixture.cs b/tests/Aspire.Hosting.Tests/TestProgramFixture.cs index 33d513de22..773596b183 100644 --- a/tests/Aspire.Hosting.Tests/TestProgramFixture.cs +++ b/tests/Aspire.Hosting.Tests/TestProgramFixture.cs @@ -74,23 +74,6 @@ public override async Task WaitReadyStateAsync(CancellationToken cancellationTok } } -/// -/// TestProgram with node app but no dashboard or integration services. -/// -/// -/// Use [Collection("NodeApp")] to inject this fixture in test constructors. -/// -public class NodeAppFixture : TestProgramFixture -{ - public override TestProgram CreateTestProgram() => TestProgram.Create(includeNodeApp: true, randomizePorts: false); - - public override async Task WaitReadyStateAsync(CancellationToken cancellationToken = default) - { - using var client = TestProgram.App!.CreateHttpClient(TestProgram.NodeAppBuilder!.Resource.Name, endpointName: "http"); - await client.GetStringAsync("/", cancellationToken); - } -} - [CollectionDefinition("SlimTestProgram")] public class SlimTestProgramCollection : ICollectionFixture { @@ -98,11 +81,3 @@ public class SlimTestProgramCollection : ICollectionFixture interfaces. } - -[CollectionDefinition("NodeApp")] -public class NodeJsCollection : ICollectionFixture -{ - // This class has no code, and is never created. Its purpose is simply - // to be the place to apply [CollectionDefinition] and all the - // ICollectionFixture<> interfaces. -} diff --git a/tests/testproject/TestProject.AppHost/Program.cs b/tests/testproject/TestProject.AppHost/Program.cs index 413c25fe15..94b30bad34 100644 --- a/tests/testproject/TestProject.AppHost/Program.cs +++ b/tests/testproject/TestProject.AppHost/Program.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -var testProgram = TestProgram.Create(args, includeIntegrationServices: true, disableDashboard: false, includeNodeApp: true); +var testProgram = TestProgram.Create(args, includeIntegrationServices: true, disableDashboard: false); // Run a task to read from the console and stop the app if an external process sends "Stop". // This allows for easier control than sending CTRL+C to the console in a cross-platform way. diff --git a/tests/testproject/TestProject.AppHost/TestProgram.cs b/tests/testproject/TestProject.AppHost/TestProgram.cs index 92404fab4c..2c00852560 100644 --- a/tests/testproject/TestProject.AppHost/TestProgram.cs +++ b/tests/testproject/TestProject.AppHost/TestProgram.cs @@ -17,7 +17,6 @@ private TestProgram( string assemblyName, bool disableDashboard, bool includeIntegrationServices, - bool includeNodeApp, bool allowUnsecuredTransport, bool randomizePorts) { @@ -63,20 +62,6 @@ private TestProgram( ServiceCBuilder = AppBuilder.AddProject("servicec", launchProfileName: "http"); WorkerABuilder = AppBuilder.AddProject("workera"); - if (includeNodeApp) - { - // Relative to this project so that it doesn't changed based on - // where this code is referenced from. - var path = Path.Combine(Projects.TestProject_AppHost.ProjectPath, "..", "nodeapp"); - var scriptPath = Path.Combine(path, "app.js"); - - NodeAppBuilder = AppBuilder.AddNodeApp("nodeapp", scriptPath) - .WithHttpEndpoint(port: 5031, env: "PORT"); - - NpmAppBuilder = AppBuilder.AddNpmApp("npmapp", path) - .WithHttpEndpoint(port: 5032, env: "PORT"); - } - if (includeIntegrationServices) { IntegrationServiceABuilder = AppBuilder.AddProject("integrationservicea"); @@ -144,7 +129,6 @@ private TestProgram( public static TestProgram Create( string[]? args = null, bool includeIntegrationServices = false, - bool includeNodeApp = false, bool disableDashboard = true, bool allowUnsecuredTransport = true, bool randomizePorts = true) @@ -154,7 +138,6 @@ public static TestProgram Create( assemblyName: typeof(T).Assembly.FullName!, disableDashboard: disableDashboard, includeIntegrationServices: includeIntegrationServices, - includeNodeApp: includeNodeApp, allowUnsecuredTransport: allowUnsecuredTransport, randomizePorts: randomizePorts); } @@ -165,8 +148,6 @@ public static TestProgram Create( public IResourceBuilder ServiceCBuilder { get; private set; } public IResourceBuilder WorkerABuilder { get; private set; } public IResourceBuilder? IntegrationServiceABuilder { get; private set; } - public IResourceBuilder? NodeAppBuilder { get; private set; } - public IResourceBuilder? NpmAppBuilder { get; private set; } public DistributedApplication? App { get; private set; } public List> ServiceProjectBuilders => [ServiceABuilder, ServiceBBuilder, ServiceCBuilder]; diff --git a/tests/testproject/TestProject.AppHost/TestProject.AppHost.csproj b/tests/testproject/TestProject.AppHost/TestProject.AppHost.csproj index 28f42236bb..97dcd63c4b 100644 --- a/tests/testproject/TestProject.AppHost/TestProject.AppHost.csproj +++ b/tests/testproject/TestProject.AppHost/TestProject.AppHost.csproj @@ -15,7 +15,6 @@ - diff --git a/tests/testproject/nodeapp/Dockerfile b/tests/testproject/nodeapp/Dockerfile deleted file mode 100644 index 969acfef96..0000000000 --- a/tests/testproject/nodeapp/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM mcr.microsoft.com/cbl-mariner/busybox:2.0 -CMD [ "ping", "localhost" ] diff --git a/tests/testproject/nodeapp/app.js b/tests/testproject/nodeapp/app.js deleted file mode 100644 index f9f2bab42a..0000000000 --- a/tests/testproject/nodeapp/app.js +++ /dev/null @@ -1,16 +0,0 @@ -const http = require('http'); -const port = process.env.PORT ?? 3000; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - if (process.env.npm_lifecycle_event === undefined) { - res.end('Hello from node!'); - } else { - res.end('Hello from npm!'); - } -}); - -server.listen(port, () => { - console.log('Web server running on on %s', port); -}); diff --git a/tests/testproject/nodeapp/package.json b/tests/testproject/nodeapp/package.json deleted file mode 100644 index 7660e52956..0000000000 --- a/tests/testproject/nodeapp/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "start": "node app.js" - } -}