hackathon/docs/testing/test-package-structure.md
Christoph Wagner 5b658e2468 docs: add architectural review and requirement refinement verification
Complete architectural analysis and requirement traceability improvements:

  1. Architecture Review Report (NEW)
     - Independent architectural review identifying 15 issues
     - 5 critical issues: security (no TLS), buffer inadequacy, performance
       bottleneck, missing circuit breaker, inefficient backoff
     - 5 major issues: no metrics, no graceful shutdown, missing rate limiting,
       no backpressure, low test coverage
     - Overall architecture score: 6.5/10
     - Recommendation: DO NOT DEPLOY until critical issues resolved
     - Detailed analysis with code examples and effort estimates

  2. Requirement Refinement Verification (NEW)
     - Verified Req-FR-25, Req-NFR-7, Req-NFR-8 refinement status
     - Added 12 missing Req-FR-25 references to architecture documents
     - Confirmed 24 Req-NFR-7 references (health check endpoint)
     - Confirmed 26 Req-NFR-8 references (health check content)
     - 100% traceability for all three requirements

  3. Architecture Documentation Updates
     - system-architecture.md: Added 4 Req-FR-25 references for data transmission
     - java-package-structure.md: Added 8 Req-FR-25 references across components
     - Updated DataTransmissionService, GrpcStreamPort, GrpcStreamingAdapter,
       DataConsumerService with proper requirement annotations

  Files changed:
  - docs/ARCHITECTURE_REVIEW_REPORT.md (NEW)
  - docs/REQUIREMENT_REFINEMENT_VERIFICATION.md (NEW)
  - docs/architecture/system-architecture.md (4 additions)
  - docs/architecture/java-package-structure.md (8 additions)

  All 62 requirements now have complete bidirectional traceability with
  documented architectural concerns and critical issues identified for resolution.
2025-11-19 11:06:02 +01:00

24 KiB

Test Package Structure

Overview

This document defines the complete test package organization, test class structure, and mock server setup for the Log Data Collector system.

Test Source Directory Structure

src/test/
├── java/
│   └── com/
│       └── logcollector/
│           ├── unit/                      # Unit Tests (75% of suite)
│           │   ├── config/
│           │   │   ├── ConfigurationLoaderTest.java
│           │   │   ├── YamlParserTest.java
│           │   │   └── ValidationServiceTest.java
│           │   ├── serialization/
│           │   │   ├── DataSerializerTest.java
│           │   │   ├── JsonSerializerTest.java
│           │   │   └── ProtobufSerializerTest.java
│           │   ├── buffer/
│           │   │   ├── CircularBufferTest.java
│           │   │   ├── BufferOverflowHandlerTest.java
│           │   │   └── BufferThreadSafetyTest.java
│           │   ├── retry/
│           │   │   ├── RetryMechanismTest.java
│           │   │   ├── ExponentialBackoffTest.java
│           │   │   └── RetryPolicyTest.java
│           │   ├── health/
│           │   │   ├── HealthCheckEndpointTest.java
│           │   │   ├── HttpHealthCheckTest.java
│           │   │   └── GrpcHealthCheckTest.java
│           │   ├── collector/
│           │   │   ├── HttpCollectorTest.java
│           │   │   ├── EndpointSchedulerTest.java
│           │   │   └── ResponseParserTest.java
│           │   ├── transmitter/
│           │   │   ├── GrpcTransmitterTest.java
│           │   │   ├── ConnectionManagerTest.java
│           │   │   └── TransmissionQueueTest.java
│           │   └── startup/
│           │       ├── StartupSequenceTest.java
│           │       ├── ComponentInitializerTest.java
│           │       └── DependencyResolverTest.java
│           │
│           ├── integration/               # Integration Tests (20% of suite)
│           │   ├── collector/
│           │   │   ├── HttpCollectionIntegrationTest.java
│           │   │   └── MultiEndpointIntegrationTest.java
│           │   ├── transmitter/
│           │   │   ├── GrpcTransmissionIntegrationTest.java
│           │   │   └── ReconnectionIntegrationTest.java
│           │   ├── e2e/
│           │   │   ├── EndToEndDataFlowTest.java
│           │   │   └── BackpressureIntegrationTest.java
│           │   ├── config/
│           │   │   ├── ConfigurationFileIntegrationTest.java
│           │   │   └── ConfigurationReloadIntegrationTest.java
│           │   └── buffer/
│           │       ├── CircularBufferIntegrationTest.java
│           │       └── BufferPerformanceIntegrationTest.java
│           │
│           ├── performance/               # Performance Tests
│           │   ├── PerformanceConcurrentEndpointsTest.java
│           │   ├── PerformanceMemoryUsageTest.java
│           │   ├── PerformanceVirtualThreadTest.java
│           │   └── PerformanceStartupTimeTest.java
│           │
│           ├── reliability/               # Reliability Tests
│           │   ├── ReliabilityStartupSequenceTest.java
│           │   ├── ReliabilityGrpcRetryTest.java
│           │   ├── ReliabilityHttpFailureTest.java
│           │   ├── ReliabilityBufferOverflowTest.java
│           │   └── ReliabilityPartialFailureTest.java
│           │
│           ├── compliance/                # Compliance Tests
│           │   ├── ComplianceErrorDetectionTest.java
│           │   ├── ComplianceIso9001Test.java
│           │   ├── ComplianceEn50716Test.java
│           │   └── ComplianceAuditLoggingTest.java
│           │
│           └── util/                      # Test Utilities
│               ├── mock/
│               │   ├── HttpMockServerSetup.java
│               │   ├── GrpcMockServerSetup.java
│               │   └── MockClockProvider.java
│               ├── builder/
│               │   ├── TestDataFactory.java
│               │   ├── TestConfigurationBuilder.java
│               │   ├── TestEndpointBuilder.java
│               │   └── TestLogEntryBuilder.java
│               ├── assertion/
│               │   ├── CustomAssertions.java
│               │   └── PerformanceAssertions.java
│               └── extension/
│                   ├── MockServerExtension.java
│                   ├── GrpcServerExtension.java
│                   └── PerformanceTestExtension.java
│
└── resources/
    ├── config/
    │   ├── test-config.yaml                  # Valid test configuration
    │   ├── test-config-invalid.yaml          # Invalid format
    │   ├── test-config-minimal.yaml          # Minimal valid config
    │   └── test-config-maximal.yaml          # Maximum complexity config
    ├── data/
    │   ├── sample-log-entries.json           # Sample log data
    │   └── large-payload.json                # Large payload test data
    ├── wiremock/
    │   ├── mappings/                         # WireMock stub mappings
    │   │   ├── health-check-success.json
    │   │   ├── health-check-failure.json
    │   │   ├── log-endpoint-success.json
    │   │   └── log-endpoint-timeout.json
    │   └── __files/                          # WireMock response files
    │       ├── sample-response.json
    │       └── error-response.json
    ├── proto/
    │   └── test-log-data.proto               # Test Protocol Buffer definitions
    ├── logback-test.xml                      # Test logging configuration
    └── junit-platform.properties             # JUnit configuration

Test Class Templates

Unit Test Template

package com.logcollector.unit.config;

import org.junit.jupiter.api.*;
import org.mockito.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

/**
 * Unit tests for ConfigurationLoader component.
 *
 * @validates Req-FR-11 - Configuration file detection
 * @validates Req-FR-12 - Configuration parsing
 * @validates Req-FR-13 - Configuration validation
 * @validates Req-Norm-3 - Error detection
 */
@DisplayName("Configuration Loader Unit Tests")
@Tag("unit")
@Tag("config")
class ConfigurationLoaderTest {

    @Mock
    private FileSystem fileSystem;

    @Mock
    private YamlParser yamlParser;

    @InjectMocks
    private ConfigurationLoader configurationLoader;

    private AutoCloseable mocks;

    @BeforeEach
    void setUp() {
        mocks = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    void tearDown() throws Exception {
        mocks.close();
    }

    @Nested
    @DisplayName("Configuration File Detection")
    class FileDetectionTests {

        @Test
        @DisplayName("should detect config file when file exists")
        void shouldDetectConfigFile_whenFileExists() {
            // Arrange
            when(fileSystem.exists("/etc/logcollector/config.yaml"))
                .thenReturn(true);

            // Act
            boolean result = configurationLoader.detectConfigFile();

            // Assert
            assertThat(result).isTrue();
            verify(fileSystem).exists("/etc/logcollector/config.yaml");
        }

        @Test
        @DisplayName("should throw exception when file not found")
        void shouldThrowException_whenFileNotFound() {
            // Arrange
            when(fileSystem.exists(anyString())).thenReturn(false);

            // Act & Assert
            assertThatThrownBy(() -> configurationLoader.load())
                .isInstanceOf(ConfigurationNotFoundException.class)
                .hasMessageContaining("Configuration file not found");
        }
    }

    @Nested
    @DisplayName("Configuration Parsing")
    class ParsingTests {
        // Parsing test methods...
    }

    @Nested
    @DisplayName("Configuration Validation")
    class ValidationTests {
        // Validation test methods...
    }
}

Integration Test Template

package com.logcollector.integration.collector;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.logcollector.util.extension.MockServerExtension;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.*;

/**
 * Integration tests for HTTP collection with mock server.
 *
 * @validates Req-Test-1 - Mock HTTP server
 * @validates Req-FR-14 - HTTP endpoint collection
 * @validates Req-FR-15 - Response parsing
 */
@DisplayName("HTTP Collection Integration Tests")
@Tag("integration")
@Tag("http")
@ExtendWith(MockServerExtension.class)
class HttpCollectionIntegrationTest {

    private WireMockServer wireMockServer;
    private HttpCollector httpCollector;

    @BeforeEach
    void setUp(WireMockServer server) {
        this.wireMockServer = server;
        this.httpCollector = new HttpCollector(
            "http://localhost:" + server.port()
        );
    }

    @Test
    @DisplayName("should collect from mock endpoint when server running")
    void shouldCollectFromMockEndpoint_whenServerRunning() {
        // Arrange
        wireMockServer.stubFor(get(urlEqualTo("/logs"))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBodyFile("sample-response.json")));

        // Act
        LogData result = httpCollector.collect("/logs");

        // Assert
        assertThat(result).isNotNull();
        assertThat(result.getEntries()).isNotEmpty();

        // Verify interaction
        wireMockServer.verify(1, getRequestedFor(urlEqualTo("/logs")));
    }

    @Test
    @DisplayName("should handle multiple endpoints concurrently")
    void shouldHandleMultipleEndpoints_concurrently() {
        // Test implementation...
    }
}

Performance Test Template

package com.logcollector.performance;

import org.junit.jupiter.api.*;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.*;

/**
 * Performance tests for concurrent endpoint handling.
 *
 * @validates Req-NFR-1 - 1000 concurrent endpoints support
 */
@DisplayName("Performance: Concurrent Endpoints")
@Tag("performance")
class PerformanceConcurrentEndpointsTest {

    @Test
    @DisplayName("should handle 1000 endpoints concurrently")
    @Timeout(value = 60, unit = TimeUnit.SECONDS)
    void shouldHandle1000Endpoints_concurrently() throws Exception {
        // Arrange
        List<String> endpoints = IntStream.range(0, 1000)
            .mapToObj(i -> "http://endpoint-" + i + ".test/logs")
            .collect(Collectors.toList());

        HttpCollector collector = new HttpCollector();

        // Act
        long startTime = System.nanoTime();
        List<CompletableFuture<LogData>> futures = endpoints.stream()
            .map(collector::collectAsync)
            .toList();

        List<LogData> results = CompletableFuture.allOf(
            futures.toArray(new CompletableFuture[0])
        ).thenApply(v -> futures.stream()
            .map(CompletableFuture::join)
            .toList()
        ).get();

        long duration = System.nanoTime() - startTime;

        // Assert
        assertThat(results).hasSize(1000);
        assertThat(duration).isLessThan(TimeUnit.MINUTES.toNanos(1));

        // Report metrics
        System.out.printf("Collected 1000 endpoints in %.2f seconds%n",
            duration / 1_000_000_000.0);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void benchmarkEndpointCollection() {
        // JMH benchmark implementation...
    }
}

Mock Server Setup

WireMock HTTP Server

package com.logcollector.util.mock;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import static com.github.tomakehurst.wiremock.client.WireMock.*;

/**
 * Mock HTTP server setup for testing HTTP collection.
 *
 * @validates Req-Test-1 - Mock HTTP server requirement
 */
public class HttpMockServerSetup {

    private final WireMockServer server;

    public HttpMockServerSetup() {
        this.server = new WireMockServer(
            WireMockConfiguration.options()
                .dynamicPort()
                .usingFilesUnderClasspath("wiremock")
        );
    }

    public void start() {
        server.start();
        configureDefaultStubs();
    }

    public void stop() {
        server.stop();
    }

    public int getPort() {
        return server.port();
    }

    public WireMockServer getServer() {
        return server;
    }

    private void configureDefaultStubs() {
        // Health check success
        server.stubFor(get(urlEqualTo("/health"))
            .willReturn(aResponse()
                .withStatus(200)
                .withBody("{\"status\":\"UP\"}")));

        // Log endpoint success
        server.stubFor(get(urlPathMatching("/logs.*"))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBodyFile("sample-response.json")));

        // Simulate timeout
        server.stubFor(get(urlEqualTo("/slow"))
            .willReturn(aResponse()
                .withStatus(200)
                .withFixedDelay(10000)));

        // Simulate error
        server.stubFor(get(urlEqualTo("/error"))
            .willReturn(aResponse()
                .withStatus(500)
                .withBody("{\"error\":\"Internal Server Error\"}")));
    }
}

gRPC Mock Server

package com.logcollector.util.mock;

import io.grpc.*;
import io.grpc.inprocess.*;
import io.grpc.stub.StreamObserver;
import java.io.IOException;

/**
 * Mock gRPC server setup for testing transmission.
 *
 * @validates Req-Test-2 - Mock gRPC server requirement
 */
public class GrpcMockServerSetup {

    private final String serverName;
    private final Server server;
    private final MockLogDataService mockService;

    public GrpcMockServerSetup() {
        this.serverName = InProcessServerBuilder.generateName();
        this.mockService = new MockLogDataService();
        this.server = InProcessServerBuilder
            .forName(serverName)
            .directExecutor()
            .addService(mockService)
            .build();
    }

    public void start() throws IOException {
        server.start();
    }

    public void stop() {
        server.shutdownNow();
    }

    public String getServerName() {
        return serverName;
    }

    public MockLogDataService getMockService() {
        return mockService;
    }

    public ManagedChannel createChannel() {
        return InProcessChannelBuilder
            .forName(serverName)
            .directExecutor()
            .build();
    }

    /**
     * Mock implementation of LogDataService for testing.
     */
    public static class MockLogDataService
            extends LogDataServiceGrpc.LogDataServiceImplBase {

        private int callCount = 0;
        private boolean shouldFail = false;

        @Override
        public void sendLogData(
                LogDataRequest request,
                StreamObserver<LogDataResponse> responseObserver) {

            callCount++;

            if (shouldFail) {
                responseObserver.onError(
                    Status.UNAVAILABLE
                        .withDescription("Mock failure")
                        .asRuntimeException()
                );
                return;
            }

            LogDataResponse response = LogDataResponse.newBuilder()
                .setSuccess(true)
                .setMessageId(UUID.randomUUID().toString())
                .build();

            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }

        public int getCallCount() {
            return callCount;
        }

        public void setShouldFail(boolean shouldFail) {
            this.shouldFail = shouldFail;
        }

        public void reset() {
            callCount = 0;
            shouldFail = false;
        }
    }
}

Test Data Builders

Test Configuration Builder

package com.logcollector.util.builder;

import com.logcollector.config.Configuration;
import com.logcollector.config.EndpointConfig;
import com.logcollector.config.GrpcConfig;
import java.util.*;

/**
 * Fluent builder for test Configuration objects.
 */
public class TestConfigurationBuilder {

    private List<EndpointConfig> endpoints = new ArrayList<>();
    private String grpcHost = "localhost";
    private int grpcPort = 9090;
    private int bufferSize = 10000;
    private int retryMaxAttempts = 3;
    private int retryBaseDelay = 100;

