Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Diagnostics.CodeAnalysis;

using Microsoft.AspNetCore.Authorization;

namespace Nullinside.Api.Common.AspNetCore.Middleware;

/// <summary>
/// Represents a requirement where a user is expected to have one role.
/// </summary>
[ExcludeFromCodeCoverage]
public class BasicAuthorizationRequirement : IAuthorizationRequirement {
/// <summary>
/// Initializes a new instance of the <see cref="BasicAuthorizationRequirement" /> class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Nullinside.Api.Common.Desktop;
using System.Diagnostics.CodeAnalysis;

namespace Nullinside.Api.Common.Desktop;

/// <summary>
/// The response information from GitHub's API.
/// </summary>
[ExcludeFromCodeCoverage]
public class GithubLatestReleaseJson {
/// <summary>
/// The url of the resource.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.Diagnostics.CodeAnalysis;

namespace Nullinside.Api.Common.Docker.Support;

/// <summary>
/// The `docker compose ls --format 'json'` output.
/// </summary>
[ExcludeFromCodeCoverage]
public class DockerComposeLsOutput {
/// <summary>
/// The name of the docker compose project.
Expand Down
3 changes: 3 additions & 0 deletions src/Nullinside.Api.Common/Docker/Support/DockerResource.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Diagnostics.CodeAnalysis;

namespace Nullinside.Api.Common.Docker.Support;

/// <summary>
/// A docker resource representing either a docker compose project
/// or a single docker container.
/// </summary>
[ExcludeFromCodeCoverage]
public class DockerResource {
/// <summary>
/// Initializes a new instance of the <see cref="DockerResource" /> class.
Expand Down
5 changes: 4 additions & 1 deletion src/Nullinside.Api.Common/Exceptions/RetryException.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Nullinside.Api.Common.Exceptions;
using System.Diagnostics.CodeAnalysis;

namespace Nullinside.Api.Common.Exceptions;

/// <summary>
/// An exception thrown if an action continues to fail after retrying.
/// </summary>
[ExcludeFromCodeCoverage]
public class RetryException : Exception {
/// <summary>
/// Initializes a new instance of the <see cref="RetryException" /> class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Nullinside.Api.Common.Twitch.Json;
using System.Diagnostics.CodeAnalysis;

namespace Nullinside.Api.Common.Twitch.Json;

/// <summary>
/// The response to a query for what channels a user is moderator for.
/// </summary>
[ExcludeFromCodeCoverage]
public class TwitchModeratedChannelsResponse {
/// <summary>
/// The list of channels the user moderates for.
Expand All @@ -18,6 +21,7 @@ public class TwitchModeratedChannelsResponse {
/// <summary>
/// A channel the user moderates.
/// </summary>
[ExcludeFromCodeCoverage]
public class TwitchModeratedChannel {
/// <summary>
/// The twitch id.
Expand All @@ -38,6 +42,7 @@ public class TwitchModeratedChannel {
/// <summary>
/// Pagination information.
/// </summary>
[ExcludeFromCodeCoverage]
public class Pagination {
/// <summary>
/// The cursor to pass to "after" for pagination.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

using Nullinside.Api.Model;
using Nullinside.Api.Model.Ddl;
using Nullinside.Api.Model.Shared;

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

public class UserHelpersTests {
private INullinsideContext _db;

[SetUp]
public void Setup() {
// Create an in-memory database to fake the SQL queries. Note that we generate a random GUID for the name
// here. If you use the same name more than once you'll get collisions between tests.
DbContextOptions<NullinsideContext> contextOptions = new DbContextOptionsBuilder<NullinsideContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.Options;
_db = new NullinsideContext(contextOptions);
}

[TearDown]
public async Task TearDown() {
await _db.DisposeAsync();
}

/// <summary>
/// Tests for the <see cref="UserHelpers" /> class.
/// </summary>
public class UserHelpersTests : UnitTestBase {
/// <summary>
/// The case where a user is generating a new token to replace their existing one.
/// </summary>
Expand Down
1 change: 0 additions & 1 deletion src/Nullinside.Api.Tests/Nullinside.Api.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
<ItemGroup>
<Folder Include="Nullinside.Api.Common.AspNetCore\"/>
<Folder Include="Nullinside.Api.Common\"/>
<Folder Include="Nullinside.Api\"/>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Microsoft.AspNetCore.Mvc;

using Moq;

using Nullinside.Api.Common.Docker;
using Nullinside.Api.Common.Docker.Support;
using Nullinside.Api.Controllers;
using Nullinside.Api.Model.Ddl;
using Nullinside.Api.Shared.Json;

namespace Nullinside.Api.Tests.Nullinside.Api.Controllers;

/// <summary>
/// Tests for the <see cref="DockerController" /> class
/// </summary>
public class DockerControllerTests : UnitTestBase {
/// <summary>
/// The docker proxy.
/// </summary>
private Mock<IDockerProxy> _docker;

/// <summary>
/// Add the docker proxy.
/// </summary>
public override void Setup() {
base.Setup();
_docker = new Mock<IDockerProxy>();
}

/// <summary>
/// Tests that given a list of docker projects from the database we can match it against the actively running
/// projects on the server.
/// </summary>
[Test]
public async Task DatabaseMatchesCommandOutputSuccessfully() {
// Create three entries in the database for what we allow people to see. All of these should be in the output.
_db.DockerDeployments.AddRange(
new DockerDeployments {
Id = 1, DisplayName = "Good", IsDockerComposeProject = true, Name = "Good", Notes = "Should be in output"
},
new DockerDeployments {
Id = 2, DisplayName = "Good without matching name", IsDockerComposeProject = true, Name = "NonMatchingName",
Notes = "Should be in output"
},
new DockerDeployments {
Id = 3, DisplayName = "Good non-compose", IsDockerComposeProject = false, Name = "Stuff",
Notes = "Should be in output"
}
);
await _db.SaveChangesAsync();

// Create two entries "in the server" for what is actually running. We should only match on the "good" one. The bad
// one is different enough that it shouldn't match.
var compose = new List<DockerResource> {
new() { Id = 1, IsOnline = true, Name = "Good", Notes = "Should be in output's IsOnline field" },
new() { Id = 2, IsOnline = false, Name = "Bad", Notes = "Should not be in output" }
};
_docker.Setup(d => d.GetDockerComposeProjects(It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(compose.AsEnumerable()));

var containers = new List<DockerResource> {
new() { Id = 3, IsOnline = true, Name = "doesn't match", Notes = "Should not be in output" }
};
_docker.Setup(d => d.GetContainers(It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(containers.AsEnumerable()));

// Make the call and ensure it's successful.
var controller = new DockerController(_db, _docker.Object);
ObjectResult obj = await controller.GetDockerResources();
Assert.That(obj.StatusCode, Is.EqualTo(200));

// There should be three results. One that was actively running "Good" and the others weren't actively running.
var deployments = obj.Value as List<DockerResource>;
Assert.That(deployments, Is.Not.Null);
Assert.That(deployments.Count, Is.EqualTo(3));
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good")?.IsOnline, Is.True);
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good without matching name")?.IsOnline, Is.False);
Assert.That(deployments.FirstOrDefault(d => d.Name == "Good non-compose")?.IsOnline, Is.False);
}

/// <summary>
/// Tests that turning on/off a compose project calls the correct thing.
/// </summary>
[Test]
public async Task OnOffComposeProjectsWork() {
// Create two entries in the database for what we allow people to adjust.
_db.DockerDeployments.AddRange(
new DockerDeployments {
Id = 1, DisplayName = "Good", IsDockerComposeProject = true, Name = "Good", Notes = "Should be in output"
},
new DockerDeployments {
Id = 2, DisplayName = "Bad", IsDockerComposeProject = true, Name = "Bad", Notes = "Should not be in output"
}
);
await _db.SaveChangesAsync();

// Only a call with "Good" will work.
_docker.Setup(d =>
d.TurnOnOffDockerCompose("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>(), It.IsAny<string>()))
.Returns(() => Task.FromResult(true));

// Make the call and ensure it's successful.
var controller = new DockerController(_db, _docker.Object);
ObjectResult obj =
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
Assert.That(obj.StatusCode, Is.EqualTo(200));

// Ensure we called the 3rd party API with a value of "Good" to turn on a compose.
bool deployments = (bool)obj.Value!;
Assert.That(deployments, Is.True);
}

/// <summary>
/// Tests that turning on/off a container project calls the correct thing.
/// </summary>
[Test]
public async Task OnOffContainerWork() {
// Create two entries in the database for what we allow people to adjust.
_db.DockerDeployments.AddRange(
new DockerDeployments {
Id = 1, DisplayName = "Good", IsDockerComposeProject = false, Name = "Good", Notes = "Should be in output"
},
new DockerDeployments {
Id = 2, DisplayName = "Bad", IsDockerComposeProject = false, Name = "Bad", Notes = "Should not be in output"
}
);
await _db.SaveChangesAsync();

// Only a call with "Good" will work.
_docker.Setup(d => d.TurnOnOffDockerContainer("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(true));

// Make the call and ensure it's successful.
var controller = new DockerController(_db, _docker.Object);
ObjectResult obj =
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
Assert.That(obj.StatusCode, Is.EqualTo(200));

// Ensure we called the 3rd party API with a value of "Good" to turn on a container.
bool deployments = (bool)obj.Value!;
Assert.That(deployments, Is.True);
}

/// <summary>
/// Tests providing an invalid id will result in a HTTP bad request.
/// </summary>
[Test]
public async Task InvalidIdIsBadRequest() {
// Create two entries in the database for what we allow people to adjust.
_db.DockerDeployments.Add(
new DockerDeployments {
Id = 2, DisplayName = "Bad", IsDockerComposeProject = false, Name = "Bad", Notes = "Should not be in output"
}
);
await _db.SaveChangesAsync();

// Only a call with "Good" will get true.
_docker.Setup(d => d.TurnOnOffDockerContainer("Good", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(true));

// Make the call and ensure it's successful.
var controller = new DockerController(_db, _docker.Object);
ObjectResult obj =
await controller.TurnOnOrOffDockerResources(1, new TurnOnOrOffDockerResourcesRequest { TurnOn = true });
Assert.That(obj.StatusCode, Is.EqualTo(400));

// Bad request is returned to user with a generic error message.
Assert.That(obj.Value, Is.TypeOf<BasicServerFailure>());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;

using Nullinside.Api.Controllers;
using Nullinside.Api.Model.Ddl;

namespace Nullinside.Api.Tests.Nullinside.Api.Controllers;

/// <summary>
/// Tests for the <see cref="FeatureToggleController" /> class
/// </summary>
public class FeatureToggleControllerTests : UnitTestBase {
/// <summary>
/// Tests that we can pull the feature toggles. It's basically a straight through.
/// </summary>
[Test]
public async Task GetAllFeatureToggles() {
// Creates two feature toggles.
_db.FeatureToggle.AddRange(
new FeatureToggle {
Id = 1, Feature = "hi", IsEnabled = true
},
new FeatureToggle {
Id = 2, Feature = "bye", IsEnabled = false
}
);
await _db.SaveChangesAsync();

// Make the call and ensure it's successful.
var controller = new FeatureToggleController(_db);
ObjectResult obj = await controller.GetAll();
Assert.That(obj.StatusCode, Is.EqualTo(200));

// Ensure they passed through cleanly.
var featureToggles = obj.Value as IEnumerable<FeatureToggle>;
Assert.That(featureToggles, Is.Not.Null);
Assert.That(featureToggles.Count, Is.EqualTo(2));
Assert.That(featureToggles.FirstOrDefault(f => f.Feature == "hi")?.IsEnabled, Is.True);
Assert.That(featureToggles.FirstOrDefault(f => f.Feature == "bye")?.IsEnabled, Is.False);
}
}
Loading
Loading