diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/WattTimeDataSourceMocker.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/WattTimeDataSourceMocker.cs index 69191dfb9..3c95e5350 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/WattTimeDataSourceMocker.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/mock/WattTimeDataSourceMocker.cs @@ -1,6 +1,6 @@ -using CarbonAware.Interfaces; -using CarbonAware.DataSources.WattTime.Constants; +using CarbonAware.DataSources.WattTime.Constants; using CarbonAware.DataSources.WattTime.Model; +using CarbonAware.Interfaces; using System.Net; using System.Net.Mime; using System.Text.Json; @@ -13,11 +13,11 @@ internal class WattTimeDataSourceMocker : IDataSourceMocker { protected WireMockServer _server; - private static readonly RegionResponse defaultBalancingAuthority = new() + private static readonly RegionResponse defaultRegion = new() { - Id = 12345, - Region = "TEST_BA", - RegionFullName = "Test Balancing Authority" + Region = "TEST_REGION", + RegionFullName = "Test Region Full Name", + SignalType = SignalTypes.co2_moer }; private static readonly LoginResult defaultLoginResult = new() { Token = "myDefaultToken123" }; @@ -39,20 +39,31 @@ public void SetupDataMock(DateTimeOffset start, DateTimeOffset end, string locat { var newDataPoint = new GridEmissionDataPoint() { - Region = defaultBalancingAuthority.Region, PointTime = pointTime, Value = 999.99F, Version = "1.0", - SignalType = "dt", Frequency = 300, Market = "mkt", }; + data.Add(newDataPoint); pointTime = newDataPoint.PointTime + duration; } - SetupResponseGivenGetRequest(Paths.Data, JsonSerializer.Serialize(data)); + var meta = new GridEmissionsMetaData() + { + Region = defaultRegion.Region, + SignalType = SignalTypes.co2_moer + }; + + var gridEmissionsResponse = new GridEmissionsDataResponse() + { + Data = data, + Meta = meta + }; + + SetupResponseGivenGetRequest(Paths.Data, JsonSerializer.Serialize(gridEmissionsResponse)); } public void SetupForecastMock() @@ -63,15 +74,13 @@ public void SetupForecastMock() var start = new DateTimeOffset(((curr.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, TimeSpan.Zero); var end = start + TimeSpan.FromDays(1.0); var pointTime = start; - var ForecastData = new List(); + var forecastData = new List(); var currValue = 200.0F; while (pointTime < end) { var newForecastPoint = new GridEmissionDataPoint() { - Region = defaultBalancingAuthority.Region, - SignalType = "dt", Frequency = 300, Market = "mkt", PointTime = start, @@ -80,17 +89,26 @@ public void SetupForecastMock() }; newForecastPoint.PointTime = pointTime; newForecastPoint.Value = currValue; - ForecastData.Add(newForecastPoint); + forecastData.Add(newForecastPoint); pointTime = pointTime + TimeSpan.FromMinutes(5); currValue = currValue + 5.0F; } - var forecast = new Forecast() + var meta = new GridEmissionsMetaData() { - ForecastData = ForecastData, + Region = defaultRegion.Region, + SignalType = SignalTypes.co2_moer, GeneratedAt = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) }; - SetupResponseGivenGetRequest(Paths.Forecast, JsonSerializer.Serialize(forecast)); + + var forecastResponse = new ForecastEmissionsDataResponse() + { + Data = forecastData, + Meta = meta + }; + + + SetupResponseGivenGetRequest(Paths.Forecast, JsonSerializer.Serialize(forecastResponse)); } public void SetupBatchForecastMock() @@ -98,14 +116,12 @@ public void SetupBatchForecastMock() var start = new DateTimeOffset(2021, 9, 1, 8, 30, 0, TimeSpan.Zero); var end = start + TimeSpan.FromDays(1.0); var pointTime = start; - var ForecastData = new List(); + var forecastData = new List(); var currValue = 200.0F; while (pointTime < end) { var newForecastPoint = new GridEmissionDataPoint() { - Region = defaultBalancingAuthority.Region, - SignalType = "dt", Frequency = 300, Market = "mkt", PointTime = start, @@ -114,19 +130,26 @@ public void SetupBatchForecastMock() }; newForecastPoint.PointTime = pointTime; newForecastPoint.Value = currValue; - ForecastData.Add(newForecastPoint); + forecastData.Add(newForecastPoint); pointTime = pointTime + TimeSpan.FromMinutes(5); currValue = currValue + 5.0F; } - var forecastData = new List { - new Forecast() + var meta = new GridEmissionsMetaData() + { + Region = defaultRegion.Region, + SignalType = SignalTypes.co2_moer, + GeneratedAt = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) + }; + + var forecastBatchData = new List { + new ForecastEmissionsDataResponse() { - ForecastData = ForecastData, - GeneratedAt = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) + Data = forecastData, + Meta = meta } }; - SetupResponseGivenGetRequest(Paths.Forecast, JsonSerializer.Serialize(forecastData)); + SetupResponseGivenGetRequest(Paths.Forecast, JsonSerializer.Serialize(forecastBatchData)); } public void Initialize() @@ -157,7 +180,7 @@ private void SetupResponseGivenGetRequest(string path, string body) ); } private void SetupBaMock(RegionResponse? content = null) => - SetupResponseGivenGetRequest(Paths.BalancingAuthorityFromLocation, JsonSerializer.Serialize(content ?? defaultBalancingAuthority)); + SetupResponseGivenGetRequest(Paths.BalancingAuthorityFromLocation, JsonSerializer.Serialize(content ?? defaultRegion)); private void SetupLoginMock(LoginResult? content = null) => SetupResponseGivenGetRequest(Paths.Login, JsonSerializer.Serialize(content ?? defaultLoginResult)); diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs index d20c8d978..b07f96cd8 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs @@ -36,7 +36,7 @@ internal interface IWattTimeClient /// Balancing authority abbreviation /// An which contains forecasted emissions data points. /// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes. - Task GetCurrentForecastAsync(string balancingAuthorityAbbreviation); + Task GetCurrentForecastAsync(string balancingAuthorityAbbreviation); /// /// Async method to get the most recent 24 hour forecasted emission data for a given balancing authority. @@ -44,7 +44,7 @@ internal interface IWattTimeClient /// Balancing authority /// An which contains forecasted emissions data points. /// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes. - Task GetCurrentForecastAsync(RegionResponse balancingAuthority); + Task GetCurrentForecastAsync(RegionResponse balancingAuthority); /// /// Async method to get generated forecast at requested time and balancing authority. @@ -53,7 +53,7 @@ internal interface IWattTimeClient /// The historical time used to fetch the most recent forecast generated as of that time. /// An which contains forecasted emissions data points or null if no Forecast generated at the requested time. /// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes. - Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt); + Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt); /// /// Async method to get generated forecast at requested time and balancing authority. @@ -62,7 +62,7 @@ internal interface IWattTimeClient /// The historical time used to fetch the most recent forecast generated as of that time. /// An which contains forecasted emissions data points or null if no Forecast generated at the requested time. /// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes. - Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt); + Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt); /// /// Async method to get the balancing authority for a given location. @@ -71,7 +71,7 @@ internal interface IWattTimeClient /// Longitude of the location /// An which contains the balancing authority details. /// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes. - Task GetBalancingAuthorityAsync(string latitude, string longitude); + Task GetRegionAsync(string latitude, string longitude); /// /// Async method to get the balancing authority abbreviation for a given location. diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs index f52955abc..8e02c7349 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs @@ -85,7 +85,7 @@ public Task GetDataAsync(RegionResponse region, DateT } /// - public async Task GetCurrentForecastAsync(string region) + public async Task GetCurrentForecastAsync(string region) { _log.LogInformation("Requesting current forecast from balancing authority {balancingAuthority}", region); @@ -106,19 +106,19 @@ public async Task GetCurrentForecastAsync(string region) var sr = new StreamReader(result); var s = sr.ReadToEnd(); - var forecast = await JsonSerializer.DeserializeAsync(result, _options) ?? throw new WattTimeClientException($"Error getting forecast for {region}"); + var forecast = await JsonSerializer.DeserializeAsync(result, _options) ?? throw new WattTimeClientException($"Error getting forecast for {region}"); return forecast; } /// - public Task GetCurrentForecastAsync(RegionResponse balancingAuthority) + public Task GetCurrentForecastAsync(RegionResponse balancingAuthority) { return this.GetCurrentForecastAsync(balancingAuthority.Region); } /// - public async Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt) + public async Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt) { _log.LogInformation($"Requesting forecast from balancingAuthority {balancingAuthorityAbbreviation} generated at {requestedAt}."); @@ -135,19 +135,19 @@ public Task GetCurrentForecastAsync(RegionResponse balancingAuthority) }; using (var result = await this.MakeRequestGetStreamAsync(Paths.Forecast, parameters, tags)) { - var forecasts = await JsonSerializer.DeserializeAsync>(result, _options) ?? throw new WattTimeClientException($"Error getting forecasts for {balancingAuthorityAbbreviation}"); + var forecasts = await JsonSerializer.DeserializeAsync>(result, _options) ?? throw new WattTimeClientException($"Error getting forecasts for {balancingAuthorityAbbreviation}"); return forecasts.FirstOrDefault(); } } /// - public Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt) + public Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt) { return this.GetForecastOnDateAsync(balancingAuthority.Region, requestedAt); } /// - public async Task GetBalancingAuthorityAsync(string latitude, string longitude) + public async Task GetRegionAsync(string latitude, string longitude) { _log.LogInformation("Requesting balancing authority for lattitude {lattitude} and longitude {longitude}", latitude, longitude); return await GetBalancingAuthorityFromCacheAsync(latitude, longitude); @@ -156,7 +156,7 @@ public async Task GetBalancingAuthorityAsync(string latitude, st /// public async Task GetBalancingAuthorityAbbreviationAsync(string latitude, string longitude) { - return (await this.GetBalancingAuthorityAsync(latitude, longitude))?.Region; + return (await this.GetRegionAsync(latitude, longitude))?.Region; } /// diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/Forecast.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/Forecast.cs deleted file mode 100644 index e7c5209e6..000000000 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/Forecast.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace CarbonAware.DataSources.WattTime.Model; - -/// -/// An emissions forecast for a given time period. -/// -[Serializable] -internal record Forecast -{ - /// - /// DateTime indicating when the forecast was generated. - /// - [JsonPropertyName("generated_at")] - public DateTimeOffset GeneratedAt { get; set; } - - /// - /// List of GridEmissionDataPoints representing the predicted values for those points in time. - /// - [JsonPropertyName("forecast")] - public List ForecastData { get; set; } = new List(); -} diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/ForecastEmissionsDataResponse.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/ForecastEmissionsDataResponse.cs new file mode 100644 index 000000000..9b4bd6597 --- /dev/null +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/ForecastEmissionsDataResponse.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CarbonAware.DataSources.WattTime.Model; + +[Serializable] +internal record ForecastEmissionsDataResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } = new List(); + + + [JsonPropertyName("meta")] + public GridEmissionsMetaData Meta { get; set; } +} + + diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsDataResponse.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsDataResponse.cs index f95918d83..e50eccb80 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsDataResponse.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsDataResponse.cs @@ -11,4 +11,6 @@ internal record GridEmissionsDataResponse [JsonPropertyName("meta")] public GridEmissionsMetaData Meta { get; set; } -} \ No newline at end of file +} + + diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsMetaData.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsMetaData.cs index 7f4768e3a..d5abb4117 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsMetaData.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/GridEmissionsMetaData.cs @@ -30,5 +30,5 @@ internal record GridEmissionsMetaData public int? GeneratedAtPeriodSeconds { get; set; } [JsonPropertyName("generated_at")] - public DateTimeOffset? GeneratedAt { get; set; } + public DateTimeOffset GeneratedAt { get; set; } = DateTimeOffset.MinValue; } \ No newline at end of file diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs index cd7d77c82..c879df636 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs @@ -103,19 +103,19 @@ public async Task GetCarbonIntensityForecastAsync(Location lo return ForecastToEmissionsForecast(forecast, location, requestedAt); } - private EmissionsForecast ForecastToEmissionsForecast(Forecast forecast, Location location, DateTimeOffset requestedAt) + private EmissionsForecast ForecastToEmissionsForecast(ForecastEmissionsDataResponse forecast, Location location, DateTimeOffset requestedAt) { - var duration = GetDurationFromGridEmissionDataPoints(forecast.ForecastData); - var forecastData = forecast.ForecastData.Select(e => new EmissionsData() + var duration = GetDurationFromGridEmissionDataPoints(forecast.Data); + var forecastData = forecast.Data.Select(e => new EmissionsData() { - Location = "", //e.Region, // TODO: VAUGHAN + Location = forecast.Meta.Region, Rating = ConvertMoerToGramsPerKilowattHour(e.Value), Time = e.PointTime, Duration = duration }); var emissionsForecast = new EmissionsForecast() { - GeneratedAt = forecast.GeneratedAt, + GeneratedAt = forecast.Meta.GeneratedAt, Location = location, ForecastData = forecastData }; @@ -177,7 +177,7 @@ private async Task GetBalancingAuthority(Location location) try { var geolocation = await this.LocationSource.ToGeopositionLocationAsync(location); - balancingAuthority = await WattTimeClient.GetBalancingAuthorityAsync(geolocation.LatitudeAsCultureInvariantString(), geolocation.LongitudeAsCultureInvariantString()); + balancingAuthority = await WattTimeClient.GetRegionAsync(geolocation.LatitudeAsCultureInvariantString(), geolocation.LongitudeAsCultureInvariantString()); } catch (Exception ex) when (ex is LocationConversionException || ex is WattTimeClientHttpException) { diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs index fbdbc8ca7..a79ca8b56 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs @@ -1,4 +1,5 @@ using CarbonAware.DataSources.WattTime.Configuration; +using CarbonAware.DataSources.WattTime.Constants; using CarbonAware.DataSources.WattTime.Model; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -73,7 +74,7 @@ public void AllPublicMethods_ThrowsWhenInvalidLogin() Assert.ThrowsAsync(async () => await client.GetDataAsync("ba", new DateTimeOffset(), new DateTimeOffset())); Assert.ThrowsAsync(async () => await client.GetCurrentForecastAsync("ba")); Assert.ThrowsAsync(async () => await client.GetForecastOnDateAsync("ba", new DateTimeOffset())); - Assert.ThrowsAsync(async () => await client.GetBalancingAuthorityAsync("lat", "long")); + Assert.ThrowsAsync(async () => await client.GetRegionAsync("lat", "long")); Assert.ThrowsAsync(async () => await client.GetBalancingAuthorityAbbreviationAsync("lat", "long")); Assert.ThrowsAsync(async () => await client.GetHistoricalDataAsync("ba")); } @@ -87,7 +88,7 @@ public void AllPublicMethods_ThrowClientException_WhenNull() client.SetBearerAuthenticationHeader(this.DefaultTokenValue); var ba = new RegionResponse() { Region = "balauth" }; - Assert.ThrowsAsync(async () => await client.GetBalancingAuthorityAsync("lat", "long")); + Assert.ThrowsAsync(async () => await client.GetRegionAsync("lat", "long")); Assert.ThrowsAsync(async () => await client.GetDataAsync(ba.Region, new DateTimeOffset(), new DateTimeOffset())); Assert.ThrowsAsync(async () => await client.GetDataAsync(ba, new DateTimeOffset(), new DateTimeOffset())); Assert.ThrowsAsync(async () => await client.GetCurrentForecastAsync(ba.Region)); @@ -105,7 +106,7 @@ public void AllPublicMethods_ThrowJsonException_WhenBadJsonIsReturned() client.SetBearerAuthenticationHeader(this.DefaultTokenValue); var ba = new RegionResponse() { Region = "balauth" }; - Assert.ThrowsAsync(async () => await client.GetBalancingAuthorityAsync("lat", "long")); + Assert.ThrowsAsync(async () => await client.GetRegionAsync("lat", "long")); Assert.ThrowsAsync(async () => await client.GetDataAsync(ba.Region, new DateTimeOffset(), new DateTimeOffset())); Assert.ThrowsAsync(async () => await client.GetDataAsync(ba, new DateTimeOffset(), new DateTimeOffset())); Assert.ThrowsAsync(async () => await client.GetCurrentForecastAsync(ba.Region)); @@ -126,12 +127,13 @@ public async Task GetDataAsync_DeserializesExpectedResponse() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var data = await client.GetDataAsync("balauth", new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero)); + var emissionsResponse = await client.GetDataAsync("region", new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero)); - Assert.IsTrue(data.Count() > 0); - var gridDataPoint = data.ToList().First(); - Assert.AreEqual("ba", gridDataPoint.Region); - Assert.AreEqual("dt", gridDataPoint.SignalType); + Assert.IsTrue(emissionsResponse.Data.Count() > 0); + var meta = emissionsResponse.Meta; + Assert.AreEqual("region", meta.Region); + Assert.AreEqual("signal_type", meta.SignalType); + var gridDataPoint = emissionsResponse.Data.ToList().First(); Assert.AreEqual(300, gridDataPoint.Frequency); Assert.AreEqual("mkt", gridDataPoint.Market); Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), gridDataPoint.PointTime); @@ -147,11 +149,10 @@ public async Task GetDataAsync_RefreshesTokenWhenExpired() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var data = await client.GetDataAsync("balauth", new DateTimeOffset(), new DateTimeOffset()); + var emissionsResponse = await client.GetDataAsync("region", new DateTimeOffset(), new DateTimeOffset()); - Assert.IsTrue(data.Count() > 0); - var gridDataPoint = data.ToList().First(); - Assert.AreEqual("ba", gridDataPoint.Region); + Assert.IsTrue(emissionsResponse.Data.Count() > 0); + Assert.AreEqual("region", emissionsResponse.Meta.Region); } [Test] @@ -161,11 +162,10 @@ public async Task GetDataAsync_RefreshesTokenWhenNoneSet() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); - var data = await client.GetDataAsync("balauth", new DateTimeOffset(), new DateTimeOffset()); + var gridEmissionsResponse = await client.GetDataAsync("region", new DateTimeOffset(), new DateTimeOffset()); - Assert.IsTrue(data.Count() > 0); - var gridDataPoint = data.ToList().First(); - Assert.AreEqual("ba", gridDataPoint.Region); + Assert.IsTrue(gridEmissionsResponse.Data.Count() > 0); + Assert.AreEqual("region", gridEmissionsResponse.Meta.Region); } [Test] @@ -180,18 +180,19 @@ public async Task GetCurrentForecastAsync_DeserializesExpectedResponse() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var ba = new RegionResponse() { Region = "balauth" }; + var regionResponse = new RegionResponse() { Region = "region" }; + + var forecastResponse = await client.GetCurrentForecastAsync(regionResponse.Region); + var overloadedForecast = await client.GetCurrentForecastAsync(regionResponse); - var forecast = await client.GetCurrentForecastAsync(ba.Region); - var overloadedForecast = await client.GetCurrentForecastAsync(ba); + Assert.AreEqual(forecastResponse.Meta.GeneratedAt, overloadedForecast.Meta.GeneratedAt); + Assert.AreEqual(forecastResponse.Data.First(), overloadedForecast.Data.First()); - Assert.AreEqual(forecast.GeneratedAt, overloadedForecast.GeneratedAt); - Assert.AreEqual(forecast.ForecastData.First(), overloadedForecast.ForecastData.First()); + Assert.IsNotNull(forecastResponse); + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse?.Meta.GeneratedAt); + Assert.AreEqual("region", forecastResponse?.Meta.Region); + var forecastDataPoint = forecastResponse?.Data.First(); - Assert.IsNotNull(forecast); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast?.GeneratedAt); - var forecastDataPoint = forecast?.ForecastData.First(); - Assert.AreEqual("ba", forecastDataPoint?.Region); Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastDataPoint?.PointTime); Assert.AreEqual("999.99", forecastDataPoint?.Value.ToString("0.00", CultureInfo.InvariantCulture)); //Format float to avoid precision issues Assert.AreEqual("1.0", forecastDataPoint?.Version); @@ -205,12 +206,11 @@ public async Task GetCurrentForecastAsync_RefreshesTokenWhenExpired() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var forecast = await client.GetCurrentForecastAsync("balauth"); + var forecastResponse = await client.GetCurrentForecastAsync("region"); - Assert.IsNotNull(forecast); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast?.GeneratedAt); - var forecastDataPoint = forecast?.ForecastData.First(); - Assert.AreEqual("ba", forecastDataPoint?.Region); + Assert.IsNotNull(forecastResponse); + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse?.Meta.GeneratedAt); + Assert.AreEqual("region", forecastResponse?.Meta.Region); } [Test] @@ -228,12 +228,11 @@ public async Task GetCurrentForecastAsync_RefreshesTokenWhenNoneSet() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); - var forecast = await client.GetCurrentForecastAsync("balauth"); + var forecastResponse = await client.GetCurrentForecastAsync("region"); - Assert.IsNotNull(forecast); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast?.GeneratedAt); - var forecastDataPoint = forecast?.ForecastData.First(); - Assert.AreEqual("ba", forecastDataPoint?.Region); + Assert.IsNotNull(forecastResponse); + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse?.Meta.GeneratedAt); + Assert.AreEqual("region", forecastResponse?.Meta.Region); } [Test] @@ -247,17 +246,18 @@ public async Task GetForecastOnDateAsync_DeserializesExpectedResponse() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var ba = new RegionResponse() { Region = "balauth" }; + var ba = new RegionResponse() { Region = "region" }; - var forecast = await client.GetForecastOnDateAsync(ba.Region, new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero)); + var forecastResponse = await client.GetForecastOnDateAsync(ba.Region, new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero)); var overloadedForecast = await client.GetForecastOnDateAsync(ba, new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero)); - Assert.AreEqual(forecast!.GeneratedAt, overloadedForecast!.GeneratedAt); - Assert.AreEqual(forecast.ForecastData.First(), overloadedForecast.ForecastData.First()); + Assert.AreEqual(forecastResponse!.Meta.GeneratedAt, overloadedForecast!.Meta.GeneratedAt); + Assert.AreEqual(forecastResponse.Data.First(), overloadedForecast.Data.First()); + + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse.Meta.GeneratedAt); + Assert.AreEqual("region", forecastResponse.Meta.Region); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast.GeneratedAt); - var forecastDataPoint = forecast.ForecastData.ToList().First(); - Assert.AreEqual("ba", forecastDataPoint.Region); + var forecastDataPoint = forecastResponse.Data.ToList().First(); Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastDataPoint.PointTime); Assert.AreEqual("999.99", forecastDataPoint.Value.ToString("0.00", CultureInfo.InvariantCulture)); //Format float to avoid precision issues Assert.AreEqual("1.0", forecastDataPoint.Version); @@ -271,8 +271,8 @@ public async Task GetForecastOnDateAsync_RefreshesTokenWhenExpired() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var forecast = await client.GetForecastOnDateAsync("balauth", new DateTimeOffset()); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast!.GeneratedAt); + var forecastResponse = await client.GetForecastOnDateAsync("region", new DateTimeOffset()); + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse!.Meta.GeneratedAt); } [Test] @@ -291,9 +291,9 @@ public async Task GetForecastOnDateAsync_RefreshesTokenWhenNoneSet() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); - var forecast = await client.GetForecastOnDateAsync("balauth", new DateTimeOffset()); + var forecastResponse = await client.GetForecastOnDateAsync("region", new DateTimeOffset()); - Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecast!.GeneratedAt); + Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse!.Meta.GeneratedAt); } [Test] @@ -308,12 +308,12 @@ public async Task GetBalancingAuthorityAsync_DeserializesExpectedResponse() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var ba = await client.GetBalancingAuthorityAsync("lat", "long"); + var regionResponse = await client.GetRegionAsync("lat", "long"); - Assert.IsNotNull(ba); - Assert.AreEqual(12345, ba?.Id); - Assert.AreEqual("TEST_BA", ba?.Region); - Assert.AreEqual("Test Balancing Authority", ba?.RegionFullName); + Assert.IsNotNull(regionResponse); + Assert.AreEqual("TEST_BA", regionResponse?.Region); + Assert.AreEqual("Test Balancing Authority", regionResponse?.RegionFullName); + Assert.AreEqual(SignalTypes.co2_moer, regionResponse?.SignalType); } [Test] @@ -324,10 +324,10 @@ public async Task GetBalancingAuthorityAsync_RefreshesTokenWhenExpired() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); client.SetBearerAuthenticationHeader(this.DefaultTokenValue); - var ba = await client.GetBalancingAuthorityAsync("lat", "long"); + var regionResponse = await client.GetRegionAsync("lat", "long"); - Assert.IsNotNull(ba); - Assert.AreEqual(12345, ba?.Id); + Assert.IsNotNull(regionResponse); + Assert.AreEqual(SignalTypes.co2_moer, regionResponse?.SignalType); } [Test] @@ -346,10 +346,10 @@ public async Task GetBalancingAuthorityAsync_RefreshesTokenWhenNoneSet() var client = new WattTimeClient(this.HttpClientFactory, this.Options.Object, this.Log.Object, this.MemoryCache); - var ba = await client.GetBalancingAuthorityAsync("lat", "long"); + var regionResponse = await client.GetRegionAsync("lat", "long"); - Assert.IsNotNull(ba); - Assert.AreEqual(12345, ba?.Id); + Assert.IsNotNull(regionResponse); + Assert.AreEqual(SignalTypes.co2_moer, regionResponse?.SignalType); } [Test] diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Configuration/ServiceCollectionExtensionTests.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Configuration/ServiceCollectionExtensionTests.cs index b3f845e3a..653b2f18e 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Configuration/ServiceCollectionExtensionTests.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Configuration/ServiceCollectionExtensionTests.cs @@ -49,7 +49,7 @@ public void ClientProxyTest_With_Invalid_ProxyURL_ThrowsException() var client = serviceProvider.GetRequiredService(); // Act & Assert - Assert.ThrowsAsync(async () => await client.GetBalancingAuthorityAsync("lat", "long")); + Assert.ThrowsAsync(async () => await client.GetRegionAsync("lat", "long")); } [Test] diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs index 967507b22..0ddf84dcb 100644 --- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs +++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs @@ -1,4 +1,5 @@ using CarbonAware.DataSources.WattTime.Client; +using CarbonAware.DataSources.WattTime.Constants; using CarbonAware.DataSources.WattTime.Model; using CarbonAware.Exceptions; using CarbonAware.Interfaces; @@ -25,7 +26,7 @@ class WattTimeDataSourceTests private Mock LocationSource { get; set; } private Location DefaultLocation { get; set; } - private RegionResponse DefaultBalancingAuthority { get; set; } + private RegionResponse DefaultRegion { get; set; } private DateTimeOffset DefaultDataStartTime { get; set; } // Magic floating point tolerance to allow for minuscule differences in floating point arithmetic. @@ -46,7 +47,7 @@ public void Setup() this.DataSource = new WattTimeDataSource(this.Logger.Object, this.WattTimeClient.Object, this.LocationSource.Object); this.DefaultLocation = new Location() { Name = "eastus" }; - this.DefaultBalancingAuthority = new RegionResponse() { Region = "BA" }; + this.DefaultRegion = new RegionResponse() { Region = "BA" }; this.DefaultDataStartTime = new DateTimeOffset(2022, 4, 18, 12, 32, 42, TimeSpan.FromHours(-6)); MockBalancingAuthorityLocationMapping(); } @@ -60,13 +61,13 @@ public async Task GetCarbonIntensity_ReturnsResultsWhenRecordsFound() var lbsPerMwhEmissions = 10; var gPerKwhEmissions = this.DataSource.ConvertMoerToGramsPerKilowattHour(lbsPerMwhEmissions); - var emissionData = GenerateDataPoints(1, value: lbsPerMwhEmissions); + var emissionDataResponse = GenerateGridEmissionsResponse(1, value: lbsPerMwhEmissions); this.WattTimeClient.Setup(w => w.GetDataAsync( - this.DefaultBalancingAuthority, + this.DefaultRegion, It.IsAny(), It.IsAny()) - ).ReturnsAsync(() => emissionData); + ).ReturnsAsync(() => emissionDataResponse); var result = await this.DataSource.GetCarbonIntensityAsync(new List() { this.DefaultLocation }, startDate, endDate); @@ -76,7 +77,7 @@ public async Task GetCarbonIntensity_ReturnsResultsWhenRecordsFound() var first = result.First(); Assert.IsNotNull(first); Assert.AreEqual(gPerKwhEmissions, first.Rating); - Assert.AreEqual(this.DefaultBalancingAuthority.Region, first.Location); + Assert.AreEqual(this.DefaultRegion.Region, first.Location); Assert.AreEqual(startDate, first.Time); this.LocationSource.Verify(r => r.ToGeopositionLocationAsync(this.DefaultLocation)); @@ -89,10 +90,10 @@ public async Task GetCarbonIntensity_ReturnsEmptyListWhenNoRecordsFound() var endDate = startDate.AddMinutes(1); this.WattTimeClient.Setup(w => w.GetDataAsync( - this.DefaultBalancingAuthority, + this.DefaultRegion, startDate, endDate) - ).ReturnsAsync(() => new List()); + ).ReturnsAsync(() => new GridEmissionsDataResponse()); var result = await this.DataSource.GetCarbonIntensityAsync(new List() { this.DefaultLocation }, startDate, endDate); @@ -123,27 +124,23 @@ public async Task GetCarbonIntensityForecastAsync_ReturnsResultsWhenRecordsFound var gPerKwhEmissions = this.DataSource.ConvertMoerToGramsPerKilowattHour(lbsPerMwhEmissions); var expectedDuration = TimeSpan.FromMinutes(5); - var emissionData = GenerateDataPoints(2, value: lbsPerMwhEmissions); - var forecast = new Forecast() - { - GeneratedAt = generatedAt, - ForecastData = emissionData - }; + var forecastResponse = GenerateForecastResponse(2, value: lbsPerMwhEmissions); + forecastResponse.Meta.GeneratedAt = generatedAt; EmissionsForecast result; if (getCurrentForecast) { - this.WattTimeClient.Setup(w => w.GetCurrentForecastAsync(this.DefaultBalancingAuthority) - ).ReturnsAsync(() => forecast); + this.WattTimeClient.Setup(w => w.GetCurrentForecastAsync(this.DefaultRegion) + ).ReturnsAsync(() => forecastResponse); // Act result = await this.DataSource.GetCurrentCarbonIntensityForecastAsync(this.DefaultLocation); } else { - this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultBalancingAuthority, generatedAt) - ).ReturnsAsync(() => forecast); + this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, generatedAt) + ).ReturnsAsync(() => forecastResponse); // Act result = await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt); @@ -158,13 +155,13 @@ public async Task GetCarbonIntensityForecastAsync_ReturnsResultsWhenRecordsFound var lastDataPoint = result.ForecastData.Last(); Assert.IsNotNull(firstDataPoint); Assert.AreEqual(gPerKwhEmissions, firstDataPoint.Rating); - Assert.AreEqual(this.DefaultBalancingAuthority.Region, firstDataPoint.Location); + Assert.AreEqual(this.DefaultRegion.Region, firstDataPoint.Location); Assert.AreEqual(startDate, firstDataPoint.Time); Assert.AreEqual(expectedDuration, firstDataPoint.Duration); Assert.IsNotNull(lastDataPoint); Assert.AreEqual(gPerKwhEmissions, lastDataPoint.Rating); - Assert.AreEqual(this.DefaultBalancingAuthority.Region, lastDataPoint.Location); + Assert.AreEqual(this.DefaultRegion.Region, lastDataPoint.Location); Assert.AreEqual(startDate + expectedDuration, lastDataPoint.Time); Assert.AreEqual(expectedDuration, lastDataPoint.Duration); @@ -185,7 +182,7 @@ public void GetCarbonIntensityForecastAsync_ThrowsWhenNoForecastFoundForReuqeste { var generatedAt = new DateTimeOffset(); - this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultBalancingAuthority, generatedAt)).Returns(Task.FromResult(null)); + this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, generatedAt)).Returns(Task.FromResult(null)); // The datasource throws an exception if no forecasts are found at the requested generatedAt time. Assert.ThrowsAsync(async () => await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt)); @@ -196,16 +193,11 @@ public void GetCarbonIntensityForecastAsync_ThrowsWhenNoForecastFoundForReuqeste public void GetCurrentCarbonIntensityForecastAsync_ThrowsWhenTooFewDatapointsReturned(int numDataPoints) { // Arrange - var emissionData = GenerateDataPoints(numDataPoints); - - var forecast = new Forecast() - { - GeneratedAt = DateTimeOffset.Now, - ForecastData = emissionData - }; + var forecastResponse = GenerateForecastResponse(numDataPoints); + forecastResponse.Meta.GeneratedAt = DateTimeOffset.Now; - this.WattTimeClient.Setup(w => w.GetCurrentForecastAsync(this.DefaultBalancingAuthority) - ).ReturnsAsync(() => forecast); + this.WattTimeClient.Setup(w => w.GetCurrentForecastAsync(this.DefaultRegion) + ).ReturnsAsync(() => forecastResponse); Assert.ThrowsAsync(async () => await this.DataSource.GetCurrentCarbonIntensityForecastAsync(this.DefaultLocation)); } @@ -219,19 +211,16 @@ public async Task GetCarbonIntensityForecastAsync_RequiredAtRounded(string reque var requestedAt = DateTimeOffset.Parse(requested); var expectedAt = DateTimeOffset.Parse(expected); - var emissionData = GenerateDataPoints(2, startTime: requestedAt); - var forecast = new Forecast() - { - GeneratedAt = expectedAt, - ForecastData = emissionData - }; + var forecastResponse = GenerateForecastResponse(2, startTime: requestedAt); + forecastResponse.Meta.GeneratedAt = expectedAt; + - this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultBalancingAuthority, expectedAt) - ).ReturnsAsync(() => forecast); + this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, expectedAt) + ).ReturnsAsync(() => forecastResponse); // Act var result = await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, requestedAt); - + // Assert Assert.IsNotNull(result); this.WattTimeClient.Verify(w => w.GetForecastOnDateAsync( @@ -239,7 +228,7 @@ public async Task GetCarbonIntensityForecastAsync_RequiredAtRounded(string reque } [DatapointSource] - public float[] moerValues = new float[] { 0.0F, 10.0F, 100.0F, 1000.0F, 596.1367F}; + public float[] moerValues = new float[] { 0.0F, 10.0F, 100.0F, 1000.0F, 596.1367F }; [Theory] public void GetCarbonIntensity_ConvertsMoerToGramsPerKwh(float lbsPerMwhEmissions) @@ -265,26 +254,26 @@ public async Task GetCarbonIntensity_CalculatesDurationBasedOnFrequency(double[] // Arrange var startDate = this.DefaultDataStartTime; var endDate = startDate.AddMinutes(10); - var emissionData = GenerateDataPoints(frequencyValues.Length); - for( int i = 0; i < frequencyValues.Length; i++) + var emissionResponse = GenerateGridEmissionsResponse(frequencyValues.Length); + for (int i = 0; i < frequencyValues.Length; i++) { - emissionData[i].Frequency = frequencyValues[i]; + emissionResponse.Data[i].Frequency = frequencyValues[i]; } - + List expectedDurationList = durationValues.ToList(); this.WattTimeClient.Setup(w => w.GetDataAsync( It.IsAny(), It.IsAny(), It.IsAny()) - ).ReturnsAsync(() => emissionData); + ).ReturnsAsync(() => emissionResponse); // Act var result = await this.DataSource.GetCarbonIntensityAsync(new List() { this.DefaultLocation }, startDate, endDate); // Assert List actualDurationList = result.Select(e => e.Duration.TotalSeconds).ToList(); - + CollectionAssert.AreEqual(expectedDurationList, actualDurationList); } @@ -294,8 +283,44 @@ private void MockBalancingAuthorityLocationMapping() var latitude = this.DefaultLocation.Latitude.ToString(); var longitude = this.DefaultLocation.Longitude.ToString(); - this.WattTimeClient.Setup(w => w.GetBalancingAuthorityAsync(latitude!, longitude!) - ).ReturnsAsync(() => this.DefaultBalancingAuthority); + this.WattTimeClient.Setup(w => w.GetRegionAsync(latitude!, longitude!) + ).ReturnsAsync(() => this.DefaultRegion); + } + + private GridEmissionsDataResponse GenerateGridEmissionsResponse(int numberOfDatapoints, float value = 10, DateTimeOffset startTime = default) + { + var data = GenerateDataPoints(numberOfDatapoints, value, startTime); + var meta = new GridEmissionsMetaData() + { + Region = this.DefaultRegion.Region, + SignalType = SignalTypes.co2_moer + }; + + var response = new GridEmissionsDataResponse() + { + Data = data, + Meta = meta + }; + + return response; + } + + private ForecastEmissionsDataResponse GenerateForecastResponse(int numberOfDatapoints, float value = 10, DateTimeOffset startTime = default) + { + var data = GenerateDataPoints(numberOfDatapoints, value, startTime); + var meta = new GridEmissionsMetaData() + { + Region = this.DefaultRegion.Region, + SignalType = SignalTypes.co2_moer + }; + + var response = new ForecastEmissionsDataResponse() + { + Data = data, + Meta = meta + }; + + return response; } private List GenerateDataPoints(int numberOfDatapoints, float value = 10, DateTimeOffset startTime = default) @@ -307,7 +332,6 @@ private List GenerateDataPoints(int numberOfDatapoints, f { var dataPoint = new GridEmissionDataPoint() { - Region = this.DefaultBalancingAuthority.Region, PointTime = pointTime, Value = value, Frequency = defaultFrequency