Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. Project Tye includes a local orchestrator to make developing microservices easier and the ability to deploy microservices to Kubernetes with min
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1395 lines
60 KiB

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.CommandLine.IO;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Microsoft.Tye;
using Microsoft.Tye.Hosting;
using Microsoft.Tye.Hosting.Model;
using Microsoft.Tye.Hosting.Model.V1;
using Test.Infrastructure;
using Xunit;
using Xunit.Abstractions;
using static Test.Infrastructure.TestHelpers;
namespace E2ETest
{
public class TyeRunTests
{
private readonly ITestOutputHelper _output;
private readonly TestOutputLogEventSink _sink;
private readonly JsonSerializerOptions _options;
public TyeRunTests(ITestOutputHelper output)
{
_output = output;
_sink = new TestOutputLogEventSink(output);
_options = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
_options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
}
[Fact]
public async Task FrontendBackendRunTest()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[Fact(Skip = "Need to figure out how to install func before running")]
public async Task FrontendBackendAzureFunctionTest()
{
// Install to directory
using var tmp = TempDirectory.Create();
await ProcessUtil.RunAsync("npm", "install azure-functions-core-tools@3`", workingDirectory: tmp.DirectoryPath);
using var projectDirectory = CopyTestProjectDirectory("azure-functions");
var content = @$"
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: frontend-backend
services:
- name: backend
azureFunction: backend/
pathToFunc: {tmp.DirectoryPath}/node_modules/azure-functions-core-tools/bin/func.dll
- name: frontend
project: frontend/frontend.csproj";
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
await File.WriteAllTextAsync(projectFile.FullName, content);
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[ConditionalTheory]
[SkipIfDockerNotRunning]
[InlineData("single-project", "mcr.microsoft.com/dotnet/aspnet:6.0")]
[InlineData("single-project-5.0", "mcr.microsoft.com/dotnet/aspnet:5.0")]
public async Task SingleProjectWithDocker_UsesCorrectBaseImage(string projectName, string baseImage)
{
using var projectDirectory = CopyTestProjectDirectory(projectName);
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
// Ensure correct image used
var dockerRunInfo = app.Services.Single().Value.Description.RunInfo as DockerRunInfo;
Assert.Equal(baseImage, dockerRunInfo?.Image);
// Ensure app runs
var testProjectUri = await GetServiceUrl(client, uri, "test-project");
var response = await client.GetAsync(testProjectUri);
Assert.True(response.IsSuccessStatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task FrontendBackendRunTestWithDocker()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[ConditionalTheory]
[SkipIfDockerNotRunning]
[InlineData("Debug")]
[InlineData("Release")]
public async Task FrontendBackendRunTestWithDockerAndBuildConfigurationAsProperty(string buildConfiguration)
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, $"tye-{buildConfiguration.ToLower()}-configuration.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
Assert.True(app.Services.All(s => s.Value.Description.RunInfo != null && ((DockerRunInfo)s.Value.Description.RunInfo).VolumeMappings.Count > 0));
var outputFileInfos = app.Services.Select(s => new FileInfo((s.Value?.Description?.RunInfo as DockerRunInfo)?.VolumeMappings[0].Source ?? throw new InvalidOperationException())).ToList();
Assert.True(outputFileInfos.All(f => f.Directory?.Parent?.Parent?.Name == buildConfiguration));
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task FrontendProjectBackendDocker()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
// Transform the backend into a docker image for testing
var project = (DotnetProjectServiceBuilder)application.Services.First(s => s.Name == "backend");
application.Services.Remove(project);
var outputFileName = project.AssemblyName + ".dll";
var container = new ContainerServiceBuilder(project.Name, $"mcr.microsoft.com/dotnet/aspnet:{project.TargetFrameworkVersion}", ServiceSource.Configuration)
{
IsAspNet = true
};
container.Volumes.Add(new VolumeBuilder(project.PublishDir, name: null, target: "/app"));
container.Args = $"dotnet /app/{outputFileName} {project.Args}";
container.Bindings.AddRange(project.Bindings.Where(b => b.Protocol != "https"));
await ProcessUtil.RunAsync("dotnet", $"publish \"{project.ProjectFile.FullName}\" /nologo", outputDataReceived: _sink.WriteLine, errorDataReceived: _sink.WriteLine);
application.Services.Add(container);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task FrontendDockerBackendProject()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
// Transform the backend into a docker image for testing
var project = (DotnetProjectServiceBuilder)application.Services.First(s => s.Name == "frontend");
application.Services.Remove(project);
var outputFileName = project.AssemblyName + ".dll";
var container = new ContainerServiceBuilder(project.Name, $"mcr.microsoft.com/dotnet/aspnet:{project.TargetFrameworkVersion}", ServiceSource.Configuration)
{
IsAspNet = true
};
container.Dependencies.UnionWith(project.Dependencies);
container.Volumes.Add(new VolumeBuilder(project.PublishDir, name: null, target: "/app"));
container.Args = $"dotnet /app/{outputFileName} {project.Args}";
// We're not setting up the dev cert here
container.Bindings.AddRange(project.Bindings.Where(b => b.Protocol != "https"));
await ProcessUtil.RunAsync("dotnet", $"publish \"{project.ProjectFile.FullName}\" /nologo", outputDataReceived: _sink.WriteLine, errorDataReceived: _sink.WriteLine);
application.Services.Add(container);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[Fact]
public async Task FrontendBackendWatchRunTest()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Watch = true }, async (app, uri) =>
{
// make sure both are running
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
var startupPath = Path.Combine(projectDirectory.DirectoryPath, "frontend", "Startup.cs");
File.AppendAllText(startupPath, "\n");
const int retries = 10;
for (var i = 0; i < retries; i++)
{
var logs = await client.GetStringAsync(new Uri(uri, $"/api/v1/logs/frontend"));
// "Application Started" should be logged twice due to the file change
if (logs.IndexOf("Application started") != logs.LastIndexOf("Application started"))
{
return;
}
await Task.Delay(5000);
}
throw new Exception("Failed to relaunch project with dotnet watch");
});
}
[Fact]
public async Task WebAppWatchRunTest()
{
using var projectDirectory = CopyTestProjectDirectory("web-app");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Watch = true }, async (app, uri) =>
{
// make sure app is running
var appUri = await GetServiceUrl(client, uri, "web-app");
var response = await client.GetAsync(appUri);
Assert.True(response.IsSuccessStatusCode);
var startupPath = Path.Combine(projectDirectory.DirectoryPath, "Pages", "index.cshtml");
File.AppendAllText(startupPath, "\n");
const int retries = 10;
for (var i = 0; i < retries; i++)
{
var logs = await client.GetStringAsync(new Uri(uri, $"/api/v1/logs/web-app"));
// "Application Started" should be logged twice due to the file change
if (logs.IndexOf("Application started") != logs.LastIndexOf("Application started"))
{
return;
}
await Task.Delay(5000);
}
throw new Exception("Failed to relaunch project with dotnet watch");
});
}
[Fact]
public async Task DockerBaseImageAndTagTest()
{
using var projectDirectory = CopyTestProjectDirectory(Path.Combine("frontend-backend", "backend"));
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "backend-baseimage.csproj"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
// Transform the backend into a docker image for testing
var project = (DotnetProjectServiceBuilder)application.Services.First(s => s.Name == "backend-baseimage");
// check ContainerInfo values
Assert.True(string.Equals(project.ContainerInfo!.BaseImageName, "mcr.microsoft.com/dotnet/sdk"));
Assert.True(string.Equals(project.ContainerInfo!.BaseImageTag, "6.0-focal"));
// check projectInfo values
var projectRunInfo = new ProjectRunInfo(project);
Assert.True(string.Equals(projectRunInfo!.ContainerBaseImage, project.ContainerInfo.BaseImageName));
Assert.True(string.Equals(projectRunInfo!.ContainerBaseTag, project.ContainerInfo.BaseImageTag));
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerNamedVolumeTest()
{
using var projectDirectory = CopyTestProjectDirectory("volume-test");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
// Add a volume
var project = ((ProjectServiceBuilder)application.Services[0]);
// Remove the existing volume so we can generate a random one for this test to avoid conflicts
var volumeName = "tye_docker_volumes_test" + Guid.NewGuid().ToString().Substring(0, 10);
project.Volumes.Clear();
project.Volumes.Add(new VolumeBuilder(source: null, name: volumeName, "/data"));
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
Assert.NotNull(serviceUri);
var response = await client.GetAsync(serviceUri);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
await client.PostAsync(serviceUri, new StringContent("Things saved to the volume!"));
Assert.Equal("Things saved to the volume!", await client.GetStringAsync(serviceUri));
});
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
Assert.NotNull(serviceUri);
// The volume has data persisted
Assert.Equal("Things saved to the volume!", await client.GetStringAsync(serviceUri));
});
// Delete the volume
await ContainerEngine.Default.RunAsync($"volume rm {volumeName}");
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerNetworkAssignmentTest()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var dockerNetwork = "tye_docker_network_test" + Guid.NewGuid().ToString().Substring(0, 10);
application.Network = dockerNetwork;
// Create the existing network
await ContainerEngine.Default.RunAsync($"network create {dockerNetwork}");
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
try
{
await RunHostingApplication(
application,
new HostOptions() { Docker = true, },
async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
foreach (var serviceBuilder in application.Services)
{
var serviceUri = await GetServiceUrl(client, uri, serviceBuilder.Name);
var serviceResponse = await client.GetAsync(serviceUri);
Assert.True(serviceResponse.IsSuccessStatusCode);
var serviceResult =
await client.GetStringAsync($"{uri}api/v1/services/{serviceBuilder.Name}");
var service = JsonSerializer.Deserialize<V1Service>(serviceResult, _options);
Assert.NotNull(service);
Assert.Equal(dockerNetwork, service!.Replicas!.FirstOrDefault().Value.DockerNetwork);
}
});
}
finally
{
// Delete the network
await ContainerEngine.Default.RunAsync($"network rm {dockerNetwork}");
}
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerNetworkAssignmentForNonExistingNetworkTest()
{
using var projectDirectory = CopyTestProjectDirectory("frontend-backend");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var dockerNetwork = "tye_docker_network_test" + Guid.NewGuid().ToString().Substring(0, 10);
application.Network = dockerNetwork;
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(
application,
new HostOptions() { Docker = true, },
async (app, uri) =>
{
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
foreach (var serviceBuilder in application.Services)
{
var serviceUri = await GetServiceUrl(client, uri, serviceBuilder.Name);
var serviceResponse = await client.GetAsync(serviceUri);
Assert.True(serviceResponse.IsSuccessStatusCode);
var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceBuilder.Name}");
var service = JsonSerializer.Deserialize<V1Service>(serviceResult, _options);
Assert.NotNull(service);
Assert.NotEqual(dockerNetwork, service!.Replicas!.FirstOrDefault().Value.DockerNetwork);
}
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerHostVolumeTest()
{
using var projectDirectory = CopyTestProjectDirectory("volume-test");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
// Add a volume
var project = (ProjectServiceBuilder)application.Services[0];
using var tempDir = TempDirectory.Create(preferUserDirectoryOnMacOS: true);
project.Volumes.Clear();
project.Volumes.Add(new VolumeBuilder(source: tempDir.DirectoryPath, name: null, target: "/data"));
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
await File.WriteAllTextAsync(Path.Combine(tempDir.DirectoryPath, "file.txt"), "This content came from the host");
var client = new HttpClient(new RetryHandler(handler));
var options = new HostOptions()
{
Docker = true,
};
await RunHostingApplication(application, options, async (app, serviceApi) =>
{
var serviceUri = await GetServiceUrl(client, serviceApi, "volume-test");
Assert.NotNull(serviceUri);
// The volume has data the host mapped data
Assert.Equal("This content came from the host", await client.GetStringAsync(serviceUri));
});
}
[Fact]
public async Task IngressRunTest()
{
using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
var wsClient = new ClientWebSocket();
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var appAUri = await GetServiceUrl(client, uri, "appa");
var appBUri = await GetServiceUrl(client, uri, "appb");
var appAResponse = await client.GetAsync(appAUri);
var appBResponse = await client.GetAsync(appBUri);
Assert.True(appAResponse.IsSuccessStatusCode);
Assert.True(appBResponse.IsSuccessStatusCode);
var responseA = await client.GetAsync(ingressUri + "/A");
var responseB = await client.GetAsync(ingressUri + "/B");
Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());
var requestA = new HttpRequestMessage(HttpMethod.Get, ingressUri);
requestA.Headers.Host = "a.example.com";
var requestB = new HttpRequestMessage(HttpMethod.Get, ingressUri);
requestB.Headers.Host = "b.example.com";
responseA = await client.SendAsync(requestA);
responseB = await client.SendAsync(requestB);
Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());
// checking preservePath behavior
var responsePreservePath = await client.GetAsync(ingressUri + "/C/test");
Assert.Contains("Hit path /C/test", await responsePreservePath.Content.ReadAsStringAsync());
string GetWebSocketUri(string uri)
{
if (uri.StartsWith("http"))
{
return "ws" + uri.Substring(4);
}
else if (uri.StartsWith("https"))
{
return "wss" + uri.Substring(5);
}
throw new NotSupportedException();
}
// Check the websocket endpoint
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var wsUri = GetWebSocketUri(ingressUri);
await wsClient.ConnectAsync(new Uri(wsUri + "/A/ws"), cts.Token);
var data = Encoding.UTF8.GetBytes("Hello World");
await wsClient.SendAsync(data, WebSocketMessageType.Text, endOfMessage: true, cts.Token);
var receiveBuffer = new byte[4096];
var result = await wsClient.ReceiveAsync(receiveBuffer.AsMemory(), cts.Token);
Assert.True(result.EndOfMessage);
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
Assert.Equal(data.Length, result.Count);
Assert.Equal(data, receiveBuffer.AsMemory(0, result.Count).ToArray());
await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "", cts.Token);
});
}
[Fact]
public async Task IngressQueryAndContentProxyingTest()
{
using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var request = new HttpRequestMessage(HttpMethod.Post, ingressUri + "/A/data?key1=value1&key2=value2");
request.Content = new StringContent("some content");
var response = await client.SendAsync(request);
var responseContent =
JsonSerializer.Deserialize<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
Assert.Equal("some content", responseContent!["content"]);
Assert.Equal("?key1=value1&key2=value2", responseContent["query"]);
});
}
[Fact]
public async Task IngressStaticFilesTest()
{
using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-ui.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = true
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var htmlRequest = new HttpRequestMessage(HttpMethod.Get,
ingressUri + "/index.html");
htmlRequest.Headers.Host = "ui.example.com";
var htmlResponse = await client.SendAsync(htmlRequest);
htmlResponse.EnsureSuccessStatusCode();
});
}
[Fact]
public async Task IngressSpecificIPTest()
{
var allIps = GetLiveIPAddresses().ToList();
var testIp = allIps[new Random().Next(allIps.Count)];
await TestIngressIP($"'{testIp}'", new[] { testIp }, allIps.Where(ip => ip != testIp).Take(1));
}
[Fact]
public async Task IngressAllIPv6Test()
{
var ipV6 = GetLiveIPAddresses(AddressFamily.InterNetworkV6).FirstOrDefault();
if (ipV6 == null) return;
await TestIngressIP($"'{IPAddress.IPv6Any}'", ipV6);
}
[Fact]
public async Task IngressAllIPv4Test()
{
var ipV4 = GetLiveIPAddresses(AddressFamily.InterNetwork).FirstOrDefault();
if (ipV4 == null) return;
var ipV6 = GetLiveIPAddresses(AddressFamily.InterNetworkV6).FirstOrDefault();
var failIp = ipV6 == null ? Enumerable.Empty<IPAddress>() : new[] { ipV6 };
await TestIngressIP($"'{IPAddress.Any}'", new[] { ipV4 }, failIp);
}
[Fact]
public async Task IngressAllIPTest()
{
var address = GetLiveIPAddresses().FirstOrDefault();
await TestIngressIP($"'*'", address == null ? Array.Empty<IPAddress>() : new[] { address });
}
private static IEnumerable<IPAddress> GetLiveIPAddresses(AddressFamily? family = null)
{
return from ni in NetworkInterface.GetAllNetworkInterfaces()
where ni.OperationalStatus == OperationalStatus.Up
let prop = ni.GetIPProperties()
from unicast in prop.UnicastAddresses
let addr = unicast.Address
where addr != IPAddress.IPv6Loopback && (family == null || addr.AddressFamily == family)
select addr;
}
private Task TestIngressIP(string ipSetting, params IPAddress[] mustAnswer) => TestIngressIP(ipSetting, mustAnswer, Enumerable.Empty<IPAddress>());
private async Task TestIngressIP(string ipSetting, IEnumerable<IPAddress> mustAnswer, IEnumerable<IPAddress> mustFail)
{
#if !DEBUG
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return; //disables running this test on windows as it stucks the test runner on the firewall open prompt
#endif
if (!mustAnswer.Any() && !mustFail.Any())
return; // no IP to test against
using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-ip_test.yaml"));
File.WriteAllText(projectFile.FullName, File.ReadAllText(projectFile.FullName).Replace("__TEST_IP_STRING__", ipSetting));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = true
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
foreach (var ip in mustAnswer.Concat(mustFail))
{
try
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var reqUri = new UriBuilder(ingressUri + "/index.html")
{
Host = ip.ToString()
};
var htmlRequest = new HttpRequestMessage(HttpMethod.Get, reqUri.Uri);
htmlRequest.Headers.Host = "ui.example.com";
var htmlResponse = await client.SendAsync(htmlRequest);
htmlResponse.EnsureSuccessStatusCode();
}
catch (Exception) when (mustFail.Contains(ip))
{
// this is an expected failure
}
}
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task NginxIngressTest()
{
// https://github.com/dotnet/tye/issues/428
// nginx container fails to start succesfully on non-Windows because it
// can't resolve the upstream hosts.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
using var projectDirectory = CopyTestProjectDirectory("nginx-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var nginxUri = await GetServiceUrl(client, uri, "nginx");
var appAUri = await GetServiceUrl(client, uri, "appa");
var appBUri = await GetServiceUrl(client, uri, "appb");
var nginxResponse = await client.GetAsync(nginxUri);
var appAResponse = await client.GetAsync(appAUri);
var appBResponse = await client.GetAsync(appBUri);
Assert.Equal(HttpStatusCode.NotFound, nginxResponse.StatusCode);
Assert.True(appAResponse.IsSuccessStatusCode);
Assert.True(appBResponse.IsSuccessStatusCode);
var responseA = await client.GetAsync(nginxUri + "/A");
var responseB = await client.GetAsync(nginxUri + "/B");
Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());
});
}
[Fact]
public async Task NullDebugTargetsDoesNotThrow()
{
using var projectDirectory = CopyTestProjectDirectory(Path.Combine("single-project", "test-project"));
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "test-project.csproj"));
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
{
Sink = _sink,
};
await host.StartAsync();
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task MultiRepo_Works()
{
using var projectDirectory = CopyTestProjectDirectory(Path.Combine("multirepo"));
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "vote", "tye.yaml"));
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var votingUri = await GetServiceUrl(client, uri, "vote");
var workerUri = await GetServiceUrl(client, uri, "worker");
var votingResponse = await client.GetAsync(votingUri);
var workerResponse = await client.GetAsync(workerUri);
Assert.True(votingResponse.IsSuccessStatusCode);
Assert.Equal(HttpStatusCode.NotFound, workerResponse.StatusCode);
// results isn't running.
var resultsResponse = await client.GetAsync($"{uri}api/v1/services/results");
Assert.Equal(HttpStatusCode.NotFound, resultsResponse.StatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task MultiRepo_WorksWithCloning()
{
using var projectDirectory = TempDirectory.Create(preferUserDirectoryOnMacOS: true);
var content = @"
name: VotingSample
services:
- name: vote
repository: https://github.com/jkotalik/TyeMultiRepoVoting
- name: results
repository: https://github.com/jkotalik/TyeMultiRepoResults";
var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
var projectFile = new FileInfo(yamlFile);
await File.WriteAllTextAsync(yamlFile, content);
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var votingUri = await GetServiceUrl(client, uri, "vote");
var workerUri = await GetServiceUrl(client, uri, "worker");
var votingResponse = await client.GetAsync(votingUri);
var workerResponse = await client.GetAsync(workerUri);
Assert.True(votingResponse.IsSuccessStatusCode);
Assert.Equal(HttpStatusCode.NotFound, workerResponse.StatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerFileTest()
{
using var projectDirectory = CopyTestProjectDirectory("dockerfile");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerFileChangeContextTest()
{
using var projectDirectory = CopyTestProjectDirectory("dockerfile");
File.Move(Path.Combine(projectDirectory.DirectoryPath, "backend", "Dockerfile"), Path.Combine(projectDirectory.DirectoryPath, "Dockerfile"));
var content = @"
name: frontend-backend
services:
- name: backend
dockerFile: Dockerfile
dockerFileContext: backend/
bindings:
- containerPort: 80
protocol: http
- name: backend2
dockerFile: ./Dockerfile
dockerFileContext: ./backend
bindings:
- containerPort: 80
protocol: http
- name: frontend
project: frontend/frontend.csproj";
var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
var projectFile = new FileInfo(yamlFile);
await File.WriteAllTextAsync(yamlFile, content);
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");
var backend2Uri = await GetServiceUrl(client, uri, "backend2");
var backendResponse = await client.GetAsync(backendUri);
var backend2Response = await client.GetAsync(backend2Uri);
var frontendResponse = await client.GetAsync(frontendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(backend2Response.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task RunExplicitYamlMultipleTargetFrameworksTest()
{
using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp31.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
// make sure it is running
var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
var backendResponse = await client.GetAsync(backendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
var responseContent = await backendResponse.Content.ReadAsStringAsync();
Assert.Contains(".NET Core 3.1", responseContent);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task RunWithArgsMultipleTargetFrameworksTest()
{
using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-no-buildproperties.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
// make sure it is running
var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
var backendResponse = await client.GetAsync(backendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
var responseContent = await backendResponse.Content.ReadAsStringAsync();
Assert.Contains(".NET Core 3.1", responseContent);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task RunCliArgDoesNotOverrideYamlMultipleTargetFrameworksTest()
{
using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp31.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp2.1");
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
// make sure it is running
var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
var backendResponse = await client.GetAsync(backendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
var responseContent = await backendResponse.Content.ReadAsStringAsync();
Assert.Contains(".NET Core 3.1", responseContent);
});
}
[ConditionalTheory]
[SkipIfDockerNotRunning]
[InlineData("non-standard-dashboard-port", "mcr.microsoft.com/dotnet/aspnet:6.0", 8005)]
[InlineData("non-standard-dashboard-port-5.0", "mcr.microsoft.com/dotnet/aspnet:5.0", 8006)]
public async Task RunUsesYamlDashboardPort(string projectName, string baseImage, int expectedDashboardPort)
{
using var projectDirectory = CopyTestProjectDirectory(projectName);
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
Assert.Equal(expectedDashboardPort, application.DashboardPort);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, }, async (app, uri) =>
{
// Make sure the dashboard is running on the expected port
Assert.Equal(expectedDashboardPort, uri.Port);
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
// Ensure correct image used
var dockerRunInfo = app.Services.Single().Value.Description.RunInfo as DockerRunInfo;
Assert.Equal(baseImage, dockerRunInfo?.Image);
// Ensure app runs
var testProjectUri = await GetServiceUrl(client, uri, "test-project");
var response = await client.GetAsync(testProjectUri);
Assert.True(response.IsSuccessStatusCode);
});
}
[ConditionalTheory]
[SkipIfDockerNotRunning]
[InlineData("non-standard-dashboard-port", "mcr.microsoft.com/dotnet/aspnet:6.0", 8005)]
[InlineData("non-standard-dashboard-port-5.0", "mcr.microsoft.com/dotnet/aspnet:5.0", 8006)]
public async Task RunCliPortOverridesYamlDashboardPort(string projectName, string baseImage, int tyeYamlDashboardPort)
{
var cliDashboardPort = 8008;
using var projectDirectory = CopyTestProjectDirectory(projectName);
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
Assert.Equal(tyeYamlDashboardPort, application.DashboardPort);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions() { Docker = true, Port = cliDashboardPort }, async (app, uri) =>
{
// Make sure the dashboard is running on the expected port passed from the CLI, not the value from tye.yaml
Assert.Equal(cliDashboardPort, uri.Port);
Assert.NotEqual(tyeYamlDashboardPort, uri.Port);
// Make sure we're running containers
Assert.True(app.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));
// Ensure correct image used
var dockerRunInfo = app.Services.Single().Value.Description.RunInfo as DockerRunInfo;
Assert.Equal(baseImage, dockerRunInfo?.Image);
// Ensure app runs
var testProjectUri = await GetServiceUrl(client, uri, "test-project");
var response = await client.GetAsync(testProjectUri);
Assert.True(response.IsSuccessStatusCode);
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task RunWithDotnetEnvVarsDoesNotGetOverriddenByDefaultDotnetEnvVars()
{
using var projectDirectory = CopyTestProjectDirectory("dotnet-env-vars");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions { Docker = true }, async (app, uri) =>
{
var backendUri = await GetServiceUrl(client, uri, "test-project");
var backendResponse = await client.GetAsync(backendUri);
Assert.True(backendResponse.IsSuccessStatusCode);
var response = await backendResponse.Content.ReadAsStringAsync();
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(response);
Assert.Contains(new KeyValuePair<string, string>("DOTNET_ENVIRONMENT", "dev"), dict);
Assert.Contains(new KeyValuePair<string, string>("ASPNETCORE_ENVIRONMENT", "dev"), dict);
});
}
private async Task<string> GetServiceUrl(HttpClient client, Uri uri, string serviceName)
{
var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceName}");
var service = JsonSerializer.Deserialize<V1Service>(serviceResult, _options);
var binding = service!.Description!.Bindings!.Where(b => b.Protocol == "http").Single();
return $"{binding.Protocol ?? "http"}://localhost:{binding.Port}";
}
private async Task RunHostingApplication(ApplicationBuilder application, HostOptions options, Func<Application, Uri, Task> execute)
{
await using var host = new TyeHost(application.ToHostingApplication(), options)
{
Sink = _sink,
};
try
{
await StartHostAndWaitForReplicasToStart(host);
var uri = new Uri(host.Addresses!.First());
await execute(host.Application, uri!);
}
finally
{
if (host.DashboardWebApplication != null)
{
var uri = new Uri(host.Addresses!.First());
using var client = new HttpClient();
foreach (var s in host.Application.Services.Values)
{
var logs = await client.GetStringAsync(new Uri(uri, $"/api/v1/logs/{s.Description.Name}"));
_output.WriteLine($"Logs for service: {s.Description.Name}");
_output.WriteLine(logs);
var description = await client.GetStringAsync(new Uri(uri, $"/api/v1/services/{s.Description.Name}"));
_output.WriteLine($"Service defintion: {s.Description.Name}");
_output.WriteLine(description);
}
}
}
}
}
}