Add instructions management functionality and update related components
- Introduced new endpoints for managing instruction intros and tabs, allowing admins to create, update, and delete instructional content. - Enhanced the FactoryResetCommandHandler to include the seeding of instruction data during a factory reset. - Updated the database schema to include InstructionIntro and InstructionTab entities, with corresponding migrations. - Improved frontend routing and components to support the new instructions section, including a dedicated page for displaying instructions and tabs. - Enhanced API documentation to reflect the new instruction management features and their expected request/response formats. - Added localization support for the new instructions functionality in both Russian and English.
This commit is contained in:
+97
@@ -0,0 +1,97 @@
|
||||
using PnvPanel.Application.Admin.Instructions;
|
||||
using PnvPanel.Application.Tests.TestSupport;
|
||||
using PnvPanel.Domain.Instructions;
|
||||
using Xunit;
|
||||
|
||||
namespace PnvPanel.Application.Tests.Admin.Instructions;
|
||||
|
||||
public class InstructionTabCommandHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Create_AddsTab()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var handler = new CreateInstructionTabCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new CreateInstructionTabCommand("Настройка роутера", "текст", 10),
|
||||
CancellationToken.None
|
||||
);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("Настройка роутера", result.Value.Title);
|
||||
Assert.Single(dbContext.InstructionTabs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_ReplacesFields()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var tab = InstructionTab.Create("Старое", "старый текст", 10);
|
||||
dbContext.InstructionTabs.Add(tab);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
var handler = new UpdateInstructionTabCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new UpdateInstructionTabCommand(tab.Id, "Новое", "новый текст", 20),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("Новое", result.Value.Title);
|
||||
Assert.Equal("новый текст", result.Value.Body);
|
||||
Assert.Equal(20, result.Value.SortOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhenTabMissing_ReturnsNotFound()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var handler = new UpdateInstructionTabCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new UpdateInstructionTabCommand(Guid.NewGuid(), "Новое", "текст", 10),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Equal(InstructionErrors.TabNotFound, result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_RemovesTab()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var tab = InstructionTab.Create("Удалить меня", "текст", 10);
|
||||
dbContext.InstructionTabs.Add(tab);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
var handler = new DeleteInstructionTabCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new DeleteInstructionTabCommand(tab.Id),
|
||||
CancellationToken.None
|
||||
);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Empty(dbContext.InstructionTabs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_WhenTabMissing_ReturnsNotFound()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var handler = new DeleteInstructionTabCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new DeleteInstructionTabCommand(Guid.NewGuid()),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Equal(InstructionErrors.TabNotFound, result.Error);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using PnvPanel.Application.Admin.Instructions;
|
||||
using PnvPanel.Application.Tests.TestSupport;
|
||||
using PnvPanel.Domain.Instructions;
|
||||
using Xunit;
|
||||
|
||||
namespace PnvPanel.Application.Tests.Admin.Instructions;
|
||||
|
||||
public class UpdateInstructionIntroCommandHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Handle_WhenNoIntroExists_CreatesIt()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var handler = new UpdateInstructionIntroCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new UpdateInstructionIntroCommand("Новый текст"),
|
||||
CancellationToken.None
|
||||
);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("Новый текст", result.Value.Body);
|
||||
Assert.Single(dbContext.InstructionIntros);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WhenIntroExists_UpdatesItInPlace()
|
||||
{
|
||||
using var dbContext = InMemoryDbContextFactory.Create();
|
||||
var intro = InstructionIntro.Create("Старый текст");
|
||||
dbContext.InstructionIntros.Add(intro);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
var handler = new UpdateInstructionIntroCommandHandler(dbContext);
|
||||
|
||||
var result = await handler.Handle(
|
||||
new UpdateInstructionIntroCommand("Обновлённый текст"),
|
||||
CancellationToken.None
|
||||
);
|
||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal(intro.Id, result.Value.Id);
|
||||
Assert.Equal("Обновлённый текст", result.Value.Body);
|
||||
// Не создаётся вторая строка — обновляется существующая (singleton).
|
||||
Assert.Single(dbContext.InstructionIntros);
|
||||
}
|
||||
}
|
||||
+4
@@ -21,6 +21,8 @@ public class FactoryResetCommandHandlerTests
|
||||
private readonly IFileStorage _fileStorage = Substitute.For<IFileStorage>();
|
||||
private readonly IClientAppCatalogSeeder _catalogSeeder =
|
||||
Substitute.For<IClientAppCatalogSeeder>();
|
||||
private readonly IInstructionIntroSeeder _instructionIntroSeeder =
|
||||
Substitute.For<IInstructionIntroSeeder>();
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WipesEverythingExceptCurrentAdminAndSystemRoles()
|
||||
@@ -101,6 +103,7 @@ public class FactoryResetCommandHandlerTests
|
||||
_gateway,
|
||||
_fileStorage,
|
||||
_catalogSeeder,
|
||||
_instructionIntroSeeder,
|
||||
currentUser
|
||||
);
|
||||
|
||||
@@ -140,5 +143,6 @@ public class FactoryResetCommandHandlerTests
|
||||
);
|
||||
await _fileStorage.Received(1).DeleteAsync("stored-name", Arg.Any<CancellationToken>());
|
||||
await _catalogSeeder.Received(1).SeedIfEmptyAsync(Arg.Any<CancellationToken>());
|
||||
await _instructionIntroSeeder.Received(1).SeedIfEmptyAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user