From 8c41f590ee9a51dd8806dd5e8c04abc9210bc151 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 28 Aug 2020 00:09:09 +0200 Subject: [PATCH 1/3] Initial notification service Most ground work has been covered --- .../Modules/NotificationService.cs | 182 ++++++++++++++++++ .../Objects/Core/LoggingEvents.cs | 5 + src/PVOutput.Net/PVOutputClient.cs | 7 + .../Modules/DeregisterNotificationRequest.cs | 25 +++ .../Modules/RegisterNotificationRequest.cs | 27 +++ .../Notification/NotificationServiceTests.cs | 114 +++++++++++ .../NotificationServiceTestsData.cs | 12 ++ 7 files changed, 372 insertions(+) create mode 100644 src/PVOutput.Net/Modules/NotificationService.cs create mode 100644 src/PVOutput.Net/Requests/Modules/DeregisterNotificationRequest.cs create mode 100644 src/PVOutput.Net/Requests/Modules/RegisterNotificationRequest.cs create mode 100644 tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs create mode 100644 tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTestsData.cs diff --git a/src/PVOutput.Net/Modules/NotificationService.cs b/src/PVOutput.Net/Modules/NotificationService.cs new file mode 100644 index 0000000..9b9c67d --- /dev/null +++ b/src/PVOutput.Net/Modules/NotificationService.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Dawn; +using PVOutput.Net.Builders; +using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; +using PVOutput.Net.Requests.Handler; +using PVOutput.Net.Requests.Modules; +using PVOutput.Net.Responses; + +namespace PVOutput.Net.Modules +{ + /// + /// The Notification service enables (de-)registering alert notifications. + /// See the official API information. + /// + public sealed class NotificationService : BaseService + { + /// + /// Alert for a new private message. + /// + public const int PrivateMessageAlert = 1; + + /// + /// Alert for joining a new team. + /// + public const int JoinedTeamAlert = 3; + + /// + /// Alert for an added favourite. + /// + public const int AddedFavouriteAlert = 4; + + /// + /// Alert for high consumption. + /// + public const int HighConsumptionAlert = 5; + + /// + /// Alert for an idle system. + /// + public const int SystemIdleAlert = 6; + + /// + /// Alert for low generation. + /// + public const int LowGenerationAlert = 8; + + /// + /// Alert for low performance. + /// + public const int PerformanceAlert = 11; + + /// + /// Alerty for a high standby cost alert. + /// + public const int StandbyCostAlert = 14; + + /// + /// Alert for extended data element 7. + /// + public const int ExtendedDataV7Alert = 15; + + /// + /// Alert for extended data element 8. + /// + public const int ExtendedDataV8Alert = 16; + + /// + /// Alert for extended data element 9. + /// + public const int ExtendedDataV9Alert = 17; + + /// + /// Alert for extended data element 10. + /// + public const int ExtendedDataV10Alert = 18; + + /// + /// Alert for extended data element 11. + /// + public const int ExtendedDataV11Alert = 19; + + /// + /// Alert for extended data element 12. + /// + public const int ExtendedDataV12Alert = 20; + + /// + /// Alert for high net power. + /// + public const int HighNetPowerAlert = 23; + + /// + /// Alert for low net power. + /// + public const int LowNetPowerAlert = 24; + + internal NotificationService(PVOutputClient client) : base(client) + { + } + + /// + /// Registers an application for a notification. + /// + /// ApplicationId to register the notification under. + /// The url that should get called for each notification. + /// A specific type of alert to send, leave empty for all alert types. + /// A cancellation token for the request. + /// If the operation succeeded. + public Task RegisterNotificationAsync(string applicationId, string callbackUrl, int? alertType = null, CancellationToken cancellationToken = default) + { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.NotificationService_RegisterNotification, + [LoggingEvents.Parameter_ApplicationId] = applicationId, + [LoggingEvents.Parameter_CallBackUrl] = callbackUrl, + [LoggingEvents.Parameter_AlertType] = alertType + + }; + + Guard.Argument(applicationId, nameof(applicationId)).MaxLength(100).NotEmpty(); + Guard.Argument(callbackUrl, nameof(callbackUrl)).MaxLength(150).NotEmpty(); + + var handler = new RequestHandler(Client); + return handler.ExecutePostRequestAsync(new RegisterNotificationRequest() { ApplicationId = applicationId, CallbackUri = new Uri(callbackUrl), AlertType = alertType }, loggingScope, cancellationToken); + } + + + /// + /// Registers an application for a notification. + /// + /// ApplicationId to register the notification under. + /// The uri that should get called for each notification. + /// A specific type of alert to send, leave empty for all alert types. + /// A cancellation token for the request. + /// If the operation succeeded. + public Task RegisterNotificationAsync(string applicationId, Uri callbackUri, int? alertType = null, CancellationToken cancellationToken = default) + { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.NotificationService_RegisterNotification, + [LoggingEvents.Parameter_ApplicationId] = applicationId, + [LoggingEvents.Parameter_CallBackUrl] = callbackUri.AbsoluteUri, + [LoggingEvents.Parameter_AlertType] = alertType + + }; + + Guard.Argument(applicationId, nameof(applicationId)).MaxLength(100).NotEmpty(); + Guard.Argument(callbackUri, nameof(callbackUri)).NotNull(); + Guard.Argument(callbackUri.AbsoluteUri, nameof(callbackUri)).MaxLength(150).NotEmpty(); + + var handler = new RequestHandler(Client); + return handler.ExecutePostRequestAsync(new RegisterNotificationRequest() { ApplicationId = applicationId, CallbackUri = callbackUri, AlertType = alertType }, loggingScope, cancellationToken); + } + + /// + /// Deregisters an application for a notification. + /// + /// ApplicationId to register the notification under. + /// A specific type of alert to send, leave empty for all alert types. + /// A cancellation token for the request. + /// If the operation succeeded. + public Task DeregisterNotificationAsync(string applicationId, int? alertType = null, CancellationToken cancellationToken = default) + { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.NotificationService_DeregisterNotification, + [LoggingEvents.Parameter_ApplicationId] = applicationId, + [LoggingEvents.Parameter_AlertType] = alertType + }; + + Guard.Argument(applicationId, nameof(applicationId)).MaxLength(100); + + var handler = new RequestHandler(Client); + return handler.ExecutePostRequestAsync(new DeregisterNotificationRequest { ApplicationId = applicationId, AlertType = alertType }, loggingScope, cancellationToken); + } + } +} diff --git a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs index 1d6e045..e22f271 100644 --- a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs +++ b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs @@ -39,6 +39,9 @@ internal class LoggingEvents public const string Parameter_Search_TeamName = "Teamname"; public const string Parameter_Search_Orientation = "Orientation"; public const string Parameter_Search_Tilt = "Tilt"; + public const string Parameter_ApplicationId = "ApplicationId"; + public const string Parameter_CallBackUrl = "CallBackUrl"; + public const string Parameter_AlertType = "AlertType"; /* * RequestHandler base events @@ -92,5 +95,7 @@ internal class LoggingEvents public static readonly EventId TeamService_GetTeam = new EventId(21101, "GetTeam"); public static readonly EventId TeamService_JoinTeam = new EventId(21102, "JoinTeam"); public static readonly EventId TeamService_LeaveTeam = new EventId(21103, "LeaveTeam"); + public static readonly EventId NotificationService_RegisterNotification = new EventId(21201, "RegisterNotification"); + public static readonly EventId NotificationService_DeregisterNotification = new EventId(21202, "DeregisterNotification"); } } diff --git a/src/PVOutput.Net/PVOutputClient.cs b/src/PVOutput.Net/PVOutputClient.cs index 65d848b..a35b339 100644 --- a/src/PVOutput.Net/PVOutputClient.cs +++ b/src/PVOutput.Net/PVOutputClient.cs @@ -116,6 +116,12 @@ internal ILogger Logger /// public SearchService Search { get; private set; } + /// + /// The notification service + /// See the official API information. + /// + public NotificationService Notification { get; private set; } + /// /// Creates a new PVOutputClient. /// @@ -177,6 +183,7 @@ private void CreateServices() Insolation = new InsolationService(this); Supply = new SupplyService(this); Search = new SearchService(this); + Notification = new NotificationService(this); } } } diff --git a/src/PVOutput.Net/Requests/Modules/DeregisterNotificationRequest.cs b/src/PVOutput.Net/Requests/Modules/DeregisterNotificationRequest.cs new file mode 100644 index 0000000..5afc3c1 --- /dev/null +++ b/src/PVOutput.Net/Requests/Modules/DeregisterNotificationRequest.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Net.Http; +using PVOutput.Net.Requests.Base; + +namespace PVOutput.Net.Requests.Modules +{ + internal class DeregisterNotificationRequest : PostRequest + { + public string ApplicationId { get; set; } + public int? AlertType { get; set; } + + public override HttpMethod Method => HttpMethod.Post; + + public override string UriTemplate => "deregisternotification.jsp{?appid,type}"; + + public override IDictionary GetUriPathParameters() + { + return new Dictionary + { + ["appid"] = ApplicationId, + ["type"] = AlertType ?? 0 + }; + } + } +} diff --git a/src/PVOutput.Net/Requests/Modules/RegisterNotificationRequest.cs b/src/PVOutput.Net/Requests/Modules/RegisterNotificationRequest.cs new file mode 100644 index 0000000..2b441e5 --- /dev/null +++ b/src/PVOutput.Net/Requests/Modules/RegisterNotificationRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Net.Http; +using PVOutput.Net.Requests.Base; + +namespace PVOutput.Net.Requests.Modules +{ + internal class RegisterNotificationRequest : PostRequest + { + public string ApplicationId { get; set; } + public System.Uri CallbackUri { get; set; } + public int? AlertType { get; set; } + + public override HttpMethod Method => HttpMethod.Post; + + public override string UriTemplate => "registernotification.jsp{?appid,url,type}"; + + public override IDictionary GetUriPathParameters() + { + return new Dictionary + { + ["appid"] = ApplicationId, + ["url"] = CallbackUri?.AbsoluteUri ?? "", + ["type"] = AlertType ?? 0 + }; + } + } +} diff --git a/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs new file mode 100644 index 0000000..b6f90d1 --- /dev/null +++ b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using PVOutput.Net.Objects; +using PVOutput.Net.Requests.Modules; +using PVOutput.Net.Tests.Utils; +using RichardSzalay.MockHttp; + +namespace PVOutput.Net.Tests.Modules.Supply +{ + [TestFixture] + public partial class NotificationServiceTests : BaseRequestsTest + { + [Test] + public void Parameter_ApplicationId_CreatesCorrectUriParameters() + { + var request = new RegisterNotificationRequest() { ApplicationId = "my.first.application" }; + var parameters = request.GetUriPathParameters(); + Assert.That(parameters["appid"], Is.EqualTo("my.first.application")); + } + + [Test] + public void Parameter_CallbackUrl_CreatesCorrectUriParameters() + { + var request = new RegisterNotificationRequest() { CallbackUri = new Uri("http://www.google.com/callmeback") }; + var parameters = request.GetUriPathParameters(); + Assert.That(parameters["url"], Is.EqualTo("http://www.google.com/callmeback")); + } + + [Test] + public void Parameter_AlertType_CreatesCorrectUriParameters() + { + var request = new RegisterNotificationRequest() { AlertType = 11 }; + var parameters = request.GetUriPathParameters(); + Assert.That(parameters["type"], Is.EqualTo(11)); + } + + [Test] + public void Parameter_ApplicationId_Deregister_CreatesCorrectUriParameters() + { + var request = new DeregisterNotificationRequest() { ApplicationId = "my.first.application" }; + var parameters = request.GetUriPathParameters(); + Assert.That(parameters["appid"], Is.EqualTo("my.first.application")); + } + + [Test] + public void Parameter_AlertType_Deregister_CreatesCorrectUriParameters() + { + var request = new DeregisterNotificationRequest() { AlertType = 11 }; + var parameters = request.GetUriPathParameters(); + Assert.That(parameters["type"], Is.EqualTo(11)); + } + + + [Test] + public async Task NotificationService_RegisterNotification_CallsCorrectUri() + { + PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); + + testProvider.ExpectUriFromBase(REGISTERNOTIFICATION_URL) + .WithQueryString("appid=my.application.id&type=14&url=http://www.google.com/callmeback") + .RespondPlainText(""); + + var response = await client.Notification.RegisterNotificationAsync("my.application.id", "http://www.google.com/callmeback", 14); + testProvider.VerifyNoOutstandingExpectation(); + AssertStandardResponse(response); + } + + [Test] + public async Task NotificationService_DeregisterNotification_CallsCorrectUri() + { + PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); + + testProvider.ExpectUriFromBase(DEREGISTERNOTIFICATION_URL) + .WithQueryString("appid=my.application.id&type=24") + .RespondPlainText(""); + + var response = await client.Notification.DeregisterNotificationAsync("my.application.id", 24); + testProvider.VerifyNoOutstandingExpectation(); + AssertStandardResponse(response); + } + + [Test] + public void ApplicationId_TooLong_Throws() + { + PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); + + testProvider.ExpectUriFromBase(REGISTERNOTIFICATION_URL) + .RespondPlainText(""); + + var exception = Assert.ThrowsAsync(async () => + { + _ = await client.Notification.RegisterNotificationAsync(new string('*', 101), ""); + }); + } + + + [Test] + public void CallbackUrl_TooLong_Throws() + { + PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); + + testProvider.ExpectUriFromBase(REGISTERNOTIFICATION_URL) + .RespondPlainText(""); + + var exception = Assert.ThrowsAsync(async () => + { + _ = await client.Notification.RegisterNotificationAsync("my.application.id", new string('*', 151)); + }); + } + } +} diff --git a/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTestsData.cs b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTestsData.cs new file mode 100644 index 0000000..f959aa3 --- /dev/null +++ b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTestsData.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PVOutput.Net.Tests.Modules.Supply +{ + public partial class NotificationServiceTests + { + public const string REGISTERNOTIFICATION_URL = "registernotification.jsp"; + public const string DEREGISTERNOTIFICATION_URL = "deregisternotification.jsp"; + } +} From 67ba1a7efaed702dc5a830d4b436a5e26f2a5e0f Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 29 Aug 2020 11:16:50 +0200 Subject: [PATCH 2/3] Improve coverage Added unit test for registering with an Uri as input --- .../Notification/NotificationServiceTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs index b6f90d1..295e016 100644 --- a/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs +++ b/tests/PVOutput.Net.Tests/Modules/Notification/NotificationServiceTests.cs @@ -68,6 +68,20 @@ public async Task NotificationService_RegisterNotification_CallsCorrectUri() AssertStandardResponse(response); } + [Test] + public async Task NotificationService_RegisterNotificationViaUri_CallsCorrectUri() + { + PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); + + testProvider.ExpectUriFromBase(REGISTERNOTIFICATION_URL) + .WithQueryString("appid=my.application.id&type=17&url=http://www.microsoft.com/callmeback") + .RespondPlainText(""); + + var response = await client.Notification.RegisterNotificationAsync("my.application.id", new Uri("http://www.microsoft.com/callmeback"), 17); + testProvider.VerifyNoOutstandingExpectation(); + AssertStandardResponse(response); + } + [Test] public async Task NotificationService_DeregisterNotification_CallsCorrectUri() { From 0d7126716f165829db31f06be14d3bd0143f411c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 29 Aug 2020 11:21:01 +0200 Subject: [PATCH 3/3] Updated packages Fixed CA not propagating `CancellationToken` in `BaseObjectStringReader` --- .../Objects/Core/BaseObjectStringReader.cs | 2 +- src/PVOutput.Net/PVOutput.Net.csproj | 8 ++++---- src/PVOutput.Net/Requests/Modules/OutputRequest.cs | 2 +- tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/PVOutput.Net/Objects/Core/BaseObjectStringReader.cs b/src/PVOutput.Net/Objects/Core/BaseObjectStringReader.cs index 4fa66cd..806e73b 100644 --- a/src/PVOutput.Net/Objects/Core/BaseObjectStringReader.cs +++ b/src/PVOutput.Net/Objects/Core/BaseObjectStringReader.cs @@ -43,7 +43,7 @@ public async Task ReadObjectAsync(TextReader reader, CancellationTo if (reader != null && reader.Peek() >= 0) { TReturnType output = CreateObjectInstance(); - ParseProperties(output, reader); + ParseProperties(output, reader, cancellationToken); return output; } diff --git a/src/PVOutput.Net/PVOutput.Net.csproj b/src/PVOutput.Net/PVOutput.Net.csproj index 926f82e..d3d81f8 100644 --- a/src/PVOutput.Net/PVOutput.Net.csproj +++ b/src/PVOutput.Net/PVOutput.Net.csproj @@ -38,13 +38,13 @@ https://github.com/pyrocumulus/pvoutput.net/blob/master/CHANGELOG.md - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/PVOutput.Net/Requests/Modules/OutputRequest.cs b/src/PVOutput.Net/Requests/Modules/OutputRequest.cs index 5cb4b5a..0aa7ceb 100644 --- a/src/PVOutput.Net/Requests/Modules/OutputRequest.cs +++ b/src/PVOutput.Net/Requests/Modules/OutputRequest.cs @@ -31,7 +31,7 @@ internal class OutputRequest : GetRequest ["insolation"] = Insolation ? 1 : 0 }; - private string GetAggregationParameter(AggregationPeriod? aggregationPeriod) + private static string GetAggregationParameter(AggregationPeriod? aggregationPeriod) { if (aggregationPeriod == null) { diff --git a/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj b/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj index 67a0d55..ec2e787 100644 --- a/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj +++ b/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj @@ -6,19 +6,19 @@ ..\..\artifacts\ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - +