| File: KubernetesDeployTypeScriptTests.cs | Web Access |
| Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Cli.EndToEnd.Tests.Helpers; using Hex1b.Automation; using Xunit; namespace Aspire.Cli.EndToEnd.Tests; /// <summary> /// E2E test for <c>aspire deploy</c> to Kubernetes with a TypeScript AppHost created from the starter template. /// Creates an Express/React project, adds Kubernetes support, and deploys to a KinD cluster. /// </summary> public sealed class KubernetesDeployTypeScriptTests(ITestOutputHelper output) { private const string ProjectName = "K8sTsTest"; [Fact] [CaptureWorkspaceOnFailure] public async Task DeployTypeScriptAppToKubernetes() { var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var strategy = CliInstallStrategy.Detect(output.WriteLine); using var workspace = TemporaryWorkspace.Create(output); var clusterName = KubernetesDeployTestHelpers.GenerateUniqueClusterName(); var k8sNamespace = $"test-{clusterName[..16]}"; output.WriteLine($"Cluster name: {clusterName}"); output.WriteLine($"Namespace: {k8sNamespace}"); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, mountDockerSocket: true, workspace: workspace); var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await using var terminalRun = CliE2ETestHelpers.StartRun(terminal, workspace, auto, counter, output, TestContext.Current.CancellationToken); await auto.PrepareDockerEnvironmentAsync(counter, workspace); await auto.InstallAspireCliAsync(strategy, counter); await auto.VerifyPullRequestCliVersionAsync(counter); try { // ===================================================================== // Phase 1: Install KinD + Helm, create cluster with local registry // ===================================================================== await auto.InstallKindAndHelmAsync(counter); await auto.CreateKindClusterWithRegistryAsync(counter, clusterName); // ===================================================================== // Phase 2: Create TypeScript project from template // ===================================================================== await auto.AspireNewAsync(ProjectName, counter, template: AspireTemplate.ExpressReact); await auto.TypeAsync($"cd {ProjectName}"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Add Kubernetes hosting package await auto.TypeAsync("aspire add Aspire.Hosting.Kubernetes"); await auto.EnterAsync(); await auto.WaitForAspireAddCompletionAsync(counter, TimeSpan.FromMinutes(2)); // Regenerate TypeScript SDK with Kubernetes types await auto.TypeAsync("aspire restore"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("SDK code restored successfully", timeout: TimeSpan.FromMinutes(3)); await auto.WaitForSuccessPromptAsync(counter); // ===================================================================== // Phase 3: Modify apphost.mts to add Kubernetes environment // ===================================================================== var projectDir = Path.Combine(workspace.WorkspaceRoot.FullName, ProjectName); var appHostFilePath = Path.Combine(projectDir, "apphost.mts"); output.WriteLine($"Looking for apphost.mts at: {appHostFilePath}"); var content = File.ReadAllText(appHostFilePath); // Add Kubernetes environment with Helm configuration before build().run() content = content.Replace( "await builder.build().run();", """ // Add container registry for image push const registryEndpoint = await builder.addParameter("registryendpoint"); await builder.addContainerRegistry("registry", registryEndpoint); // Register parameters before using them in the Helm callback const k8sNamespace = await builder.addParameter("namespace"); const chartVersion = await builder.addParameter("chartversion"); // Add Kubernetes environment with Helm deployment const k8sEnv = await builder.addKubernetesEnvironment("env"); await k8sEnv.withHelm({ configure: async (helm) => { await helm.withNamespace(k8sNamespace); await helm.withChartVersion(chartVersion); }, }); await builder.build().run(); """); File.WriteAllText(appHostFilePath, content); output.WriteLine("Modified apphost.mts with Kubernetes environment configuration"); // ===================================================================== // Phase 4: Run aspire deploy interactively // ===================================================================== await auto.AspireDeployInteractiveAsync( counter, parameterResponses: [ ("registryendpoint", "localhost:5001"), ("namespace", k8sNamespace), ("chartversion", "0.1.0"), ]); // ===================================================================== // Phase 5: Verify the deployment // ===================================================================== // Verify pods are running in the namespace await auto.TypeAsync($"kubectl get pods -n {k8sNamespace} --no-headers"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter, timeout: TimeSpan.FromSeconds(30)); // ===================================================================== // Phase 6: Cleanup // ===================================================================== await auto.CleanupKubernetesDeploymentAsync(counter, clusterName); } finally { await KubernetesDeployTestHelpers.CleanupKindClusterOutOfBandAsync(clusterName, output); } } }