framework

package
v0.0.0-...-1f1a93d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: MIT Imports: 11 Imported by: 0

README

NemesisBot Testing Framework

This directory provides comprehensive testing utilities and infrastructure for the NemesisBot project.

Overview

The testing framework includes:

  • Mock implementations for all major interfaces
  • Test data builders for creating test objects
  • Test utilities for common testing patterns
  • Temporary workspace management for isolated testing

Files

test_framework.go

Core testing utilities including:

  • TempWorkspace: Manages temporary workspace directories for testing
  • TestContext(): Creates test contexts with timeout
  • AssertNoError(), AssertError(), AssertEqual(): Custom assertions
  • Eventually(): Retries conditions until success or timeout
  • WaitFor(): Waits for channels with timeout

Example:

func TestSomething(t *testing.T) {
    ws := framework.NewTempWorkspace(t)
    defer ws.Cleanup()

    // Use workspace.Path() for testing
    config := &config.Config{
        Agents: config.AgentsConfig{
            Defaults: config.AgentDefaults{
                Workspace: ws.Path(),
            },
        },
    }

    // Test implementation...
}
mocks.go

Mock implementations for all major interfaces:

MockLLMProvider

Configurable mock LLM provider for testing Agent components.

provider := framework.NewMockLLMProvider()
provider.SetResponses([]string{"Response 1", "Response 2"})
provider.SetDelay(100 * time.Millisecond)

// Later...
callCount := provider.GetCallCount()
calls := provider.GetCalls()
MockMessageBus

In-memory message bus for testing message flows.

msgBus := framework.NewMockMessageBus()

// Publish test message
msg := framework.NewMessageBuilder().
    WithChannel("test").
    WithContent("Hello").
    BuildInbound()

msgBus.PublishInbound(ctx, msg)

// Verify messages
inbound := msgBus.GetInboundMessages()
MockChannel

Mock channel implementation for testing channel management.

channel := framework.NewMockChannel("test-channel")
channel.SetAllowed(map[string]bool{"user123": true})

// Start and test
if err := channel.Start(); err != nil {
    t.Fatal(err)
}

// Verify state
if !channel.IsRunning() {
    t.Error("Channel should be running")
}
MockSecurityAuditor

Mock security auditor for testing permission flows.

auditor := framework.NewMockSecurityAuditor()
auditor.SetAllowed(false)
auditor.SetPolicy("file_write", "/tmp/test.txt", true)

allowed, err := auditor.RequestPermission(ctx, "file_write", "/tmp/test.txt", nil)
builders.go

Fluent API builders for creating test data:

MessageBuilder
msg := framework.NewMessageBuilder().
    WithChannel("rpc").
    WithSenderID("user123").
    WithChatID("chat456").
    WithContent("Hello, Bot!").
    WithSessionKey("session:789").
    BuildInbound()
OutboundMessageBuilder
msg := framework.NewOutboundMessageBuilder().
    WithChannel("discord").
    WithChatID("channel-id").
    WithContent("Response").
    WithMetadata("source", "test").
    Build()
PayloadBuilder
payload := framework.NewPayloadBuilder().
    WithMessage("Test message").
    WithAction("test_action").
    WithSenderID("user123").
    WithTimestamp(time.Now().Unix()).
    Build()
MockConfigBuilder
config := framework.NewMockConfigBuilder().
    WithWorkspace(ws.Path()).
    WithLLM("mock/model").
    WithMaxTokens(4000).
    WithMaxToolIterations(10).
    WithConcurrentRequestMode("queue").
    WithQueueSize(16).
    WithRestrictToWorkspace(true).
    WithAgent("agent1", "Agent 1").
    Build()

Test Patterns

Table-Driven Tests
func TestFunction(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    string
        wantErr bool
    }{
        {"basic", "input", "output", false},
        {"error", "", "", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Function(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("Function() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("Function() = %v, want %v", got, tt.want)
            }
        })
    }
}
Concurrent Testing
func TestConcurrent(t *testing.T) {
    var wg sync.WaitGroup
    numGoroutines := 100

    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            // Concurrent operation
        }(i)
    }

    wg.Wait()
}
Temporary File Testing
func TestFileOperations(t *testing.T) {
    ws := framework.NewTempWorkspace(t)

    // Create test file
    err := ws.CreateFile("test.txt", "content")
    if err != nil {
        t.Fatal(err)
    }

    // Test file operations...
}
Eventually Pattern
func TestAsyncOperation(t *testing.T) {
    startAsyncOperation()

    framework.Eventually(t, func() bool {
        return checkCondition()
    }, 5*time.Second, "condition not met")
}

