Files
trakqr/src/TrackApi/TrackQrApi.Tests/QrCodeEndpointTests.cs

305 lines
13 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using FluentAssertions;
using TrackQrApi.Features.Auth.Common;
using TrackQrApi.Features.Links.Common;
using TrackQrApi.Features.QRCodes.Common;
using TrackQrApi.Features.Workspaces.Common;
namespace TrackQrApi.Tests;
public class QrCodeEndpointTests(
ApiWebApplicationFactory factory)
: IClassFixture<ApiWebApplicationFactory>
{
private readonly HttpClient _client = factory.CreateClient();
private async Task<(string Token, Guid WorkspaceId)> SetupAuthAndWorkspaceAsync(string email)
{
var response = await _client.PostAsJsonAsync("/auth/register", new { Email = email, Password = "password123" });
if (response.StatusCode == HttpStatusCode.Conflict)
response = await _client.PostAsJsonAsync("/auth/login", new { Email = email, Password = "password123" });
var authResult = await response.Content.ReadFromJsonAsync<AuthResponse>();
var token = authResult!.Token;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var wsResponse = await _client.GetAsync("/workspaces");
var wsResult = await wsResponse.Content.ReadFromJsonAsync<WorkspaceListResponse>();
var workspaceId = wsResult!.Workspaces.First().Id;
return (token, workspaceId);
}
private async Task<LinkResponse> CreateLinkAsync(Guid workspaceId, string slug)
{
var createResponse = await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/links", new
{
DestinationUrl = "https://example.com",
Slug = slug
});
return (await createResponse.Content.ReadFromJsonAsync<LinkResponse>())!;
}
[Fact]
public async Task CreateQRCode_WithValidData_ReturnsCreated()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-create@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-create-link");
// Act
var response = await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new
{
ShortLinkId = link.Id
});
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var result = await response.Content.ReadFromJsonAsync<QRCodeResponse>();
result.Should().NotBeNull();
result!.ShortLinkId.Should().Be(link.Id);
result.ShortLinkSlug.Should().Be(link.Slug);
result.Style.Should().NotBeNull();
result.Style.ForegroundColor.Should().Be("#000000");
result.Style.BackgroundColor.Should().Be("#FFFFFF");
}
[Fact]
public async Task CreateQRCode_WithCustomStyle_ReturnsCreatedWithStyle()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-style@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-style-link");
// Act
var response = await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new
{
ShortLinkId = link.Id,
Style = new
{
ForegroundColor = "#FF0000",
BackgroundColor = "#00FF00",
ErrorCorrectionLevel = "H"
}
});
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var result = await response.Content.ReadFromJsonAsync<QRCodeResponse>();
result!.Style.ForegroundColor.Should().Be("#FF0000");
result.Style.BackgroundColor.Should().Be("#00FF00");
result.Style.ErrorCorrectionLevel.Should().Be("H");
}
[Fact]
public async Task CreateQRCode_WithoutShortLink_ReturnsBadRequest()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-nolink@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// Act
var response = await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { });
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task ListQRCodes_ReturnsQRCodes()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-list@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-list-link");
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<QRCodeListResponse>();
result!.QRCodes.Should().HaveCountGreaterThanOrEqualTo(1);
}
[Fact]
public async Task GetQRCode_WithValidId_ReturnsQRCode()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-get@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-get-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<QRCodeResponse>();
result!.Id.Should().Be(created.Id);
}
[Fact]
public async Task GetQRCode_WithInvalidId_ReturnsNotFound()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-get-invalid@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{Guid.NewGuid()}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task UpdateQRCode_WithValidData_ReturnsUpdated()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-update@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-update-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.PutAsJsonAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}", new
{
Style = new
{
ForegroundColor = "#0000FF",
BackgroundColor = "#FFFF00"
}
});
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<QRCodeResponse>();
result!.Style.ForegroundColor.Should().Be("#0000FF");
result.Style.BackgroundColor.Should().Be("#FFFF00");
}
[Fact]
public async Task DeleteQRCode_WithValidId_ReturnsSuccess()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-delete@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-delete-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.DeleteAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
// Verify it's deleted
var getResponse = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created.Id}");
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task PreviewQRCode_ReturnsDataUrl()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-preview@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-preview-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}/preview");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<QRCodePreviewResponse>();
result!.DataUrl.Should().StartWith("data:image/png;base64,");
result.Format.Should().Be("png");
}
[Fact]
public async Task ExportQRCode_AsPng_ReturnsPngImage()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-export-png@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-export-png-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}/export?format=png");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType!.MediaType.Should().Be("image/png");
}
[Fact]
public async Task ExportQRCode_AsSvg_ReturnsSvgImage()
{
// Arrange
var (token, workspaceId) = await SetupAuthAndWorkspaceAsync("qr-export-svg@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var link = await CreateLinkAsync(workspaceId, "qr-export-svg-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
// Act
var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}/export?format=svg");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType!.MediaType.Should().Be("image/svg+xml");
}
[Fact]
public async Task QRCode_CannotAccessOtherUsersQRCode()
{
// Arrange - Create two users
var (token1, workspaceId1) = await SetupAuthAndWorkspaceAsync("qr-user1@example.com");
var link = await CreateLinkAsync(workspaceId1, "qr-user1-link");
var createResponse =
await _client.PostAsJsonAsync($"/workspaces/{workspaceId1}/qrcodes", new { ShortLinkId = link.Id });
var created = await createResponse.Content.ReadFromJsonAsync<QRCodeResponse>();
var (token2, _) = await SetupAuthAndWorkspaceAsync("qr-user2@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token2);
// Act
var getResponse = await _client.GetAsync($"/workspaces/{workspaceId1}/qrcodes/{created!.Id}");
var updateResponse = await _client.PutAsJsonAsync($"/workspaces/{workspaceId1}/qrcodes/{created.Id}", new { });
var deleteResponse = await _client.DeleteAsync($"/workspaces/{workspaceId1}/qrcodes/{created.Id}");
// Assert - All should return NotFound
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
updateResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
deleteResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
}