File: Commands\DocsCommandTests.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.Cli.Mcp.Docs;
using Aspire.Cli.Tests.Utils;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Cli.Tests.Commands;
 
public class DocsCommandTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task DocsCommand_WithNoSubcommand_ShowsHelp()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        // Returns InvalidCommand exit code when no subcommand is provided (shows help)
        Assert.Equal(ExitCodeConstants.InvalidCommand, exitCode);
    }
 
    [Fact]
    public async Task DocsListCommand_ReturnsDocuments()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs list");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsListCommand_WithJsonFormat_ReturnsJson()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs list --format json");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsSearchCommand_WithQuery_ReturnsResults()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
            options.DocsSearchServiceFactory = _ => new TestDocsSearchService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs search redis");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsSearchCommand_WithLimit_RespectsLimit()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
            options.DocsSearchServiceFactory = _ => new TestDocsSearchService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs search redis -n 3");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsSearchCommand_WithJsonFormat_ReturnsJson()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
            options.DocsSearchServiceFactory = _ => new TestDocsSearchService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs search redis --format json");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsGetCommand_WithValidSlug_ReturnsContent()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs get redis-integration");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsGetCommand_WithSection_ReturnsSection()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs get redis-integration --section \"Getting Started\"");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task DocsGetCommand_WithInvalidSlug_ReturnsError()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DocsIndexServiceFactory = _ => new TestDocsIndexService();
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<Aspire.Cli.Commands.RootCommand>();
        var result = command.Parse("docs get nonexistent-page");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.NotEqual(0, exitCode);
    }
}
 
internal sealed class TestDocsIndexService : IDocsIndexService
{
    public bool IsIndexed => true;
 
    public ValueTask EnsureIndexedAsync(CancellationToken cancellationToken = default)
    {
        return ValueTask.CompletedTask;
    }
 
    public ValueTask<IReadOnlyList<DocsListItem>> ListDocumentsAsync(CancellationToken cancellationToken = default)
    {
        var docs = new List<DocsListItem>
        {
            new() { Title = "Redis Integration", Slug = "redis-integration", Summary = "Learn how to use Redis" },
            new() { Title = "PostgreSQL Integration", Slug = "postgresql-integration", Summary = "Learn how to use PostgreSQL" },
            new() { Title = "Getting Started", Slug = "getting-started", Summary = "Get started with Aspire" }
        };
        return ValueTask.FromResult<IReadOnlyList<DocsListItem>>(docs);
    }
 
    public ValueTask<IReadOnlyList<DocsSearchResult>> SearchAsync(string query, int topK = 10, CancellationToken cancellationToken = default)
    {
        var results = new List<DocsSearchResult>
        {
            new() { Title = "Redis Integration", Slug = "redis-integration", Summary = "Learn how to use Redis", Score = 100.0f, MatchedSection = "Hosting integration" },
            new() { Title = "Azure Cache for Redis", Slug = "azure-cache-redis", Summary = "Azure Redis integration", Score = 80.0f, MatchedSection = "Client integration" }
        };
        return ValueTask.FromResult<IReadOnlyList<DocsSearchResult>>(results.Take(topK).ToList() as IReadOnlyList<DocsSearchResult>);
    }
 
    public ValueTask<DocsContent?> GetDocumentAsync(string slug, string? section = null, CancellationToken cancellationToken = default)
    {
        if (slug == "redis-integration")
        {
            return ValueTask.FromResult<DocsContent?>(new DocsContent
            {
                Title = "Redis Integration",
                Slug = "redis-integration",
                Summary = "Learn how to use Redis",
                Content = "# Redis Integration\n\nThis is the Redis integration documentation.",
                Sections = new[] { "Getting Started", "Hosting integration", "Client integration" }
            });
        }
 
        return ValueTask.FromResult<DocsContent?>(null);
    }
}
 
internal sealed class TestDocsSearchService : IDocsSearchService
{
    public Task<DocsSearchResponse?> SearchAsync(string query, int topK = 5, CancellationToken cancellationToken = default)
    {
        var results = new List<SearchResult>
        {
            new() { Title = "Redis Integration", Slug = "redis-integration", Content = "Learn how to use Redis", Score = 100.0f, Section = "Hosting integration" },
            new() { Title = "Azure Cache for Redis", Slug = "azure-cache-redis", Content = "Azure Redis integration", Score = 80.0f, Section = "Client integration" }
        };
 
        return Task.FromResult<DocsSearchResponse?>(new DocsSearchResponse
        {
            Query = query,
            Results = results.Take(topK).ToList()
        });
    }
}