using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Svrnty.GeoManagement.Abstractions.Models; using Svrnty.GeoManagement.Google; using Svrnty.GeoManagement.Google.Configuration; using Svrnty.GeoManagement.Tests.Configuration; namespace Svrnty.GeoManagement.Tests; public class GoogleProviderTests : IDisposable { private readonly GeoManagementGoogleProvider _provider; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly TestDataConfiguration _testData; public GoogleProviderTests() { // Build configuration from appsettings.Development.json _configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.Development.json", optional: false, reloadOnChange: false) .AddEnvironmentVariables() .Build(); // Setup logger var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Debug); }); _logger = loggerFactory.CreateLogger(); // Get options from configuration var options = _configuration.GetSection("GoogleGeoManagement").Get(); if (options == null || string.IsNullOrWhiteSpace(options.ApiKey)) { throw new InvalidOperationException( "Google API key not configured. Please add your API key to appsettings.Development.json"); } // Load test data from configuration _testData = _configuration.GetSection("TestData").Get() ?? throw new InvalidOperationException( "Test data not configured. Please add TestData section to appsettings.Development.json"); _provider = new GeoManagementGoogleProvider( Options.Create(options), _logger); } [Fact] public async Task GetGeoPointAsync_WithValidAddress_ReturnsCoordinates() { // Arrange var address = new Address( Line1: _testData.ValidAddress.Line1, Line2: _testData.ValidAddress.Line2, City: _testData.ValidAddress.City, Subdivision: _testData.ValidAddress.Subdivision, PostalCode: _testData.ValidAddress.PostalCode, Country: _testData.ValidAddress.Country, Location: null, Note: null); // Act var result = await _provider.GetGeoPointAsync(address); // Assert Assert.NotNull(result); Assert.InRange(result.Latitude, _testData.ExpectedLatRange.Min, _testData.ExpectedLatRange.Max); Assert.InRange(result.Longitude, _testData.ExpectedLongRange.Min, _testData.ExpectedLongRange.Max); } [Fact] public async Task ReverseGeocodeAsync_WithValidCoordinates_ReturnsAddress() { // Arrange var geoPoint = new GeoPoint( _testData.ValidGeoPoint.Latitude, _testData.ValidGeoPoint.Longitude); // Act var result = await _provider.ReverseGeocodeAsync(geoPoint); // Assert Assert.NotNull(result); Assert.NotNull(result.Line1); Assert.NotNull(result.City); Assert.NotNull(result.Country); Assert.True(result.IsNormalized); Assert.NotNull(result.Location); } [Fact] public async Task NormalizeAddressAsync_WithAddressObject_ReturnsNormalizedAddress() { // Arrange var address = new Address( Line1: _testData.NormalizeTestAddress.Line1, Line2: _testData.NormalizeTestAddress.Line2, City: _testData.NormalizeTestAddress.City, Subdivision: _testData.NormalizeTestAddress.Subdivision, PostalCode: _testData.NormalizeTestAddress.PostalCode, Country: _testData.NormalizeTestAddress.Country, Location: null, Note: null); // Act var result = await _provider.NormalizeAddressAsync(address); // Assert Assert.NotNull(result); Assert.True(result.IsNormalized); Assert.NotNull(result.Location); Assert.NotNull(result.Line1); Assert.Contains("Amphitheatre", result.Line1, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task NormalizeAddressAsync_WithAddressString_ReturnsNormalizedAddress() { // Arrange var addressString = _testData.AddressString; // Act var result = await _provider.NormalizeAddressAsync(addressString); // Assert Assert.NotNull(result); Assert.True(result.IsNormalized); Assert.NotNull(result.Location); Assert.NotNull(result.Line1); Assert.Contains("Mountain View", result.City, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task GetGeoPointAsync_WithInvalidAddress_ReturnsNull() { // Arrange var address = new Address( Line1: _testData.InvalidAddress.Line1, Line2: _testData.InvalidAddress.Line2, City: _testData.InvalidAddress.City, Subdivision: _testData.InvalidAddress.Subdivision, PostalCode: _testData.InvalidAddress.PostalCode, Country: _testData.InvalidAddress.Country, Location: null, Note: null); // Act var result = await _provider.GetGeoPointAsync(address); // Assert - Google might still return something, so we just verify it doesn't crash // In real scenarios, very invalid addresses should return null Assert.True(result == null || result.Latitude != 0 || result.Longitude != 0); } [Fact] public async Task ReverseGeocodeAsync_WithOceanCoordinates_ReturnsAddress() { // Arrange var geoPoint = new GeoPoint( _testData.OceanGeoPoint.Latitude, _testData.OceanGeoPoint.Longitude); // Act var result = await _provider.ReverseGeocodeAsync(geoPoint); // Assert - May return null or a very general location // This test just verifies it doesn't crash if (result != null) { Assert.True(result.IsNormalized); } } public void Dispose() { // Cleanup if needed GC.SuppressFinalize(this); } }