using System.Net; using System.Text; using BuildFlow.Shared.Domain.Exceptions; using FluentAssertions; using Microsoft.Extensions.Logging; using Monitoring.Domain.Entities.ExternalSystem; using Monitoring.Domain.Enums; using Monitoring.Domain.Values; using Monitoring.Infrastructure.Connectors; namespace BuildFlow.Monitoring.Tests.Connectors; /// u号租订单同步日志安全测试。 public class UHaoZuConnectorLoggingTests { [Fact] public async Task FetchAsync_ShouldNotLogOrderResponseBody() { const string sensitiveOrderId = "secret-order-123"; var handler = new SequenceHttpMessageHandler( """{"responseCode":"0000","object":{}}""", $$"""{"responseCode":"0000","object":[{"id":"{{sensitiveOrderId}}","createTime":1721300000000}]}"""); var resolver = new ConnectorCredentialResolver(new StubHttpClientFactory(handler)); var logger = new CapturingLogger(); var connector = new UHaoZuConnector(resolver, logger); var system = new ExternalSystemEntity { Data = new SystemData { Name = "u号租", SystemType = ExternalSystemTypeEnum.UHaoZu, BaseUrl = "https://mapi.uhaozu.com", Parameters = new Dictionary { ["maxPages"] = "1" }, }, }; await connector.FetchAsync(system, default); logger.Messages.Should().NotContain(message => message.Contains(sensitiveOrderId)); } [Fact] public async Task FetchAsync_ShouldFailWhenOrderApiRejectsSession() { var handler = new SequenceHttpMessageHandler( """{"responseCode":"0000","object":{}}""", """{"responseCode":"9996","responseMsg":"not logged in"}"""); var resolver = new ConnectorCredentialResolver(new StubHttpClientFactory(handler)); var connector = new UHaoZuConnector(resolver, new CapturingLogger()); var system = CreateSystem(); var action = () => connector.FetchAsync(system, default); await action.Should().ThrowAsync() .WithMessage("*not logged in*"); } private static ExternalSystemEntity CreateSystem() => new() { Data = new SystemData { Name = "u号租", SystemType = ExternalSystemTypeEnum.UHaoZu, BaseUrl = "https://mapi.uhaozu.com", Parameters = new Dictionary { ["maxPages"] = "1" }, }, }; private sealed class StubHttpClientFactory(HttpMessageHandler handler) : IHttpClientFactory { public HttpClient CreateClient(string name) => new(handler, disposeHandler: false); } private sealed class SequenceHttpMessageHandler(params string[] responses) : HttpMessageHandler { private readonly Queue _responses = new(responses); protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { var body = _responses.Dequeue(); return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(body, Encoding.UTF8, "application/json"), }); } } private sealed class CapturingLogger : ILogger { public List Messages { get; } = []; public IDisposable? BeginScope(TState state) where TState : notnull => null; public bool IsEnabled(LogLevel logLevel) => true; public void Log( LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { Messages.Add(formatter(state, exception)); } } }