mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 10:48:49 -04:00
1391 lines
50 KiB
C#
1391 lines
50 KiB
C#
using System.Text.Json;
|
|
using Cleanuparr.Api.Features.Notifications.Contracts.Requests;
|
|
using Cleanuparr.Api.Features.Notifications.Contracts.Responses;
|
|
using Cleanuparr.Api.Features.Notifications.Controllers;
|
|
using Cleanuparr.Api.Features.Notifications.Descriptors;
|
|
using Cleanuparr.Api.Json;
|
|
using Cleanuparr.Api.Tests.TestHelpers;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Domain.Exceptions;
|
|
using Cleanuparr.Infrastructure.Features.Notifications;
|
|
using Cleanuparr.Infrastructure.Features.Notifications.Apprise;
|
|
using Cleanuparr.Infrastructure.Features.Notifications.Models;
|
|
using Cleanuparr.Persistence;
|
|
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using NSubstitute.ExceptionExtensions;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace Cleanuparr.Api.Tests.Features.Notifications;
|
|
|
|
public class NotificationProvidersControllerTests : IDisposable
|
|
{
|
|
private readonly DataContext _dataContext;
|
|
private readonly NotificationProvidersController _controller;
|
|
private readonly JsonSerializerOptions _jsonOptions;
|
|
private readonly INotificationProviderFactory _providerFactory;
|
|
private readonly IAppriseCliDetector _appriseCliDetector;
|
|
|
|
public NotificationProvidersControllerTests()
|
|
{
|
|
_dataContext = ConfigControllerTestDataFactory.CreateDataContext();
|
|
|
|
_jsonOptions = new JsonSerializerOptions();
|
|
CleanuparrJsonConfiguration.ConfigureApiInbound(_jsonOptions);
|
|
|
|
INotificationConfigurationService configurationService =
|
|
Substitute.For<INotificationConfigurationService>();
|
|
|
|
_providerFactory = Substitute.For<INotificationProviderFactory>();
|
|
_providerFactory.CreateProvider(Arg.Any<Infrastructure.Features.Notifications.Models.NotificationProviderDto>())
|
|
.Returns(Substitute.For<INotificationProvider>());
|
|
|
|
_appriseCliDetector = Substitute.For<IAppriseCliDetector>();
|
|
|
|
NotificationService notificationService = new(
|
|
Substitute.For<ILogger<NotificationService>>(),
|
|
configurationService,
|
|
_providerFactory,
|
|
TimeProvider.System);
|
|
|
|
_controller = new NotificationProvidersController(
|
|
Substitute.For<ILogger<NotificationProvidersController>>(),
|
|
_dataContext,
|
|
configurationService,
|
|
notificationService,
|
|
_appriseCliDetector,
|
|
TimeProvider.System,
|
|
new NotificationProviderDescriptorRegistry(),
|
|
_jsonOptions);
|
|
|
|
ConfigControllerTestDataFactory.ConfigureProblemDetails(_controller);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dataContext.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private async Task ShouldBeStampedNow(Guid id)
|
|
{
|
|
NotificationConfig stored = await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.FirstAsync(c => c.Id == id);
|
|
|
|
stored.UpdatedAt.ShouldBe(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
|
|
}
|
|
|
|
private JsonElement ToJson<T>(T request) where T : notnull =>
|
|
JsonSerializer.SerializeToElement(request, _jsonOptions);
|
|
|
|
private static NotificationProviderResponse Created(IActionResult result) =>
|
|
result.ShouldBeOfType<CreatedAtActionResult>().Value.ShouldBeOfType<NotificationProviderResponse>();
|
|
|
|
private static NotificationProviderResponse Updated(IActionResult result) =>
|
|
result.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<NotificationProviderResponse>();
|
|
|
|
private static ProblemDetails Problem(IActionResult result) =>
|
|
result.ShouldBeOfType<ObjectResult>().Value.ShouldBeOfType<ProblemDetails>();
|
|
|
|
#region Notifiarr
|
|
|
|
[Fact]
|
|
public async Task CreateNotifiarrProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateNotifiarrProviderRequest request = new()
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Notifiarr, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Notifiarr);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateNotifiarrProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Notifiarr, ToJson(new CreateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Notifiarr, id,
|
|
ToJson(new UpdateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateNotifiarrProvider_WithPlaceholderApiKey_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Notifiarr, ToJson(new CreateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "••••••••",
|
|
ChannelId = "123456789",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("API key cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateNotifiarrProvider_WithPlaceholderApiKey_PreservesTheExistingKey()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Notifiarr, ToJson(new CreateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Notifiarr, id, ToJson(new UpdateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "••••••••",
|
|
ChannelId = "987654321",
|
|
}));
|
|
|
|
NotifiarrConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.NotifiarrConfiguration)
|
|
.FirstAsync(p => p.Id == id)).NotifiarrConfiguration!;
|
|
|
|
stored.ApiKey.ShouldBe("0123456789abcdef");
|
|
stored.ChannelId.ShouldBe("987654321");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNotifiarrProvider_WithPlaceholderApiKeyAndNoProviderId_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Notifiarr, ToJson(new TestNotifiarrProviderRequest
|
|
{
|
|
ApiKey = "••••••••",
|
|
ChannelId = "123456789",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("API key cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNotifiarrProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Notifiarr, ToJson(new TestNotifiarrProviderRequest
|
|
{
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNotifiarrProvider_WithPlaceholderApiKeyAndProviderId_UsesTheStoredKey()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Notifiarr, ToJson(new CreateNotifiarrProviderRequest
|
|
{
|
|
Name = "Notifiarr",
|
|
ApiKey = "0123456789abcdef",
|
|
ChannelId = "123456789",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Notifiarr, ToJson(new TestNotifiarrProviderRequest
|
|
{
|
|
ApiKey = "••••••••",
|
|
ChannelId = "123456789",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Apprise
|
|
|
|
[Fact]
|
|
public async Task CreateAppriseProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateAppriseProviderRequest request = new()
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Apprise);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAppriseProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(new CreateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Apprise, id,
|
|
ToJson(new UpdateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAppriseProvider_WithPlaceholderKey_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(new CreateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Key cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestAppriseProvider_WithPlaceholderKeyAndNoProviderId_ReturnsTheGenericMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Apprise, ToJson(new TestAppriseProviderRequest
|
|
{
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Sensitive fields cannot be placeholder values");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAppriseProvider_WithPlaceholderKey_PreservesTheExistingKeyButUpdatesServiceUrls()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(new CreateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Apprise, id, ToJson(new UpdateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com/new",
|
|
Key = "••••••••",
|
|
}));
|
|
|
|
AppriseConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.AppriseConfiguration)
|
|
.FirstAsync(p => p.Id == id)).AppriseConfiguration!;
|
|
|
|
stored.Key.ShouldBe("config-key");
|
|
stored.Url.ShouldBe("https://apprise.example.com/new");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAppriseProvider_WithPlaceholderServiceUrls_PreservesTheExistingServiceUrls()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(new CreateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
ServiceUrls = "discord://webhook_id/webhook_token",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Apprise, id, ToJson(new UpdateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "new-config-key",
|
|
ServiceUrls = "••••••••",
|
|
}));
|
|
|
|
AppriseConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.AppriseConfiguration)
|
|
.FirstAsync(p => p.Id == id)).AppriseConfiguration!;
|
|
|
|
stored.Key.ShouldBe("new-config-key");
|
|
stored.ServiceUrls.ShouldBe("discord://webhook_id/webhook_token");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestAppriseProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Apprise, ToJson(new TestAppriseProviderRequest
|
|
{
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestAppriseProvider_WithPlaceholderFieldsAndProviderId_UsesTheStoredValues()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Apprise, ToJson(new CreateAppriseProviderRequest
|
|
{
|
|
Name = "Apprise",
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "config-key",
|
|
ServiceUrls = "discord://webhook_id/webhook_token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Apprise, ToJson(new TestAppriseProviderRequest
|
|
{
|
|
Mode = AppriseMode.Api,
|
|
Url = "https://apprise.example.com",
|
|
Key = "••••••••",
|
|
ServiceUrls = "••••••••",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Ntfy
|
|
|
|
[Fact]
|
|
public async Task CreateNtfyProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateNtfyProviderRequest request = new()
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Ntfy, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Ntfy);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateNtfyProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Ntfy, ToJson(new CreateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Ntfy, id,
|
|
ToJson(new UpdateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateNtfyProvider_WithPlaceholderPassword_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Ntfy, ToJson(new CreateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Password cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateNtfyProvider_WithPlaceholderFields_PreservesTheExistingValuesButUpdatesServerUrl()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Ntfy, ToJson(new CreateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "password",
|
|
AccessToken = "access-token",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Ntfy, id, ToJson(new UpdateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.example.com",
|
|
Topics = ["cleanuparr"],
|
|
Password = "••••••••",
|
|
AccessToken = "••••••••",
|
|
}));
|
|
|
|
NtfyConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.NtfyConfiguration)
|
|
.FirstAsync(p => p.Id == id)).NtfyConfiguration!;
|
|
|
|
stored.Password.ShouldBe("password");
|
|
stored.AccessToken.ShouldBe("access-token");
|
|
stored.ServerUrl.ShouldBe("https://ntfy.example.com");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNtfyProvider_WithPlaceholderPasswordAndNoProviderId_ReturnsTheGenericMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Ntfy, ToJson(new TestNtfyProviderRequest
|
|
{
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "••••••••",
|
|
AccessToken = "access-token",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Sensitive fields cannot be placeholder values");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNtfyProvider_WithPlaceholderAccessTokenAndNoProviderId_ReturnsTheGenericMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Ntfy, ToJson(new TestNtfyProviderRequest
|
|
{
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "password",
|
|
AccessToken = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Sensitive fields cannot be placeholder values");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNtfyProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Ntfy, ToJson(new TestNtfyProviderRequest
|
|
{
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "password",
|
|
AccessToken = "access-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestNtfyProvider_WithPlaceholderFieldsAndProviderId_UsesTheStoredValues()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Ntfy, ToJson(new CreateNtfyProviderRequest
|
|
{
|
|
Name = "Ntfy",
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "password",
|
|
AccessToken = "access-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Ntfy, ToJson(new TestNtfyProviderRequest
|
|
{
|
|
ServerUrl = "https://ntfy.sh",
|
|
Topics = ["cleanuparr"],
|
|
Password = "••••••••",
|
|
AccessToken = "••••••••",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Telegram
|
|
|
|
[Fact]
|
|
public async Task CreateTelegramProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateTelegramProviderRequest request = new()
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Telegram, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Telegram);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateTelegramProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Telegram, ToJson(new CreateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Telegram, id,
|
|
ToJson(new UpdateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestTelegramProvider_WithPlaceholderBotTokenAndNoProviderId_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Telegram, ToJson(new TestTelegramProviderRequest
|
|
{
|
|
BotToken = "••••••••",
|
|
ChatId = "-1001234567890",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Bot token cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateTelegramProvider_WithPlaceholderBotToken_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Telegram, ToJson(new CreateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "••••••••",
|
|
ChatId = "-1001234567890",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Bot token cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateTelegramProvider_WithPlaceholderBotToken_PreservesTheExistingBotTokenButUpdatesChatId()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Telegram, ToJson(new CreateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Telegram, id, ToJson(new UpdateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "••••••••",
|
|
ChatId = "-1009876543210",
|
|
}));
|
|
|
|
TelegramConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.TelegramConfiguration)
|
|
.FirstAsync(p => p.Id == id)).TelegramConfiguration!;
|
|
|
|
stored.BotToken.ShouldBe("0123456789:token");
|
|
stored.ChatId.ShouldBe("-1009876543210");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestTelegramProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Telegram, ToJson(new TestTelegramProviderRequest
|
|
{
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestTelegramProvider_WithPlaceholderBotTokenAndProviderId_UsesTheStoredBotToken()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Telegram, ToJson(new CreateTelegramProviderRequest
|
|
{
|
|
Name = "Telegram",
|
|
BotToken = "0123456789:token",
|
|
ChatId = "-1001234567890",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Telegram, ToJson(new TestTelegramProviderRequest
|
|
{
|
|
BotToken = "••••••••",
|
|
ChatId = "-1001234567890",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Discord
|
|
|
|
[Fact]
|
|
public async Task CreateDiscordProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateDiscordProviderRequest request = new()
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Discord, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Discord);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateDiscordProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Discord, ToJson(new CreateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Discord, id,
|
|
ToJson(new UpdateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateDiscordProvider_WithPlaceholderWebhookUrl_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Discord, ToJson(new CreateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Webhook URL cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateDiscordProvider_WithPlaceholderWebhookUrl_PreservesTheExistingWebhookUrl()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Discord, ToJson(new CreateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Discord, id, ToJson(new UpdateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "••••••••",
|
|
}));
|
|
|
|
DiscordConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.DiscordConfiguration)
|
|
.FirstAsync(p => p.Id == id)).DiscordConfiguration!;
|
|
|
|
stored.WebhookUrl.ShouldBe("https://discord.com/api/webhooks/1/token");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestDiscordProvider_WithPlaceholderWebhookUrlAndNoProviderId_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Discord, ToJson(new TestDiscordProviderRequest
|
|
{
|
|
WebhookUrl = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Webhook URL cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestDiscordProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Discord, ToJson(new TestDiscordProviderRequest
|
|
{
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestDiscordProvider_WithPlaceholderWebhookUrlAndProviderId_UsesTheStoredWebhookUrl()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Discord, ToJson(new CreateDiscordProviderRequest
|
|
{
|
|
Name = "Discord",
|
|
WebhookUrl = "https://discord.com/api/webhooks/1/token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Discord, ToJson(new TestDiscordProviderRequest
|
|
{
|
|
WebhookUrl = "••••••••",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Pushover
|
|
|
|
[Fact]
|
|
public async Task CreatePushoverProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreatePushoverProviderRequest request = new()
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Pushover, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Pushover);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdatePushoverProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Pushover, ToJson(new CreatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Pushover, id,
|
|
ToJson(new UpdatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreatePushoverProvider_WithPlaceholderApiToken_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Pushover, ToJson(new CreatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "••••••••",
|
|
UserKey = "user-key",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("API token cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdatePushoverProvider_WithPlaceholderFields_PreservesTheExistingValues()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Pushover, ToJson(new CreatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Pushover, id, ToJson(new UpdatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "••••••••",
|
|
UserKey = "••••••••",
|
|
}));
|
|
|
|
PushoverConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.PushoverConfiguration)
|
|
.FirstAsync(p => p.Id == id)).PushoverConfiguration!;
|
|
|
|
stored.ApiToken.ShouldBe("api-token");
|
|
stored.UserKey.ShouldBe("user-key");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestPushoverProvider_WithPlaceholderFieldsAndNoProviderId_ReturnsTheGenericMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Pushover, ToJson(new TestPushoverProviderRequest
|
|
{
|
|
ApiToken = "••••••••",
|
|
UserKey = "user-key",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Sensitive fields cannot be placeholder values");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestPushoverProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Pushover, ToJson(new TestPushoverProviderRequest
|
|
{
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestPushoverProvider_WithPlaceholderFieldsAndProviderId_UsesTheStoredValues()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Pushover, ToJson(new CreatePushoverProviderRequest
|
|
{
|
|
Name = "Pushover",
|
|
ApiToken = "api-token",
|
|
UserKey = "user-key",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Pushover, ToJson(new TestPushoverProviderRequest
|
|
{
|
|
ApiToken = "••••••••",
|
|
UserKey = "••••••••",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Gotify
|
|
|
|
[Fact]
|
|
public async Task CreateGotifyProvider_PersistsTheDownloadStoppedEvent()
|
|
{
|
|
CreateGotifyProviderRequest request = new()
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
OnDownloadStopped = true,
|
|
};
|
|
|
|
NotificationProviderResponse provider = Created(
|
|
await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(request)));
|
|
|
|
provider.Type.ShouldBe(NotificationProviderType.Gotify);
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateGotifyProvider_ChangesTheDownloadStoppedEvent()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
OnDownloadStopped = false,
|
|
}))).Id;
|
|
|
|
NotificationProviderResponse provider = Updated(await _controller.UpdateProvider(NotificationProviderType.Gotify, id,
|
|
ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
OnDownloadStopped = true,
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeTrue();
|
|
await ShouldBeStampedNow(provider.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateGotifyProvider_WithPlaceholderApplicationToken_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Application token cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateGotifyProvider_WithPlaceholderApplicationToken_PreservesTheExistingTokenButUpdatesServerUrl()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
await _controller.UpdateProvider(NotificationProviderType.Gotify, id, ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com/new",
|
|
ApplicationToken = "••••••••",
|
|
}));
|
|
|
|
GotifyConfig stored = (await _dataContext.NotificationConfigs
|
|
.AsNoTracking()
|
|
.Include(p => p.GotifyConfiguration)
|
|
.FirstAsync(p => p.Id == id)).GotifyConfiguration!;
|
|
|
|
stored.ApplicationToken.ShouldBe("app-token");
|
|
stored.ServerUrl.ShouldBe("https://gotify.example.com/new");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestGotifyProvider_WithPlaceholderApplicationTokenAndNoProviderId_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "••••••••",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Application token cannot be a placeholder value");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestGotifyProvider_WithRealValues_SendsSuccessfully()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestGotifyProvider_WithPlaceholderApplicationTokenAndProviderId_UsesTheStoredToken()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "••••••••",
|
|
ProviderId = id,
|
|
}));
|
|
|
|
result.ShouldBeOfType<OkObjectResult>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
[Fact]
|
|
public async Task GetNotificationProviders_ReturnsTheDownloadStoppedEvent()
|
|
{
|
|
await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
OnDownloadStopped = true,
|
|
}));
|
|
|
|
NotificationProvidersResponse response = (await _controller.GetNotificationProviders())
|
|
.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<NotificationProvidersResponse>();
|
|
|
|
response.Providers.ShouldHaveSingleItem().Events.OnDownloadStopped.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateGotifyProvider_WithoutEvents_LeavesDownloadStoppedDisabled()
|
|
{
|
|
NotificationProviderResponse provider = Created(await _controller.CreateProvider(NotificationProviderType.Gotify,
|
|
ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
})));
|
|
|
|
provider.Events.OnDownloadStopped.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateProvider_DuplicateName_ReturnsBadRequest()
|
|
{
|
|
await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token-2",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("A provider with this name already exists");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateProvider_MissingName_ReturnsBadRequest()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Provider name is required");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateProvider_UnsupportedType_ReturnsNotFound()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider((NotificationProviderType)999, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(404);
|
|
}
|
|
|
|
#region UpdateProvider
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_UnsupportedType_ReturnsNotFound()
|
|
{
|
|
IActionResult result = await _controller.UpdateProvider((NotificationProviderType)999, Guid.NewGuid(), ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(404);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_UnknownId_ReturnsNotFound()
|
|
{
|
|
IActionResult result = await _controller.UpdateProvider(NotificationProviderType.Gotify, Guid.NewGuid(), ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(404);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_MissingName_ReturnsBadRequest()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.UpdateProvider(NotificationProviderType.Gotify, id, ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Provider name is required");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_DuplicateName_ReturnsBadRequest()
|
|
{
|
|
await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify-2",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token-2",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.UpdateProvider(NotificationProviderType.Gotify, id, ToJson(new UpdateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token-2",
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("A provider with this name already exists");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DeleteNotificationProvider
|
|
|
|
[Fact]
|
|
public async Task DeleteNotificationProvider_RemovesProvider()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.DeleteNotificationProvider(id);
|
|
|
|
result.ShouldBeOfType<NoContentResult>();
|
|
(await _dataContext.NotificationConfigs.CountAsync()).ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteNotificationProvider_UnknownId_ReturnsNotFound()
|
|
{
|
|
IActionResult result = await _controller.DeleteNotificationProvider(Guid.NewGuid());
|
|
|
|
result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(404);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TestProvider
|
|
|
|
[Fact]
|
|
public async Task TestProvider_UnsupportedType_ReturnsNotFound()
|
|
{
|
|
IActionResult result = await _controller.TestProvider((NotificationProviderType)999, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}));
|
|
|
|
result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(404);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestProvider_SendFailure_ThrowsTheTranslatedException()
|
|
{
|
|
INotificationProvider failingProvider = Substitute.For<INotificationProvider>();
|
|
failingProvider.SendNotificationAsync(Arg.Any<NotificationContext>())
|
|
.ThrowsAsync(new InvalidOperationException("connection refused"));
|
|
_providerFactory.CreateProvider(Arg.Any<NotificationProviderDto>()).Returns(failingProvider);
|
|
|
|
NotificationTestException exception = await Should.ThrowAsync<NotificationTestException>(
|
|
() => _controller.TestProvider(NotificationProviderType.Gotify, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
})));
|
|
|
|
exception.Message.ShouldBe("Test failed: connection refused");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestProvider_WithPlaceholderFieldAndUnknownProviderId_ReturnsTheFieldSpecificMessage()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, ToJson(new TestGotifyProviderRequest
|
|
{
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "••••••••",
|
|
ProviderId = Guid.NewGuid(),
|
|
}));
|
|
|
|
Problem(result).Detail.ShouldBe("Application token cannot be a placeholder value");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAppriseCliStatus
|
|
|
|
[Fact]
|
|
public async Task GetAppriseCliStatus_WhenDetectorReturnsVersion_ReturnsAvailableAndVersion()
|
|
{
|
|
_appriseCliDetector.GetAppriseVersionAsync().Returns("1.2.3");
|
|
|
|
object value = (await _controller.GetAppriseCliStatus()).ShouldBeOfType<OkObjectResult>().Value!;
|
|
|
|
value.GetType().GetProperty("Available")!.GetValue(value).ShouldBe(true);
|
|
value.GetType().GetProperty("Version")!.GetValue(value).ShouldBe("1.2.3");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAppriseCliStatus_WhenDetectorReturnsNull_ReturnsNotAvailable()
|
|
{
|
|
_appriseCliDetector.GetAppriseVersionAsync().Returns((string?)null);
|
|
|
|
object value = (await _controller.GetAppriseCliStatus()).ShouldBeOfType<OkObjectResult>().Value!;
|
|
|
|
value.GetType().GetProperty("Available")!.GetValue(value).ShouldBe(false);
|
|
value.GetType().GetProperty("Version")!.GetValue(value).ShouldBeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Invalid request bodies
|
|
|
|
private static JsonElement NullBody() =>
|
|
JsonSerializer.Deserialize<JsonElement>("null");
|
|
|
|
private static JsonElement WrongTypedFieldBody() =>
|
|
JsonSerializer.Deserialize<JsonElement>("""{"Name": 123}""");
|
|
|
|
private static JsonElement WrongTypedProviderIdBody() =>
|
|
JsonSerializer.Deserialize<JsonElement>("""{"ProviderId": 123}""");
|
|
|
|
[Fact]
|
|
public async Task CreateProvider_NullBody_ReturnsBadRequestWithoutTouchingTheDatabase()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Gotify, NullBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
(await _dataContext.NotificationConfigs.CountAsync()).ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateProvider_WrongTypedField_ReturnsBadRequestWithoutTouchingTheDatabase()
|
|
{
|
|
IActionResult result = await _controller.CreateProvider(NotificationProviderType.Gotify, WrongTypedFieldBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
(await _dataContext.NotificationConfigs.CountAsync()).ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_NullBody_ReturnsBadRequestWithoutTouchingTheDatabase()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.UpdateProvider(NotificationProviderType.Gotify, id, NullBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
(await _dataContext.NotificationConfigs.AsNoTracking().FirstAsync(p => p.Id == id)).Name.ShouldBe("Gotify");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateProvider_WrongTypedField_ReturnsBadRequestWithoutTouchingTheDatabase()
|
|
{
|
|
Guid id = Created(await _controller.CreateProvider(NotificationProviderType.Gotify, ToJson(new CreateGotifyProviderRequest
|
|
{
|
|
Name = "Gotify",
|
|
ServerUrl = "https://gotify.example.com",
|
|
ApplicationToken = "app-token",
|
|
}))).Id;
|
|
|
|
IActionResult result = await _controller.UpdateProvider(NotificationProviderType.Gotify, id, WrongTypedFieldBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
(await _dataContext.NotificationConfigs.AsNoTracking().FirstAsync(p => p.Id == id)).Name.ShouldBe("Gotify");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestProvider_NullBody_ReturnsBadRequest()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, NullBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TestProvider_WrongTypedField_ReturnsBadRequest()
|
|
{
|
|
IActionResult result = await _controller.TestProvider(NotificationProviderType.Gotify, WrongTypedProviderIdBody());
|
|
|
|
Problem(result).Detail.ShouldBe("Invalid request body");
|
|
}
|
|
|
|
#endregion
|
|
}
|