# ColaFlow Testing Guide

This document explains the testing strategy, setup, and best practices for ColaFlow project.

## Table of Contents

- [Testing Philosophy](#testing-philosophy)
- [Test Structure](#test-structure)
- [Getting Started](#getting-started)
- [Running Tests](#running-tests)
- [Writing Tests](#writing-tests)
- [Test Coverage](#test-coverage)
- [CI/CD Integration](#cicd-integration)
- [Best Practices](#best-practices)

## Testing Philosophy

ColaFlow follows the **Test Pyramid** approach:

```
        /\
       /  \      E2E Tests (5%)
      /    \     - Critical user flows
     /------\
    /        \   Integration Tests (15%)
   /          \  - API endpoints
  /            \ - Database operations
 /--------------\
/                \  Unit Tests (80%)
------------------  - Domain logic
                    - Application services
                    - Business rules
```

### Quality Standards

- **Minimum Code Coverage**: 80%
- **Target Code Coverage**: 90%+
- **Critical Path Coverage**: 100%
- **All tests must**:
  - Be repeatable and deterministic
  - Run independently (no order dependency)
  - Clean up after themselves
  - Have clear assertions and error messages

## Test Structure

```
tests/
├── ColaFlow.Domain.Tests/           # Domain unit tests
│   ├── Aggregates/
│   │   ├── ProjectTests.cs
│   │   ├── EpicTests.cs
│   │   └── TaskTests.cs
│   ├── ValueObjects/
│   │   ├── ProjectIdTests.cs
│   │   └── TaskPriorityTests.cs
│   └── DomainEvents/
│       └── EventHandlerTests.cs
│
├── ColaFlow.Application.Tests/      # Application layer tests
│   ├── Commands/
│   │   ├── CreateProjectCommandTests.cs
│   │   └── UpdateProjectCommandTests.cs
│   ├── Queries/
│   │   ├── GetProjectByIdQueryTests.cs
│   │   └── GetKanbanBoardQueryTests.cs
│   └── Behaviors/
│       ├── ValidationBehaviorTests.cs
│       └── TransactionBehaviorTests.cs
│
├── ColaFlow.IntegrationTests/       # Integration tests
│   ├── API/
│   │   ├── ProjectsApiTests.cs
│   │   ├── TasksApiTests.cs
│   │   └── WorkflowsApiTests.cs
│   ├── Infrastructure/
│   │   ├── IntegrationTestBase.cs
│   │   └── WebApplicationFactoryBase.cs
│   └── Database/
│       ├── RepositoryTests.cs
│       └── MigrationTests.cs
│
├── ExampleDomainTest.cs             # Template domain test
├── ExampleIntegrationTest.cs        # Template integration test
├── IntegrationTestBase.cs           # Base class for integration tests
├── WebApplicationFactoryBase.cs     # WebApplicationFactory setup
└── README.md                        # This file
```

## Getting Started

### Prerequisites

- **.NET 9 SDK** (includes testing tools)
- **Docker Desktop** (for Testcontainers)
- **IDE**: Visual Studio 2022, JetBrains Rider, or VS Code

### Initial Setup

1. **Ensure Docker Desktop is running**:
   ```bash
   docker --version
   docker ps
   ```

2. **Restore NuGet packages** (if not already done):
   ```bash
   cd tests
   dotnet restore
   ```

3. **Verify test projects build**:
   ```bash
   dotnet build
   ```

### Creating Test Projects

If test projects don't exist yet, use the provided templates:

```bash
# Domain Tests
cd tests
dotnet new xunit -n ColaFlow.Domain.Tests
cp ColaFlow.Domain.Tests.csproj.template ColaFlow.Domain.Tests/ColaFlow.Domain.Tests.csproj

# Application Tests
dotnet new xunit -n ColaFlow.Application.Tests
cp ColaFlow.Application.Tests.csproj.template ColaFlow.Application.Tests/ColaFlow.Application.Tests.csproj

# Integration Tests
dotnet new xunit -n ColaFlow.IntegrationTests
cp ColaFlow.IntegrationTests.csproj.template ColaFlow.IntegrationTests/ColaFlow.IntegrationTests.csproj

# Restore packages
dotnet restore
```

## Running Tests

### Run All Tests

```bash
# From repository root
dotnet test

# From tests directory
cd tests
dotnet test
```

### Run Specific Test Project

```bash
# Domain tests only
dotnet test ColaFlow.Domain.Tests/ColaFlow.Domain.Tests.csproj

# Integration tests only
dotnet test ColaFlow.IntegrationTests/ColaFlow.IntegrationTests.csproj
```

### Run Specific Test Class

```bash
dotnet test --filter FullyQualifiedName~ProjectTests
```

### Run Specific Test Method

```bash
dotnet test --filter FullyQualifiedName~ProjectTests.Create_ValidData_ShouldCreateProject
```

### Run Tests by Category

```bash
# Run only unit tests
dotnet test --filter Category=Unit

# Run only integration tests
dotnet test --filter Category=Integration

# Exclude slow tests
dotnet test --filter Category!=Slow
```

### Parallel Execution

```bash
# Run tests in parallel (default)
dotnet test --parallel

# Run tests sequentially (for debugging)
dotnet test --parallel none
```

### Verbose Output

```bash
# Detailed output
dotnet test --logger "console;verbosity=detailed"

# Minimal output
dotnet test --logger "console;verbosity=minimal"
```

## Writing Tests

### Unit Tests (Domain Layer)

**Example**: Testing Project Aggregate

```csharp
using FluentAssertions;
using Xunit;

namespace ColaFlow.Domain.Tests.Aggregates;

public class ProjectTests
{
    [Fact]
    public void Create_ValidData_ShouldCreateProject()
    {
        // Arrange
        var name = "Test Project";
        var description = "Test Description";
        var key = "TEST";
        var ownerId = UserId.Create(Guid.NewGuid());

        // Act
        var project = Project.Create(name, description, key, ownerId);

        // Assert
        project.Should().NotBeNull();
        project.Name.Should().Be(name);
        project.Key.Value.Should().Be(key);
        project.Status.Should().Be(ProjectStatus.Active);
        project.DomainEvents.Should().ContainSingle(e => e is ProjectCreatedEvent);
    }

    [Theory]
    [InlineData("")]
    [InlineData(null)]
    [InlineData("   ")]
    public void Create_InvalidName_ShouldThrowException(string invalidName)
    {
        // Arrange
        var key = "TEST";
        var ownerId = UserId.Create(Guid.NewGuid());

        // Act
        Action act = () => Project.Create(invalidName, "", key, ownerId);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Project name cannot be empty");
    }
}
```

### Application Layer Tests (CQRS)

**Example**: Testing Command Handler

```csharp
using FluentAssertions;
using Moq;
using Xunit;

namespace ColaFlow.Application.Tests.Commands;

public class CreateProjectCommandHandlerTests
{
    private readonly Mock<IProjectRepository> _projectRepositoryMock;
    private readonly Mock<IUnitOfWork> _unitOfWorkMock;
    private readonly Mock<ICurrentUserService> _currentUserServiceMock;
    private readonly CreateProjectCommandHandler _handler;

    public CreateProjectCommandHandlerTests()
    {
        _projectRepositoryMock = new Mock<IProjectRepository>();
        _unitOfWorkMock = new Mock<IUnitOfWork>();
        _currentUserServiceMock = new Mock<ICurrentUserService>();
        _handler = new CreateProjectCommandHandler(
            _projectRepositoryMock.Object,
            _unitOfWorkMock.Object,
            _currentUserServiceMock.Object,
            Mock.Of<IMapper>(),
            Mock.Of<ILogger<CreateProjectCommandHandler>>()
        );
    }

    [Fact]
    public async Task Handle_ValidCommand_CreatesProject()
    {
        // Arrange
        var command = new CreateProjectCommand
        {
            Name = "Test Project",
            Description = "Description",
            Key = "TEST"
        };

        _currentUserServiceMock.Setup(x => x.UserId).Returns(Guid.NewGuid());
        _projectRepositoryMock.Setup(x => x.GetByKeyAsync(It.IsAny<string>(), default))
            .ReturnsAsync((Project?)null);

        // Act
        var result = await _handler.Handle(command, default);

        // Assert
        result.Should().NotBeNull();
        _projectRepositoryMock.Verify(x => x.AddAsync(It.IsAny<Project>(), default), Times.Once);
        _unitOfWorkMock.Verify(x => x.CommitAsync(default), Times.Once);
    }

    [Fact]
    public async Task Handle_DuplicateKey_ThrowsException()
    {
        // Arrange
        var command = new CreateProjectCommand { Name = "Test", Key = "TEST" };
        var existingProject = Project.Create("Existing", "", "TEST", UserId.Create(Guid.NewGuid()));

        _projectRepositoryMock.Setup(x => x.GetByKeyAsync("TEST", default))
            .ReturnsAsync(existingProject);

        // Act
        Func<Task> act = async () => await _handler.Handle(command, default);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*already exists*");
    }
}
```

### Integration Tests (API)

**Example**: Testing API Endpoint

```csharp
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Xunit;

namespace ColaFlow.IntegrationTests.API;

[Collection("IntegrationTests")]
public class ProjectsApiTests : IClassFixture<ColaFlowWebApplicationFactory<Program, ColaFlowDbContext>>
{
    private readonly HttpClient _client;
    private readonly ColaFlowWebApplicationFactory<Program, ColaFlowDbContext> _factory;

    public ProjectsApiTests(ColaFlowWebApplicationFactory<Program, ColaFlowDbContext> factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task CreateProject_ValidData_ReturnsCreated()
    {
        // Arrange
        var createRequest = new CreateProjectDto
        {
            Name = "Test Project",
            Description = "Test Description",
            Key = "TEST"
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/projects", createRequest);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        var project = await response.Content.ReadFromJsonAsync<ProjectDto>();
        project.Should().NotBeNull();
        project!.Name.Should().Be("Test Project");
    }

    [Fact]
    public async Task GetProject_ExistingId_ReturnsProject()
    {
        // Arrange - Seed data
        using var scope = _factory.CreateScope();
        var dbContext = _factory.GetDbContext(scope);

        var project = Project.Create("Test", "Description", "TEST", UserId.Create(Guid.NewGuid()));
        await dbContext.Projects.AddAsync(project);
        await dbContext.SaveChangesAsync();

        // Act
        var response = await _client.GetAsync($"/api/v1/projects/{project.Id.Value}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var returnedProject = await response.Content.ReadFromJsonAsync<ProjectDto>();
        returnedProject.Should().NotBeNull();
        returnedProject!.Name.Should().Be("Test");
    }
}
```

## Test Coverage

### Generate Coverage Report

```bash
# Run tests with coverage
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

# Generate HTML report (requires ReportGenerator)
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator -reports:coverage.opencover.xml -targetdir:coveragereport -reporttypes:Html

# Open report
start coveragereport/index.html  # Windows
open coveragereport/index.html   # Mac
```

### Coverage Thresholds

Configure in test project `.csproj`:

```xml
<PropertyGroup>
  <CoverletOutputFormat>opencover</CoverletOutputFormat>
  <Threshold>80</Threshold>
  <ThresholdType>line,branch,method</ThresholdType>
  <ThresholdStat>total</ThresholdStat>
</PropertyGroup>
```

### Exclude from Coverage

```csharp
[ExcludeFromCodeCoverage]
public class Startup { }
```

## CI/CD Integration

### GitHub Actions

Tests run automatically on every push and pull request. See `.github/workflows/test.yml`.

### Local CI Simulation

```bash
# Simulate CI environment
dotnet clean
dotnet restore
dotnet build --no-restore
dotnet test --no-build --verbosity normal
```

## Best Practices

### General Principles

1. **Arrange-Act-Assert (AAA) Pattern**
   ```csharp
   [Fact]
   public void TestMethod()
   {
       // Arrange - Setup test data and dependencies
       var input = "test";

       // Act - Execute the method under test
       var result = MethodUnderTest(input);

       // Assert - Verify the result
       result.Should().Be("expected");
   }
   ```

2. **One Assertion Per Test** (when practical)
   - Makes failures easier to diagnose
   - Exception: Related assertions (e.g., checking object properties)

3. **Test Naming Convention**
   ```
   MethodName_StateUnderTest_ExpectedBehavior
   ```
   Examples:
   - `Create_ValidData_ShouldCreateProject`
   - `Create_EmptyName_ShouldThrowException`
   - `GetById_NonExistentId_ReturnsNotFound`

4. **Test Independence**
   - Tests should not depend on execution order
   - Each test should clean up after itself
   - Use test fixtures for shared setup

5. **Avoid Test Logic**
   - No loops, conditionals, or complex logic in tests
   - Tests should be simple and readable

### Domain Tests

- Test business rules and invariants
- Test domain events are raised
- Test value object equality
- No mocking (pure unit tests)

### Application Tests

- Mock infrastructure dependencies (repositories, services)
- Test command/query handlers
- Test validation logic
- Test MediatR pipeline behaviors

### Integration Tests

- Use Testcontainers for real databases
- Test complete request/response flows
- Test database operations
- Test authentication/authorization
- Clean database between tests

### Performance Considerations

- Keep unit tests fast (< 100ms each)
- Integration tests can be slower (< 5s each)
- Use `[Fact(Skip = "Reason")]` for slow tests during development
- Run slow tests in CI only

### Data Builders

Use builder pattern for complex test data:

```csharp
public class ProjectBuilder
{
    private string _name = "Test Project";
    private string _key = "TEST";

    public ProjectBuilder WithName(string name)
    {
        _name = name;
        return this;
    }

    public ProjectBuilder WithKey(string key)
    {
        _key = key;
        return this;
    }

    public Project Build()
    {
        return Project.Create(_name, "Description", _key, UserId.Create(Guid.NewGuid()));
    }
}

// Usage
var project = new ProjectBuilder()
    .WithName("Custom Project")
    .WithKey("CUSTOM")
    .Build();
```

## Troubleshooting

### Docker Not Running

**Error**: `Unable to connect to Docker daemon`

**Solution**: Start Docker Desktop and ensure it's fully initialized.

### Port Conflicts

**Error**: `Address already in use`

**Solution**: Stop conflicting services or use different ports in `docker-compose.yml`.

### Test Database Not Clean

**Issue**: Tests fail due to leftover data

**Solution**: Use `CleanDatabaseAsync()` in test setup or use Testcontainers (auto-cleanup).

### Slow Tests

**Issue**: Integration tests taking too long

**Solutions**:
- Use Testcontainers' shared fixture (reuse containers)
- Optimize database queries
- Use in-memory database for simple tests
- Run integration tests selectively

### Flaky Tests

**Issue**: Tests pass/fail intermittently

**Common causes**:
- Race conditions (async/await issues)
- Time-dependent assertions
- External service dependencies
- Database transaction issues

**Solutions**:
- Use proper async/await
- Mock time-dependent code
- Use Testcontainers for isolation
- Ensure proper transaction handling

## Resources

- [xUnit Documentation](https://xunit.net/)
- [FluentAssertions Documentation](https://fluentassertions.com/)
- [Testcontainers Documentation](https://dotnet.testcontainers.org/)
- [Architecture Design](../docs/M1-Architecture-Design.md)
- [Docker Setup](../DOCKER-README.md)

## Support

For testing issues:
1. Check this README
2. Review test examples in this directory
3. Consult architecture documentation
4. Ask team for help

---

**Last Updated**: 2025-11-02
**Maintained By**: QA Team
**Quality Standard**: 80%+ Coverage, All Tests Green
