Implement NotifyOnStatusChange feature for nodes and enhance Telegram notifications
CI / Backend (build + test) (push) Successful in 1m24s
CI / Frontend (lint + typecheck + build) (push) Successful in 34s

- Added `NotifyOnStatusChange` property to the `Node` class and related DTOs to allow individual node configuration for status change notifications.
- Updated `NodeHealthCheckService` to send Telegram notifications to admins when a node's status changes, based on the new property.
- Enhanced the `ITelegramNotifier` interface with a method for notifying admins about node status changes.
- Modified the frontend to include a checkbox for `NotifyOnStatusChange` in the node editing dialog, allowing admins to easily configure this setting.
- Updated API documentation to reflect the new `notifyOnStatusChange` parameter in the node update endpoint.
- Added tests to ensure the correct behavior of the new feature and its integration with existing functionality.
This commit is contained in:
Leonid Pershin
2026-07-23 11:04:38 +03:00
parent 2f6bb26e97
commit fb97093bd6
20 changed files with 1272 additions and 6 deletions
@@ -38,6 +38,7 @@ public class UpdateNodeCommandHandlerTests
"https://node1-new.example.com",
null,
true,
false,
null,
null
);
@@ -66,6 +67,7 @@ public class UpdateNodeCommandHandlerTests
"https://node1.example.com",
"eu",
true,
false,
null,
null
);
@@ -88,7 +90,7 @@ public class UpdateNodeCommandHandlerTests
var handler = new UpdateNodeCommandHandler(dbContext, _gateway, _secretProtector, _currentUser);
var command = new UpdateNodeCommand(node.Id, "node-1", "not-a-uri", null, true, null, null);
var command = new UpdateNodeCommand(node.Id, "node-1", "not-a-uri", null, true, false, null, null);
var result = await handler.Handle(command, CancellationToken.None);
@@ -117,6 +119,7 @@ public class UpdateNodeCommandHandlerTests
"http://node1-new.example.com",
null,
true,
false,
null,
null
);
@@ -147,6 +150,7 @@ public class UpdateNodeCommandHandlerTests
"https://node1.example.com",
null,
true,
false,
"new-admin",
"new-password"
);
@@ -175,6 +179,7 @@ public class UpdateNodeCommandHandlerTests
"https://node1.example.com",
null,
true,
false,
"new-admin",
null
);
@@ -187,6 +192,34 @@ public class UpdateNodeCommandHandlerTests
_gateway.DidNotReceive().InvalidateClient(Arg.Any<Guid>());
}
[Fact]
public async Task Handle_SetsNotifyOnStatusChange()
{
using var dbContext = InMemoryDbContextFactory.Create();
var node = SeedNode();
dbContext.Nodes.Add(node);
await dbContext.SaveChangesAsync(CancellationToken.None);
Assert.False(node.NotifyOnStatusChange);
var handler = new UpdateNodeCommandHandler(dbContext, _gateway, _secretProtector, _currentUser);
var command = new UpdateNodeCommand(
node.Id,
"node-1",
"https://node1.example.com",
null,
true,
true,
null,
null
);
var result = await handler.Handle(command, CancellationToken.None);
Assert.True(result.IsSuccess);
Assert.True(node.NotifyOnStatusChange);
}
[Fact]
public async Task Handle_WhenNodeNotFound_ReturnsNotFound()
{
@@ -200,6 +233,7 @@ public class UpdateNodeCommandHandlerTests
"https://node1.example.com",
null,
true,
false,
null,
null
);
@@ -97,6 +97,19 @@ public class NodeTests
Assert.True(node.IsEnabled);
}
[Fact]
public void SetNotifyOnStatusChange_DefaultsFalse_AndCanBeToggled()
{
var node = Node.Register("Node", new Uri("https://example.com"), Credentials, null);
Assert.False(node.NotifyOnStatusChange);
node.SetNotifyOnStatusChange(true);
Assert.True(node.NotifyOnStatusChange);
node.SetNotifyOnStatusChange(false);
Assert.False(node.NotifyOnStatusChange);
}
[Fact]
public void UpdateStatus_SetsStatus()
{