Skip to content

Phase 9.1: Implement Comprehensive API Integration Tests #110

@artcava

Description

@artcava

📋 Task Description

Implement comprehensive API integration tests covering all REST endpoints, authentication, authorization, validation, error handling, and API contracts. Tests should run against real infrastructure (MongoDB, Redis, RabbitMQ) using Docker containers.

🎯 Objectives

  • Create integration test infrastructure with TestContainers
  • Implement tests for all Process API endpoints
  • Implement tests for all Policy API endpoints
  • Test authentication and authorization flows
  • Test request validation and error responses
  • Test API contract compliance (OpenAPI spec)
  • Verify HTTP status codes and headers
  • Test pagination and filtering
  • Test idempotency guarantees
  • Add test data builders and fixtures
  • Configure test logging and diagnostics
  • Document integration testing approach

📦 Deliverables

1. Setup Integration Test Infrastructure

Update tests/StarGate.IntegrationTests/StarGate.IntegrationTests.csproj:

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
  <PackageReference Include="Testcontainers" Version="3.7.0" />
  <PackageReference Include="Testcontainers.MongoDb" Version="3.7.0" />
  <PackageReference Include="Testcontainers.Redis" Version="3.7.0" />
  <PackageReference Include="Testcontainers.RabbitMq" Version="3.7.0" />
  <PackageReference Include="FluentAssertions" Version="6.12.0" />
  <PackageReference Include="xunit" Version="2.6.5" />
  <PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" />
</ItemGroup>

2. Create Web Application Factory

Create tests/StarGate.IntegrationTests/Infrastructure/StarGateWebApplicationFactory.cs:

namespace StarGate.IntegrationTests.Infrastructure;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Testcontainers.MongoDb;
using Testcontainers.RabbitMq;
using Testcontainers.Redis;

/// <summary>
/// Custom web application factory for integration tests.
/// </summary>
public class StarGateWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
    private readonly MongoDbContainer _mongoContainer = new MongoDbBuilder()
        .WithImage("mongo:7.0")
        .WithPortBinding(27017, true)
        .Build();

    private readonly RedisContainer _redisContainer = new RedisBuilder()
        .WithImage("redis:7.2")
        .WithPortBinding(6379, true)
        .Build();

    private readonly RabbitMqContainer _rabbitMqContainer = new RabbitMqBuilder()
        .WithImage("rabbitmq:3.13-management")
        .WithPortBinding(5672, true)
        .WithPortBinding(15672, true)
        .Build();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((context, config) =>
        {
            // Override configuration with test container connection strings
            var testConfig = new Dictionary<string, string>
            {
                ["MongoDB:ConnectionString"] = _mongoContainer.GetConnectionString(),
                ["MongoDB:DatabaseName"] = "stargate-test",
                ["Redis:ConnectionString"] = _redisContainer.GetConnectionString(),
                ["RabbitMQ:HostName"] = _rabbitMqContainer.Hostname,
                ["RabbitMQ:Port"] = _rabbitMqContainer.GetMappedPublicPort(5672).ToString(),
                ["RabbitMQ:UserName"] = "guest",
                ["RabbitMQ:Password"] = "guest",
                // Reduce timeouts for faster tests
                ["Resilience:Retry:MaxRetryAttempts"] = "2",
                ["Resilience:CircuitBreaker:BreakDurationSeconds"] = "5",
                ["Resilience:Timeout:DatabaseTimeoutSeconds"] = "5"
            };

            config.AddInMemoryCollection(testConfig!);
        });

        builder.ConfigureServices(services =>
        {
            // Additional test service configuration if needed
        });
    }

    public async Task InitializeAsync()
    {
        await _mongoContainer.StartAsync();
        await _redisContainer.StartAsync();
        await _rabbitMqContainer.StartAsync();
    }

    public new async Task DisposeAsync()
    {
        await _mongoContainer.DisposeAsync();
        await _redisContainer.DisposeAsync();
        await _rabbitMqContainer.DisposeAsync();
        await base.DisposeAsync();
    }
}

3. Create Test Helpers and Builders

Create tests/StarGate.IntegrationTests/Helpers/HttpClientExtensions.cs:

namespace StarGate.IntegrationTests.Helpers;

using System.Net.Http.Json;
using System.Text.Json;

public static class HttpClientExtensions
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    public static async Task<T?> GetJsonAsync<T>(this HttpClient client, string requestUri)
    {
        var response = await client.GetAsync(requestUri);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<T>(JsonOptions);
    }

    public static async Task<HttpResponseMessage> PostJsonAsync<T>(
        this HttpClient client,
        string requestUri,
        T content)
    {
        return await client.PostAsJsonAsync(requestUri, content, JsonOptions);
    }

    public static async Task<TResponse?> PostAndGetJsonAsync<TRequest, TResponse>(
        this HttpClient client,
        string requestUri,
        TRequest content)
    {
        var response = await client.PostAsJsonAsync(requestUri, content, JsonOptions);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<TResponse>(JsonOptions);
    }
}

Create tests/StarGate.IntegrationTests/Builders/CreateProcessRequestBuilder.cs:

namespace StarGate.IntegrationTests.Builders;

public class CreateProcessRequestBuilder
{
    private string _clientId = "test-client";
    private string _processType = "test-process";
    private string _clientProcessId = Guid.NewGuid().ToString();
    private Dictionary<string, string> _metadata = new();

    public CreateProcessRequestBuilder WithClientId(string clientId)
    {
        _clientId = clientId;
        return this;
    }

    public CreateProcessRequestBuilder WithProcessType(string processType)
    {
        _processType = processType;
        return this;
    }

    public CreateProcessRequestBuilder WithClientProcessId(string clientProcessId)
    {
        _clientProcessId = clientProcessId;
        return this;
    }

    public CreateProcessRequestBuilder WithMetadata(string key, string value)
    {
        _metadata[key] = value;
        return this;
    }

    public CreateProcessRequestBuilder WithMetadata(Dictionary<string, string> metadata)
    {
        _metadata = metadata;
        return this;
    }

    public object Build()
    {
        return new
        {
            clientId = _clientId,
            processType = _processType,
            clientProcessId = _clientProcessId,
            metadata = _metadata
        };
    }
}

4. Create Process API Integration Tests

Create tests/StarGate.IntegrationTests/Api/ProcessEndpointsTests.cs:

namespace StarGate.IntegrationTests.Api;

using FluentAssertions;
using StarGate.IntegrationTests.Builders;
using StarGate.IntegrationTests.Helpers;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using Xunit;

public class ProcessEndpointsTests : IClassFixture<StarGateWebApplicationFactory>
{
    private readonly HttpClient _client;