Running Tests

Run All Tests
go test ./module/...
Run Specific Module Tests
go test ./module/agent/...
go test ./module/channels/...
Run with Coverage
go test -coverprofile=coverage.out ./module/...
go tool cover -html=coverage.out
Run with Race Detector
go test -race ./module/...
Run Verbose Tests
go test -v ./module/...

Coverage Reports

Use the coverage report script to generate comprehensive coverage reports:

./test/scripts/coverage_report.sh

This generates:

  • Console output with module-by-module coverage
  • Combined coverage file at test/coverage/coverage.out
  • HTML report at test/coverage/coverage.html

Conventions

Test File Organization
  • Unit tests: test/unit/<module>/...
  • Integration tests: test/integration/...
  • Performance tests: test/performance/...
Test Naming
  • Test functions: Test<FunctionName> or Test<FunctionName>_<Scenario>
  • Example: TestLoop_ProcessMessage_Success
Setup/Teardown

Use t.Cleanup() for cleanup:

func TestSomething(t *testing.T) {
    file := setupFile(t)
    defer t.Cleanup(func() {
        os.Remove(file)
    })

    // Test...
}
Parallel Tests

Mark independent tests as parallel:

func TestParallelOperation(t *testing.T) {
    t.Parallel()
    // Test...
}

Best Practices

  1. Use table-driven tests for multiple scenarios
  2. Test both success and error paths
  3. Use mocks to isolate dependencies
  4. Test concurrent access with race detector
  5. Use temporary directories via t.TempDir() or TempWorkspace
  6. Keep tests fast - avoid sleep, use channels/context
  7. Make tests readable - clear names, good structure
  8. Test edge cases - empty inputs, nil values, boundaries

Troubleshooting

Tests Timing Out
  • Use shorter timeouts in test contexts
  • Avoid long delays in mocks
  • Check for deadlocks
Race Detector Failures
  • Add mutexes around shared state
  • Use atomic operations for counters
  • Check for data races in mocks
Flaky Tests
  • Avoid time-based assertions
  • Use Eventually pattern for async operations
  • Ensure proper cleanup between tests

Contributing

When adding new tests:

  1. Use the framework utilities and builders
  2. Follow existing patterns and conventions
  3. Ensure >95% coverage for new code
  4. Run tests with race detector before committing
  5. Update this documentation for new patterns

Documentation

Overview

Package framework provides comprehensive testing utilities for NemesisBot

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertEqual

func AssertEqual(t *testing.T, got, want interface{}, msg ...interface{})

AssertEqual fails the test if got != want

func AssertError

func AssertError(t *testing.T, err error, msg ...interface{})

AssertError fails the test if err is nil

func AssertNoError

func AssertNoError(t *testing.T, err error, msg ...interface{})

AssertNoError fails the test if err is not nil

func AssertNotNil

func AssertNotNil(t *testing.T, value interface{}, msg ...interface{})

AssertNotNil fails the test if value is nil

func Eventually

func Eventually(t *testing.T, condition func() bool, timeout time.Duration, msg string)

Eventually retries a condition until it succeeds or times out

func TestContext

func TestContext(t *testing.T, timeout time.Duration) context.Context

TestContext provides a test context with timeout

func WaitFor

func WaitFor(t *testing.T, timeout time.Duration, msg string) (chan struct{}, <-chan string)

WaitFor waits for a channel to have a value or timeout Returns a done channel that should be closed when the operation completes Also returns an error channel that will receive a timeout error message The caller should select on both channels in the main goroutine:

done, errCh := WaitFor(t, timeout, "operation")
select {
case <-done:
    // Operation completed successfully
case err := <-errCh:
    t.Fatal(err)
}

Or simply:

