- Introduced `MediaImage` entity to manage images for markdown in instructions and news. - Updated `IAppDbContext` and `AppDbContext` to include `MediaImages` DbSet. - Implemented `DeleteMediaImageFilesAsync` method in `FactoryResetCommandHandler` to remove media images during factory reset. - Added new API endpoints for uploading and retrieving media images, enhancing markdown support. - Updated frontend components to utilize the new `MarkdownEditor` for image uploads in instructions and news. - Enhanced documentation to reflect the new media handling features and API specifications.
99 lines
3.6 KiB
C#
99 lines
3.6 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using PnvPanel.IntegrationTests.TestSupport;
|
|
using Xunit;
|
|
using static PnvPanel.IntegrationTests.TestSupport.AuthTestHelper;
|
|
|
|
namespace PnvPanel.IntegrationTests.Media;
|
|
|
|
[Collection(IntegrationTestCollection.Name)]
|
|
public class MediaImageFlowTests(PnvPanelWebApplicationFactory factory)
|
|
{
|
|
private sealed record MediaImageResponse(
|
|
Guid Id,
|
|
string FileName,
|
|
string ContentType,
|
|
long SizeBytes
|
|
);
|
|
|
|
/// <summary>Минимальный валидный PNG (1×1) — не важно содержимое, важен путь загрузка → отдача.</summary>
|
|
private static readonly byte[] PngBytes = Convert.FromBase64String(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
|
|
);
|
|
|
|
private static MultipartFormDataContent ImageContent(
|
|
byte[] bytes,
|
|
string contentType,
|
|
string fileName
|
|
)
|
|
{
|
|
var file = new ByteArrayContent(bytes);
|
|
file.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
|
return new MultipartFormDataContent { { file, "file", fileName } };
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AdminUploadsImage_ThenAnyoneCanFetchItWithoutAuth()
|
|
{
|
|
using var adminClient = factory.CreateClient();
|
|
adminClient.UseBearerToken(await LoginAsAdminAsync(adminClient));
|
|
|
|
var uploadResponse = await adminClient.PostAsync(
|
|
"/api/admin/media/images",
|
|
ImageContent(PngBytes, "image/png", "shot.png")
|
|
);
|
|
Assert.Equal(HttpStatusCode.OK, uploadResponse.StatusCode);
|
|
var image = await uploadResponse.ReadAsAsync<MediaImageResponse>();
|
|
Assert.NotNull(image);
|
|
Assert.Equal("shot.png", image!.FileName);
|
|
|
|
// Анонимный клиент: markdown-картинку грузит обычный <img> без Authorization.
|
|
using var anonymousClient = factory.CreateClient();
|
|
var getResponse = await anonymousClient.GetAsync($"/api/media/images/{image.Id}");
|
|
|
|
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
|
|
Assert.Equal("image/png", getResponse.Content.Headers.ContentType?.MediaType);
|
|
Assert.Equal(PngBytes, await getResponse.Content.ReadAsByteArrayAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_WhenNotAdmin_ReturnsForbidden()
|
|
{
|
|
using var userClient = factory.CreateClient();
|
|
var userName = $"mia_{Guid.NewGuid():N}"[..20];
|
|
var (_, token) = await RegisterAndLoginAsync(userClient, userName, "P@ssw0rd123");
|
|
userClient.UseBearerToken(token);
|
|
|
|
var response = await userClient.PostAsync(
|
|
"/api/admin/media/images",
|
|
ImageContent(PngBytes, "image/png", "shot.png")
|
|
);
|
|
|
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_WithUnsupportedType_ReturnsBadRequest()
|
|
{
|
|
using var adminClient = factory.CreateClient();
|
|
adminClient.UseBearerToken(await LoginAsAdminAsync(adminClient));
|
|
|
|
var response = await adminClient.PostAsync(
|
|
"/api/admin/media/images",
|
|
ImageContent("<svg/>"u8.ToArray(), "image/svg+xml", "x.svg")
|
|
);
|
|
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetImage_WhenUnknownId_ReturnsNotFound()
|
|
{
|
|
using var anonymousClient = factory.CreateClient();
|
|
|
|
var response = await anonymousClient.GetAsync($"/api/media/images/{Guid.NewGuid()}");
|
|
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
}
|