    public ProcessEndpointsTests(StarGateWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task CreateProcess_Should_ReturnCreated_WithValidRequest()
    {
        // Arrange
        var request = new CreateProcessRequestBuilder()
            .WithClientId("test-client")
            .WithProcessType("order")
            .WithMetadata("orderId", "12345")
            .Build();

        // Act
        var response = await _client.PostJsonAsync("/api/processes", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        response.Headers.Location.Should().NotBeNull();
        
        var content = await response.Content.ReadAsStringAsync();
        content.Should().Contain("processId");
    }

    [Fact]
    public async Task CreateProcess_Should_ReturnBadRequest_WhenClientIdMissing()
    {
        // Arrange
        var request = new
        {
            processType = "order",
            clientProcessId = "test-123"
        };

        // Act
        var response = await _client.PostJsonAsync("/api/processes", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task CreateProcess_Should_BeIdempotent_WithSameClientProcessId()
    {
        // Arrange
        var clientProcessId = Guid.NewGuid().ToString();
        var request = new CreateProcessRequestBuilder()
            .WithClientProcessId(clientProcessId)
            .Build();

        // Act
        var response1 = await _client.PostJsonAsync("/api/processes", request);
        var response2 = await _client.PostJsonAsync("/api/processes", request);

        // Assert
        response1.StatusCode.Should().Be(HttpStatusCode.Created);
        response2.StatusCode.Should().Be(HttpStatusCode.OK); // Returns existing
        
        var location1 = response1.Headers.Location?.ToString();
        var location2 = response2.Headers.Location?.ToString();
        location1.Should().Be(location2);
    }

    [Fact]
    public async Task GetProcess_Should_ReturnProcess_WhenExists()
    {
        // Arrange
        var request = new CreateProcessRequestBuilder().Build();
        var createResponse = await _client.PostJsonAsync("/api/processes", request);
        var processId = ExtractProcessIdFromLocation(createResponse.Headers.Location!);

        // Act
        var response = await _client.GetAsync($"/api/processes/{processId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var process = await response.Content.ReadFromJsonAsync<ProcessResponse>();
        process.Should().NotBeNull();
        process!.ProcessId.Should().Be(processId);
    }

    [Fact]
    public async Task GetProcess_Should_ReturnNotFound_WhenNotExists()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();

        // Act
        var response = await _client.GetAsync($"/api/processes/{nonExistentId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task GetProcessByClientId_Should_ReturnProcess_WhenExists()
    {
        // Arrange
        var clientId = "test-client-" + Guid.NewGuid();
        var clientProcessId = "client-process-" + Guid.NewGuid();
        var request = new CreateProcessRequestBuilder()
            .WithClientId(clientId)
            .WithClientProcessId(clientProcessId)
            .Build();
        await _client.PostJsonAsync("/api/processes", request);

        // Act
        var response = await _client.GetAsync(
            $"/api/processes/by-client/{clientId}/{clientProcessId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var process = await response.Content.ReadFromJsonAsync<ProcessResponse>();
        process.Should().NotBeNull();
        process!.ClientId.Should().Be(clientId);
        process.ClientProcessId.Should().Be(clientProcessId);
    }

    [Fact]
    public async Task ListProcesses_Should_ReturnPaginatedResults()
    {
        // Arrange - Create multiple processes
        var clientId = "test-client-" + Guid.NewGuid();
        for (int i = 0; i < 5; i++)
        {
            var request = new CreateProcessRequestBuilder()
                .WithClientId(clientId)
                .Build();
            await _client.PostJsonAsync("/api/processes", request);
        }

        // Act
        var response = await _client.GetAsync(
            $"/api/processes?clientId={clientId}&pageSize=3");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var result = await response.Content.ReadFromJsonAsync<PagedResponse<ProcessResponse>>();
        result.Should().NotBeNull();
        result!.Items.Should().HaveCount(3);
        result.HasNextPage.Should().BeTrue();
    }

    private static Guid ExtractProcessIdFromLocation(Uri location)
    {
        var segments = location.Segments;
        var idString = segments[^1];
        return Guid.Parse(idString);
    }
}

public class ProcessResponse
{
    public Guid ProcessId { get; set; }
    public string ClientId { get; set; } = string.Empty;
    public string ProcessType { get; set; } = string.Empty;
    public string ClientProcessId { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
    public Dictionary<string, string>? Metadata { get; set; }
}

public class PagedResponse<T>
{
    public List<T> Items { get; set; } = new();
    public int TotalCount { get; set; }
    public bool HasNextPage { get; set; }
    public string? NextCursor { get; set; }
}

5. Create Policy API Integration Tests

Create tests/StarGate.IntegrationTests/Api/PolicyEndpointsTests.cs:

namespace StarGate.IntegrationTests.Api;

using FluentAssertions;
using StarGate.IntegrationTests.Helpers;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using Xunit;

public class PolicyEndpointsTests : IClassFixture<StarGateWebApplicationFactory>
{
    private readonly HttpClient _client;

    public PolicyEndpointsTests(StarGateWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task CreateProcessTypePolicy_Should_ReturnCreated_WithValidRequest()
    {
        // Arrange
        var request = new
        {
            processType = "test-type-" + Guid.NewGuid(),
            maxRetries = 3,
            timeoutSeconds = 30,
            maxConcurrentProcesses = 10,
            retentionDays = 30
        };

        // Act
        var response = await _client.PostJsonAsync("/api/policies/process-types", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
    }

    [Fact]
    public async Task CreateProcessTypePolicy_Should_ReturnBadRequest_WhenMaxRetriesInvalid()
    {
        // Arrange
        var request = new
        {
            processType = "test-type",
            maxRetries = -1, // Invalid
            timeoutSeconds = 30
        };

        // Act
        var response = await _client.PostJsonAsync("/api/policies/process-types", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task GetProcessTypePolicy_Should_ReturnPolicy_WhenExists()
    {
        // Arrange
        var processType = "test-type-" + Guid.NewGuid();
        var createRequest = new
        {
            processType,
            maxRetries = 5,
            timeoutSeconds = 60
        };
        await _client.PostJsonAsync("/api/policies/process-types", createRequest);

        // Act
        var response = await _client.GetAsync($"/api/policies/process-types/{processType}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        
        var policy = await response.Content.ReadFromJsonAsync<PolicyResponse>();
        policy.Should().NotBeNull();
        policy!.ProcessType.Should().Be(processType);
        policy.MaxRetries.Should().Be(5);
    }

    [Fact]
    public async Task CreateClientPolicyOverride_Should_OverrideProcessTypeDefaults()
    {
        // Arrange
        var processType = "order";
        var clientId = "premium-client";
        
        // Create process type policy
        var typePolicy = new { processType, maxRetries = 3, timeoutSeconds = 30 };
        await _client.PostJsonAsync("/api/policies/process-types", typePolicy);
        
        // Create client override
        var clientOverride = new
        {
            clientId,
            processType,
            maxRetries = 10, // Override
            timeoutSeconds = 120 // Override
        };

        // Act
        var response = await _client.PostJsonAsync("/api/policies/client-overrides", clientOverride);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        
        // Verify override is retrieved
        var getResponse = await _client.GetAsync(
            $"/api/policies/client-overrides/{clientId}/{processType}");
        var policy = await getResponse.Content.ReadFromJsonAsync<PolicyResponse>();
        policy!.MaxRetries.Should().Be(10);
    }
}

public class PolicyResponse
{
    public string ProcessType { get; set; } = string.Empty;
    public string? ClientId { get; set; }
    public int MaxRetries { get; set; }
    public int TimeoutSeconds { get; set; }
    public int? MaxConcurrentProcesses { get; set; }
    public int? RetentionDays { get; set; }
}

6. Create Authentication Tests

Create tests/StarGate.IntegrationTests/Api/AuthenticationTests.cs:

namespace StarGate.IntegrationTests.Api;

using FluentAssertions;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using System.Net.Http.Headers;
using Xunit;

public class AuthenticationTests : IClassFixture<StarGateWebApplicationFactory>
{
    private readonly HttpClient _client;

    public AuthenticationTests(StarGateWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Request_Should_ReturnUnauthorized_WithoutAuthorizationHeader()
    {
        // Act
        var response = await _client.GetAsync("/api/processes");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Fact]
    public async Task Request_Should_ReturnUnauthorized_WithInvalidToken()
    {
        // Arrange
        _client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", "invalid-token");

        // Act
        var response = await _client.GetAsync("/api/processes");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Fact]
    public async Task Request_Should_Succeed_WithValidToken()
    {
        // Arrange
        var token = await GetValidTokenAsync();
        _client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", token);

        // Act
        var response = await _client.GetAsync("/api/processes");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    private async Task<string> GetValidTokenAsync()
    {
        // Generate or retrieve a valid test token
        // Implementation depends on auth strategy
        return await Task.FromResult("test-valid-token");
    }
}

7. Configure Test Coverage

Create coverlet.runsettings:

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat Code Coverage">
        <Configuration>
          <Format>opencover,cobertura</Format>
          <Include>[StarGate.*]*</Include>
          <Exclude>[StarGate.*.Tests]*,[*]*.Program</Exclude>
          <ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated</ExcludeByAttribute>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

Update .github/workflows/ci.yml:

- name: Run Tests with Coverage
  run: |
    dotnet test --no-build --verbosity normal \
      --collect:"XPlat Code Coverage" \
      --settings coverlet.runsettings \
      --results-directory ./coverage

- name: Generate Coverage Report
  run: |
    dotnet tool install -g dotnet-reportgenerator-globaltool
    reportgenerator \
      -reports:./coverage/**/coverage.cobertura.xml \
      -targetdir:./coverage/report \
      -reporttypes:Html;Badges

- name: Check Coverage Threshold
  run: |
    dotnet test --no-build \
      /p:Threshold=80 \
      /p:ThresholdType=line \
      /p:ThresholdStat=total

✅ Acceptance Criteria

  • TestContainers infrastructure configured
  • StarGateWebApplicationFactory implemented
  • All Process API endpoints tested
  • All Policy API endpoints tested
  • Authentication and authorization tested
  • Request validation tested
  • Error responses tested with proper status codes
  • Idempotency guarantees tested
  • Pagination tested
  • Test builders and helpers implemented
  • Code coverage reporting configured
  • Coverage threshold >80% achieved
  • Tests run in CI pipeline
  • Code follows CODING-CONVENTIONS.md

📝 Testing Instructions

# Run all integration tests
dotnet test tests/StarGate.IntegrationTests

# Run with coverage
dotnet test tests/StarGate.IntegrationTests \
  --collect:"XPlat Code Coverage" \
  --settings coverlet.runsettings

# Generate coverage report
reportgenerator \
  -reports:./coverage/**/coverage.cobertura.xml \
  -targetdir:./coverage/report \
  -reporttypes:Html

# Open coverage report
open ./coverage/report/index.html

# Run specific test class
dotnet test --filter "FullyQualifiedName~ProcessEndpointsTests"

# Run tests with detailed output
dotnet test tests/StarGate.IntegrationTests --verbosity detailed

# Verify containers start correctly
docker ps # Should show MongoDB, Redis, RabbitMQ containers

# Check test logs
cat tests/StarGate.IntegrationTests/bin/Debug/net8.0/logs/test-*.log

📚 References

🏷️ Labels

phase-9 testing sprint-9.1 integration-tests api-tests

⏱️ Estimated Effort

12-16 hours

🔗 Dependencies

  • Phase 5: API Gateway (endpoints must exist)
  • Phase 6: ProcessService (business logic must work)
  • Phase 8: Resilience policies (for timeout/retry tests)

🔗 Related Issues

Part of Phase 9: Testing & Quality - Sprint 9.1: Integration Tests

📌 Important Notes

TestContainers Benefits

Why TestContainers:

  • Real infrastructure (not mocks)
  • Isolated test environment
  • Reproducible tests
  • Easy CI/CD integration
  • Automatic cleanup

Container Startup:

Test Run Start
  ↓
Start MongoDB container (~3s)
  ↓
Start Redis container (~2s)
  ↓
Start RabbitMQ container (~5s)
  ↓
Run Tests
  ↓
Stop & Remove Containers

Test Organization

By Feature:

ProcessEndpointsTests
├── CreateProcess tests
├── GetProcess tests
├── ListProcesses tests
└── Idempotency tests

PolicyEndpointsTests
├── ProcessTypePolicy tests
├── ClientOverride tests
└── Policy resolution tests

Test Naming Convention

[Fact]
public async Task MethodName_Should_ExpectedBehavior_When_Condition()
{
    // Arrange
    // Act
    // Assert
}

Examples:

  • CreateProcess_Should_ReturnCreated_WithValidRequest
  • GetProcess_Should_ReturnNotFound_WhenNotExists
  • ListProcesses_Should_ReturnPaginatedResults

Builder Pattern for Test Data

Benefits:

  • Readable test setup
  • Reusable across tests
  • Fluent interface
  • Reduces duplication

Example:

var request = new CreateProcessRequestBuilder()
    .WithClientId("test-client")
    .WithProcessType("order")
    .WithMetadata("orderId", "12345")
    .Build();

Coverage Targets

Overall Target: >80%

By Project:

  • StarGate.Core: >90% (domain logic)
  • StarGate.Infrastructure: >85% (repos, services)
  • StarGate.Server: >75% (endpoints, workers)

Exclusions:

  • Program.cs (bootstrapping)
  • Generated code
  • Test projects

Performance Considerations

Container Startup:

  • MongoDB: ~3 seconds
  • Redis: ~2 seconds
  • RabbitMQ: ~5 seconds
  • Total: ~10 seconds overhead per test run

Optimization:

  • Use ClassFixture (containers shared per test class)
  • Parallel test execution where possible
  • Fast test configuration (shorter timeouts)

CI/CD Integration

GitHub Actions:

- name: Run Integration Tests
  run: dotnet test tests/StarGate.IntegrationTests
  
- name: Upload Coverage
  uses: codecov/codecov-action@v3
  with:
    files: ./coverage/**/coverage.cobertura.xml

Coverage Badge:

![Coverage](https://img.shields.io/codecov/c/github/artcava/StarGate)

Common Test Scenarios

Happy Path:

  • Valid requests succeed
  • Data persisted correctly
  • Correct status codes returned

Validation:

  • Missing required fields → 400
  • Invalid data format → 400
  • Constraint violations → 400

Error Handling:

  • Not found → 404
  • Unauthorized → 401
  • Forbidden → 403
  • Internal error → 500

Edge Cases:

  • Empty collections
  • Boundary values
  • Special characters
  • Very long strings

Idempotency Testing

Critical for Process Creation:

// First call: Creates process
POST /api/processes { clientProcessId: "ABC" } → 201 Created

// Second call: Returns existing
POST /api/processes { clientProcessId: "ABC" } → 200 OK

// Verify same processId returned

Test Data Isolation

Strategies:

  1. Unique IDs per test (Guid.NewGuid())
  2. Database cleanup between tests
  3. Separate test database
  4. Container recreation per class

Debugging Tests

View Container Logs:

docker logs <container-id>

Attach Debugger:

  • Set breakpoint in test
  • Run test in debug mode
  • Containers remain running

Manual Container Inspection:

# MongoDB
docker exec -it <mongo-container> mongosh

# Redis
docker exec -it <redis-container> redis-cli

# RabbitMQ
open http://localhost:15672 # Management UI

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions