File: ApplicationModel\ResourceCollectionTests.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 System.Collections.ObjectModel;
 
namespace Aspire.Hosting.Tests.ApplicationModel;
 
public class ResourceCollectionTests
{
    [Fact]
    public void TryGetByName_FindsExistingResource()
    {
        var collection = new ResourceCollection();
        var resource = new TestResource("myResource");
        collection.Add(resource);
 
        var found = collection.TryGetByName("myResource", out var result);
 
        Assert.True(found);
        Assert.Same(resource, result);
    }
 
    [Fact]
    public void TryGetByName_IsCaseInsensitive()
    {
        var collection = new ResourceCollection();
        var resource = new TestResource("myResource");
        collection.Add(resource);
 
        var found = collection.TryGetByName("MYRESOURCE", out var result);
 
        Assert.True(found);
        Assert.Same(resource, result);
    }
 
    [Fact]
    public void TryGetByName_ReturnsFalseWhenNotFound()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("other"));
 
        var found = collection.TryGetByName("missing", out var result);
 
        Assert.False(found);
        Assert.Null(result);
    }
 
    [Fact]
    public void TryGetByName_ReturnsFalseWhenEmpty()
    {
        var collection = new ResourceCollection();
 
        var found = collection.TryGetByName("anything", out var result);
 
        Assert.False(found);
        Assert.Null(result);
    }
 
    [Fact]
    public void TryGetByName_ReflectsAddedResources()
    {
        var collection = new ResourceCollection();
 
        Assert.False(collection.TryGetByName("res", out _));
 
        collection.Add(new TestResource("res"));
 
        Assert.True(collection.TryGetByName("res", out _));
    }
 
    [Fact]
    public void TryGetByName_ReflectsRemovedResources()
    {
        var collection = new ResourceCollection();
        var resource = new TestResource("res");
        collection.Add(resource);
 
        Assert.True(collection.TryGetByName("res", out _));
 
        collection.Remove(resource);
 
        Assert.False(collection.TryGetByName("res", out _));
    }
 
    [Fact]
    public void TryGetByName_ReflectsRemovedAtResources()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res"));
 
        Assert.True(collection.TryGetByName("res", out _));
 
        collection.RemoveAt(0);
 
        Assert.False(collection.TryGetByName("res", out _));
    }
 
    [Fact]
    public void TryGetByName_ReflectsClear()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res1"));
        collection.Add(new TestResource("res2"));
 
        Assert.True(collection.TryGetByName("res1", out _));
 
        collection.Clear();
 
        Assert.False(collection.TryGetByName("res1", out _));
        Assert.False(collection.TryGetByName("res2", out _));
    }
 
    [Fact]
    public void TryGetByName_ReflectsInsert()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res1"));
 
        var inserted = new TestResource("res2");
        collection.Insert(0, inserted);
 
        Assert.True(collection.TryGetByName("res2", out var result));
        Assert.Same(inserted, result);
    }
 
    [Fact]
    public void TryGetByName_ReflectsIndexerSet()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("old"));
 
        var replacement = new TestResource("new");
        collection[0] = replacement;
 
        Assert.False(collection.TryGetByName("old", out _));
        Assert.True(collection.TryGetByName("new", out var result));
        Assert.Same(replacement, result);
    }
 
    [Fact]
    public void Add_ThrowsOnDuplicateName()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res"));
 
        var ex = Assert.Throws<DistributedApplicationException>(() => collection.Add(new TestResource("res")));
        Assert.Contains("res", ex.Message);
        Assert.Contains(nameof(TestResource), ex.Message);
    }
 
    [Fact]
    public void Add_ThrowsOnDuplicateNameCaseInsensitive()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res"));
 
        Assert.Throws<DistributedApplicationException>(() => collection.Add(new TestResource("RES")));
    }
 
    [Fact]
    public void Insert_ThrowsOnDuplicateName()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res"));
 
        Assert.Throws<DistributedApplicationException>(() => collection.Insert(0, new TestResource("res")));
    }
 
    [Fact]
    public void IndexerSet_ThrowsOnDuplicateName()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res1"));
        collection.Add(new TestResource("res2"));
 
        // Setting [0] to a resource whose name matches [1] should throw.
        Assert.Throws<DistributedApplicationException>(() => collection[0] = new TestResource("res2"));
    }
 
    [Fact]
    public void IndexerSet_AllowsReplacingSameSlot()
    {
        var collection = new ResourceCollection();
        collection.Add(new TestResource("res"));
 
        // Replacing [0] with a new resource with the same name should succeed.
        var replacement = new TestResource("res");
        collection[0] = replacement;
 
        Assert.True(collection.TryGetByName("res", out var result));
        Assert.Same(replacement, result);
    }
 
    [Fact]
    public void Constructor_ThrowsOnDuplicateName()
    {
        var resources = new IResource[]
        {
            new TestResource("res"),
            new TestResource("res")
        };
 
        Assert.Throws<DistributedApplicationException>(() => new ResourceCollection(resources));
    }
 
    [Fact]
    public void TryGetByName_WorksWithConstructorEnumerable()
    {
        var resources = new IResource[]
        {
            new TestResource("res1"),
            new TestResource("res2")
        };
        var collection = new ResourceCollection(resources);
 
        Assert.True(collection.TryGetByName("res1", out var r1));
        Assert.Same(resources[0], r1);
 
        Assert.True(collection.TryGetByName("res2", out var r2));
        Assert.Same(resources[1], r2);
    }
 
    [Fact]
    public void TryGetByName_DefaultInterfaceMethod_WorksOnInterface()
    {
        // Test the DIM on IResourceCollection by using a wrapper that doesn't override TryGetByName.
        // ResourceCollection overrides TryGetByName, so calling through an IResourceCollection reference
        // would still dispatch to the class implementation. This wrapper exercises the DIM fallback.
        IResourceCollection collection = new SimpleResourceCollection();
        collection.Add(new TestResource("test"));
 
        Assert.True(collection.TryGetByName("test", out var result));
        Assert.Equal("test", result.Name);
 
        Assert.True(collection.TryGetByName("TEST", out _));
 
        Assert.False(collection.TryGetByName("nonexistent", out _));
    }
 
    private sealed class TestResource(string name) : Resource(name)
    {
    }
 
    /// <summary>
    /// A minimal IResourceCollection that does NOT override TryGetByName,
    /// so the default interface method (DIM) enumerator-based fallback is exercised.
    /// </summary>
    private sealed class SimpleResourceCollection : Collection<IResource>, IResourceCollection
    {
    }
}