books/backend/Books.Api.Tests/GraphQL/CompanyGraphQLTests.cs
Nicolaj Hartmann 66f6fa138d Initial commit: Books accounting system with EventFlow CQRS
Backend (.NET 10):
- EventFlow CQRS/Event Sourcing with PostgreSQL
- GraphQL.NET API with mutations and queries
- Custom ReadModelSqlGenerator for snake_case PostgreSQL columns
- Hangfire for background job processing
- Integration tests with isolated test databases

Frontend (React/Vite):
- Initial project structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 02:52:30 +01:00

260 lines
8.3 KiB
C#

using Books.Api.Domain.Companies;
using Books.Api.EventFlow.Repositories;
using Books.Api.Tests.Helpers;
using Books.Api.Tests.Infrastructure;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
namespace Books.Api.Tests.GraphQL;
/// <summary>
/// Integration tests for Company GraphQL operations.
/// Each test class runs with its own isolated database.
/// </summary>
[Trait("Category", "Integration")]
public class CompanyGraphQLTests(TestWebApplicationFactory factory)
: IntegrationTestBase(factory)
{
[Fact]
public async Task Query_Companies_ReturnsEmptyList_WhenNoCompaniesExist()
{
// Arrange
var graphqlClient = new GraphQLTestClient(Client);
// Act
var response = await graphqlClient.QueryAsync<CompaniesResponse>("""
query {
companies {
id
name
}
}
""");
// Assert
response.EnsureNoErrors();
response.Data.Should().NotBeNull();
response.Data!.Companies.Should().BeEmpty();
}
[Fact]
public async Task Mutation_CreateCompany_CreatesCompanySuccessfully()
{
// Arrange
var graphqlClient = new GraphQLTestClient(Client);
// Act
var response = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
mutation CreateCompany($input: CreateCompanyInput!) {
createCompany(input: $input) {
id
name
cvr
country
currency
fiscalYearStartMonth
}
}
""",
new
{
input = new
{
name = "Test Virksomhed A/S",
cvr = "12345678",
country = "DK",
currency = "DKK",
fiscalYearStartMonth = 1
}
});
// Assert
response.EnsureNoErrors();
response.Data.Should().NotBeNull();
response.Data!.CreateCompany.Should().NotBeNull();
response.Data.CreateCompany!.Name.Should().Be("Test Virksomhed A/S");
response.Data.CreateCompany.Cvr.Should().Be("12345678");
response.Data.CreateCompany.Country.Should().Be("DK");
response.Data.CreateCompany.Currency.Should().Be("DKK");
response.Data.CreateCompany.FiscalYearStartMonth.Should().Be(1);
}
[Fact]
public async Task Query_Company_ReturnsCompany_AfterCreation()
{
// Arrange
var graphqlClient = new GraphQLTestClient(Client);
// Create a company first
var createResponse = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
mutation CreateCompany($input: CreateCompanyInput!) {
createCompany(input: $input) {
id
name
}
}
""",
new
{
input = new
{
name = "Query Test Virksomhed",
cvr = "87654321"
}
});
createResponse.EnsureNoErrors();
var companyId = createResponse.Data!.CreateCompany!.Id;
// Act - Query the company by ID (with eventual consistency)
var company = await Eventually.GetAsync(async () =>
{
var response = await graphqlClient.QueryAsync<CompanyResponse>("""
query GetCompany($id: ID!) {
company(id: $id) {
id
name
cvr
}
}
""",
new { id = companyId });
return response.Data?.Company;
});
// Assert
company.Should().NotBeNull();
company!.Name.Should().Be("Query Test Virksomhed");
company.Cvr.Should().Be("87654321");
}
[Fact]
public async Task Query_Companies_ReturnsAllCompanies_AfterMultipleCreations()
{
// Arrange
var graphqlClient = new GraphQLTestClient(Client);
// Create multiple companies with unique names for this test
var company1Name = $"ListTest-Virksomhed-{Guid.NewGuid():N}";
var company2Name = $"ListTest-Virksomhed-{Guid.NewGuid():N}";
await graphqlClient.MutateAsync<CreateCompanyResponse>($$"""
mutation { createCompany(input: { name: "{{company1Name}}" }) { id } }
""");
await graphqlClient.MutateAsync<CreateCompanyResponse>($$"""
mutation { createCompany(input: { name: "{{company2Name}}" }) { id } }
""");
// Act - Wait for eventual consistency (at least 2 companies with our prefix)
var companies = await Eventually.GetAsync(
async () =>
{
var response = await graphqlClient.QueryAsync<CompaniesResponse>("""
query {
companies {
id
name
}
}
""");
var allCompanies = response.Data?.Companies ?? [];
var ourCompanies = allCompanies.Where(c => c.Name.StartsWith("ListTest-")).ToList();
return ourCompanies.Count >= 2 ? ourCompanies : null;
});
// Assert - verify our specific companies are present
companies.Should().Contain(c => c.Name == company1Name);
companies.Should().Contain(c => c.Name == company2Name);
}
[Fact]
public async Task Mutation_UpdateCompany_UpdatesCompanySuccessfully()
{
// Arrange
var graphqlClient = new GraphQLTestClient(Client);
// Create a company first
var createResponse = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
mutation { createCompany(input: { name: "Original Name" }) { id } }
""");
createResponse.EnsureNoErrors();
var companyId = createResponse.Data!.CreateCompany!.Id;
// Wait for the company to be created
await Eventually.GetAsync(async () =>
{
var repository = GetService<ICompanyRepository>();
return await repository.GetByIds([CompanyId.With(companyId)]);
});
// Act - Update the company
var updateResponse = await graphqlClient.MutateAsync<UpdateCompanyResponse>("""
mutation UpdateCompany($id: ID!, $input: UpdateCompanyInput!) {
updateCompany(id: $id, input: $input) {
id
name
cvr
}
}
""",
new
{
id = companyId,
input = new
{
name = "Updated Name",
cvr = "11111111"
}
});
// Assert
updateResponse.EnsureNoErrors();
// Wait for eventual consistency and verify update
var updatedCompany = await Eventually.GetAsync(async () =>
{
var repository = GetService<ICompanyRepository>();
var companies = await repository.GetByIds([CompanyId.With(companyId)]);
var company = companies.First();
return company?.Name == "Updated Name" ? company : null;
});
updatedCompany.Should().NotBeNull();
updatedCompany!.Name.Should().Be("Updated Name");
updatedCompany.Cvr.Should().Be("11111111");
}
// Response DTOs for deserialization
private class CompaniesResponse
{
public List<CompanyDto> Companies { get; set; } = [];
}
private class CompanyResponse
{
public CompanyDto? Company { get; set; }
}
private class CreateCompanyResponse
{
public CompanyDto? CreateCompany { get; set; }
}
private class UpdateCompanyResponse
{
public CompanyDto? UpdateCompany { get; set; }
}
private class CompanyDto
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? Cvr { get; set; }
public string? Country { get; set; }
public string? Currency { get; set; }
public int? FiscalYearStartMonth { get; set; }
}
}