NUC-42: Write partial unit tests for TelemetryPublisherFirestore

This commit is contained in:
Denis-Cosmin Nutiu 2020-04-20 18:19:32 +03:00
parent 5d16e93bda
commit 7efc49596e
9 changed files with 292 additions and 79 deletions

View file

@ -186,10 +186,12 @@ namespace NucuCar.Common
/// <returns></returns> /// <returns></returns>
private sNetHttp.HttpRequestMessage _makeRequest(sNetHttp.HttpMethod method, string path) private sNetHttp.HttpRequestMessage _makeRequest(sNetHttp.HttpMethod method, string path)
{ {
var uri = _httpClient.BaseAddress == null ? new Uri(path) : new Uri(_httpClient.BaseAddress, path);
var requestMessage = new sNetHttp.HttpRequestMessage var requestMessage = new sNetHttp.HttpRequestMessage
{ {
Method = method, Method = method,
RequestUri = new Uri(_httpClient.BaseAddress, path) RequestUri = uri
}; };
requestMessage.Headers.Authorization = _httpClient.DefaultRequestHeaders.Authorization; requestMessage.Headers.Authorization = _httpClient.DefaultRequestHeaders.Authorization;

View file

@ -96,7 +96,7 @@ namespace NucuCar.Domain.Telemetry
/// It also adds metadata information such as: source and timestamp. /// It also adds metadata information such as: source and timestamp.
/// </summary> /// </summary>
/// <returns>A dictionary containing all telemetry data.</returns> /// <returns>A dictionary containing all telemetry data.</returns>
protected Dictionary<string, object> GetTelemetry() protected virtual Dictionary<string, object> GetTelemetry()
{ {
var data = new List<Dictionary<string, object>>(); var data = new List<Dictionary<string, object>>();
foreach (var telemeter in RegisteredTelemeters) foreach (var telemeter in RegisteredTelemeters)

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@ -26,7 +27,7 @@ namespace NucuCar.Telemetry
/// </summary> /// </summary>
public class TelemetryPublisherFirestore : TelemetryPublisher public class TelemetryPublisherFirestore : TelemetryPublisher
{ {
private readonly HttpClient _httpClient; protected HttpClient HttpClient;
private string _idToken; private string _idToken;
@ -44,6 +45,7 @@ namespace NucuCar.Telemetry
Logger?.LogCritical( Logger?.LogCritical(
$"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " + $"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " +
$"Missing ProjectId!"); $"Missing ProjectId!");
throw new ArgumentException("Malformed connection string!");
} }
if (!options.TryGetValue("CollectionName", out var firestoreCollection)) if (!options.TryGetValue("CollectionName", out var firestoreCollection))
@ -51,6 +53,7 @@ namespace NucuCar.Telemetry
Logger?.LogCritical( Logger?.LogCritical(
$"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " + $"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " +
$"Missing CollectionName!"); $"Missing CollectionName!");
throw new ArgumentException("Malformed connection string!");
} }
var timeout = int.Parse(options.GetValueOrDefault("Timeout", "10000") ?? "10000"); var timeout = int.Parse(options.GetValueOrDefault("Timeout", "10000") ?? "10000");
@ -61,7 +64,7 @@ namespace NucuCar.Telemetry
// Setup HttpClient // Setup HttpClient
var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" + var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" +
$"databases/(default)/documents/{firestoreCollection}/"; $"databases/(default)/documents/{firestoreCollection}/";
_httpClient = new HttpClient(requestUrl) {Timeout = timeout, Logger = Logger}; HttpClient = new HttpClient(requestUrl) {Timeout = timeout, Logger = Logger};
Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}"); Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}");
Logger?.LogInformation($"ProjectId: {firestoreProjectId}; CollectionName: {firestoreCollection}."); Logger?.LogInformation($"ProjectId: {firestoreProjectId}; CollectionName: {firestoreCollection}.");
} }
@ -77,13 +80,13 @@ namespace NucuCar.Telemetry
["returnSecureToken"] = true ["returnSecureToken"] = true
}; };
var response = await _httpClient.PostAsync(requestUrl, data); var response = await HttpClient.PostAsync(requestUrl, data);
if (response?.StatusCode == HttpStatusCode.OK) if (response?.StatusCode == HttpStatusCode.OK)
{ {
var jsonContent = await response.GetJson(); var jsonContent = await response.GetJson();
_idToken = jsonContent.GetProperty("idToken").ToString(); _idToken = jsonContent.GetProperty("idToken").ToString();
_httpClient.Authorization(_idToken); HttpClient.Authorization(_idToken);
} }
else else
{ {
@ -100,7 +103,7 @@ namespace NucuCar.Telemetry
} }
var data = FirebaseRestTranslator.Translator.Translate(null, GetTelemetry()); var data = FirebaseRestTranslator.Translator.Translate(null, GetTelemetry());
var responseMessage = await _httpClient.PostAsync("", data); var responseMessage = await HttpClient.PostAsync("", data);
switch (responseMessage?.StatusCode) switch (responseMessage?.StatusCode)
{ {
@ -111,7 +114,7 @@ namespace NucuCar.Telemetry
{ {
Logger?.LogError($"Failed to publish telemetry data! {responseMessage.StatusCode}. Retrying..."); Logger?.LogError($"Failed to publish telemetry data! {responseMessage.StatusCode}. Retrying...");
await SetupAuthorization(); await SetupAuthorization();
responseMessage = await _httpClient.PostAsync("", data); responseMessage = await HttpClient.PostAsync("", data);
if (responseMessage != null && responseMessage.IsSuccessStatusCode) if (responseMessage != null && responseMessage.IsSuccessStatusCode)
{ {
Logger?.LogInformation("Published data to Firestore on retry!"); Logger?.LogInformation("Published data to Firestore on retry!");

View file

@ -1,4 +1,6 @@
namespace NucuCar.Domain.Telemetry using NucuCar.Domain.Telemetry;
namespace NucuCar.Telemetry
{ {
/// <summary> /// <summary>
/// TelemetryPublisherType holds constants for instantiating <see cref="TelemetryPublisher"/>, /// TelemetryPublisherType holds constants for instantiating <see cref="TelemetryPublisher"/>,

View file

@ -1,61 +1,61 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NucuCar.Common.Utilities; using NucuCar.Common.Utilities;
using Xunit; using Xunit;
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Utilities namespace NucuCar.UnitTests.NucuCar.Common.Tests
{ {
public class ConnectionStringParserTest public class ConnectionStringParserTest
{ {
[Fact] [Fact]
private void Test_ConnectionStringParser_Valid() private void Test_ConnectionStringParser_Valid()
{ {
const string connectionString = "Test=1;Test2=2"; const string connectionString = "Test=1;Test2=2";
var parsedString = ConnectionStringParser.Parse(connectionString); var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test")); Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal("2", parsedString.GetValueOrDefault("Test2")); Assert.Equal("2", parsedString.GetValueOrDefault("Test2"));
} }
[Fact] [Fact]
private void Test_ConnectionStringParser_EmptyValue() private void Test_ConnectionStringParser_EmptyValue()
{ {
const string connectionString = "Test=1;Test2="; const string connectionString = "Test=1;Test2=";
var parsedString = ConnectionStringParser.Parse(connectionString); var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test")); Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal(string.Empty, parsedString.GetValueOrDefault("Test2")); Assert.Equal(string.Empty, parsedString.GetValueOrDefault("Test2"));
} }
[Fact] [Fact]
private void Test_ConnectionStringParser_EmptyValue2() private void Test_ConnectionStringParser_EmptyValue2()
{ {
Assert.Throws<ArgumentException>(() => Assert.Throws<ArgumentException>(() =>
{ {
ConnectionStringParser.Parse(string.Empty); ConnectionStringParser.Parse(string.Empty);
}); });
} }
[Fact] [Fact]
private void Test_ConnectionStringParser_Invalid() private void Test_ConnectionStringParser_Invalid()
{ {
const string connectionString = "Test=1;Test2=;d"; const string connectionString = "Test=1;Test2=;d";
Assert.Throws<ArgumentException>(() => Assert.Throws<ArgumentException>(() =>
{ {
ConnectionStringParser.Parse(connectionString); ConnectionStringParser.Parse(connectionString);
}); });
} }
[Fact] [Fact]
private void Test_ConnectionStringParser_ValueWithMultipleEquals() private void Test_ConnectionStringParser_ValueWithMultipleEquals()
{ {
const string connectionString = "Test=1;Test2=base64="; const string connectionString = "Test=1;Test2=base64=";
var parsedString = ConnectionStringParser.Parse(connectionString); var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test")); Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal("base64=", parsedString.GetValueOrDefault("Test2")); Assert.Equal("base64=", parsedString.GetValueOrDefault("Test2"));
} }
} }
} }

View file

@ -1,7 +0,0 @@
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry
{
public class TelemetryPublisherFirestoreTest
{
// TODO after refactoring
}
}

View file

@ -1,9 +1,8 @@
using System; using System;
using NucuCar.Domain.Telemetry;
using NucuCar.Telemetry; using NucuCar.Telemetry;
using Xunit; using Xunit;
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry namespace NucuCar.UnitTests.NucuCar.Telemetry.Tests
{ {
public class TelemetryPublisherFactoryTest public class TelemetryPublisherFactoryTest
{ {
@ -26,6 +25,16 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry
TelemetryPublisherFactory.CreateFromConnectionString(TelemetryPublisherType.Disk, connectionString); TelemetryPublisherFactory.CreateFromConnectionString(TelemetryPublisherType.Disk, connectionString);
Assert.IsType<TelemetryPublisherDisk>(telemetryPublisher); Assert.IsType<TelemetryPublisherDisk>(telemetryPublisher);
} }
[Fact]
private void Test_Build_TelemetryPublisherFiresstore()
{
const string connectionString =
"ProjectId=test;CollectionName=test";
var telemetryPublisher =
TelemetryPublisherFactory.CreateFromConnectionString(TelemetryPublisherType.Firestore, connectionString);
Assert.IsType<TelemetryPublisherFirestore>(telemetryPublisher);
}
[Fact] [Fact]
private void Test_Build_ThrowsOnInvalidType() private void Test_Build_ThrowsOnInvalidType()

View file

@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using NucuCar.Domain.Telemetry;
using NucuCar.Telemetry;
using Xunit;
using HttpClient = NucuCar.Common.HttpClient;
namespace NucuCar.UnitTests.NucuCar.Telemetry.Tests
{
/// <summary>
/// Class used to test the TelemetryPublisherFirestore by mocking the GetTelemetry method and HttpClient field.
/// </summary>
internal class MockTelemetryPublisherFirestore : TelemetryPublisherFirestore
{
private Dictionary<string, object> _mockData;
public MockTelemetryPublisherFirestore(TelemetryPublisherBuilderOptions opts) : base(opts)
{
_mockData = new Dictionary<string, object>();
}
public void SetHttpClient(HttpClient client)
{
HttpClient = client;
}
public void SetMockData(Dictionary<string, object> data)
{
_mockData = data;
}
protected override Dictionary<string, object> GetTelemetry()
{
return _mockData;
}
}
public class TelemetryPublisherFirestoreTest
{
[Fact]
private void Test_Construct_BadProjectId()
{
// Setup
var opts = new TelemetryPublisherBuilderOptions()
{
ConnectionString = "ProjectIdBAD=test;CollectionName=test"
};
// Run & Assert
Assert.Throws<ArgumentException>(() => { new MockTelemetryPublisherFirestore(opts); });
}
[Fact]
private void Test_Construct_BadCollectiontName()
{
// Setup
var opts = new TelemetryPublisherBuilderOptions()
{
ConnectionString = "ProjectId=test;CollectionNameBAD=test"
};
// Run & Assert
Assert.Throws<ArgumentException>(() => { new MockTelemetryPublisherFirestore(opts); });
}
[Fact]
private async Task Test_PublishAsync_OK()
{
// Setup
var opts = new TelemetryPublisherBuilderOptions()
{
ConnectionString = "ProjectId=test;CollectionName=test"
};
var publisher = new MockTelemetryPublisherFirestore(opts);
var mockHttpClient = new Mock<HttpClient>("http://testing.com");
mockHttpClient.Setup(c => c.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
publisher.SetHttpClient(mockHttpClient.Object);
publisher.SetMockData(new Dictionary<string, object> {["testData"] = 1});
// Run
await publisher.PublishAsync(CancellationToken.None);
// Assert
var expectedContent = "{\"fields\":{\"testData\":{\"integerValue\":1}}}";
mockHttpClient.Verify(
m => m.SendAsync(
It.Is<HttpRequestMessage>(
request => request.Method.Equals(HttpMethod.Post))));
mockHttpClient.Verify(
m => m.SendAsync(
It.Is<HttpRequestMessage>(
request => request.RequestUri.Equals(new Uri("http://testing.com")))));
mockHttpClient.Verify(
m => m.SendAsync(
It.Is<HttpRequestMessage>(
request => request.Content.ReadAsStringAsync().GetAwaiter().GetResult()
.Equals(expectedContent))));
}
[Fact]
private async Task Test_PublishAsync_Cancel()
{
// Setup
var opts = new TelemetryPublisherBuilderOptions()
{
ConnectionString = "ProjectId=test;CollectionName=test"
};
var publisher = new MockTelemetryPublisherFirestore(opts);
var mockHttpClient = new Mock<HttpClient>("http://testing.com");
mockHttpClient.Setup(c => c.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
publisher.SetHttpClient(mockHttpClient.Object);
publisher.SetMockData(new Dictionary<string, object> {["testData"] = 1});
var cts = new CancellationTokenSource();
cts.Cancel();
// Run
await publisher.PublishAsync(cts.Token);
// Assert
mockHttpClient.Verify(m => m.SendAsync(It.IsAny<HttpRequestMessage>()), Times.Never());
}
[Fact]
private async Task Test_PublishAsync_Authorization_OK()
{
// Setup
var sendAsyncInvocations = new List<HttpRequestMessage>();
var opts = new TelemetryPublisherBuilderOptions()
{
ConnectionString = "ProjectId=test;CollectionName=test"
};
var publisher = new MockTelemetryPublisherFirestore(opts);
var mockHttpClient = new Mock<HttpClient>("http://testing.com");
mockHttpClient.SetupSequence(c => c.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden)))
.Returns(Task.FromResult(
new HttpResponseMessage(HttpStatusCode.OK)
{Content = new StringContent("{\"idToken\":\"testauthtoken\"}")}
))
.Returns(Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
publisher.SetHttpClient(mockHttpClient.Object);
publisher.SetMockData(new Dictionary<string, object> {["testData"] = 1});
// Run
await publisher.PublishAsync(CancellationToken.None);
// Assert
// Can't verify because moq doesn't support that, damn C#.
}
}
}

View file

@ -2,4 +2,44 @@
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt; <s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;
&lt;Assembly Path="/home/denis/.nuget/packages/iot.device.bindings/1.0.0/lib/netcoreapp2.1/Iot.Device.Bindings.dll" /&gt; &lt;Assembly Path="/home/denis/.nuget/packages/iot.device.bindings/1.0.0/lib/netcoreapp2.1/Iot.Device.Bindings.dll" /&gt;
&lt;Assembly Path="/home/denis/.nuget/packages/firebaseresttranslator/0.1.1/lib/netcoreapp3.0/FirebaseRestTranslator.dll" /&gt; &lt;Assembly Path="/home/denis/.nuget/packages/firebaseresttranslator/0.1.1/lib/netcoreapp3.0/FirebaseRestTranslator.dll" /&gt;
&lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary> &lt;/AssemblyExplorer&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=2d4aa020_002D92b4_002D4819_002Da752_002Df0a1e75a5e68/@EntryIndexedValue">&lt;SessionState ContinuousTestingIsOn="False" ContinuousTestingMode="0" FrameworkVersion="{x:Null}" IsLocked="False" Name="Test_PublishAsync_Authorization_OK" PlatformMonoPreference="{x:Null}" PlatformType="{x:Null}" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_Authorization_OK&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=8687c1ea_002Dea4c_002D426e_002Da605_002D3d34b0c68f64/@EntryIndexedValue">&lt;SessionState ContinuousTestingIsOn="False" ContinuousTestingMode="0" FrameworkVersion="{x:Null}" IsLocked="False" Name="Session" PlatformMonoPreference="{x:Null}" PlatformType="{x:Null}" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
&lt;Or&gt;
&lt;Or&gt;
&lt;Or&gt;
&lt;Or&gt;
&lt;Or&gt;
&lt;Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_OK&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_BadProjectId&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_CollectiontName&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_Error_Timeout&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_Cancel&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_UnknownError&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::C6F07921-1052-4945-911E-F328A622F229::.NETCoreApp,Version=v3.0::NucuCar.UnitTests.NucuCar.Telemetry.Tests.TelemetryPublisherFirestoreTest.Test_PublishAsync_AuthorizationFail&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/Or&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>