// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Media; using ICSharpCode.ILSpy.NuGetFeeds; namespace ICSharpCode.ILSpy.Tests.NuGetFeeds; /// /// In-memory : records every requested URL and returns a distinct /// placeholder image per URL (or null to model a missing/broken icon), so tests can assert that a /// package's icon was fetched and swapped in without touching the network. /// public sealed class FakeNuGetIconLoader : INuGetIconLoader { public ConcurrentBag RequestedUrls { get; } = new(); /// When false, every load resolves to null (models a broken/missing icon). public bool ReturnImage { get; set; } = true; /// /// When set, loads do not complete until this source is resolved (or the call's token fires), /// letting a test hold icon loads in flight and then cancel them with a new search. /// public TaskCompletionSource? Gate { get; set; } public Task LoadIconAsync(string iconUrl, CancellationToken cancellationToken) { RequestedUrls.Add(iconUrl); if (cancellationToken.IsCancellationRequested) return Task.FromResult(null); if (Gate != null) return AwaitGateAsync(iconUrl, cancellationToken); return Task.FromResult(Result(iconUrl)); } async Task AwaitGateAsync(string iconUrl, CancellationToken cancellationToken) { // The contract is "cancellation yields a null icon", not an exception; model that so tests // exercise the same path production takes when a search is superseded mid-load. try { await Gate!.Task.WaitAsync(cancellationToken); } catch (OperationCanceledException) { return null; } return Result(iconUrl); } IImage? Result(string iconUrl) => ReturnImage ? new StubImage(iconUrl) : null; /// A trivial non-drawing that carries the URL it stands in for. public sealed class StubImage(string url) : IImage { public string Url { get; } = url; public Size Size => new(64, 64); public void Draw(DrawingContext context, Rect sourceRect, Rect destRect) { } } }