| File: Mcp\ApiDocs\ApiDocsFetcherTests.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 System.Net; using Aspire.Cli.Documentation.ApiDocs; using Aspire.Cli.Tests.Utils; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; namespace Aspire.Cli.Tests.Documentation.ApiDocs; public class ApiDocsFetcherTests { private const string DefaultSitemapUrl = "https://aspire.dev/sitemap-0.xml"; private const string DefaultPageUrl = "https://aspire.dev/reference/api/csharp/aspire.test.package/testtype/methods"; private static ApiDocsFetcher CreateFetcher(HttpClient httpClient, IApiDocsCache cache, IConfiguration? configuration = null) { configuration ??= new ConfigurationBuilder().Build(); return new ApiDocsFetcher(httpClient, cache, configuration, NullLogger<ApiDocsFetcher>.Instance); } [Fact] public async Task FetchSitemapAsync_CachesContentWithFriendlyKey() { const string expectedContent = "<urlset></urlset>"; using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedContent) }; response.Headers.ETag = new System.Net.Http.Headers.EntityTagHeaderValue("\"etag\""); using var handler = new MockHttpMessageHandler(response, request => { Assert.Equal(DefaultSitemapUrl, request.RequestUri?.ToString()); }); using var httpClient = new HttpClient(handler); var cache = new MockApiDocsCache(); var fetcher = CreateFetcher(httpClient, cache); _ = await fetcher.FetchSitemapAsync(); var cacheKey = ApiDocsSourceConfiguration.GetSitemapContentCacheKey(DefaultSitemapUrl); Assert.Equal(expectedContent, await cache.GetAsync(cacheKey)); Assert.Equal("\"etag\"", await cache.GetETagAsync(cacheKey)); } [Fact] public async Task FetchPageAsync_CachesContentWithFriendlyKey() { const string expectedContent = "# Methods"; using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedContent) }; response.Headers.ETag = new System.Net.Http.Headers.EntityTagHeaderValue("\"etag\""); using var handler = new MockHttpMessageHandler(response, request => { Assert.Equal($"{DefaultPageUrl}.md", request.RequestUri?.ToString()); }); using var httpClient = new HttpClient(handler); var cache = new MockApiDocsCache(); var fetcher = CreateFetcher(httpClient, cache); _ = await fetcher.FetchPageAsync(DefaultPageUrl); var cacheKey = ApiDocsSourceConfiguration.GetPageContentCacheKey(DefaultPageUrl, DefaultSitemapUrl); Assert.Equal(expectedContent, await cache.GetAsync(cacheKey)); Assert.Equal("\"etag\"", await cache.GetETagAsync(cacheKey)); } [Fact] public async Task FetchPageAsync_StripsMemberAnchorFromMarkdownFetchAndCacheKey() { const string expectedContent = "# Methods"; const string anchoredPageUrl = $"{DefaultPageUrl}#withcommand-iresourcebuilder-t-string"; using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedContent) }; response.Headers.ETag = new System.Net.Http.Headers.EntityTagHeaderValue("\"etag\""); using var handler = new MockHttpMessageHandler(response, request => { Assert.Equal($"{DefaultPageUrl}.md", request.RequestUri?.ToString()); }); using var httpClient = new HttpClient(handler); var cache = new MockApiDocsCache(); var fetcher = CreateFetcher(httpClient, cache); _ = await fetcher.FetchPageAsync(anchoredPageUrl); var cacheKey = ApiDocsSourceConfiguration.GetPageContentCacheKey(anchoredPageUrl, DefaultSitemapUrl); Assert.Equal(expectedContent, await cache.GetAsync(cacheKey)); Assert.Equal("\"etag\"", await cache.GetETagAsync(cacheKey)); } private sealed class MockApiDocsCache : IApiDocsCache { private readonly Dictionary<string, string> _content = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, string?> _etags = new(StringComparer.OrdinalIgnoreCase); private ApiReferenceItem[]? _index; private string? _indexFingerprint; private ApiReferenceItem[]? _memberIndex; private string? _memberIndexFingerprint; private string[]? _indexedMemberContainerIds; public Task<string?> GetAsync(string key, CancellationToken cancellationToken = default) => Task.FromResult(_content.TryGetValue(key, out var value) ? value : null); public Task SetAsync(string key, string content, CancellationToken cancellationToken = default) { _content[key] = content; return Task.CompletedTask; } public Task<string?> GetETagAsync(string url, CancellationToken cancellationToken = default) => Task.FromResult(_etags.TryGetValue(url, out var value) ? value : null); public Task SetETagAsync(string url, string? etag, CancellationToken cancellationToken = default) { if (etag is null) { _etags.Remove(url); } else { _etags[url] = etag; } return Task.CompletedTask; } public Task InvalidateAsync(string key, CancellationToken cancellationToken = default) { _content.Remove(key); return Task.CompletedTask; } public Task<ApiReferenceItem[]?> GetIndexAsync(CancellationToken cancellationToken = default) => Task.FromResult(_index); public Task SetIndexAsync(ApiReferenceItem[] documents, CancellationToken cancellationToken = default) { _index = documents; return Task.CompletedTask; } public Task<string?> GetIndexSourceFingerprintAsync(CancellationToken cancellationToken = default) => Task.FromResult(_indexFingerprint); public Task SetIndexSourceFingerprintAsync(string fingerprint, CancellationToken cancellationToken = default) { _indexFingerprint = fingerprint; return Task.CompletedTask; } public Task<ApiReferenceItem[]?> GetMemberIndexAsync(CancellationToken cancellationToken = default) => Task.FromResult(_memberIndex); public Task SetMemberIndexAsync(ApiReferenceItem[] documents, CancellationToken cancellationToken = default) { _memberIndex = documents; return Task.CompletedTask; } public Task<string?> GetMemberIndexSourceFingerprintAsync(CancellationToken cancellationToken = default) => Task.FromResult(_memberIndexFingerprint); public Task SetMemberIndexSourceFingerprintAsync(string fingerprint, CancellationToken cancellationToken = default) { _memberIndexFingerprint = fingerprint; return Task.CompletedTask; } public Task<string[]?> GetIndexedMemberContainerIdsAsync(CancellationToken cancellationToken = default) => Task.FromResult(_indexedMemberContainerIds); public Task SetIndexedMemberContainerIdsAsync(string[] containerIds, CancellationToken cancellationToken = default) { _indexedMemberContainerIds = containerIds; return Task.CompletedTask; } } }