audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration

This commit is contained in:
master
2026-01-14 10:48:00 +02:00
parent d7be6ba34b
commit 95d5898650
379 changed files with 40695 additions and 19041 deletions

View File

@@ -0,0 +1,138 @@
using StellaOps.Notify.Connectors.Shared;
using Xunit;
namespace StellaOps.Notify.Connectors.Shared.Tests;
/// <summary>
/// Tests for ConnectorValueRedactor class.
/// </summary>
public sealed class ConnectorValueRedactorTests
{
[Fact]
public void RedactSecret_ReturnsConstantMask()
{
var result = ConnectorValueRedactor.RedactSecret("my-super-secret-value");
Assert.Equal("***", result);
}
[Fact]
public void RedactToken_ShortToken_ReturnsMask()
{
var result = ConnectorValueRedactor.RedactToken("short");
Assert.Equal("***", result);
}
[Fact]
public void RedactToken_LongToken_PreservePrefixAndSuffix()
{
var token = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxx1234";
var result = ConnectorValueRedactor.RedactToken(token);
Assert.StartsWith("ghp_xx", result);
Assert.EndsWith("1234", result);
Assert.Contains("***", result);
}
[Fact]
public void RedactToken_CustomLengths_Applied()
{
var token = "my-very-long-token-value-here";
var result = ConnectorValueRedactor.RedactToken(token, prefixLength: 3, suffixLength: 5);
Assert.StartsWith("my-", result);
Assert.EndsWith("-here", result);
}
[Fact]
public void RedactToken_NullValue_ReturnsMask()
{
var result = ConnectorValueRedactor.RedactToken(null!);
Assert.Equal("***", result);
}
[Fact]
public void RedactToken_EmptyValue_ReturnsMask()
{
var result = ConnectorValueRedactor.RedactToken("");
Assert.Equal("***", result);
}
[Fact]
public void RedactToken_WhitespaceValue_ReturnsMask()
{
var result = ConnectorValueRedactor.RedactToken(" ");
Assert.Equal("***", result);
}
[Theory]
[InlineData("token")]
[InlineData("auth_token")]
[InlineData("api_secret")]
[InlineData("Authorization")]
[InlineData("session_cookie")]
[InlineData("password")]
[InlineData("api_key")]
[InlineData("user_credential")]
public void IsSensitiveKey_SensitiveKeys_ReturnsTrue(string key)
{
var result = ConnectorValueRedactor.IsSensitiveKey(key);
Assert.True(result);
}
[Theory]
[InlineData("username")]
[InlineData("host")]
[InlineData("port")]
[InlineData("channel")]
[InlineData("recipient")]
public void IsSensitiveKey_NonSensitiveKeys_ReturnsFalse(string key)
{
var result = ConnectorValueRedactor.IsSensitiveKey(key);
Assert.False(result);
}
[Fact]
public void IsSensitiveKey_NullKey_ReturnsFalse()
{
var result = ConnectorValueRedactor.IsSensitiveKey(null!);
Assert.False(result);
}
[Fact]
public void IsSensitiveKey_EmptyKey_ReturnsFalse()
{
var result = ConnectorValueRedactor.IsSensitiveKey("");
Assert.False(result);
}
[Fact]
public void IsSensitiveKey_WhitespaceKey_ReturnsFalse()
{
var result = ConnectorValueRedactor.IsSensitiveKey(" ");
Assert.False(result);
}
[Fact]
public void IsSensitiveKey_CustomFragments_Used()
{
var customFragments = new[] { "custom", "special" };
Assert.True(ConnectorValueRedactor.IsSensitiveKey("my_custom_key", customFragments));
Assert.True(ConnectorValueRedactor.IsSensitiveKey("special_value", customFragments));
Assert.False(ConnectorValueRedactor.IsSensitiveKey("token", customFragments));
}
[Fact]
public void DefaultSensitiveKeyFragments_ContainsExpectedFragments()
{
var fragments = ConnectorValueRedactor.DefaultSensitiveKeyFragments;
Assert.Contains("token", fragments);
Assert.Contains("secret", fragments);
Assert.Contains("authorization", fragments);
Assert.Contains("cookie", fragments);
Assert.Contains("password", fragments);
Assert.Contains("key", fragments);
Assert.Contains("credential", fragments);
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<UseXunitV3>true</UseXunitV3>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Notify.Connectors.Shared\StellaOps.Notify.Connectors.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": -1
}

View File

@@ -0,0 +1,273 @@
using StellaOps.Notify.Storage.InMemory.Documents;
using StellaOps.Notify.Storage.InMemory.Repositories;
using Xunit;
namespace StellaOps.Notify.Storage.InMemory.Tests;
/// <summary>
/// Fake TimeProvider for deterministic testing.
/// </summary>
public sealed class FakeTimeProvider : TimeProvider
{
private DateTimeOffset _utcNow;
public FakeTimeProvider(DateTimeOffset? initialTime = null)
{
_utcNow = initialTime ?? new DateTimeOffset(2026, 1, 14, 12, 0, 0, TimeSpan.Zero);
}
public override DateTimeOffset GetUtcNow() => _utcNow;
public void Advance(TimeSpan duration) => _utcNow = _utcNow.Add(duration);
public void SetUtcNow(DateTimeOffset time) => _utcNow = time;
}
/// <summary>
/// Tests for NotifyChannelRepositoryAdapter.
/// </summary>
public sealed class NotifyChannelRepositoryAdapterTests
{
private readonly FakeTimeProvider _timeProvider = new();
[Fact]
public async Task UpsertAsync_NewChannel_SetsUpdatedAt()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var channel = new NotifyChannelDocument
{
Id = "ch-001",
TenantId = "tenant-001",
Name = "Email Channel",
ChannelType = "email",
Enabled = true
};
var result = await repo.UpsertAsync(channel);
Assert.Equal(_timeProvider.GetUtcNow(), result.UpdatedAt);
}
[Fact]
public async Task GetByIdAsync_ExistingChannel_ReturnsChannel()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var channel = new NotifyChannelDocument
{
Id = "ch-002",
TenantId = "tenant-001",
Name = "Slack Channel",
ChannelType = "slack"
};
await repo.UpsertAsync(channel);
var result = await repo.GetByIdAsync("tenant-001", "ch-002");
Assert.NotNull(result);
Assert.Equal("ch-002", result.Id);
Assert.Equal("Slack Channel", result.Name);
}
[Fact]
public async Task GetByIdAsync_NonExistent_ReturnsNull()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var result = await repo.GetByIdAsync("tenant-001", "non-existent");
Assert.Null(result);
}
[Fact]
public async Task GetByNameAsync_ExistingChannel_ReturnsChannel()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var channel = new NotifyChannelDocument
{
Id = "ch-003",
TenantId = "tenant-001",
Name = "Teams Notifications",
ChannelType = "teams"
};
await repo.UpsertAsync(channel);
var result = await repo.GetByNameAsync("tenant-001", "Teams Notifications");
Assert.NotNull(result);
Assert.Equal("ch-003", result.Id);
}
[Fact]
public async Task GetAllAsync_FilteredByEnabled_ReturnsOnlyEnabled()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-e1", TenantId = "t1", Name = "E1", ChannelType = "email", Enabled = true });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-e2", TenantId = "t1", Name = "E2", ChannelType = "email", Enabled = false });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-e3", TenantId = "t1", Name = "E3", ChannelType = "slack", Enabled = true });
var enabled = await repo.GetAllAsync("t1", enabled: true);
var disabled = await repo.GetAllAsync("t1", enabled: false);
Assert.Equal(2, enabled.Count);
Assert.Single(disabled);
}
[Fact]
public async Task GetAllAsync_FilteredByChannelType_ReturnsMatchingType()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-t1", TenantId = "t1", Name = "T1", ChannelType = "email" });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-t2", TenantId = "t1", Name = "T2", ChannelType = "slack" });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-t3", TenantId = "t1", Name = "T3", ChannelType = "email" });
var result = await repo.GetAllAsync("t1", channelType: "email");
Assert.Equal(2, result.Count);
Assert.All(result, c => Assert.Equal("email", c.ChannelType));
}
[Fact]
public async Task DeleteAsync_ExistingChannel_ReturnsTrue()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-del", TenantId = "t1", Name = "Delete Me", ChannelType = "webhook" });
var deleted = await repo.DeleteAsync("t1", "ch-del");
var afterDelete = await repo.GetByIdAsync("t1", "ch-del");
Assert.True(deleted);
Assert.Null(afterDelete);
}
[Fact]
public async Task DeleteAsync_NonExistent_ReturnsFalse()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var deleted = await repo.DeleteAsync("t1", "non-existent");
Assert.False(deleted);
}
[Fact]
public async Task GetEnabledByTypeAsync_ReturnsOnlyEnabledOfType()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-ebt1", TenantId = "t1", Name = "EBT1", ChannelType = "slack", Enabled = true });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-ebt2", TenantId = "t1", Name = "EBT2", ChannelType = "slack", Enabled = false });
await repo.UpsertAsync(new NotifyChannelDocument { Id = "ch-ebt3", TenantId = "t1", Name = "EBT3", ChannelType = "email", Enabled = true });
var result = await repo.GetEnabledByTypeAsync("t1", "slack");
Assert.Single(result);
Assert.Equal("ch-ebt1", result[0].Id);
}
[Fact]
public async Task UpsertAsync_UpdateExisting_UpdatesTimestamp()
{
var repo = new NotifyChannelRepositoryAdapter(_timeProvider);
var channel = new NotifyChannelDocument { Id = "ch-upd", TenantId = "t1", Name = "Original", ChannelType = "email" };
await repo.UpsertAsync(channel);
var firstUpdate = _timeProvider.GetUtcNow();
_timeProvider.Advance(TimeSpan.FromMinutes(5));
channel.Name = "Updated";
await repo.UpsertAsync(channel);
var result = await repo.GetByIdAsync("t1", "ch-upd");
Assert.NotNull(result);
Assert.Equal("Updated", result.Name);
Assert.True(result.UpdatedAt > firstUpdate);
}
}
/// <summary>
/// Tests for NotifyChannelDocument.
/// </summary>
public sealed class NotifyChannelDocumentTests
{
[Fact]
public void NotifyChannelDocument_DefaultValues_AreSet()
{
var doc = new NotifyChannelDocument();
Assert.NotEmpty(doc.Id);
Assert.Equal(string.Empty, doc.TenantId);
Assert.Equal(string.Empty, doc.Name);
Assert.Equal(string.Empty, doc.ChannelType);
Assert.True(doc.Enabled);
Assert.Equal("{}", doc.Config);
Assert.Null(doc.Credentials);
Assert.Equal("{}", doc.Metadata);
}
}
/// <summary>
/// Tests for NotifyRuleDocument.
/// </summary>
public sealed class NotifyRuleDocumentTests
{
[Fact]
public void NotifyRuleDocument_DefaultValues_AreSet()
{
var doc = new NotifyRuleDocument();
Assert.NotEmpty(doc.Id);
Assert.Equal(string.Empty, doc.TenantId);
Assert.Equal(string.Empty, doc.Name);
Assert.True(doc.Enabled);
Assert.Equal(0, doc.Priority);
Assert.Equal("{}", doc.EventFilter);
}
}
/// <summary>
/// Tests for NotifyTemplateDocument.
/// </summary>
public sealed class NotifyTemplateDocumentTests
{
[Fact]
public void NotifyTemplateDocument_DefaultValues_AreSet()
{
var doc = new NotifyTemplateDocument();
Assert.NotEmpty(doc.Id);
Assert.Equal(string.Empty, doc.TenantId);
Assert.Equal(string.Empty, doc.Name);
Assert.Equal(string.Empty, doc.Subject);
Assert.Equal(string.Empty, doc.Body);
Assert.Equal("text", doc.Format);
}
}
/// <summary>
/// Tests for NotifyDeliveryDocument.
/// </summary>
public sealed class NotifyDeliveryDocumentTests
{
[Fact]
public void NotifyDeliveryDocument_DefaultValues_AreSet()
{
var doc = new NotifyDeliveryDocument();
Assert.NotEmpty(doc.Id);
Assert.Equal(string.Empty, doc.TenantId);
Assert.Equal("pending", doc.Status);
Assert.Equal(0, doc.RetryCount);
Assert.Equal("{}", doc.Payload);
}
[Theory]
[InlineData("pending")]
[InlineData("sending")]
[InlineData("sent")]
[InlineData("failed")]
[InlineData("retrying")]
public void NotifyDeliveryDocument_Status_SupportedValues(string status)
{
var doc = new NotifyDeliveryDocument { Status = status };
Assert.Equal(status, doc.Status);
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<UseXunitV3>true</UseXunitV3>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Notify.Storage.InMemory\StellaOps.Notify.Storage.InMemory.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": -1
}