Skip to content

Commit 1826a07

Browse files
Merge pull request #51 from nullinside-development-group/feature/Testing
Unit testing coverage for API project
2 parents 36345b0 + 05e6dfc commit 1826a07

File tree

20 files changed

+645
-44
lines changed

20 files changed

+645
-44
lines changed

src/Nullinside.Api.Common.AspNetCore/Middleware/BasicAuthorizationRequirement.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
using Microsoft.AspNetCore.Authorization;
24

35
namespace Nullinside.Api.Common.AspNetCore.Middleware;
46

57
/// <summary>
68
/// Represents a requirement where a user is expected to have one role.
79
/// </summary>
10+
[ExcludeFromCodeCoverage]
811
public class BasicAuthorizationRequirement : IAuthorizationRequirement {
912
/// <summary>
1013
/// Initializes a new instance of the <see cref="BasicAuthorizationRequirement" /> class.

src/Nullinside.Api.Common/Desktop/GithubLatestReleaseJson.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
namespace Nullinside.Api.Common.Desktop;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Nullinside.Api.Common.Desktop;
24

35
/// <summary>
46
/// The response information from GitHub's API.
57
/// </summary>
8+
[ExcludeFromCodeCoverage]
69
public class GithubLatestReleaseJson {
710
/// <summary>
811
/// The url of the resource.

src/Nullinside.Api.Common/Docker/Support/DockerComposeLsOutput.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace Nullinside.Api.Common.Docker.Support;
24

35
/// <summary>
46
/// The `docker compose ls --format 'json'` output.
57
/// </summary>
8+
[ExcludeFromCodeCoverage]
69
public class DockerComposeLsOutput {
710
/// <summary>
811
/// The name of the docker compose project.

src/Nullinside.Api.Common/Docker/Support/DockerResource.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace Nullinside.Api.Common.Docker.Support;
24

35
/// <summary>
46
/// A docker resource representing either a docker compose project
57
/// or a single docker container.
68
/// </summary>
9+
[ExcludeFromCodeCoverage]
710
public class DockerResource {
811
/// <summary>
912
/// Initializes a new instance of the <see cref="DockerResource" /> class.

src/Nullinside.Api.Common/Exceptions/RetryException.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
namespace Nullinside.Api.Common.Exceptions;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Nullinside.Api.Common.Exceptions;
24

35
/// <summary>
46
/// An exception thrown if an action continues to fail after retrying.
57
/// </summary>
8+
[ExcludeFromCodeCoverage]
69
public class RetryException : Exception {
710
/// <summary>
811
/// Initializes a new instance of the <see cref="RetryException" /> class.

src/Nullinside.Api.Common/Twitch/Json/TwitchModeratedChannelsResponse.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
namespace Nullinside.Api.Common.Twitch.Json;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Nullinside.Api.Common.Twitch.Json;
24

35
/// <summary>
46
/// The response to a query for what channels a user is moderator for.
57
/// </summary>
8+
[ExcludeFromCodeCoverage]
69
public class TwitchModeratedChannelsResponse {
710
/// <summary>
811
/// The list of channels the user moderates for.
@@ -18,6 +21,7 @@ public class TwitchModeratedChannelsResponse {
1821
/// <summary>
1922
/// A channel the user moderates.
2023
/// </summary>
24+
[ExcludeFromCodeCoverage]
2125
public class TwitchModeratedChannel {
2226
/// <summary>
2327
/// The twitch id.
@@ -38,6 +42,7 @@ public class TwitchModeratedChannel {
3842
/// <summary>
3943
/// Pagination information.
4044
/// </summary>
45+
[ExcludeFromCodeCoverage]
4146
public class Pagination {
4247
/// <summary>
4348
/// The cursor to pass to "after" for pagination.

src/Nullinside.Api.Tests/Nullinside.Api.Model/Shared/UserHelpersTests.cs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
1-
using Microsoft.EntityFrameworkCore;
2-
using Microsoft.EntityFrameworkCore.Diagnostics;
3-
4-
using Nullinside.Api.Model;
51
using Nullinside.Api.Model.Ddl;
62
using Nullinside.Api.Model.Shared;
73

84
namespace Nullinside.Api.Tests.Nullinside.Api.Model.Shared;
95

10-
public class UserHelpersTests {
11-
private INullinsideContext _db;
12-
13-
[SetUp]
14-
public void Setup() {
15-
// Create an in-memory database to fake the SQL queries. Note that we generate a random GUID for the name
16-
// here. If you use the same name more than once you'll get collisions between tests.
17-
DbContextOptions<NullinsideContext> contextOptions = new DbContextOptionsBuilder<NullinsideContext>()
18-
.UseInMemoryDatabase(Guid.NewGuid().ToString())
19-
.ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
20-
.Options;
21-
_db = new NullinsideContext(contextOptions);
22-
}
23-
24-
[TearDown]
25-
public async Task TearDown() {
26-
await _db.DisposeAsync();
27-
}
28-
6+
/// <summary>
7+
/// Tests for the <see cref="UserHelpers" /> class.
8+
/// </summary>
9+
public class UserHelpersTests : UnitTestBase {
2910
/// <summary>
3011
/// The case where a user is generating a new token to replace their existing one.
3112
/// </summary>

src/Nullinside.Api.Tests/Nullinside.Api.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
<ItemGroup>
3333
<Folder Include="Nullinside.Api.Common.AspNetCore\"/>
3434
<Folder Include="Nullinside.Api.Common\"/>
35-
<Folder Include="Nullinside.Api\"/>
3635
</ItemGroup>
3736

3837
<ItemGroup>
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
using Moq;
4+
5+
using Nullinside.Api.Common.Docker;
6+
using Nullinside.Api.Common.Docker.Support;
7+
using Nullinside.Api.Controllers;
8+
using Nullinside.Api.Model.Ddl;
9+
using Nullinside.Api.Shared.Json;
10+
11+
namespace Nullinside.Api.Tests.Nullinside.Api.Controllers;
12+
13+
/// <summary>
14+
/// Tests for the <see cref="DockerController" /> class
15+
/// </summary>
16+
public class DockerControllerTests : UnitTestBase {
17+
/// <summary>
18+
/// The docker proxy.
19+
/// </summary>
20+
private Mock<IDockerProxy> _docker;
21+
22+
/// <summary>
23+
/// Add the docker proxy.
24+
/// </summary>
25+
public override void Setup() {
26+
base.Setup();
27+
_docker = new Mock<IDockerProxy>();
28+
}
29+
30+
/// <summary>
31+
/// Tests that given a list of docker projects from the database we can match it against the actively running
32+
/// projects on the server.
33+
/// </summary>
34+
[Test]
35+
public async Task DatabaseMatchesCommandOutputSuccessfully() {
36+
// Create three entries in the database for what we allow people to see. All of these should be in the output.
37+
_db.DockerDeployments.AddRange(
38+
new DockerDeployments {
39+
Id = 1, DisplayName = "Good", IsDockerComposeProject = true, Name = "Good", Notes = "Should be in output"
40+
},
41+
new DockerDeployments {
42+
Id = 2, DisplayName = "Good without matching name", IsDockerComposeProject = true, Name = "NonMatchingName",
43+
Notes = "Should be in output"
44+
},
45+
new DockerDeployments {
46+
Id = 3, DisplayName = "Good non-compose", IsDockerComposeProject = false, Name = "Stuff",
47+
Notes = "Should be in output"
48+
}
49+
);
50+
await _db.SaveChangesAsync();
51+
52+
// Create two entries "in the server" for what is actually running. We should only match on the "good" one. The bad
53+
// one is different enough that it shouldn't match.
54+
var compose = new List<DockerResource> {
55+
new() { Id = 1, IsOnline = true, Name = "Good", Notes = "Should be in output's IsOnline field" },
56+
new() { Id = 2, IsOnline = false, Name = "Bad", Notes = "Should not be in output" }
57+
};
58+
_docker.Setup(d => d.GetDockerComposeProjects(It.IsAny<CancellationToken>()))
59+
.Returns(() => Task.FromResult(compose.AsEnumerable()));
60+
61+
var containers = new List<DockerResource> {
62+
new() { Id = 3, IsOnline = true, Name = "doesn't match", Notes = "Should not be in output" }
63+
};
64+
_docker.Setup(d => d.GetContainers(It.IsAny<CancellationToken>()))
65+
.Returns(() => Task.FromResult(containers.AsEnumerable()));
66+
67+
// Make the call and ensure it's successful.
68+
var controller = new DockerController(_db, _docker.Object);
69+
ObjectResult obj = await controller.GetDockerResources();
70+
Assert.That(obj.StatusCode, Is.EqualTo(200));
71+
72+
// There should be three results. One that was actively running "Good" and the others weren't actively running.
73+
var deployments = obj.Value as List<DockerResource>;
74+
Assert.That(deployments, Is.Not.Null);
75+
Assert.That(deployments.Count, Is.EqualTo(3));
76+
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good")?.IsOnline, Is.True);
77+
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good without matching name")?.IsOnline, Is.False);
78+
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good non-compose")?.IsOnline, Is.False);
79+
}
80+
81+
/// <summary>
82+
/// Tests that turning on/off a compose project calls the correct thing.
83+
/// </summary>
84+
[Test]
85+
public async Task OnOffComposeProjectsWork() {
86+
// Create two entries in the database for what we allow people to adjust.
87+
_db.DockerDeployments.AddRange(
88+
new DockerDeployments {
89+
Id = 1, DisplayName = "Good", IsDockerComposeProject = true, Name = "Good", Notes = "Should be in output"
90+
},
91+
new DockerDeployments {
92+
Id = 2, DisplayName = "Bad", IsDockerComposeProject = true, Name = "Bad", Notes = "Should not be in output"
93+
}
94+
);
95+
await _db.SaveChangesAsync();
96+
97+
// Only a call with "Good" will work.
98+
_docker.Setup(d =>
99+
d.TurnOnOffDockerCompose("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>(), It.IsAny<string>()))
100+
.Returns(() => Task.FromResult(true));
101+
102+
// Make the call and ensure it's successful.
103+
var controller = new DockerController(_db, _docker.Object);
104+
ObjectResult obj =
105+
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
106+
Assert.That(obj.StatusCode, Is.EqualTo(200));
107+
108+
// Ensure we called the 3rd party API with a value of "Good" to turn on a compose.
109+
bool deployments = (bool)obj.Value!;
110+
Assert.That(deployments, Is.True);
111+
}
112+
113+
/// <summary>
114+
/// Tests that turning on/off a container project calls the correct thing.
115+
/// </summary>
116+
[Test]
117+
public async Task OnOffContainerWork() {
118+
// Create two entries in the database for what we allow people to adjust.
119+
_db.DockerDeployments.AddRange(
120+
new DockerDeployments {
121+
Id = 1, DisplayName = "Good", IsDockerComposeProject = false, Name = "Good", Notes = "Should be in output"
122+
},
123+
new DockerDeployments {
124+
Id = 2, DisplayName = "Bad", IsDockerComposeProject = false, Name = "Bad", Notes = "Should not be in output"
125+
}
126+
);
127+
await _db.SaveChangesAsync();
128+
129+
// Only a call with "Good" will work.
130+
_docker.Setup(d => d.TurnOnOffDockerContainer("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
131+
.Returns(() => Task.FromResult(true));
132+
133+
// Make the call and ensure it's successful.
134+
var controller = new DockerController(_db, _docker.Object);
135+
ObjectResult obj =
136+
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
137+
Assert.That(obj.StatusCode, Is.EqualTo(200));
138+
139+
// Ensure we called the 3rd party API with a value of "Good" to turn on a container.
140+
bool deployments = (bool)obj.Value!;
141+
Assert.That(deployments, Is.True);
142+
}
143+
144+
/// <summary>
145+
/// Tests providing an invalid id will result in a HTTP bad request.
146+
/// </summary>
147+
[Test]
148+
public async Task InvalidIdIsBadRequest() {
149+
// Create two entries in the database for what we allow people to adjust.
150+
_db.DockerDeployments.Add(
151+
new DockerDeployments {
152+
Id = 2, DisplayName = "Bad", IsDockerComposeProject = false, Name = "Bad", Notes = "Should not be in output"
153+
}
154+
);
155+
await _db.SaveChangesAsync();
156+
157+
// Only a call with "Good" will get true.
158+
_docker.Setup(d => d.TurnOnOffDockerContainer("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
159+
.Returns(() => Task.FromResult(true));
160+
161+
// Make the call and ensure it's successful.
162+
var controller = new DockerController(_db, _docker.Object);
163+
ObjectResult obj =
164+
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
165+
Assert.That(obj.StatusCode, Is.EqualTo(400));
166+
167+
// Bad request is returned to user with a generic error message.
168+
Assert.That(obj.Value, Is.TypeOf<BasicServerFailure>());
169+
}
170+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
using Nullinside.Api.Controllers;
4+
using Nullinside.Api.Model.Ddl;
5+
6+
namespace Nullinside.Api.Tests.Nullinside.Api.Controllers;
7+
8+
/// <summary>
9+
/// Tests for the <see cref="FeatureToggleController" /> class
10+
/// </summary>
11+
public class FeatureToggleControllerTests : UnitTestBase {
12+
/// <summary>
13+
/// Tests that we can pull the feature toggles. It's basically a straight through.
14+
/// </summary>
15+
[Test]
16+
public async Task GetAllFeatureToggles() {
17+
// Creates two feature toggles.
18+
_db.FeatureToggle.AddRange(
19+
new FeatureToggle {
20+
Id = 1, Feature = "hi", IsEnabled = true
21+
},
22+
new FeatureToggle {
23+
Id = 2, Feature = "bye", IsEnabled = false
24+
}
25+
);
26+
await _db.SaveChangesAsync();
27+
28+
// Make the call and ensure it's successful.
29+
var controller = new FeatureToggleController(_db);
30+
ObjectResult obj = await controller.GetAll();
31+
Assert.That(obj.StatusCode, Is.EqualTo(200));
32+
33+
// Ensure they passed through cleanly.
34+
var featureToggles = obj.Value as IEnumerable<FeatureToggle>;
35+
Assert.That(featureToggles, Is.Not.Null);
36+
Assert.That(featureToggles.Count, Is.EqualTo(2));
37+
Assert.That(featureToggles.FirstOrDefault(f => f.Feature == "hi")?.IsEnabled, Is.True);
38+
Assert.That(featureToggles.FirstOrDefault(f => f.Feature == "bye")?.IsEnabled, Is.False);
39+
}
40+
}

0 commit comments

Comments
 (0)