> ## Documentation Index
> Fetch the complete documentation index at: https://platform.docs.zenoo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Component Tester

> Documentation for the Component Tester, a testing framework for Zenoo Hub components.

# Component Tester Documentation

## Overview

The Component Tester is a testing framework designed to facilitate unit testing of Zenoo Hub components. It provides a comprehensive testing environment that simulates the execution of components, workflows, and functions in an isolated testing context using Kafka Streams test utilities.

## Key Features

* **Isolated Testing Environment**: Uses Kafka Streams `TopologyTestDriver` for testing without external dependencies
* **Component Testing**: Test individual components, functions, and workflows
* **Component Validation**: Comprehensive validation testing for component definitions, configurations, and HTTP workflows
* **Mock Support**: Mock component dependencies and external services
* **File Upload Testing**: Test file upload functionality with mock file cache
* **Dependency Management**: Test components with their dependencies
* **Spock Framework Integration**: Built on Spock for expressive test specifications

## Architecture

The Component Tester consists of several core classes:

### Core Classes

#### `ComponentTester`

The main testing engine that manages the Kafka Streams test environment.

**Key Responsibilities:**

* Manages Kafka Streams `TopologyTestDriver`
* Handles execution request/response topics
* Provides file upload capabilities
* Stores components in mock repository

**Key Methods:**

* `request(UUID, ExecutionRequest)`: Sends execution requests
* `executionResponseFor(ExecutionRequestEvent)`: Retrieves execution responses
* `upload(UUID, InputStream)`: Uploads files for testing
* `storeComponent(Component)`: Stores components in mock repository

#### `ComponentTest`

Represents a test instance for a specific component execution.

**Key Methods:**

* `execute(payload)`: Executes a function or workflow
* `getResult()`: Retrieves the execution result
* `getRoute()`: Gets the current route information
* `getCurrentRoute()`: Queries the current route state
* `submit(payload)`: Submits data to the current route
* `upload(Resource)`: Uploads a file resource

#### `ComponentTestBuilder`

Fluid builder API for creating component tests.

**Key Methods:**

* `function(String name)`: Sets up a function test
* `workflow(String name)`: Sets up a workflow test
* `component(Component)`: Sets the component to test
* `component(ComponentFactory)`: Sets the component to test using `ComponentFactory`
* `dependency(Component)`: Adds component dependencies
* `mock(Closure)`: Adds mock behavior
* `config(payload)`: Sets component configuration

#### `ComponentValidator`

Validation engine for testing component definitions and configurations.

**Key Methods:**

* `validateDefinition(Component)`: Validates component DSL structure and syntax
* `validateConfig(Component)`: Validates component configuration requirements

#### `ComponentValidatorBuilder`

Builder API for creating component validation tests.

**Key Methods:**

* `component(Component)`: Sets the component to validate
* `config(Map)`: Sets component configuration for validation

## Usage Examples

The Component Tester provides two main testing approaches:

1. **ComponentTesterSpec**: For execution testing of components, functions, and workflows
2. **ComponentValidatorSpec**: For validation testing of component definitions and configurations

## ComponentTesterSpec - Execution Testing

ComponentTesterSpec is used for testing the actual execution behavior of components, including functions, workflows, file uploads, and dependency interactions.

### Basic Function Testing

```groovy theme={null}
class MyFunctionSpec extends ComponentTesterSpec {

    static component = ComponentBuilder.component('my-component:v1') {
        function("hello") { name ->
            success(message: "Hello, ${name}!")
        }
    }

    def "should execute hello function"() {
        given:
        def test = testBuilder()
                .component(component)
                .function('hello')
                .build()

        when:
        test.execute("World")

        then:
        test.getResult().payload.message == "Hello, World!"
    }
}
```

### Using ComponentFactory with testBuilder

```groovy theme={null}
class MyFunctionWithFactorySpec extends ComponentTesterSpec {

    def "should execute hello function using ComponentFactory"() {
        given:
        def test = testBuilder(MyComponentFactory)
                .function('hello')
                .build()

        when:
        test.execute("World")

        then:
        test.getResult().payload.message == "Hello, World!"
    }

    static class MyComponentFactory implements ComponentFactory {
        @Override
        Component getComponent() {
            return ComponentBuilder.component('my-component:v1') {
                function("hello") { name ->
                    success(message: "Hello, ${name}!")
                }
            }
        }
    }
}
```

### Workflow Testing

```groovy theme={null}
class MyWorkflowSpec extends ComponentTesterSpec {

    static component = ComponentBuilder.component('workflow:v1') {
        workflow('user-registration') {
            route('validate') {
                uri '/validate'
                namespace user
                validate {
                    required('email', 'password')
                }
            }

            function('process') {
                input user
                namespace result
            }

            route('complete') {
                uri '/complete'
                export result
                terminal()
            }
        }
    }

    def "should execute user registration workflow"() {
        given:
        def test = testBuilder()
                .component(component)
                .workflow('user-registration')
                .build()

        when:
        test.execute([email: 'test@example.com', password: 'secret'])

        then:
        test.currentRoute.uri == '/validate'
        test.submit([email: 'test@example.com', password: 'secret'])
        test.route.uri == '/complete'
    }
}
```

