Connectiontest von loadbalancedten Services

Wer Webapps oder Restservices hinter einem Loadbalancer betreibt und diese überwachen will, steht zwecks Monitoring vor der Herausforderung, die einzelnen Nodes einzeln zu überwachen zu müssen. Wird auf allen Verbindungen SSL verlangt, muss dein Request auf einen Zielnode einerseits SSL machen und auf der anderen Seite aber einen anderen Hostnamen ansprechen wie der Hostname in der URL.

Mit curl lässt sich diese Aufgabe elegant lösen.

curl --connect-to node1.example.com:443 https://api.example.com/myservice/myaction

Dabei kann mit dem Parameter „–connect-to“ der Hostname/Port angegeben werden, mit dem curl sprechen soll und mit dem URL-Parameter die aufzurufende URL.

Achtung Windows

Während bei den meisten Linux-Distributionen curl standardmässig installiert ist oder einfach als Package installieren kann, muss man unter Windows curl.exe nachinstallieren.

Powershell liefert standardmässig ein Alias namens „curl“, das auf „Invoke-Webrequest“ umleitet. Dieses Commandlet verfügt aber über ganz andere Parameter und ist mit dem regulären curl überhaupt nicht kompatibel.

C#11 String Literals für Unit-Tests

Wollte man grössere Datenstrukturen als Input für Unit-Tests verwenden, war bislang der einfachste Weg, im Projekt ein Json-File mit den entsprechenden Daten abzulegen. Neu können Json-Daten auch inline in den Code kopiert werden ohne diese mühsam escapen zu müssen.

Anbei ein Beispiel-Datensatz:

const string testjson = """
{
    "CreationDate": "2022-11-14T00:00:00+01:00",
    "DocumentLanguage": "de",
    "TemplateName": "LetterTemplate",
    "DocumentId": "b12056b8-4452-4df1-b83c-b288c1c9fd35",
    "IsVerified": true
}
""";

Wichtig ist dabei, die 3 Quotes jeweils auf einer separaten Zeile zu belassen. Falls die letzte Zeile eingerückt wird, sind die entsprechenden Spaces/Tabs nicht im String-Literal enthalten.

DataTest.cs

namespace UnitTests;

public static class DataTests
{
    const string testjson = """
{
    "CreationDate": "2022-11-14T00:00:00+01:00",
    "DocumentLanguage": "de",
    "TemplateName": "LetterTemplate",
    "DocumentId": "b12056b8-4452-4df1-b83c-b288c1c9fd35",
    "IsVerified": true
}
    """;

    public enum Language { de, fr, it, en }

    public record DocumentMetadata(
        Guid DocumentId,
        Language DocumentLanguage,
        string TemplateName,
        bool IsVerified,
        DateTimeOffset CreationDate);

    private static JsonSerializerOptions serializerOptions
        = new JsonSerializerOptions
        {
            Converters = {
                new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)
            }
        };

    [Fact]
    public static void TestWithData()
    {
        DocumentMetadata? dm = JsonSerializer.Deserialize<DocumentMetadata>(testjson, serializerOptions);

        dm.Should().NotBeNull();
        dm.CreationDate.Should().Be(new DateTimeOffset(2022, 11, 14, 0, 0, 0, TimeSpan.FromHours(1)));
        dm.DocumentLanguage.Should().Be(Language.de);
        dm.TemplateName.Should().Be("LetterTemplate");
        dm.IsVerified.Should().BeTrue();
    }
}

Usings.cs

global using FluentAssertions;
global using Xunit;
global using System.Text.Json.Serialization;
global using System.Text.Json;

UnitTests.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="FluentAssertions" Version="6.8.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
    <PackageReference Include="xunit" Version="2.4.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.1.2">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>