File: Secrets\SecretsStoreTests.cs
Web Access
Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.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.Shared.UserSecrets;
 
namespace Aspire.Cli.Tests.Secrets;
 
public class SecretsStoreTests : IDisposable
{
    private readonly string _tempDir;
 
    public SecretsStoreTests()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), $"aspire-test-{Guid.NewGuid():N}");
        Directory.CreateDirectory(_tempDir);
    }
 
    public void Dispose()
    {
        if (Directory.Exists(_tempDir))
        {
            Directory.Delete(_tempDir, recursive: true);
        }
    }
 
    private string GetSecretsPath() => Path.Combine(_tempDir, "secrets.json");
 
    [Fact]
    public void Set_CreatesFileAndSetsValue()
    {
        var path = GetSecretsPath();
        var store = new SecretsStore(path);
 
        store.Set("key1", "value1");
        store.Save();
 
        Assert.True(File.Exists(path));
 
        // Reload and verify
        var store2 = new SecretsStore(path);
        Assert.Equal("value1", store2.Get("key1"));
    }
 
    [Fact]
    public void Get_ReturnsNull_WhenKeyMissing()
    {
        var store = new SecretsStore(GetSecretsPath());
        Assert.Null(store.Get("nonexistent"));
    }
 
    [Fact]
    public void Set_OverwritesExistingValue()
    {
        var path = GetSecretsPath();
        var store = new SecretsStore(path);
 
        store.Set("key1", "original");
        store.Save();
 
        var store2 = new SecretsStore(path);
        store2.Set("key1", "updated");
        store2.Save();
 
        var store3 = new SecretsStore(path);
        Assert.Equal("updated", store3.Get("key1"));
    }
 
    [Fact]
    public void List_ReturnsAllPairs()
    {
        var path = GetSecretsPath();
        var store = new SecretsStore(path);
 
        store.Set("a", "1");
        store.Set("b", "2");
        store.Set("c", "3");
        store.Save();
 
        var store2 = new SecretsStore(path);
        var secrets = store2.AsEnumerable().ToList();
        Assert.Equal(3, secrets.Count);
    }
 
    [Fact]
    public void Delete_RemovesKey()
    {
        var path = GetSecretsPath();
        var store = new SecretsStore(path);
 
        store.Set("key1", "value1");
        store.Set("key2", "value2");
        store.Save();
 
        var store2 = new SecretsStore(path);
        Assert.True(store2.Remove("key1"));
        store2.Save();
 
        var store3 = new SecretsStore(path);
        Assert.Null(store3.Get("key1"));
        Assert.Equal("value2", store3.Get("key2"));
    }
 
    [Fact]
    public void Delete_ReturnsFalse_WhenKeyMissing()
    {
        var store = new SecretsStore(GetSecretsPath());
        Assert.False(store.Remove("nonexistent"));
    }
 
    [Fact]
    public void Load_FlattensNestedJson()
    {
        var path = GetSecretsPath();
        var dir = Path.GetDirectoryName(path)!;
        Directory.CreateDirectory(dir);
 
        // Write nested JSON manually
        File.WriteAllText(path, """
        {
            "Azure": {
                "Location": "eastus2",
                "SubscriptionId": "abc-123"
            }
        }
        """);
 
        var store = new SecretsStore(path);
        Assert.Equal("eastus2", store.Get("Azure:Location"));
        Assert.Equal("abc-123", store.Get("Azure:SubscriptionId"));
    }
 
    [Fact]
    public void Save_UsesRelaxedEscaping()
    {
        var path = GetSecretsPath();
        var store = new SecretsStore(path);
 
        store.Set("key", "value with 'quotes' and <angle> brackets");
        store.Save();
 
        var json = File.ReadAllText(path);
        // Should NOT contain \u0027 or \u003C escaping
        Assert.DoesNotContain("\\u0027", json);
        Assert.DoesNotContain("\\u003C", json);
        Assert.Contains("'quotes'", json);
        Assert.Contains("<angle>", json);
    }
 
    [Fact]
    public void Save_CreatesDirectoryIfMissing()
    {
        var path = Path.Combine(_tempDir, "nested", "dir", "secrets.json");
        var store = new SecretsStore(path);
 
        store.Set("key", "value");
        store.Save();
 
        Assert.True(File.Exists(path));
    }
 
    [Fact]
    public void ContainsKey_ReturnsTrueForExistingKey()
    {
        var store = new SecretsStore(GetSecretsPath());
        store.Set("exists", "yes");
 
        Assert.True(store.ContainsKey("exists"));
        Assert.False(store.ContainsKey("missing"));
    }
 
    [Fact]
    public void Count_ReflectsNumberOfSecrets()
    {
        var store = new SecretsStore(GetSecretsPath());
        Assert.Equal(0, store.Count);
 
        store.Set("a", "1");
        store.Set("b", "2");
        Assert.Equal(2, store.Count);
 
        store.Remove("a");
        Assert.Equal(1, store.Count);
    }
}