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>
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
{
Method = method,
RequestUri = new Uri(_httpClient.BaseAddress, path)
RequestUri = uri
};
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.
/// </summary>
/// <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>>();
foreach (var telemeter in RegisteredTelemeters)

View file

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

View file

@ -1,61 +1,61 @@
using System;
using System.Collections.Generic;
using NucuCar.Common.Utilities;
using Xunit;
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Utilities
{
public class ConnectionStringParserTest
{
[Fact]
private void Test_ConnectionStringParser_Valid()
{
const string connectionString = "Test=1;Test2=2";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal("2", parsedString.GetValueOrDefault("Test2"));
}
[Fact]
private void Test_ConnectionStringParser_EmptyValue()
{
const string connectionString = "Test=1;Test2=";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal(string.Empty, parsedString.GetValueOrDefault("Test2"));
}
[Fact]
private void Test_ConnectionStringParser_EmptyValue2()
{
Assert.Throws<ArgumentException>(() =>
{
ConnectionStringParser.Parse(string.Empty);
});
}
[Fact]
private void Test_ConnectionStringParser_Invalid()
{
const string connectionString = "Test=1;Test2=;d";
Assert.Throws<ArgumentException>(() =>
{
ConnectionStringParser.Parse(connectionString);
});
}
[Fact]
private void Test_ConnectionStringParser_ValueWithMultipleEquals()
{
const string connectionString = "Test=1;Test2=base64=";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal("base64=", parsedString.GetValueOrDefault("Test2"));
}
}
using System;
using System.Collections.Generic;
using NucuCar.Common.Utilities;
using Xunit;
namespace NucuCar.UnitTests.NucuCar.Common.Tests
{
public class ConnectionStringParserTest
{
[Fact]
private void Test_ConnectionStringParser_Valid()
{
const string connectionString = "Test=1;Test2=2";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal("2", parsedString.GetValueOrDefault("Test2"));
}
[Fact]
private void Test_ConnectionStringParser_EmptyValue()
{
const string connectionString = "Test=1;Test2=";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
Assert.Equal(string.Empty, parsedString.GetValueOrDefault("Test2"));
}
[Fact]
private void Test_ConnectionStringParser_EmptyValue2()
{
Assert.Throws<ArgumentException>(() =>
{
ConnectionStringParser.Parse(string.Empty);
});
}
[Fact]
private void Test_ConnectionStringParser_Invalid()
{
const string connectionString = "Test=1;Test2=;d";
Assert.Throws<ArgumentException>(() =>
{
ConnectionStringParser.Parse(connectionString);
});
}
[Fact]
private void Test_ConnectionStringParser_ValueWithMultipleEquals()
{
const string connectionString = "Test=1;Test2=base64=";
var parsedString = ConnectionStringParser.Parse(connectionString);
Assert.Equal("1", parsedString.GetValueOrDefault("Test"));
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 NucuCar.Domain.Telemetry;
using NucuCar.Telemetry;
using Xunit;
namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry
namespace NucuCar.UnitTests.NucuCar.Telemetry.Tests
{
public class TelemetryPublisherFactoryTest
{
@ -26,6 +25,16 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Tests.Telemetry
TelemetryPublisherFactory.CreateFromConnectionString(TelemetryPublisherType.Disk, connectionString);
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]
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;
&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;/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>