    public static TestConfigurationBuilder aConfiguration() {
        return new TestConfigurationBuilder();
    }

    public TestConfigurationBuilder withEndpoint(EndpointConfig endpoint) {
        this.endpoints.add(endpoint);
        return this;
    }

    public TestConfigurationBuilder withEndpoints(int count) {
        for (int i = 0; i < count; i++) {
            endpoints.add(TestEndpointBuilder.anEndpoint()
                .withUrl("http://endpoint-" + i + ".test/logs")
                .withSchedule("0/30 * * * * ?")
                .build());
        }
        return this;
    }

    public TestConfigurationBuilder withGrpcHost(String host) {
        this.grpcHost = host;
        return this;
    }

    public TestConfigurationBuilder withGrpcPort(int port) {
        this.grpcPort = port;
        return this;
    }

    public TestConfigurationBuilder withBufferSize(int size) {
        this.bufferSize = size;
        return this;
    }

    public TestConfigurationBuilder withRetryConfig(int maxAttempts, int baseDelay) {
        this.retryMaxAttempts = maxAttempts;
        this.retryBaseDelay = baseDelay;
        return this;
    }

    public Configuration build() {
        return Configuration.builder()
            .endpoints(endpoints)
            .grpcConfig(GrpcConfig.builder()
                .host(grpcHost)
                .port(grpcPort)
                .build())
            .bufferSize(bufferSize)
            .retryMaxAttempts(retryMaxAttempts)
            .retryBaseDelayMs(retryBaseDelay)
            .build();
    }
}

JUnit Extensions

Mock Server Extension

package com.logcollector.util.extension;

import com.logcollector.util.mock.HttpMockServerSetup;
import org.junit.jupiter.api.extension.*;

/**
 * JUnit 5 extension for WireMock server lifecycle management.
 */
public class MockServerExtension implements BeforeEachCallback, AfterEachCallback {

    private static final String SERVER_KEY = "mockServer";

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        HttpMockServerSetup server = new HttpMockServerSetup();
        server.start();

        context.getStore(ExtensionContext.Namespace.GLOBAL)
            .put(SERVER_KEY, server);
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        HttpMockServerSetup server = context.getStore(
            ExtensionContext.Namespace.GLOBAL
        ).get(SERVER_KEY, HttpMockServerSetup.class);

        if (server != null) {
            server.stop();
        }
    }
}

Test Resource Files

test-config.yaml

# Valid test configuration
endpoints:
  - url: "http://endpoint1.test/logs"
    schedule: "0/30 * * * * ?"
    timeout: 5000
  - url: "http://endpoint2.test/logs"
    schedule: "0/60 * * * * ?"
    timeout: 5000

grpc:
  host: "localhost"
  port: 9090
  tls: false

buffer:
  size: 10000
  overflow: "overwrite"

retry:
  maxAttempts: 3
  baseDelayMs: 100
  maxDelayMs: 5000

logback-test.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>

    <!-- Reduce noise from test frameworks -->
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate" level="WARN"/>
    <logger name="io.grpc" level="WARN"/>
    <logger name="com.github.tomakehurst.wiremock" level="WARN"/>
</configuration>

Maven Test Configuration

pom.xml (Test Section)

<build>
    <testSourceDirectory>src/test/java</testSourceDirectory>
    <testResources>
        <testResource>
            <directory>src/test/resources</directory>
        </testResource>
    </testResources>

    <plugins>
        <!-- Surefire for unit tests -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <includes>
                    <include>**/*Test.java</include>
                </includes>
                <excludes>
                    <exclude>**/integration/**</exclude>
                    <exclude>**/performance/**</exclude>
                </excludes>
                <parallel>classes</parallel>
                <threadCount>4</threadCount>
            </configuration>
        </plugin>

        <!-- Failsafe for integration tests -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <includes>
                    <include>**/integration/**/*Test.java</include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<profiles>
    <!-- Performance testing profile -->
    <profile>
        <id>performance-tests</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <includes>
                            <include>**/performance/**/*Test.java</include>
                        </includes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Version: 1.0 Last Updated: 2025-11-19 Author: Test Strategist Agent Status: Complete - Test Infrastructure Defined