### Mocking Dependencies

```groovy theme={null}
class MockedComponentSpec extends ComponentTesterSpec {

    static component = ComponentBuilder.component('api-client:v1') {
        function("fetch-data") {
            http {
                url "https://api.example.com/data"
                method "GET"
            }
        }
    }

    def "should mock HTTP calls"() {
        given:
        def test = testBuilder()
                .component(component)
                .function('fetch-data')
                .mock {
                    http {
                        url "https://api.example.com/data"
                        response {
                            status 200
                            body [data: "mocked response"]
                        }
                    }
                }
                .build()

        when:
        test.execute()

        then:
        test.getResult().payload.data == "mocked response"
    }
}
```

### Testing with Dependencies

```groovy theme={null}
class DependencySpec extends ComponentTesterSpec {

    static dependency = ComponentBuilder.component('dependency:v1') {
        function("process") { input ->
            success(processed: input.toUpperCase())
        }
    }

    static component = ComponentBuilder.component('main:v1') {
        dependencies {
            component 'dependency'
        }

        function("main") { input ->
            function('dependency@process') {
                input input
            }
        }
    }

    def "should execute with dependency"() {
        given:
        def test = testBuilder()
                .component(component)
                .dependency(dependency)
                .function('main')
                .build()

        when:
        test.execute("hello")

        then:
        test.getResult().payload.processed == "HELLO"
    }
}
```

### File Upload Testing

```groovy theme={null}
class FileUploadSpec extends ComponentTesterSpec {

    @Value("classpath:files/test-image.jpg")
    Resource testImage

    static component = ComponentBuilder.component('upload:v1') {
        workflow('upload') {
            route('upload') {
                uri '/upload'
                namespace file
                validate {
                    file()
                }
            }

            function('process') {
                input file
                namespace result
            }
        }
    }

    def "should handle file upload"() {
        given:
        def test = testBuilder()
                .component(component)
                .workflow('upload')
                .build()

        when:
        test.execute()
        def descriptor = test.upload(testImage)
        test.submit(descriptor)

        then:
        test.getResult().payload.descriptor == descriptor
    }
}
```

## ComponentValidatorSpec - Validation Testing

ComponentValidatorSpec is used for testing component definitions, configurations, and DSL structure without executing the components. This is useful for ensuring components are properly structured and configured before deployment.

### Basic Definition Validation

```groovy theme={null}
class ComponentValidationSpec extends ComponentValidatorSpec {

    def "should validate simple workflow component"() {
        given:
        def component = ComponentBuilder.component('simple:v1') {
            workflow('main') {
                route('start') {
                    uri '/start'
                    terminal()
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .build()

        then:
        assertDefinitionValid(validatorTest)
    }

    def "should fail validation when function is missing"() {
        given:
        def component = ComponentBuilder.component('invalid:v1') {
            workflow('main') {
                function('nonExistentFunction')

                route('result') {
                    uri '/result'
                    terminal()
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .build()

        then:
        assertDefinitionInvalid(validatorTest)
    }
}
```

### Using ComponentFactory with validatorBuilder

```groovy theme={null}
class ComponentValidationWithFactorySpec extends ComponentValidatorSpec {

    def "should validate component using ComponentFactory"() {
        when:
        def validatorTest = validatorBuilder(SimpleComponentFactory)
                .build()

        then:
        assertDefinitionValid(validatorTest)
    }

    def "should validate component with config using ComponentFactory"() {
        when:
        def validatorTest = validatorBuilder(ConfigurableComponentFactory)
                .config([
                    username: 'testuser',
                    password: 'secret123'
                ])
                .build()

        then:
        assertDefinitionValid(validatorTest)
        assertConfigValid(validatorTest)
    }

    static class SimpleComponentFactory implements ComponentFactory {
        @Override
        Component getComponent() {
            return ComponentBuilder.component('simple:v1') {
                workflow('main') {
                    route('start') {
                        uri '/start'
                        terminal()
                    }
                }
            }
        }
    }

    static class ConfigurableComponentFactory implements ComponentFactory {
        @Override
        Component getComponent() {
            return ComponentBuilder.component('configurable:v1') {
                require {
                    username
                    password
                }

                workflow('main') {
                    route('start') {
                        uri '/start'
                        terminal()
                    }
                }
            }
        }
    }
}
```

#### Configuration Validation

```groovy theme={null}
class ComponentConfigValidationSpec extends ComponentValidatorSpec {

    def "should validate component with required config"() {
        given:
        def component = ComponentBuilder.component('config-required:v1') {
            require {
                username
                password
                apiEndpoint
            }

            workflow('main') {
                route('start') {
                    uri '/start'
                    terminal()
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .config([
                    username: 'testuser',
                    password: 'secret123',
                    apiEndpoint: 'https://api.example.com'
                ])
                .build()

        then:
        assertDefinitionValid(validatorTest)
        assertConfigValid(validatorTest)
    }

    def "should fail validation when required config is missing"() {
        given:
        def component = ComponentBuilder.component('config-missing:v1') {
            require {
                username
                password
                apiEndpoint
            }

            workflow('main') {
                route('start') {
                    uri '/start'
                    terminal()
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .build()

        then:
        assertDefinitionValid(validatorTest)
        assertConfigInvalid(validatorTest)
    }
}
```

