Add complete integration test suite
Backend Integration Tests: - Create IntegrationTestBase class with WebApplicationFactory - AuthIntegrationTests: Registration, login, validation tests (5 tests) - EventsIntegrationTests: CRUD operations, authorization tests (8 tests) Frontend-Backend Integration (E2E): - Install Playwright with Chromium - Create playwright.config.ts with configuration - Auth E2E tests: login/register page visibility, navigation - Event List E2E tests: page display - Navigation E2E tests: main page navigation flow Total Integration Tests: - Backend: 13 tests covering Auth and Events - E2E: 6 tests covering UI flows
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using FluentAssertions;
|
||||
using RacePlannerApi.DTOs;
|
||||
using Xunit;
|
||||
|
||||
namespace backend.Tests.Integration;
|
||||
|
||||
public class AuthIntegrationTests : IntegrationTestBase
|
||||
{
|
||||
public AuthIntegrationTests(WebApplicationFactory<Program> factory) : base(factory) { }
|
||||
|
||||
[Fact]
|
||||
public async Task Register_WithValidData_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
Email = "test@example.com",
|
||||
Password = "SecurePass123!",
|
||||
Name = "Test User",
|
||||
Role = UserRole.Participant
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Token.Should().NotBeNullOrEmpty();
|
||||
result.User.Email.Should().Be(request.Email);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Register_WithDuplicateEmail_ReturnsConflict()
|
||||
{
|
||||
// Arrange
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
Email = "duplicate@example.com",
|
||||
Password = "SecurePass123!",
|
||||
Name = "Test User",
|
||||
Role = UserRole.Participant
|
||||
};
|
||||
|
||||
// Register first user
|
||||
await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||
|
||||
// Act - Try to register again with same email
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithValidCredentials_ReturnsToken()
|
||||
{
|
||||
// Arrange
|
||||
var registerRequest = new RegisterRequest
|
||||
{
|
||||
Email = "login@example.com",
|
||||
Password = "SecurePass123!",
|
||||
Name = "Test User",
|
||||
Role = UserRole.Participant
|
||||
};
|
||||
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||
|
||||
var loginRequest = new LoginRequest
|
||||
{
|
||||
Email = "login@example.com",
|
||||
Password = "SecurePass123!"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Token.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithInvalidCredentials_ReturnsUnauthorized()
|
||||
{
|
||||
// Arrange
|
||||
var loginRequest = new LoginRequest
|
||||
{
|
||||
Email = "nonexistent@example.com",
|
||||
Password = "WrongPassword123!"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithIncorrectPassword_ReturnsUnauthorized()
|
||||
{
|
||||
// Arrange
|
||||
var registerRequest = new RegisterRequest
|
||||
{
|
||||
Email = "wrongpass@example.com",
|
||||
Password = "CorrectPass123!",
|
||||
Name = "Test User",
|
||||
Role = UserRole.Participant
|
||||
};
|
||||
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||
|
||||
var loginRequest = new LoginRequest
|
||||
{
|
||||
Email = "wrongpass@example.com",
|
||||
Password = "WrongPass123!"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using FluentAssertions;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace backend.Tests.Integration;
|
||||
|
||||
public class EventsIntegrationTests : IntegrationTestBase
|
||||
{
|
||||
public EventsIntegrationTests(WebApplicationFactory<Program> factory) : base(factory) { }
|
||||
|
||||
private async Task<string> GetOrganizerTokenAsync()
|
||||
{
|
||||
// Register and login as organizer
|
||||
var registerRequest = new RegisterRequest
|
||||
{
|
||||
Email = "organizer@test.com",
|
||||
Password = "SecurePass123!",
|
||||
Name = "Test Organizer",
|
||||
Role = UserRole.Organizer
|
||||
};
|
||||
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||
|
||||
var loginRequest = new LoginRequest
|
||||
{
|
||||
Email = "organizer@test.com",
|
||||
Password = "SecurePass123!"
|
||||
};
|
||||
var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
var authResult = await loginResponse.Content.ReadFromJsonAsync<AuthResponse>();
|
||||
return authResult!.Token;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEvents_ReturnsPublishedEvents()
|
||||
{
|
||||
// Act
|
||||
var response = await _client.GetAsync("/api/events");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var events = await response.Content.ReadFromJsonAsync<List<EventDto>>();
|
||||
events.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEvent_WithValidData_ReturnsCreated()
|
||||
{
|
||||
// Arrange
|
||||
var token = await GetOrganizerTokenAsync();
|
||||
var client = CreateAuthenticatedClient(token);
|
||||
|
||||
var request = new CreateEventRequest
|
||||
{
|
||||
Name = "Test Marathon",
|
||||
Description = "A test marathon event",
|
||||
EventDate = DateTime.UtcNow.AddDays(30),
|
||||
Location = "Test City",
|
||||
Category = "Running",
|
||||
Tags = new List<string>(),
|
||||
MaxParticipants = 100
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsJsonAsync("/api/events", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
var result = await response.Content.ReadFromJsonAsync<EventDto>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Name.Should().Be(request.Name);
|
||||
result.Status.Should().Be("Draft");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEvent_WithoutAuth_ReturnsUnauthorized()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateEventRequest
|
||||
{
|
||||
Name = "Test Event",
|
||||
EventDate = DateTime.UtcNow.AddDays(30),
|
||||
Location = "Test City"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/events", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEvent_WithValidId_ReturnsEvent()
|
||||
{
|
||||
// Arrange - Create an event first
|
||||
var token = await GetOrganizerTokenAsync();
|
||||
var client = CreateAuthenticatedClient(token);
|
||||
|
||||
var createRequest = new CreateEventRequest
|
||||
{
|
||||
Name = "Test Event",
|
||||
Description = "Test description",
|
||||
EventDate = DateTime.UtcNow.AddDays(30),
|
||||
Location = "Test City",
|
||||
Category = "Running",
|
||||
Tags = new List<string>(),
|
||||
MaxParticipants = 50
|
||||
};
|
||||
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||
|
||||
// Publish the event so it's visible
|
||||
var updateRequest = new UpdateEventRequest
|
||||
{
|
||||
Status = EventStatus.Published
|
||||
};
|
||||
await client.PutAsJsonAsync($"/api/events/{createdEvent!.Id}", updateRequest);
|
||||
|
||||
// Act - Get the event as anonymous user
|
||||
var getResponse = await _client.GetAsync($"/api/events/{createdEvent.Id}");
|
||||
|
||||
// Assert
|
||||
getResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var result = await getResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Id.Should().Be(createdEvent.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEvent_WithInvalidId_ReturnsNotFound()
|
||||
{
|
||||
// Act
|
||||
var response = await _client.GetAsync($"/api/events/{Guid.NewGuid()}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateEvent_WithValidData_ReturnsUpdatedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var token = await GetOrganizerTokenAsync();
|
||||
var client = CreateAuthenticatedClient(token);
|
||||
|
||||
var createRequest = new CreateEventRequest
|
||||
{
|
||||
Name = "Original Name",
|
||||
EventDate = DateTime.UtcNow.AddDays(30),
|
||||
Location = "Original Location",
|
||||
Category = "Running",
|
||||
Tags = new List<string>(),
|
||||
MaxParticipants = 50
|
||||
};
|
||||
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||
|
||||
var updateRequest = new UpdateEventRequest
|
||||
{
|
||||
Name = "Updated Name",
|
||||
Description = "Updated description"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await client.PutAsJsonAsync($"/api/events/{createdEvent!.Id}", updateRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var result = await response.Content.ReadFromJsonAsync<EventDto>();
|
||||
result.Should().NotBeNull();
|
||||
result!.Name.Should().Be("Updated Name");
|
||||
result.Description.Should().Be("Updated description");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteEvent_AsOrganizer_ReturnsNoContent()
|
||||
{
|
||||
// Arrange
|
||||
var token = await GetOrganizerTokenAsync();
|
||||
var client = CreateAuthenticatedClient(token);
|
||||
|
||||
var createRequest = new CreateEventRequest
|
||||
{
|
||||
Name = "Event to Delete",
|
||||
EventDate = DateTime.UtcNow.AddDays(30),
|
||||
Location = "Test City",
|
||||
Category = "Running",
|
||||
Tags = new List<string>(),
|
||||
MaxParticipants = 50
|
||||
};
|
||||
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||
|
||||
// Act
|
||||
var response = await client.DeleteAsync($"/api/events/{createdEvent!.Id}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
||||
|
||||
// Verify event is deleted
|
||||
var getResponse = await client.GetAsync($"/api/events/{createdEvent.Id}");
|
||||
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("backend.Tests.Integration")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d3ec22aa99708887598cc6e0dad1ae530bc4505c")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3421818d414ad1a8389553b9c193a05c84cecc96")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("backend.Tests.Integration")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("backend.Tests.Integration")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3a476acf2ab48eec36c3e512b88b42a88f23e6ed262fcfd761c69c1963f0df44
|
||||
a5a6240bf357ac4768ce3a5586d3e9f518946078b7126d051306396143212e02
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Authentication E2E Tests', () => {
|
||||
test('should display login page', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Check that login form is visible
|
||||
await expect(page.getByRole('heading', { name: /login/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/email/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/password/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /login/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display register page', async ({ page }) => {
|
||||
await page.goto('/register');
|
||||
|
||||
// Check that register form is visible
|
||||
await expect(page.getByRole('heading', { name: /register/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/full name/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/email/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/password/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /register/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate from login to register', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Click on register link
|
||||
await page.getByRole('link', { name: /register/i }).click();
|
||||
|
||||
// Should be on register page
|
||||
await expect(page).toHaveURL(/.*register/);
|
||||
await expect(page.getByRole('heading', { name: /register/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Event List E2E Tests', () => {
|
||||
test('should display events page', async ({ page }) => {
|
||||
await page.goto('/events');
|
||||
|
||||
// Check that events list is visible
|
||||
await expect(page.getByRole('heading', { name: /events/i })).toBeVisible();
|
||||
|
||||
// Check for filters
|
||||
await expect(page.getByRole('combobox').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Navigation E2E Tests', () => {
|
||||
test('should navigate through main pages', async ({ page }) => {
|
||||
// Start at home
|
||||
await page.goto('/');
|
||||
|
||||
// Navigate to events
|
||||
await page.getByRole('link', { name: /events/i }).first().click();
|
||||
await expect(page).toHaveURL(/.*events/);
|
||||
|
||||
// Navigate to login
|
||||
await page.getByRole('link', { name: /login/i }).click();
|
||||
await expect(page).toHaveURL(/.*login/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: '.',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: 'list',
|
||||
use: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'cd ../../frontend && npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user