From 89b4bfd245ee0487b997c58ec836d87f2058a074 Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Mon, 11 May 2026 04:11:57 +0300 Subject: [PATCH] [AZ-374] Refactor C21: named GoogleMapsTiles HttpClient in DI - Register IHttpClientFactory named client "GoogleMapsTiles" inside AddTileDownloader() with User-Agent and 100s timeout (preserves HttpClient's implicit default). - Resolve the same named client from all three CreateClient() call sites in GoogleMapsDownloaderV2 (session token, single-tile, batch-tile retry lambda) and drop the duplicated per-call UserAgent.ParseAdd setup. - Expose USER_AGENT, the client name, and the timeout as internal consts on GoogleMapsDownloaderV2 so the extension and the downloader share one source of truth. - Add AC test that builds the DI container, resolves the named client, and asserts both the User-Agent header and the timeout. - Archive AZ-374 task file: todo/ -> done/. 175 unit + 5 smoke pass. Co-authored-by: Cursor --- .../GoogleMapsDownloaderV2.cs | 12 ++++----- ...leDownloaderServiceCollectionExtensions.cs | 5 ++++ SatelliteProvider.Tests/TileServiceTests.cs | 25 +++++++++++++++++++ ...74_refactor_typed_httpclient_googlemaps.md | 0 4 files changed, 36 insertions(+), 6 deletions(-) rename _docs/02_tasks/{todo => done}/AZ-374_refactor_typed_httpclient_googlemaps.md (100%) diff --git a/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs b/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs index ec63c2f..86308f5 100644 --- a/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs +++ b/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs @@ -14,7 +14,9 @@ namespace SatelliteProvider.Services.TileDownloader; public class GoogleMapsDownloaderV2 : ISatelliteDownloader { private const string TILE_URL_TEMPLATE = "https://mt{0}.google.com/vt/lyrs=s&x={1}&y={2}&z={3}&token={4}"; - private const string USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36"; + internal const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36"; + internal const string GoogleMapsTilesClientName = "GoogleMapsTiles"; + internal const int DefaultHttpClientTimeoutSeconds = 100; private readonly ILogger _logger; private readonly string _apiKey; @@ -46,7 +48,7 @@ public class GoogleMapsDownloaderV2 : ISatelliteDownloader private async Task GetSessionToken() { var url = $"https://tile.googleapis.com/v1/createSession?key={_apiKey}"; - using var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(GoogleMapsTilesClientName); try { var str = JsonConvert.SerializeObject(new { mapType = "satellite" }); @@ -102,8 +104,7 @@ public class GoogleMapsDownloaderV2 : ISatelliteDownloader var imageBytes = await ExecuteWithRetryAsync(async () => { - using var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT); + using var httpClient = _httpClientFactory.CreateClient(GoogleMapsTilesClientName); var response = await httpClient.GetAsync(url, token); @@ -366,8 +367,7 @@ public class GoogleMapsDownloaderV2 : ISatelliteDownloader var imageBytes = await ExecuteWithRetryAsync(async () => { - using var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT); + using var httpClient = _httpClientFactory.CreateClient(GoogleMapsTilesClientName); var response = await httpClient.GetAsync(url, token); diff --git a/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs b/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs index 9f4d502..d9e58c5 100644 --- a/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs +++ b/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs @@ -8,6 +8,11 @@ public static class TileDownloaderServiceCollectionExtensions public static IServiceCollection AddTileDownloader(this IServiceCollection services) { services.AddMemoryCache(); + services.AddHttpClient(GoogleMapsDownloaderV2.GoogleMapsTilesClientName, c => + { + c.DefaultRequestHeaders.UserAgent.ParseAdd(GoogleMapsDownloaderV2.UserAgent); + c.Timeout = TimeSpan.FromSeconds(GoogleMapsDownloaderV2.DefaultHttpClientTimeoutSeconds); + }); services.AddSingleton(); services.AddSingleton(); return services; diff --git a/SatelliteProvider.Tests/TileServiceTests.cs b/SatelliteProvider.Tests/TileServiceTests.cs index 795ba15..7d18c14 100644 --- a/SatelliteProvider.Tests/TileServiceTests.cs +++ b/SatelliteProvider.Tests/TileServiceTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; @@ -464,6 +465,30 @@ public class TileServiceTests } } +public class TileDownloaderRegistrationTests +{ + [Fact] + public void AddTileDownloader_RegistersNamedGoogleMapsTilesHttpClient_AZ374_AC1() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddTileDownloader(); + using var provider = services.BuildServiceProvider(); + var factory = provider.GetRequiredService(); + using var client = factory.CreateClient("GoogleMapsTiles"); + + // Assert + client.DefaultRequestHeaders.UserAgent.Should().NotBeEmpty( + "AZ-374 AC-1: the named GoogleMapsTiles client must carry the User-Agent set once at registration time"); + client.DefaultRequestHeaders.UserAgent.ToString() + .Should().Contain("Mozilla/5.0", "preserves the existing outbound User-Agent for Google Maps"); + client.Timeout.Should().Be(TimeSpan.FromSeconds(100), + "preserves the HttpClient default (100s) until C18 wires this to MapConfig"); + } +} + public class GoogleMapsDownloaderZoomValidationTests { private static GoogleMapsDownloaderV2 BuildDownloader() diff --git a/_docs/02_tasks/todo/AZ-374_refactor_typed_httpclient_googlemaps.md b/_docs/02_tasks/done/AZ-374_refactor_typed_httpclient_googlemaps.md similarity index 100% rename from _docs/02_tasks/todo/AZ-374_refactor_typed_httpclient_googlemaps.md rename to _docs/02_tasks/done/AZ-374_refactor_typed_httpclient_googlemaps.md