#### HTTP Workflow Validation

```groovy theme={null}
class ComponentHttpValidationSpec extends ComponentValidatorSpec {

    def "should validate HTTP workflow with multiple handlers"() {
        given:
        def component = ComponentBuilder.component('http-workflow:v1') {
            workflow('main') {
                http {
                    url 'http://api.example.com'
                    timeout 3000
                }.onStatus(200) {
                    route('success') {
                        uri '/success'
                        terminal()
                    }
                }.onStatus(400) {
                    route('error') {
                        uri '/error'
                        terminal()
                    }
                }.onError {
                    route('system-error') {
                        uri '/system-error'
                        terminal()
                    }
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .build()

        then:
        assertDefinitionValid(validatorTest)
    }

    def "should validate HTTP in function context"() {
        given:
        def component = ComponentBuilder.component('http-function:v1') {
            function('callApi') {
                http {
                    url 'http://api.example.com'
                }.onSuccess {
                    success()
                }.onError {
                    error("API call failed")
                }
            }

            workflow('main') {
                function('callApi')

                route('result') {
                    uri '/result'
                    terminal()
                }
            }
        }

        when:
        def validatorTest = validatorBuilder()
                .component(component)
                .build()

        then:
        assertDefinitionValid(validatorTest)
    }
}
```

#### Validation Testing Features

The Component Validator supports testing of:

* **Definition Validation**: DSL syntax, function references, workflow structure
* **Configuration Validation**: Required config fields, typed constraints, nested configurations
* **HTTP Validation**: HTTP blocks, status handlers, response handlers, timeout configuration
* **Dependency Validation**: Component and connector dependencies
* **Complex Scenarios**: Multi-step workflows, conditional logic, error handling

#### Validation Test Base Classes

The framework provides specialized base classes for validation testing:

* **`ComponentValidatorSpec`**: Base class for all validation tests

## Testing Best Practices

### 1. Choose the Right Base Class

* **ComponentTesterSpec**: For execution testing (functions, workflows, file uploads, dependencies)
* **ComponentValidatorSpec**: For validation testing (component definitions, configurations)

```groovy theme={null}
// For execution testing
class MyExecutionSpec extends ComponentTesterSpec {
    // Test actual component execution
}

// For validation testing
class MyValidationSpec extends ComponentValidatorSpec {
    // Test component structure and configuration
}
```

### 2. Use the Builder Pattern

The framework provides two builder patterns:

#### Direct Component Usage

```groovy theme={null}
// For ComponentTesterSpec
def test = testBuilder()
        .component(component)
        .function('my-function')
        .config([key: 'value'])
        .build()

// For ComponentValidatorSpec
def validatorTest = validatorBuilder()
        .component(component)
        .config([key: 'value'])
        .build()
```

#### ComponentFactory Usage

```groovy theme={null}
// For ComponentTesterSpec
def test = testBuilder(MyComponentFactory)
        .function('my-function')
        .config([key: 'value'])
        .build()

// For ComponentValidatorSpec
def validatorTest = validatorBuilder(MyComponentFactory)
        .config([key: 'value'])
        .build()
```

### 3. Mock External Dependencies

Mock external services and dependencies to ensure isolated testing:

```groovy theme={null}
.mock {
    http {
        url "https://external-api.com"
        response {
            status 200
            body [result: "success"]
        }
    }
}
```

### 4. Test Different Execution Types

Test various execution scenarios:

* **Functions**: Direct execution with input/output validation
* **Workflows**: Multi-step execution with route transitions
* **File Uploads**: File handling and processing
* **Dependencies**: Component interaction testing
* **Validation**: Component definition and configuration validation

#### Component Validation Testing

Use the validation framework to test component structure without execution:

```groovy theme={null}
// Direct component usage
def test = validatorBuilder()
        .component(component)
        .config([key: 'value'])
        .build()

assertDefinitionValid(test)
assertConfigValid(test)

// ComponentFactory usage
def factoryTest = validatorBuilder(MyComponentFactory)
        .config([key: 'value'])
        .build()

assertDefinitionValid(factoryTest)
assertConfigValid(factoryTest)
```

## Dependencies

The Component Tester includes the following key dependencies:

* **Spring Boot Test**: Testing framework integration
* **Kafka Streams Test Utils**: Kafka Streams testing utilities
* **Spock Framework**: Testing specification framework
* **Groovy**: Dynamic language support
* **Reactor Test**: Reactive testing utilities

## Integration with Main Framework

The Component Tester integrates seamlessly with the main Zenoo Hub framework by:

1. **Using the same component model**: Tests use the same `Component` and DSL structures
2. **Leveraging existing topics**: Uses the same execution request/response topics
3. **Supporting all DSL features**: Functions, workflows, routes, validation, etc.
4. **Mocking framework components**: Provides mocks for all major framework components

This ensures that tests accurately reflect real-world component behavior while providing the isolation and control needed for effective unit testing.
