feat(workspace): adds workspace and projects

This commit is contained in:
2026-01-28 12:58:28 -05:00
parent 11d6390884
commit c23156c6b4
15 changed files with 928 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
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<ApiWebApplicationFactory>
{
private readonly HttpClient _client = factory.CreateClient();
private async Task<string> GetAuthTokenAsync(string email = "workspace-test@example.com")
{
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<AuthResponse>();
return result!.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<WorkspaceListResponse>();
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
var token = await GetAuthTokenAsync("create-ws@example.com");
_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<WorkspaceResponse>();
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
var token = await GetAuthTokenAsync("get-ws@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var createResponse = await _client.PostAsJsonAsync("/workspaces", new { Name = "Get Test" });
var created = await createResponse.Content.ReadFromJsonAsync<WorkspaceResponse>();
// Act
var response = await _client.GetAsync($"/workspaces/{created!.Id}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<WorkspaceResponse>();
result!.Id.Should().Be(created.Id);
result.Name.Should().Be("Get Test");
}
[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
var token = await GetAuthTokenAsync("update-ws@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var createResponse = await _client.PostAsJsonAsync("/workspaces", new { Name = "Original Name" });
var created = await createResponse.Content.ReadFromJsonAsync<WorkspaceResponse>();
// Act
var response = await _client.PutAsJsonAsync($"/workspaces/{created!.Id}", new { Name = "Updated Name" });
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<WorkspaceResponse>();
result!.Name.Should().Be("Updated Name");
}
[Fact]
public async Task DeleteWorkspace_WithValidId_ReturnsSuccess()
{
// Arrange
var token = await GetAuthTokenAsync("delete-ws@example.com");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var createResponse = await _client.PostAsJsonAsync("/workspaces", new { Name = "To Delete" });
var created = await createResponse.Content.ReadFromJsonAsync<WorkspaceResponse>();
// 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<WorkspaceResponse>();
// 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);
}
}