done, _ := WaitFor(t, timeout, "operation")
<-done  // Will block until timeout or completion

Types

type AuditLogEntry

type AuditLogEntry struct {
	Operation string
	Target    string
	Allowed   bool
}

type LLMCall

type LLMCall struct {
	Messages []providers.Message
	Tools    []providers.ToolDefinition
	Model    string
}

type MediaBuilder

type MediaBuilder struct {
	// contains filtered or unexported fields
}

MediaBuilder helps build test media URLs (media is represented as strings)

func NewMediaBuilder

func NewMediaBuilder() *MediaBuilder

NewMediaBuilder creates a new media builder

func (*MediaBuilder) AddURL

func (b *MediaBuilder) AddURL(url string) *MediaBuilder

AddURL adds a media URL

func (*MediaBuilder) Build

func (b *MediaBuilder) Build() []string

Build builds the media URL list

type MessageBuilder

type MessageBuilder struct {
	// contains filtered or unexported fields
}

MessageBuilder helps build test messages

func NewMessageBuilder

func NewMessageBuilder() *MessageBuilder

NewMessageBuilder creates a new message builder

func (*MessageBuilder) BuildInbound

func (b *MessageBuilder) BuildInbound() bus.InboundMessage

BuildInbound builds an inbound message

func (*MessageBuilder) BuildOutbound

func (b *MessageBuilder) BuildOutbound() bus.OutboundMessage

BuildOutbound builds an outbound message

func (*MessageBuilder) WithChannel

func (b *MessageBuilder) WithChannel(channel string) *MessageBuilder

WithChannel sets the channel

func (*MessageBuilder) WithChatID

func (b *MessageBuilder) WithChatID(chatID string) *MessageBuilder

WithChatID sets the chat ID

func (*MessageBuilder) WithContent

func (b *MessageBuilder) WithContent(content string) *MessageBuilder

WithContent sets the message content

func (*MessageBuilder) WithCorrelationID

func (b *MessageBuilder) WithCorrelationID(correlationID string) *MessageBuilder

WithCorrelationID sets the correlation ID

func (*MessageBuilder) WithMedia

func (b *MessageBuilder) WithMedia(media []string) *MessageBuilder

WithMedia adds media to the message

func (*MessageBuilder) WithSenderID

func (b *MessageBuilder) WithSenderID(senderID string) *MessageBuilder

WithSenderID sets the sender ID

func (*MessageBuilder) WithSessionKey

func (b *MessageBuilder) WithSessionKey(sessionKey string) *MessageBuilder

WithSessionKey sets the session key

type MessageListBuilder

type MessageListBuilder struct {
	// contains filtered or unexported fields
}

MessageListBuilder helps build lists of messages for conversation history

func NewMessageListBuilder

func NewMessageListBuilder() *MessageListBuilder

NewMessageListBuilder creates a new message list builder

func (*MessageListBuilder) AddAssistant

func (b *MessageListBuilder) AddAssistant(content string) *MessageListBuilder

AddAssistant adds an assistant message

func (*MessageListBuilder) AddSystem

func (b *MessageListBuilder) AddSystem(content string) *MessageListBuilder

AddSystem adds a system message

func (*MessageListBuilder) AddTool

func (b *MessageListBuilder) AddTool(toolID, content string) *MessageListBuilder

AddTool adds a tool result message

func (*MessageListBuilder) AddUser

func (b *MessageListBuilder) AddUser(content string) *MessageListBuilder

AddUser adds a user message

func (*MessageListBuilder) Build

func (b *MessageListBuilder) Build() []map[string]interface{}

Build builds the message list

type MockChannel

type MockChannel struct {
	// contains filtered or unexported fields
}

MockChannel is a mock channel implementation

func NewMockChannel

func NewMockChannel(name string) *MockChannel

NewMockChannel creates a new mock channel

func (*MockChannel) AddSyncTarget

func (m *MockChannel) AddSyncTarget(channelName string)

AddSyncTarget adds a sync target

func (*MockChannel) GetMessages

func (m *MockChannel) GetMessages() []bus.OutboundMessage

GetMessages returns all sent messages

func (*MockChannel) GetSyncTargets

