Skip to content

Commit e56f553

Browse files
authored
Initialize unit tests project (#11)
# Description Initialize unit tests on this repo, with a bit of tooling, code coverage calculation and reporting. Necessary refactor: API controller now receive a Kuzzle API interface (`IKuzzleApi`) instead of a Kuzzle instance, because mocking can only be done with interfaces or abstract classes. :warning: this project cannot be built nor tested using monodevelop due to current compatibility problems between dotnet-core and msbuild. Read the README documentation to build this SDK from the CLI. See NuGet/Home#7956
1 parent d55b852 commit e56f553

20 files changed

+338
-237
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ packages/
55
*/bin/
66
*/obj/
77
*.csproj.nuget.cache
8+
mono_crash.*
9+
lcov.info
10+
codecov

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ install:
88
- dotnet restore
99

1010
script:
11+
# incompatibilities with msbuild, so building with .net core instead
1112
- dotnet build $TRAVIS_BUILD_DIR/Kuzzle/Kuzzle.csproj -c Release
13+
- dotnet build $TRAVIS_BUILD_DIR/Kuzzle.Tests/Kuzzle.Tests.csproj
14+
- dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=$TRAVIS_BUILD_DIR/lcov.info
15+
- curl -s https://codecov.io/bash > codecov && bash codecov
1216

1317
deploy:
1418
skip_cleanup: true
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using Xunit;
2+
using KuzzleSdk.API.Controllers;
3+
using Newtonsoft.Json.Linq;
4+
using KuzzleSdk.Exceptions;
5+
6+
namespace Kuzzle.Tests.API.Controllers {
7+
public class AuthControllerTest {
8+
private readonly AuthController _authController;
9+
private readonly KuzzleApiMock _api;
10+
11+
public AuthControllerTest() {
12+
_api = new KuzzleApiMock();
13+
_authController = new AuthController(_api.MockedObject);
14+
}
15+
16+
[Fact]
17+
public async void CheckTokenAsyncTestSuccess() {
18+
string token = "foobar";
19+
20+
_api.SetResult(@"{result: {foo: 123}}");
21+
_api.Mock.SetupProperty(a => a.Jwt, "foo");
22+
23+
JObject result = await _authController.CheckTokenAsync(token);
24+
25+
_api.Verify(new JObject {
26+
{ "controller", "auth" },
27+
{ "action", "checkToken" },
28+
{ "body", new JObject{ { "token", token } } }
29+
});
30+
31+
Assert.Equal<JObject>(
32+
new JObject { { "foo", 123 } },
33+
result,
34+
new JTokenEqualityComparer());
35+
36+
_api.Mock.VerifySet(a => a.Jwt = null);
37+
_api.Mock.VerifySet(a => a.Jwt = "foo");
38+
}
39+
40+
[Fact]
41+
public async void CheckTokenAsyncTestFailure() {
42+
string token = "foobar";
43+
44+
_api.SetError();
45+
_api.Mock.SetupProperty(a => a.Jwt, "foo");
46+
47+
await Assert.ThrowsAsync<ApiErrorException>(
48+
() => _authController.CheckTokenAsync(token));
49+
50+
_api.Verify(new JObject {
51+
{ "controller", "auth" },
52+
{ "action", "checkToken" },
53+
{ "body", new JObject{ { "token", token } } }
54+
});
55+
56+
_api.Mock.VerifySet(a => a.Jwt = null);
57+
_api.Mock.VerifySet(a => a.Jwt = "foo");
58+
}
59+
}
60+
}

Kuzzle.Tests/API/KuzzleApiMock.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using KuzzleSdk;
4+
using KuzzleSdk.API;
5+
using KuzzleSdk.Exceptions;
6+
using Moq;
7+
using Newtonsoft.Json.Linq;
8+
9+
namespace Kuzzle.Tests.API {
10+
public class KuzzleApiMock {
11+
public Mock<IKuzzleApi> Mock { get; }
12+
13+
public IKuzzleApi MockedObject {
14+
get { return Mock.Object; }
15+
}
16+
17+
private ApiErrorException GetApiErrorException(int status, string message) {
18+
var r = Response.FromString($"{{error: {{message: \"{message}\", status: {status} }} }}");
19+
return new ApiErrorException(r);
20+
}
21+
22+
public KuzzleApiMock() {
23+
Mock = new Mock<IKuzzleApi>();
24+
}
25+
26+
public void SetResult(string apiResult) {
27+
Mock
28+
.Setup(api => api.QueryAsync(It.IsAny<JObject>()))
29+
.Returns(Task.FromResult(Response.FromString(apiResult)));
30+
}
31+
32+
public void SetError(int status = 400, string message = "Errored Test") {
33+
var t = new TaskCompletionSource<Response>();
34+
t.SetException(GetApiErrorException(status, message));
35+
36+
Mock
37+
.Setup(api => api.QueryAsync(It.IsAny<JObject>()))
38+
.Returns(t.Task);
39+
}
40+
41+
public void Verify(JObject expectedQuery) {
42+
Mock.Verify(
43+
api => api.QueryAsync(
44+
It.Is<JObject>(o => JToken.DeepEquals(o, expectedQuery))));
45+
}
46+
}
47+
}

Kuzzle.Tests/Kuzzle.Tests.csproj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<!--
5+
Do not upgrade to netcoreapp2.2 or upper
6+
until msbuild+mono+monodevelop are compatible with it.
7+
See https://github.com/NuGet/Home/issues/7956
8+
-->
9+
<TargetFramework>netcoreapp2.1</TargetFramework>
10+
11+
<IsPackable>false</IsPackable>
12+
<ReleaseVersion>0.1.0</ReleaseVersion>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
17+
<PackageReference Include="xunit" Version="2.4.0" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
19+
<PackageReference Include="Moq" Version="4.11.0" />
20+
<PackageReference Include="coverlet.msbuild" Version="2.6.2" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\Kuzzle\Kuzzle.csproj" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<Folder Include="API\" />
29+
<Folder Include="API\Controllers\" />
30+
</ItemGroup>
31+
</Project>

Kuzzle/API/Controllers/AuthController.cs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
using System;
22
using System.Threading.Tasks;
3+
using System.Runtime.CompilerServices;
34
using Newtonsoft.Json.Linq;
45

6+
[assembly: InternalsVisibleTo("Kuzzle.Tests")]
7+
58
namespace KuzzleSdk.API.Controllers {
69
/// <summary>
710
/// Implements the "auth" Kuzzle API controller
811
/// </summary>
912
public sealed class AuthController : BaseController {
10-
internal AuthController(Kuzzle k) : base(k) { }
13+
internal AuthController(IKuzzleApi api) : base(api) { }
1114

1215
/// <summary>
1316
/// Checks the validity of an authentication token.
1417
/// </summary>
1518
public async Task<JObject> CheckTokenAsync(string token) {
16-
string jwt = kuzzle.Jwt;
17-
kuzzle.Jwt = null;
19+
string jwt = api.Jwt;
20+
api.Jwt = null;
1821
Response response;
1922

2023
try {
21-
response = await kuzzle.QueryAsync(new JObject {
24+
response = await api.QueryAsync(new JObject {
2225
{ "controller", "auth" },
2326
{ "action", "checkToken" },
2427
{
@@ -29,7 +32,7 @@ public async Task<JObject> CheckTokenAsync(string token) {
2932
}
3033
});
3134
} finally {
32-
kuzzle.Jwt = jwt;
35+
api.Jwt = jwt;
3336
}
3437

3538
return (JObject)response.Result;
@@ -41,7 +44,7 @@ public async Task<JObject> CheckTokenAsync(string token) {
4144
public async Task<JObject> CreateMyCredentialsAsync(
4245
string strategy,
4346
JObject credentials) {
44-
Response response = await kuzzle.QueryAsync(new JObject {
47+
Response response = await api.QueryAsync(new JObject {
4548
{ "controller", "auth" },
4649
{ "action", "checkToken" },
4750
{ "body", credentials },
@@ -56,7 +59,7 @@ public async Task<JObject> CreateMyCredentialsAsync(
5659
/// specified authentication strategy.
5760
/// </summary>
5861
public async Task<bool> CredentialsExistAsync(string strategy) {
59-
Response response = await kuzzle.QueryAsync(new JObject {
62+
Response response = await api.QueryAsync(new JObject {
6063
{ "controller", "auth" },
6164
{ "action", "credentialsExist" },
6265
{ "strategy", strategy }
@@ -74,7 +77,7 @@ public async Task<bool> CredentialsExistAsync(string strategy) {
7477
/// the deleted credentials.
7578
/// </summary>
7679
public async Task DeleteMyCredentialsAsync(string strategy) {
77-
await kuzzle.QueryAsync(new JObject {
80+
await api.QueryAsync(new JObject {
7881
{ "controller", "auth" },
7982
{ "action", "deleteMyCredentials" },
8083
{ "strategy", strategy }
@@ -85,7 +88,7 @@ await kuzzle.QueryAsync(new JObject {
8588
/// Returns information about the currently logged in user.
8689
/// </summary>
8790
public async Task<JObject> GetCurrentUserAsync() {
88-
Response response = await kuzzle.QueryAsync(new JObject {
91+
Response response = await api.QueryAsync(new JObject {
8992
{ "controller", "auth" },
9093
{ "action", "getCurrentUser" }
9194
});
@@ -99,7 +102,7 @@ public async Task<JObject> GetCurrentUserAsync() {
99102
/// should never include any sensitive information.
100103
/// </summary>
101104
public async Task<JObject> GetMyCredentialsAsync(string strategy) {
102-
Response response = await kuzzle.QueryAsync(new JObject {
105+
Response response = await api.QueryAsync(new JObject {
103106
{ "controller", "auth" },
104107
{ "action", "getMyCredentials" },
105108
{ "strategy", strategy }
@@ -113,7 +116,7 @@ public async Task<JObject> GetMyCredentialsAsync(string strategy) {
113116
/// current user.
114117
/// </summary>
115118
public async Task<JArray> GetMyRightsAsync() {
116-
Response response = await kuzzle.QueryAsync(new JObject {
119+
Response response = await api.QueryAsync(new JObject {
117120
{ "controller", "auth" },
118121
{ "action", "getMyRights" }
119122
});
@@ -125,7 +128,7 @@ public async Task<JArray> GetMyRightsAsync() {
125128
/// Gets the exhaustive list of registered authentication strategies.
126129
/// </summary>
127130
public async Task<JArray> GetStrategiesAsync() {
128-
Response response = await kuzzle.QueryAsync(new JObject {
131+
Response response = await api.QueryAsync(new JObject {
129132
{ "controller", "auth" },
130133
{ "action", "getStrategies" }
131134
});
@@ -138,24 +141,24 @@ public async Task<JArray> GetStrategiesAsync() {
138141
/// </summary>
139142
public async Task<JObject> LoginAsync(
140143
string strategy, JObject credentials, string expiresIn = null) {
141-
string jwt = kuzzle.Jwt;
142-
kuzzle.Jwt = null;
144+
string jwt = api.Jwt;
145+
api.Jwt = null;
143146
Response response;
144147

145148
try {
146-
response = await kuzzle.QueryAsync(new JObject {
149+
response = await api.QueryAsync(new JObject {
147150
{ "controller", "auth" },
148151
{ "action", "login" },
149152
{ "strategy", strategy },
150153
{ "body", credentials },
151154
{ "expiresIn", expiresIn }
152155
});
153156
} catch (Exception) {
154-
kuzzle.Jwt = jwt;
157+
api.Jwt = jwt;
155158
throw;
156159
}
157160

158-
kuzzle.Jwt = (string)response.Result["jwt"];
161+
api.Jwt = (string)response.Result["jwt"];
159162

160163
return (JObject)response.Result;
161164
}
@@ -165,7 +168,7 @@ public async Task<JObject> LoginAsync(
165168
/// If there were any, real-time subscriptions are cancelled.
166169
/// </summary>
167170
public async Task LogoutAsync() {
168-
await kuzzle.QueryAsync(new JObject {
171+
await api.QueryAsync(new JObject {
169172
{ "controller", "auth" },
170173
{ "action", "logout" }
171174
});
@@ -175,13 +178,13 @@ await kuzzle.QueryAsync(new JObject {
175178
/// Refreshes an authentication token.
176179
/// </summary>
177180
public async Task<JObject> RefreshTokenAsync(string expiresIn = null) {
178-
Response response = await kuzzle.QueryAsync(new JObject {
181+
Response response = await api.QueryAsync(new JObject {
179182
{ "controller", "auth" },
180183
{ "action", "login" },
181184
{ "expiresIn", expiresIn }
182185
});
183186

184-
kuzzle.Jwt = (string)response.Result["jwt"];
187+
api.Jwt = (string)response.Result["jwt"];
185188

186189
return (JObject)response.Result;
187190
}
@@ -192,7 +195,7 @@ public async Task<JObject> RefreshTokenAsync(string expiresIn = null) {
192195
public async Task<JObject> UpdateMyCredentialsAsync(
193196
string strategy,
194197
JObject credentials) {
195-
Response response = await kuzzle.QueryAsync(new JObject {
198+
Response response = await api.QueryAsync(new JObject {
196199
{ "controller", "auth" },
197200
{ "action", "updateMyCredentials" },
198201
{ "strategy", strategy },
@@ -207,7 +210,7 @@ public async Task<JObject> UpdateMyCredentialsAsync(
207210
/// associated profiles cannot be updated)
208211
/// </summary>
209212
public async Task<JObject> UpdateSelfAsync(JObject content) {
210-
Response response = await kuzzle.QueryAsync(new JObject {
213+
Response response = await api.QueryAsync(new JObject {
211214
{ "controller", "auth" },
212215
{ "action", "updateSelf" },
213216
{ "body", content }
@@ -224,7 +227,7 @@ public async Task<JObject> UpdateSelfAsync(JObject content) {
224227
public async Task<bool> ValidateMyCredentialsAsync(
225228
string strategy,
226229
JObject credentials) {
227-
Response response = await kuzzle.QueryAsync(new JObject {
230+
Response response = await api.QueryAsync(new JObject {
228231
{ "controller", "auth" },
229232
{ "action", "validateMyCredentials" },
230233
{ "body", credentials },

Kuzzle/API/Controllers/BaseController.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ public class BaseController {
66
/// <summary>
77
/// Kuzzle instance.
88
/// </summary>
9-
protected readonly Kuzzle kuzzle;
9+
protected readonly IKuzzleApi api;
1010

11-
internal BaseController(Kuzzle k) {
12-
kuzzle = k;
11+
internal BaseController(IKuzzleApi k) {
12+
api = k;
1313
}
1414
}
1515
}

0 commit comments

Comments
 (0)