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 { 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(); var token = authResult!.Token; _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var wsResponse = await _client.GetAsync("/workspaces"); var wsResult = await wsResponse.Content.ReadFromJsonAsync(); var workspaceId = wsResult!.Workspaces.First().Id; return (token, workspaceId); } private async Task 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())!; } [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(); 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(); 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(); 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(); // Act var response = await _client.GetAsync($"/workspaces/{workspaceId}/qrcodes/{created!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync(); 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(); // 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(); 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(); // 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(); // 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(); 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(); // 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(); // 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(); 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); } }