func (m *MockChannel) GetSyncTargets() []string

GetSyncTargets returns the sync targets

func (*MockChannel) IsAllowed

func (m *MockChannel) IsAllowed(senderID string) bool

IsAllowed checks if a sender is allowed

func (*MockChannel) IsRunning

func (m *MockChannel) IsRunning() bool

IsRunning returns whether the channel is running

func (*MockChannel) Name

func (m *MockChannel) Name() string

Name returns the channel name

func (*MockChannel) Send

func (m *MockChannel) Send(ctx context.Context, msg bus.OutboundMessage) error

Send sends a message

func (*MockChannel) SetAllowed

func (m *MockChannel) SetAllowed(allowed map[string]bool)

SetAllowed sets the allowed senders

func (*MockChannel) SetSendError

func (m *MockChannel) SetSendError(err error)

SetSendError sets an error to return on Send

func (*MockChannel) SetStartError

func (m *MockChannel) SetStartError(err error)

SetStartError sets an error to return on Start

func (*MockChannel) SetStopError

func (m *MockChannel) SetStopError(err error)

SetStopError sets an error to return on Stop

func (*MockChannel) Start

func (m *MockChannel) Start() error

Start starts the channel

func (*MockChannel) Stop

func (m *MockChannel) Stop() error

Stop stops the channel

type MockConfigBuilder

type MockConfigBuilder struct {
	// contains filtered or unexported fields
}

MockConfigBuilder helps build test configurations

func NewMockConfigBuilder

func NewMockConfigBuilder() *MockConfigBuilder

NewMockConfigBuilder creates a new mock config builder

func (*MockConfigBuilder) Build

func (b *MockConfigBuilder) Build() *config.Config

Build returns the built configuration

func (*MockConfigBuilder) WithAgent

func (b *MockConfigBuilder) WithAgent(id, name string) *MockConfigBuilder

WithAgent adds an agent configuration

func (*MockConfigBuilder) WithConcurrentRequestMode

func (b *MockConfigBuilder) WithConcurrentRequestMode(mode string) *MockConfigBuilder

WithConcurrentRequestMode sets the concurrent request mode

func (*MockConfigBuilder) WithLLM

func (b *MockConfigBuilder) WithLLM(model string) *MockConfigBuilder

WithLLM sets the LLM model

func (*MockConfigBuilder) WithMaxTokens

func (b *MockConfigBuilder) WithMaxTokens(tokens int) *MockConfigBuilder

WithMaxTokens sets the max tokens

func (*MockConfigBuilder) WithMaxToolIterations

func (b *MockConfigBuilder) WithMaxToolIterations(iterations int) *MockConfigBuilder

WithMaxToolIterations sets the max tool iterations

func (*MockConfigBuilder) WithQueueSize

func (b *MockConfigBuilder) WithQueueSize(size int) *MockConfigBuilder

WithQueueSize sets the queue size

func (*MockConfigBuilder) WithRestrictToWorkspace

func (b *MockConfigBuilder) WithRestrictToWorkspace(restrict bool) *MockConfigBuilder

WithRestrictToWorkspace sets whether to restrict to workspace

func (*MockConfigBuilder) WithWorkspace

func (b *MockConfigBuilder) WithWorkspace(path string) *MockConfigBuilder

WithWorkspace sets the workspace path

type MockLLMProvider

type MockLLMProvider struct {
	// contains filtered or unexported fields
}

MockLLMProvider is a configurable mock LLM provider for testing

func NewMockLLMProvider

func NewMockLLMProvider() *MockLLMProvider

NewMockLLMProvider creates a new mock LLM provider

func (*MockLLMProvider) Chat

func (m *MockLLMProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]interface{}) (*providers.LLMResponse, error)

Chat implements the LLMProvider interface

func (*MockLLMProvider) GetCallCount

func (m *MockLLMProvider) GetCallCount() int

GetCallCount returns the number of times Chat was called

func (*MockLLMProvider) GetCalls

func (m *MockLLMProvider) GetCalls() []LLMCall

GetCalls returns the list of calls made to Chat

func (*MockLLMProvider) GetDefaultModel

func (m *MockLLMProvider) GetDefaultModel() string

