using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using api.Features.Auth.Common; using api.Features.Workspaces.Common; using FluentAssertions; namespace Api.Tests; public class WorkspaceEndpointTests(ApiWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); private async Task<(string Token, Guid WorkspaceId)> GetAuthAndWorkspaceAsync(string email, bool upgradeToPro = false) { 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 result = await response.Content.ReadFromJsonAsync(); var token = result!.Token; _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var workspacesResponse = await _client.GetAsync("/workspaces"); var workspaces = await workspacesResponse.Content.ReadFromJsonAsync(); var workspaceId = workspaces!.Workspaces.First().Id; if (upgradeToPro) { await factory.UpgradeWorkspaceToPro(workspaceId); } return (token, workspaceId); } private async Task GetAuthTokenAsync(string email = "workspace-test@example.com") { var (token, _) = await GetAuthAndWorkspaceAsync(email); return token; } [Fact] public async Task ListWorkspaces_WithValidToken_ReturnsWorkspaces() { // Arrange var token = await GetAuthTokenAsync("list-ws@example.com"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.GetAsync("/workspaces"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync(); result.Should().NotBeNull(); result!.Workspaces.Should().HaveCountGreaterThanOrEqualTo(1); // Default workspace created on registration } [Fact] public async Task ListWorkspaces_WithoutToken_ReturnsUnauthorized() { // Act var response = await _client.GetAsync("/workspaces"); // Assert response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Fact] public async Task CreateWorkspace_WithValidData_ReturnsCreated() { // Arrange - upgrade to Pro to allow creating additional workspaces var (token, _) = await GetAuthAndWorkspaceAsync("create-ws@example.com", upgradeToPro: true); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.PostAsJsonAsync("/workspaces", new { Name = "Test Workspace" }); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var result = await response.Content.ReadFromJsonAsync(); result.Should().NotBeNull(); result!.Name.Should().Be("Test Workspace"); result.Plan.Should().Be("Free"); result.Id.Should().NotBeEmpty(); } [Fact] public async Task CreateWorkspace_WithEmptyName_ReturnsBadRequest() { // Arrange var token = await GetAuthTokenAsync("create-ws-invalid@example.com"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.PostAsJsonAsync("/workspaces", new { Name = "" }); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task GetWorkspace_WithValidId_ReturnsWorkspace() { // Arrange - use the default workspace (created on registration) var (token, workspaceId) = await GetAuthAndWorkspaceAsync("get-ws@example.com"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.GetAsync($"/workspaces/{workspaceId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync(); result!.Id.Should().Be(workspaceId); result.Name.Should().NotBeNullOrEmpty(); } [Fact] public async Task GetWorkspace_WithInvalidId_ReturnsNotFound() { // Arrange var token = await GetAuthTokenAsync("get-ws-invalid@example.com"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.GetAsync($"/workspaces/{Guid.NewGuid()}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task UpdateWorkspace_WithValidData_ReturnsUpdated() { // Arrange - use the default workspace (created on registration) var (token, workspaceId) = await GetAuthAndWorkspaceAsync("update-ws@example.com"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.PutAsJsonAsync($"/workspaces/{workspaceId}", new { Name = "Updated Name" }); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync(); result!.Name.Should().Be("Updated Name"); } [Fact] public async Task DeleteWorkspace_WithValidId_ReturnsSuccess() { // Arrange - upgrade to Pro to allow creating additional workspaces var (token, _) = await GetAuthAndWorkspaceAsync("delete-ws@example.com", upgradeToPro: true); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var createResponse = await _client.PostAsJsonAsync("/workspaces", new { Name = "To Delete" }); var created = await createResponse.Content.ReadFromJsonAsync(); // Act var response = await _client.DeleteAsync($"/workspaces/{created!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); // Verify it's deleted var getResponse = await _client.GetAsync($"/workspaces/{created.Id}"); getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task Workspace_CannotAccessOtherUsersWorkspace() { // Arrange - Create two users var token1 = await GetAuthTokenAsync("user1-ws@example.com"); var token2 = await GetAuthTokenAsync("user2-ws@example.com"); // Create workspace as user1 _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token1); var createResponse = await _client.PostAsJsonAsync("/workspaces", new { Name = "User1 Workspace" }); var created = await createResponse.Content.ReadFromJsonAsync(); // Try to access as user2 _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token2); // Act var getResponse = await _client.GetAsync($"/workspaces/{created!.Id}"); var updateResponse = await _client.PutAsJsonAsync($"/workspaces/{created.Id}", new { Name = "Hacked" }); var deleteResponse = await _client.DeleteAsync($"/workspaces/{created.Id}"); // Assert - All should return NotFound (not exposing existence) getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); updateResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); deleteResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); } }