File: ConnectionStringParserTests.cs
Web Access
Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.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.Dashboard.Model;
using Xunit;
 
namespace Aspire.Dashboard.Tests;
 
public class ConnectionStringParserTests
{
    [Theory]
    [InlineData("redis://[fe80::1]:6380", true, "fe80::1", 6380)]
    [InlineData("postgres://h/db", true, "h", 5432)]
    [InlineData("Endpoint=h:6379;password=pw", true, "h", 6379)]
    [InlineData("host=h;user=foo", true, "h", null)]
    [InlineData("broker1:9092,broker2:9092", true, "broker1", 9092)]
    [InlineData("/var/sqlite/file.db", false, "", null)]
    [InlineData("foo bar baz", false, "", null)]
    [InlineData("https://models.github.ai/inference", true, "models.github.ai", 443)]
    [InlineData("Server=tcp:localhost,1433;Database=test", true, "localhost", 1433)]
    [InlineData("Server=localhost;port=5432", true, "localhost", 5432)]
    // SQL Server patterns
    [InlineData("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;", true, "myServerAddress", null)]
    [InlineData("Server=myServerAddress,1433;Database=myDataBase;Trusted_Connection=True;", true, "myServerAddress", 1433)]
    [InlineData("Data Source=tcp:localhost,1433;Initial Catalog=TestDB;", true, "localhost", 1433)]
    [InlineData("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|mydbfile.mdf;Integrated Security=true;User Instance=true;", true, ".\\SQLEXPRESS", null)]
    [InlineData("Server=(localdb)\\MSSQLLocalDB;Database=AspNetCore.StarterSite;Trusted_Connection=true;MultipleActiveResultSets=true", true, "(localdb)\\MSSQLLocalDB", null)]
    // PostgreSQL patterns  
    [InlineData("Host=localhost;Database=mydb;Username=myuser;Password=mypass", true, "localhost", null)]
    [InlineData("Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypass", true, "localhost", 5432)]
    [InlineData("postgresql://user:password@localhost:5432/dbname", true, "localhost", 5432)]
    [InlineData("postgres://user:password@localhost/dbname", true, "localhost", 5432)]
    // MySQL patterns
    [InlineData("Server=localhost;Database=myDataBase;Uid=myUsername;Pwd=myPassword;", true, "localhost", null)]
    [InlineData("Server=localhost;Port=3306;Database=myDataBase;Uid=myUsername;Pwd=myPassword;", true, "localhost", 3306)]
    [InlineData("mysql://user:password@localhost:3306/database", true, "localhost", 3306)]
    // MongoDB patterns
    [InlineData("mongodb://localhost:27017", true, "localhost", 27017)]
    [InlineData("mongodb://user:password@localhost:27017/database", true, "localhost", 27017)]
    [InlineData("mongodb://localhost", true, "localhost", 27017)]
    [InlineData("mongodb+srv://cluster0.example.mongodb.net/database", true, "cluster0.example.mongodb.net", null)]
    // Redis patterns
    [InlineData("localhost:6379", true, "localhost", 6379)]
    [InlineData("redis://localhost:6379", true, "localhost", 6379)]
    [InlineData("rediss://localhost:6380", true, "localhost", 6380)]
    [InlineData("redis://user:password@localhost:6379/0", true, "localhost", 6379)]
    [InlineData("Endpoint=localhost:6379;Password=mypassword", true, "localhost", 6379)]
    // Oracle patterns
    [InlineData("Data Source=localhost:1521/XE;User Id=hr;Password=password;", true, "localhost", null)] // Won't parse port from path syntax
    // JDBC patterns (basic ones that should work - but many JDBC URLs are complex)
    [InlineData("jdbc:postgresql://localhost:5432/database", true, "localhost", 5432)]
    [InlineData("jdbc:mysql://localhost:3306/database", true, "localhost", 3306)]
    [InlineData("jdbc:sqlserver://localhost:1433;databaseName=TestDB", true, "localhost", 1433)]
    // Cloud provider patterns
    [InlineData("https://myaccount.blob.core.windows.net/", true, "myaccount.blob.core.windows.net", 443)]
    [InlineData("https://myvault.vault.azure.net:8080/", true, "myvault.vault.azure.net", 8080)]
    [InlineData("Server=tcp:myserver.database.windows.net,1433;Database=mydatabase;", true, "myserver.database.windows.net", 1433)]
    // Kafka patterns
    [InlineData("localhost:9092,localhost:9093,localhost:9094", true, "localhost", 9092)]
    [InlineData("broker-1:9092,broker-2:9092", true, "broker-1", 9092)]
    // RabbitMQ patterns
    [InlineData("amqp://localhost", true, "localhost", 5672)]
    [InlineData("amqp://user:pass@localhost:5672/vhost", true, "localhost", 5672)]
    [InlineData("amqps://localhost:5671", true, "localhost", 5671)]
    [InlineData("Host=localhost;Port=5672;VirtualHost=/;Username=guest;Password=guest", true, "localhost", 5672)]
    // Elasticsearch patterns
    [InlineData("http://localhost:9200", true, "localhost", 9200)]
    [InlineData("https://elastic:password@localhost:9200", true, "localhost", 9200)]
    // InfluxDB patterns  
    [InlineData("http://localhost:8086", true, "localhost", 8086)]
    [InlineData("https://localhost:8086", true, "localhost", 8086)]
    // Cassandra patterns
    [InlineData("Contact Points=localhost;Port=9042", true, "localhost", 9042)]
    [InlineData("Contact Points=node1,node2,node3;Port=9042", false, "", null)] // Multiple contact points - too complex
    // Neo4j patterns
    [InlineData("bolt://localhost:7687", true, "localhost", 7687)]
    [InlineData("neo4j://localhost:7687", true, "localhost", 7687)]
    // Docker/container patterns
    [InlineData("server.local", true, "server.local", null)]
    [InlineData("my-service:5432", true, "my-service", 5432)]
    [InlineData("my-namespace.my-service.svc.cluster.local:5432", true, "my-namespace.my-service.svc.cluster.local", 5432)]
    // IPv6 patterns
    [InlineData("Server=[::1],1433", true, "::1", 1433)]
    [InlineData("Host=[2001:db8::1];Port=5432", true, "2001:db8::1", 5432)]
    [InlineData("http://[2001:db8::1]:8080", true, "2001:db8::1", 8080)]
    // Edge cases and invalid patterns
    [InlineData("", false, "", null)]
    [InlineData("   ", false, "", null)]
    [InlineData("=", false, "", null)]
    [InlineData("key=", false, "", null)]
    [InlineData("=value", false, "", null)]
    [InlineData("C:\\path\\to\\file.db", false, "", null)]
    [InlineData("./relative/path/file.db", false, "", null)]
    [InlineData("/absolute/path/file.db", false, "", null)]
    [InlineData("just some random text", false, "", null)]
    [InlineData("host=;port=5432", false, "", null)] // Empty host
    [InlineData("server=localhost;port=abc", true, "localhost", null)] // Invalid port
    [InlineData("server=localhost;port=99999", true, "localhost", null)] // Port out of range
    public void TryDetectHostAndPort_VariousFormats_ReturnsExpectedResults(
        string connectionString, 
        bool expectedResult, 
        string expectedHost, 
        int? expectedPort)
    {
        // Act
        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
        
        // Assert
        Assert.Equal(expectedResult, result);
        if (expectedResult)
        {
            Assert.Equal(expectedHost, host);
            Assert.Equal(expectedPort, port);
        }
        else
        {
            Assert.Null(host);
            Assert.Null(port);
        }
    }
 
    [Fact]
    public void TryDetectHostAndPort_IPv6URI_ReturnsCorrectHost()
    {
        // Test case specifically for IPv6 addresses with brackets
        var connectionString = "redis://[fe80::1]:6380";
        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
        
        Assert.True(result);
        Assert.Equal("fe80::1", host); // Brackets should be trimmed
        Assert.Equal(6380, port);
    }
 
    [Fact]
    public void TryDetectHostAndPort_KeyValuePairsWithSemicolon_ParsesCorrectly()
    {
        var connectionString = "Endpoint=h:6379;password=pw;database=0";
        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
        
        Assert.True(result);
        Assert.Equal("h", host);
        Assert.Equal(6379, port);
    }
 
    [Fact]
    public void TryDetectHostAndPort_DelimitedList_TakesFirstEntry()
    {
        var connectionString = "broker1:9092,broker2:9093,broker3:9094";
        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
        
        Assert.True(result);
        Assert.Equal("broker1", host);
        Assert.Equal(9092, port);
    }
}