GetDefaultModel returns the default model

func (*MockLLMProvider) Reset

func (m *MockLLMProvider) Reset()

Reset resets the mock state

func (*MockLLMProvider) SetCustomResponse

func (m *MockLLMProvider) SetCustomResponse(response *providers.LLMResponse)

SetCustomResponse sets a custom response to return

func (*MockLLMProvider) SetDelay

func (m *MockLLMProvider) SetDelay(delay time.Duration)

SetDelay sets the delay before responding

func (*MockLLMProvider) SetError

func (m *MockLLMProvider) SetError(shouldError bool)

SetError sets whether to return an error

func (*MockLLMProvider) SetResponses

func (m *MockLLMProvider) SetResponses(responses []string)

SetResponses sets the responses to return

type MockMessageBus

type MockMessageBus struct {
	// contains filtered or unexported fields
}

MockMessageBus is an in-memory message bus for testing

func NewMockMessageBus

func NewMockMessageBus() *MockMessageBus

NewMockMessageBus creates a new mock message bus

func (*MockMessageBus) Clear

func (m *MockMessageBus) Clear()

Clear clears all messages

func (*MockMessageBus) GetInboundMessages

func (m *MockMessageBus) GetInboundMessages() []bus.InboundMessage

GetInboundMessages returns all published inbound messages

func (*MockMessageBus) GetOutboundMessages

func (m *MockMessageBus) GetOutboundMessages() []bus.OutboundMessage

GetOutboundMessages returns all published outbound messages

func (*MockMessageBus) OutboundChannel

func (m *MockMessageBus) OutboundChannel() <-chan bus.OutboundMessage

OutboundChannel returns the outbound message channel

func (*MockMessageBus) PublishInbound

func (m *MockMessageBus) PublishInbound(ctx context.Context, msg bus.InboundMessage) error

PublishInbound publishes an inbound message

func (*MockMessageBus) PublishOutbound

func (m *MockMessageBus) PublishOutbound(ctx context.Context, msg bus.OutboundMessage) error

PublishOutbound publishes an outbound message

func (*MockMessageBus) SubscribeInbound

func (m *MockMessageBus) SubscribeInbound() <-chan bus.InboundMessage

SubscribeInbound subscribes to inbound messages

type MockSecurityAuditor

type MockSecurityAuditor struct {
	// contains filtered or unexported fields
}

MockSecurityAuditor is a mock security auditor

func NewMockSecurityAuditor

func NewMockSecurityAuditor() *MockSecurityAuditor

NewMockSecurityAuditor creates a new mock security auditor

func (*MockSecurityAuditor) ClearLogs

func (m *MockSecurityAuditor) ClearLogs()

ClearLogs clears all audit logs

func (*MockSecurityAuditor) GetLogs

func (m *MockSecurityAuditor) GetLogs() []AuditLogEntry

GetLogs returns all audit logs

func (*MockSecurityAuditor) RequestPermission

func (m *MockSecurityAuditor) RequestPermission(ctx context.Context, permissionType, target string, metadata map[string]interface{}) (bool, error)

RequestPermission requests permission for an operation

func (*MockSecurityAuditor) SetAllowed

func (m *MockSecurityAuditor) SetAllowed(allowed bool)

SetAllowed sets whether to allow operations

func (*MockSecurityAuditor) SetPolicy

func (m *MockSecurityAuditor) SetPolicy(permissionType, target string, allowed bool)

SetPolicy sets a policy for a specific operation

type OutboundMessageBuilder

type OutboundMessageBuilder struct {
	// contains filtered or unexported fields
}

OutboundMessageBuilder helps build outbound messages

func NewOutboundMessageBuilder

func NewOutboundMessageBuilder() *OutboundMessageBuilder

NewOutboundMessageBuilder creates a new outbound message builder

func (*OutboundMessageBuilder) Build

Build builds the outbound message

func (*OutboundMessageBuilder) WithChannel

func (b *OutboundMessageBuilder) WithChannel(channel string) *OutboundMessageBuilder

WithChannel sets the channel

func (*OutboundMessageBuilder) WithChatID

func (b *OutboundMessageBuilder) WithChatID(chatID string) *OutboundMessageBuilder

