305 lines
13 KiB
C#
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);
|
|
}
|
|
} |