File: Utils\ContainerReferenceParserTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.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.Hosting.Utils;
using Xunit;
 
namespace Aspire.Hosting.Tests.Utils;
 
// Based on tests at https://github.com/distribution/reference/blob/main/reference_test.go
public class ContainerReferenceParserTests
{
    [Fact]
    public void EmptyInput()
    {
        var ex = Assert.Throws<ArgumentOutOfRangeException>(() => ContainerReferenceParser.Parse(""));
        Assert.StartsWith("repository name must have at least one component", ex.Message);
    }
 
    [Theory]
    [InlineData("  ")]
    [InlineData(":justtag")]
    [InlineData("@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
    [InlineData("aa/asdf$$^/aa")]
    [InlineData("[2001:db8::1]")]
    [InlineData("[2001:db8::1]:5000")]
    [InlineData("[2001:db8::1]:tag")]
    [InlineData("[fe80::1%eth0]:5000/repo")]
    [InlineData("[fe80::1%@invalidzone]:5000/repo")]
    public void InvalidReferenceFormat(string input)
    {
        var ex = Assert.Throws<ArgumentOutOfRangeException>(() => ContainerReferenceParser.Parse(input));
        Assert.StartsWith("invalid reference format", ex.Message);
    }
 
    [Theory]
    [InlineData("test_com", "test_com")]
    [InlineData("192.168.1.1", "192.168.1.1")]
    public void ImageTests(string input, string expectedImage)
        => ParserTest(input, null, expectedImage, null, null);
 
    [Theory]
    [InlineData("test_com:tag", "test_com", "tag")]
    [InlineData("test.com:5000", "test.com", "5000")]
    [InlineData("lowercase:Uppercase", "lowercase", "Uppercase")]
    [InlineData("foo_bar.com:8080", "foo_bar.com", "8080")]
    [InlineData("192.168.1.1:tag", "192.168.1.1", "tag")]
    [InlineData("192.168.1.1:5000", "192.168.1.1", "5000")]
    [InlineData("a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a:tag-puts-this-over-max", "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a", "tag-puts-this-over-max")]
    [InlineData("foo/foo_bar.com:8080", "foo/foo_bar.com", "8080")]
    public void ImageAndTagTests(string input, string expectedImage, string expectedTag)
        => ParserTest(input, null, expectedImage, expectedTag: expectedTag, null);
 
    [Theory]
    [InlineData("test:5000/repo", "test:5000", "repo")]
    [InlineData("sub-dom1.foo.com/bar/baz/quux", "sub-dom1.foo.com", "bar/baz/quux")]
    [InlineData("192.168.1.1/repo", "192.168.1.1", "repo")]
    [InlineData("192.168.1.1:5000/repo", "192.168.1.1:5000", "repo")]
    [InlineData("[2001:db8::1]/repo", "[2001:db8::1]", "repo")]
    [InlineData("[2001:db8::1]:5000/repo", "[2001:db8::1]:5000", "repo")]
    [InlineData("[2001:db8::]:5000/repo", "[2001:db8::]:5000", "repo")]
    [InlineData("[::1]:5000/repo", "[::1]:5000", "repo")]
    public void DomainAndImageTests(string input, string expectedRegistry, string expectedImage)
        => ParserTest(input, expectedRegistry, expectedImage, null, null);
 
    [Theory]
    [InlineData("test.com/repo:tag", "test.com", "repo", "tag")]
    [InlineData("test:5000/repo:tag", "test:5000", "repo", "tag")]
    [InlineData("sub-dom1.foo.com/bar/baz/quux:some-long-tag", "sub-dom1.foo.com", "bar/baz/quux", "some-long-tag")]
    [InlineData("b.gcr.io/test.example.com/my-app:test.example.com", "b.gcr.io", "test.example.com/my-app", "test.example.com")]
    [InlineData("xn--n3h.com/myimage:xn--n3h.com", "xn--n3h.com", "myimage", "xn--n3h.com")] // ☃.com in punycode
    [InlineData("192.168.1.1:5000/repo:5050", "192.168.1.1:5000", "repo", "5050")]
    [InlineData("[2001:db8:1:2:3:4:5:6]/repo:tag", "[2001:db8:1:2:3:4:5:6]", "repo", "tag")]
    [InlineData("[2001:db8::1]:5000/repo:tag", "[2001:db8::1]:5000", "repo", "tag")]
    [InlineData("localhost/repo:tag", "localhost", "repo", "tag")]
    public void DomainImageAndTagTests(string input, string expectedRegistry, string expectedImage, string expectedTag)
        => ParserTest(input, expectedRegistry, expectedImage, expectedTag, null);
 
    [Theory]
    [InlineData("test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "test:5000", "repo", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
    [InlineData("[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "[2001:db8::1]:5000", "repo", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
    [InlineData("whatever:5000/repo@algo:value", "whatever:5000", "repo", "algo:value")]
    [InlineData("localhost/repo@digest", "localhost", "repo", "digest")]
    public void DomainImageAndDigestTests(string input, string expectedRegistry, string expectedImage, string expectedDigest)
        => ParserTest(input, expectedRegistry, expectedImage, null, expectedDigest);
 
    [Theory]
    [InlineData("test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "test:5000", "repo", "tag", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
    [InlineData("xn--7o8h.com/myimage:xn--7o8h.com@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "xn--7o8h.com", "myimage", "xn--7o8h.com", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")] // 🐳.com in punycode
    [InlineData("[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "[2001:db8::1]:5000", "repo", "tag", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
    public void DomainImageTagAndDigestTests(string input, string expectedRegistry, string expectedImage, string expectedTag, string expectedDigest)
        => ParserTest(input, expectedRegistry, expectedImage, expectedTag, expectedDigest);
 
    private static void ParserTest(string input, string? expectedRegistry, string expectedImage, string? expectedTag, string? expectedDigest)
    {
        var result = ContainerReferenceParser.Parse(input);
 
        Assert.Multiple(() =>
        {
            Assert.Equal(expectedRegistry, result.Registry);
            Assert.Equal(expectedImage, result.Image);
            Assert.Equal(expectedTag, result.Tag);
            Assert.Equal(expectedDigest, result.Digest);
        });
 
    }
 
}