WithChatID sets the chat ID

func (*OutboundMessageBuilder) WithContent

func (b *OutboundMessageBuilder) WithContent(content string) *OutboundMessageBuilder

WithContent sets the content

type PayloadBuilder

type PayloadBuilder struct {
	// contains filtered or unexported fields
}

PayloadBuilder helps build test payloads for RPC calls

func NewPayloadBuilder

func NewPayloadBuilder() *PayloadBuilder

NewPayloadBuilder creates a new payload builder

func (*PayloadBuilder) Build

func (b *PayloadBuilder) Build() map[string]interface{}

Build builds the payload

func (*PayloadBuilder) With

func (b *PayloadBuilder) With(key string, value interface{}) *PayloadBuilder

With sets a key-value pair

func (*PayloadBuilder) WithAction

func (b *PayloadBuilder) WithAction(action string) *PayloadBuilder

WithAction sets the action

func (*PayloadBuilder) WithChatID

func (b *PayloadBuilder) WithChatID(chatID string) *PayloadBuilder

WithChatID sets the chat ID

func (*PayloadBuilder) WithMessage

func (b *PayloadBuilder) WithMessage(msg string) *PayloadBuilder

WithMessage sets the message

func (*PayloadBuilder) WithSenderID

func (b *PayloadBuilder) WithSenderID(senderID string) *PayloadBuilder

WithSenderID sets the sender ID

func (*PayloadBuilder) WithSessionKey

func (b *PayloadBuilder) WithSessionKey(sessionKey string) *PayloadBuilder

WithSessionKey sets the session key

func (*PayloadBuilder) WithTimestamp

func (b *PayloadBuilder) WithTimestamp(timestamp int64) *PayloadBuilder

WithTimestamp sets the timestamp

type TempWorkspace

type TempWorkspace struct {
	// contains filtered or unexported fields
}

TempWorkspace provides a temporary workspace directory for testing

func NewTempWorkspace

func NewTempWorkspace(t *testing.T) *TempWorkspace

NewTempWorkspace creates a new temporary workspace for testing

func (*TempWorkspace) Cleanup

func (w *TempWorkspace) Cleanup()

Cleanup removes all temporary files

func (*TempWorkspace) CreateFile

func (w *TempWorkspace) CreateFile(name, content string) error

CreateFile creates a file in the workspace with the given content

func (*TempWorkspace) LogsPath

func (w *TempWorkspace) LogsPath() string

LogsPath returns the logs directory path

func (*TempWorkspace) MemoryPath

func (w *TempWorkspace) MemoryPath() string

MemoryPath returns the memory directory path

func (*TempWorkspace) Path

func (w *TempWorkspace) Path() string

Path returns the workspace path

func (*TempWorkspace) Root

func (w *TempWorkspace) Root() string

Root returns the root temp directory

func (*TempWorkspace) SessionsPath

func (w *TempWorkspace) SessionsPath() string

SessionsPath returns the sessions directory path

func (*TempWorkspace) StatePath

func (w *TempWorkspace) StatePath() string

StatePath returns the state directory path

type ToolCallBuilder

type ToolCallBuilder struct {
	// contains filtered or unexported fields
}

ToolCallBuilder helps build tool calls for testing

func NewToolCallBuilder

func NewToolCallBuilder() *ToolCallBuilder

NewToolCallBuilder creates a new tool call builder

func (*ToolCallBuilder) Build

func (b *ToolCallBuilder) Build() map[string]interface{}

Build builds the tool call

func (*ToolCallBuilder) WithArgument

func (b *ToolCallBuilder) WithArgument(key string, value interface{}) *ToolCallBuilder

WithArgument sets an argument

func (*ToolCallBuilder) WithArguments

func (b *ToolCallBuilder) WithArguments(args map[string]interface{}) *ToolCallBuilder

WithArguments sets all arguments

func (*ToolCallBuilder) WithID

func (b *ToolCallBuilder) WithID(id string) *ToolCallBuilder

WithID sets the tool call ID

func (*ToolCallBuilder) WithName

func (b *ToolCallBuilder) WithName(name string) *ToolCallBuilder

WithName sets the tool name