testing

package
v0.0.0-...-6deb405 Latest Latest
Warning

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

Go to latest
Published: Apr 12, 2026 License: MIT Imports: 13 Imported by: 0

README

Agent Testing Framework

This package provides a comprehensive testing framework for CryptoFunk trading agents. It enables testing agents in isolation without requiring real MCP servers, external APIs, or message brokers.

Overview

The testing framework consists of three main components:

  1. MockMCPServer (mock_mcp_server.go) - Simulates MCP servers without spawning processes
  2. Test Fixtures (fixtures.go) - Provides realistic sample data for all agent types
  3. Test Helpers (helpers.go) - High-level utilities for test setup, teardown, and assertions

Quick Start

import (
    "testing"
    agenttesting "github.com/ajitpratap0/cryptofunk/internal/agents/testing"
)

func TestMyAgent(t *testing.T) {
    // Create test helper (handles cleanup automatically)
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Create a test agent
    agent := helper.CreateTestAgent("my-agent", "technical", 9900)

    // Create mock MCP server with tools
    server := helper.CreateMockMarketDataServer()

    // Call tool and verify behavior
    result, err := server.CallTool(helper.Context(), &mcp.CallToolParams{
        Name: "get_price",
        Arguments: map[string]interface{}{
            "symbol": "BTC/USDT",
        },
    })

    require.NoError(t, err)
    helper.AssertMCPCallMade(server, "get_price", 1)
}

Components

1. MockMCPServer

The MockMCPServer simulates an MCP server for testing without spawning real processes.

Key Features
  • Thread-safe: All operations are protected by mutex
  • Tool registration: Register tools with custom handler functions
  • Call recording: Automatically records all tool calls for verification
  • MCP protocol compliance: Implements CallTool and ListTools interfaces
Usage
// Create mock server
server := agenttesting.NewMockMCPServer("market-data", "1.0.0")

// Register a tool with handler
server.RegisterTool(
    tools.GetPriceTool(),
    func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
        return map[string]interface{}{
            "symbol": args["symbol"],
            "price":  42800.0,
        }, nil
    },
)

// Call tool
result, err := server.CallTool(ctx, &mcp.CallToolParams{
    Name: "get_price",
    Arguments: map[string]interface{}{"symbol": "BTC/USDT"},
})

// Verify calls
callCount := server.GetCallCount("get_price")
lastCall := server.GetLastCall("get_price")
allCalls := server.GetCalls()

// Reset call history
server.Reset()
2. Test Fixtures

Fixtures provide realistic sample data for testing. All fixtures return map[string]interface{} that can be easily customized.

Available Fixtures

Market Data (MarketDataFixtures):

fixtures := agenttesting.MarketDataFixtures{}

// Get sample price data
price := fixtures.SamplePrice()
// Returns: {"symbol": "BTC/USDT", "price": 42800.0, "volume": 1520.8, "change": 1.42}

// Get sample OHLCV data (3 candles)
ohlcv := fixtures.SampleOHLCV()
// Returns: {"symbol": "BTC/USDT", "timeframe": "1h", "data": [[timestamp, o, h, l, c, v], ...]}

// Get sample order book
orderBook := fixtures.SampleOrderBook()
// Returns: {"symbol": "BTC/USDT", "bids": [[price, size], ...], "asks": [[price, size], ...]}

Technical Indicators (TechnicalIndicatorFixtures):

fixtures := agenttesting.TechnicalIndicatorFixtures{}

// RSI data
rsi := fixtures.SampleRSI()
// Returns: {"indicator": "rsi", "period": 14, "values": [65.2, 68.5, 72.1, 69.8], "signal": "neutral"}

// MACD data
macd := fixtures.SampleMACD()
// Returns: {"indicator": "macd", "macd": [...], "signal": [...], "histogram": [...], "trend": "bullish"}

// Bollinger Bands
bollinger := fixtures.SampleBollinger()
// Returns: {"indicator": "bollinger", "upper": [...], "middle": [...], "lower": [...], "bandwidth": 4.7}

News & Sentiment (NewsFixtures):

fixtures := agenttesting.NewsFixtures{}

// News articles with sentiment
news := fixtures.SampleNews()
// Returns: {"articles": [...], "overall_sentiment": "positive", "confidence": 0.68}

// Fear & Greed Index
fgi := fixtures.SampleFearGreedIndex()
// Returns: {"value": 65, "classification": "greed", "timestamp": 1234567890}

Risk Management (RiskFixtures):

fixtures := agenttesting.RiskFixtures{}

// Risk limits
limits := fixtures.SampleRiskLimits()
// Returns: {"max_position_size": 10000.0, "max_leverage": 3.0, "max_drawdown": 0.15, ...}

// Portfolio data
portfolio := fixtures.SamplePortfolio()
// Returns: {"total_value": 50000.0, "cash": 20000.0, "positions": [...]}

Order Execution (OrderExecutionFixtures):

fixtures := agenttesting.OrderExecutionFixtures{}

// Market order result
marketOrder := fixtures.SampleMarketOrder()
// Returns: {"order_id": "...", "symbol": "BTC/USDT", "side": "buy", "status": "filled", ...}

// Limit order result
limitOrder := fixtures.SampleLimitOrder()
// Returns: {"order_id": "...", "symbol": "BTC/USDT", "side": "sell", "type": "limit", ...}
Common MCP Tools

Pre-defined tool schemas for common operations:

tools := agenttesting.CommonTools{}

// Market data tools
priceTool := tools.GetPriceTool()       // get_price tool definition
ohlcvTool := tools.GetOHLCVTool()       // get_ohlcv tool definition

// Technical indicator tools
rsiTool := tools.CalculateRSITool()     // calculate_rsi tool definition

// Order execution tools
orderTool := tools.PlaceOrderTool()     // place_market_order tool definition
3. Test Helpers

The AgentTestHelper provides high-level utilities for test setup, teardown, and assertions.

Creating Test Helper
func TestMyFeature(t *testing.T) {
    // Create helper with automatic cleanup
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Helper provides:
    // - Context with 30s timeout: helper.Context()
    // - Structured logger: helper.Logger()
    // - Automatic cleanup registration
}
Logging

By default, logs are disabled during tests. Enable with environment variable:

TEST_LOG=1 go test ./...
Factory Methods

Create Test Agent:

// Creates agent with automatic cleanup
agent := helper.CreateTestAgent("technical-agent", "technical", 9900)

// Agent is automatically shut down on test completion

Create Mock Servers:

// Market data server with get_price and get_ohlcv tools
marketServer := helper.CreateMockMarketDataServer()

// Technical indicators server with calculate_rsi tool
indicatorServer := helper.CreateMockTechnicalIndicatorsServer()

// Order executor server with place_market_order tool
orderServer := helper.CreateMockOrderExecutorServer()
Assertion Utilities

Verify Tool Calls:

// Assert tool was called specific number of times
helper.AssertMCPCallMade(server, "get_price", 3)

// Assert tool was called with specific arguments
helper.AssertMCPCallArguments(server, "get_ohlcv", map[string]interface{}{
    "symbol":    "BTC/USDT",
    "timeframe": "1h",
    "limit":     100,
})

Wait for Conditions:

// Poll condition with timeout
success := helper.WaitForCondition(
    func() bool { return agent.IsRunning() },
    1000,  // milliseconds
    "agent should be running",
)
Cleanup Management
// Register custom cleanup function
helper.AddCleanup(func() {
    // Custom cleanup code
})

// All cleanup functions run in reverse order on defer helper.Cleanup()
Metrics Port Allocation
// Get unique metrics port for test
port := agenttesting.GetTestMetricsPort(t)
// Returns port in range 9900-9999 based on test name hash

Usage Patterns

Pattern 1: Testing Agent with Mock Tools
func TestAgentUsesMarketData(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Create mock server
    server := helper.CreateMockMarketDataServer()

    // Simulate agent calling tools
    result, err := server.CallTool(helper.Context(), &mcp.CallToolParams{
        Name: "get_price",
        Arguments: map[string]interface{}{"symbol": "BTC/USDT"},
    })

    require.NoError(t, err)

    // Verify call was made
    helper.AssertMCPCallMade(server, "get_price", 1)
}
Pattern 2: Testing with Custom Handlers
func TestAgentHandlesErrors(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    server := agenttesting.NewMockMCPServer("test", "1.0.0")

    // Register tool with error handler
    tools := agenttesting.CommonTools{}
    server.RegisterTool(
        tools.GetPriceTool(),
        func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
            return nil, errors.New("API rate limit exceeded")
        },
    )

    // Test error handling
    _, err := server.CallTool(helper.Context(), &mcp.CallToolParams{
        Name: "get_price",
        Arguments: map[string]interface{}{"symbol": "BTC/USDT"},
    })

    require.Error(t, err)
    assert.Contains(t, err.Error(), "rate limit")
}
Pattern 3: Testing Agent Configuration
func TestAgentConfiguration(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Create custom config
    config := agenttesting.TestAgentConfig("custom-agent", "technical")
    config.Config["threshold"] = 0.7
    config.Config["timeframe"] = "1h"

    // Create agent with custom config
    agent := agents.NewBaseAgent(config, helper.Logger(), 9900)

    // Verify configuration
    assert.Equal(t, "custom-agent", agent.GetName())
    // Test agent behavior with custom config...
}
Pattern 4: Testing Multiple Agents
func TestMultiAgentCoordination(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Create multiple agents
    technical := helper.CreateTestAgent("technical", "technical", 9900)
    sentiment := helper.CreateTestAgent("sentiment", "sentiment", 9901)
    risk := helper.CreateTestAgent("risk", "risk", 9902)

    // Create shared mock server
    server := helper.CreateMockMarketDataServer()

    // Test coordination logic...
}
Pattern 5: Testing with Realistic Data
func TestAgentProcessesMarketData(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    // Use fixtures for realistic data
    fixtures := agenttesting.MarketDataFixtures{}
    ohlcv := fixtures.SampleOHLCV()

    server := agenttesting.NewMockMCPServer("market-data", "1.0.0")
    tools := agenttesting.CommonTools{}

    server.RegisterTool(
        tools.GetOHLCVTool(),
        func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
            return ohlcv, nil
        },
    )

    // Test agent processing...
}

Best Practices

1. Always Use defer Cleanup
func TestSomething(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()  // Critical: ensures cleanup even on test failure
    // ...
}
2. Use Factory Methods for Common Servers
// Good: Use pre-configured factory methods
server := helper.CreateMockMarketDataServer()

// Avoid: Manually configuring every tool
server := agenttesting.NewMockMCPServer("market-data", "1.0.0")
server.RegisterTool(...) // Repetitive
server.RegisterTool(...) // Repetitive
3. Test One Scenario Per Test
// Good: Focused test
func TestAgentHandlesRateLimit(t *testing.T) { ... }
func TestAgentHandlesTimeout(t *testing.T) { ... }

// Avoid: Testing multiple scenarios in one test
func TestAgentErrorHandling(t *testing.T) {
    // Tests rate limits, timeouts, invalid data...
}
4. Use Assertions for Verification
// Good: Clear assertion with helper
helper.AssertMCPCallMade(server, "get_price", 1)

// Avoid: Manual verification
if server.GetCallCount("get_price") != 1 {
    t.Errorf("Expected 1 call, got %d", server.GetCallCount("get_price"))
}
5. Reset Mock State Between Subtests
func TestMultipleScenarios(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()

    server := helper.CreateMockMarketDataServer()

    t.Run("scenario1", func(t *testing.T) {
        // ...
        server.Reset() // Clear call history
    })

    t.Run("scenario2", func(t *testing.T) {
        // ...
    })
}

Integration with Existing Tests

This framework is designed to complement existing unit tests:

  • Unit tests: Test individual functions in isolation (e.g., sentiment scoring logic)
  • Integration tests (this framework): Test agent behavior with mocked dependencies
  • E2E tests (future): Test full system with real infrastructure

Examples

See example_test.go for comprehensive examples demonstrating:

  • Basic agent testing
  • Tool registration and calling
  • Call history verification
  • Error handling
  • Using fixtures
  • Multi-agent scenarios
  • Condition waiting
  • Custom handlers

Troubleshooting

"Tool not found" errors

Ensure the tool is registered before calling:

server := agenttesting.NewMockMCPServer("test", "1.0.0")
tools := agenttesting.CommonTools{}
server.RegisterTool(tools.GetPriceTool(), handler)  // Must register first
Context timeout errors

Default context timeout is 30 seconds. For longer tests, create custom context:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Use ctx instead of helper.Context()
Cleanup not running

Always use defer helper.Cleanup():

func TestSomething(t *testing.T) {
    helper := agenttesting.NewAgentTestHelper(t)
    defer helper.Cleanup()  // Must be deferred immediately
    // ...
}
Port conflicts in parallel tests

Use GetTestMetricsPort(t) to get unique port per test:

port := agenttesting.GetTestMetricsPort(t)
agent := helper.CreateTestAgent("test", "technical", port)

Future Enhancements

Planned improvements (see T074, T075 in TASKS.md):

  • Mock Orchestrator: Simulate orchestrator for testing signal publishing
  • NATS Mocking: Mock NATS message broker for event-driven testing
  • Viper Config Mocking: Utilities for mocking global configuration
  • Performance Benchmarks: Benchmark utilities for measuring agent performance
  • Test Recorder: Record real MCP interactions for replay in tests

Contributing

When adding new fixtures or helpers:

  1. Add to appropriate file (fixtures.go, helpers.go, mock_mcp_server.go)
  2. Include example usage in example_test.go
  3. Document in this README
  4. Ensure thread-safety for mock implementations
  5. Follow existing naming conventions
  • TASKS.md - See T073 (this framework), T074 (mock orchestrator), T075 (benchmarks)
  • internal/agents/base.go - BaseAgent implementation
  • docs/MCP_INTEGRATION.md - MCP protocol details
  • T072 Completion Report - Background on testing requirements

Documentation

Overview

Package testing provides utilities for testing trading agents

Package testing provides utilities for testing trading agents

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddMockMCPServer

func AddMockMCPServer(config *agents.AgentConfig, serverName, url string)

AddMockMCPServer adds a mock MCP server configuration to an agent config. url is the Streamable HTTP endpoint of the server (e.g., "http://localhost:8093/mcp").

func GetTestMetricsPort

func GetTestMetricsPort(t *testing.T) int

GetTestMetricsPort returns a unique metrics port for testing

func TestAgentConfig

func TestAgentConfig(name, agentType string) *agents.AgentConfig

TestAgentConfig creates a standard test agent configuration

Types

type AgentTestHelper

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

AgentTestHelper provides utilities for testing agents

func NewAgentTestHelper

func NewAgentTestHelper(t *testing.T) *AgentTestHelper

NewAgentTestHelper creates a new test helper

func (*AgentTestHelper) AddCleanup

func (h *AgentTestHelper) AddCleanup(fn func())

AddCleanup registers a cleanup function

func (*AgentTestHelper) AssertMCPCallArguments

func (h *AgentTestHelper) AssertMCPCallArguments(server *MockMCPServer, toolName string, expectedArgs map[string]interface{})

AssertMCPCallArguments verifies the arguments of the last call to a tool

func (*AgentTestHelper) AssertMCPCallMade

func (h *AgentTestHelper) AssertMCPCallMade(server *MockMCPServer, toolName string, expectedCount int)

AssertMCPCallMade verifies that a tool was called on a mock server

func (*AgentTestHelper) Cleanup

func (h *AgentTestHelper) Cleanup()

Cleanup runs all registered cleanup functions

func (*AgentTestHelper) Context

func (h *AgentTestHelper) Context() context.Context

Context returns the test context

func (*AgentTestHelper) CreateMockMarketDataServer

func (h *AgentTestHelper) CreateMockMarketDataServer() *MockMCPServer

CreateMockMarketDataServer creates a mock market data server with common tools

func (*AgentTestHelper) CreateMockOrderExecutorServer

func (h *AgentTestHelper) CreateMockOrderExecutorServer() *MockMCPServer

CreateMockOrderExecutorServer creates a mock order executor server

func (*AgentTestHelper) CreateMockTechnicalIndicatorsServer

func (h *AgentTestHelper) CreateMockTechnicalIndicatorsServer() *MockMCPServer

CreateMockTechnicalIndicatorsServer creates a mock technical indicators server

func (*AgentTestHelper) CreateTestAgent

func (h *AgentTestHelper) CreateTestAgent(name, agentType string, metricsPort int) *agents.BaseAgent

CreateTestAgent creates a basic test agent with no MCP servers

func (*AgentTestHelper) Logger

func (h *AgentTestHelper) Logger() zerolog.Logger

Logger returns the test logger

func (*AgentTestHelper) SimulateAgentStep

func (h *AgentTestHelper) SimulateAgentStep(agent *agents.BaseAgent) error

SimulateAgentStep simulates a single agent step without actually running the agent

func (*AgentTestHelper) WaitForCondition

func (h *AgentTestHelper) WaitForCondition(condition func() bool, timeout time.Duration, message string) bool

WaitForCondition waits for a condition to be true or timeout

type CommonTools

type CommonTools struct{}

CommonTools provides common MCP tool definitions for testing

func (CommonTools) CalculateRSITool

func (CommonTools) CalculateRSITool() *mcp.Tool

CalculateRSITool returns a standard calculate_rsi tool definition

func (CommonTools) GetOHLCVTool

func (CommonTools) GetOHLCVTool() *mcp.Tool

GetOHLCVTool returns a standard get_ohlcv tool definition

func (CommonTools) GetPriceTool

func (CommonTools) GetPriceTool() *mcp.Tool

GetPriceTool returns a standard get_price tool definition

func (CommonTools) PlaceOrderTool

func (CommonTools) PlaceOrderTool() *mcp.Tool

PlaceOrderTool returns a standard place_order tool definition

type Decision

type Decision struct {
	Action     string // BUY, SELL, HOLD
	Symbol     string
	Confidence float64
	Reasoning  string
	Timestamp  time.Time
	BasedOn    []string // Topics that contributed to decision
}

Decision represents a simulated orchestrator decision

func DefaultDecisionPolicy

func DefaultDecisionPolicy(signals []ReceivedSignal) *Decision

DefaultDecisionPolicy is a simple majority voting policy

type DecisionPolicy

type DecisionPolicy func(signals []ReceivedSignal) *Decision

DecisionPolicy determines how the mock orchestrator makes decisions

type MarketDataFixtures

type MarketDataFixtures struct{}

MarketDataFixtures provides common market data for testing

func (MarketDataFixtures) SampleOHLCV

func (MarketDataFixtures) SampleOHLCV() map[string]interface{}

SampleOHLCV returns sample OHLCV data for testing

func (MarketDataFixtures) SampleOrderBook

func (MarketDataFixtures) SampleOrderBook() map[string]interface{}

SampleOrderBook returns sample order book data

func (MarketDataFixtures) SamplePrice

func (MarketDataFixtures) SamplePrice() map[string]interface{}

SamplePrice returns sample price data

type MockMCPServer

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

MockMCPServer simulates an MCP server for testing agents

func NewMockMCPServer

func NewMockMCPServer(name, version string) *MockMCPServer

NewMockMCPServer creates a new mock MCP server

func (*MockMCPServer) CallTool

func (m *MockMCPServer) CallTool(ctx context.Context, params *mcp.CallToolParams) (*mcp.CallToolResult, error)

CallTool simulates calling a tool on the server

func (*MockMCPServer) GetCallCount

func (m *MockMCPServer) GetCallCount(toolName string) int

GetCallCount returns the number of times a tool was called

func (*MockMCPServer) GetCalls

func (m *MockMCPServer) GetCalls() []ToolCall

GetCalls returns all recorded tool calls

func (*MockMCPServer) GetLastCall

func (m *MockMCPServer) GetLastCall(toolName string) *ToolCall

GetLastCall returns the last call for a specific tool

func (*MockMCPServer) GetName

func (m *MockMCPServer) GetName() string

GetName returns the server name

func (*MockMCPServer) GetVersion

func (m *MockMCPServer) GetVersion() string

GetVersion returns the server version

func (*MockMCPServer) ListTools

func (m *MockMCPServer) ListTools(ctx context.Context, params *mcp.ListToolsParams) (*mcp.ListToolsResult, error)

ListTools returns all registered tools

func (*MockMCPServer) RegisterTool

func (m *MockMCPServer) RegisterTool(tool *mcp.Tool, handler ToolHandler)

RegisterTool registers a tool with its handler

func (*MockMCPServer) Reset

func (m *MockMCPServer) Reset()

Reset clears all recorded calls

type MockOrchestrator

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

MockOrchestrator simulates an orchestrator for testing agent signal publishing

Example

ExampleMockOrchestrator demonstrates basic usage of the MockOrchestrator

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"

	"github.com/ajitpratap0/cryptofunk/internal/agents/testing"
)

func main() {
	// Connect to NATS
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		fmt.Printf("Failed to connect to NATS: %v\n", err)
		return
	}
	defer nc.Close()

	// Create mock orchestrator
	mo, err := testing.NewMockOrchestrator(testing.MockOrchestratorConfig{
		NATSConn: nc,
		Topics: []string{
			"agents.analysis.technical",
			"agents.analysis.orderbook",
		},
	})
	if err != nil {
		fmt.Printf("Failed to create mock orchestrator: %v\n", err)
		return
	}

	// Start listening for signals
	ctx := context.Background()
	if err := mo.Start(ctx); err != nil {
		fmt.Printf("Failed to start mock orchestrator: %v\n", err)
		return
	}
	defer func() { _ = mo.Stop() }() // Example cleanup

	// Simulate agent publishing a signal
	signal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{
		Symbol:     "BTC/USDT",
		Signal:     "BUY",
		Confidence: 0.85,
	}

	data, _ := json.Marshal(signal)
	_ = nc.Publish("agents.analysis.technical", data) // Example - error acceptable

	// Wait for signal to be received
	if err := mo.WaitForSignals(1, 2*time.Second); err != nil {
		fmt.Printf("Failed to receive signal: %v\n", err)
		return
	}

	// Get received signals
	signals := mo.GetReceivedSignals()
	fmt.Printf("Received %d signal(s)\n", len(signals))

	// Wait for decision
	decision, err := mo.WaitForDecision(2 * time.Second)
	if err != nil {
		fmt.Printf("Failed to get decision: %v\n", err)
		return
	}

	fmt.Printf("Decision: %s %s (confidence: %.2f)\n",
		decision.Action, decision.Symbol, decision.Confidence)

	// Example output when NATS is available:
	// Received 1 signal(s)
	// Decision: BUY BTC/USDT (confidence: 0.85)
}
Example (CustomDecisionPolicy)

ExampleMockOrchestrator_customDecisionPolicy demonstrates custom decision-making

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"

	"github.com/ajitpratap0/cryptofunk/internal/agents/testing"
)

func main() {
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		fmt.Printf("Failed to connect to NATS: %v\n", err)
		return
	}
	defer nc.Close()

	// Custom policy that requires >0.90 confidence for BUY
	customPolicy := func(signals []testing.ReceivedSignal) *testing.Decision {
		if len(signals) == 0 {
			return nil
		}

		// Extract signal data
		var totalConfidence float64
		var lastSymbol string

		for _, sig := range signals {
			if gs, ok := sig.Signal.(struct {
				Symbol     string  `json:"symbol"`
				Signal     string  `json:"signal"`
				Confidence float64 `json:"confidence"`
			}); ok {
				totalConfidence += gs.Confidence
				lastSymbol = gs.Symbol
			}
		}

		avgConfidence := totalConfidence / float64(len(signals))

		// High confidence threshold
		if avgConfidence > 0.90 {
			return &testing.Decision{
				Action:     "BUY",
				Symbol:     lastSymbol,
				Confidence: avgConfidence,
				Reasoning:  "High confidence threshold met",
			}
		}

		return &testing.Decision{
			Action:     "HOLD",
			Symbol:     lastSymbol,
			Confidence: avgConfidence,
			Reasoning:  "Confidence threshold not met",
		}
	}

	// Create orchestrator with custom policy
	mo, err := testing.NewMockOrchestrator(testing.MockOrchestratorConfig{
		NATSConn:       nc,
		Topics:         []string{"agents.analysis.technical"},
		DecisionPolicy: customPolicy,
	})
	if err != nil {
		fmt.Printf("Failed to create mock orchestrator: %v\n", err)
		return
	}

	ctx := context.Background()
	if err := mo.Start(ctx); err != nil {
		fmt.Printf("Failed to start: %v\n", err)
		return
	}
	defer func() { _ = mo.Stop() }() // Example cleanup

	// Publish low confidence signal
	signal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{
		Symbol:     "ETH/USDT",
		Signal:     "BUY",
		Confidence: 0.80, // Below 0.90 threshold
	}

	data, _ := json.Marshal(signal)
	_ = nc.Publish("agents.analysis.technical", data) // Example - error acceptable

	_ = mo.WaitForSignals(1, 2*time.Second) // Example - timeout acceptable
	decision, _ := mo.WaitForDecision(2 * time.Second)

	fmt.Printf("Decision: %s (reasoning: %s)\n",
		decision.Action, decision.Reasoning)

	// Example output when NATS is available:
	// Decision: HOLD (reasoning: Confidence threshold not met)
}
Example (MultipleAgents)

ExampleMockOrchestrator_multipleAgents demonstrates aggregating signals from multiple agents

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"

	"github.com/ajitpratap0/cryptofunk/internal/agents/testing"
)

func main() {
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		fmt.Printf("Failed to connect to NATS: %v\n", err)
		return
	}
	defer nc.Close()

	mo, err := testing.NewMockOrchestrator(testing.MockOrchestratorConfig{
		NATSConn: nc,
		Topics: []string{
			"agents.analysis.technical",
			"agents.analysis.orderbook",
			"agents.analysis.sentiment",
		},
	})
	if err != nil {
		fmt.Printf("Failed to create mock orchestrator: %v\n", err)
		return
	}

	ctx := context.Background()
	_ = mo.Start(ctx)                // Example - error logged
	defer func() { _ = mo.Stop() }() // Example cleanup

	// Technical agent signal
	techSignal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{Symbol: "BTC/USDT", Signal: "BUY", Confidence: 0.85}
	techData, _ := json.Marshal(techSignal)
	_ = nc.Publish("agents.analysis.technical", techData) // Example - error acceptable

	// Order book agent signal
	obSignal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{Symbol: "BTC/USDT", Signal: "BUY", Confidence: 0.80}
	obData, _ := json.Marshal(obSignal)
	_ = nc.Publish("agents.analysis.orderbook", obData) // Example - error acceptable

	// Sentiment agent signal
	sentSignal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{Symbol: "BTC/USDT", Signal: "BULLISH", Confidence: 0.75}
	sentData, _ := json.Marshal(sentSignal)
	_ = nc.Publish("agents.analysis.sentiment", sentData) // Example - error acceptable

	// Wait for all signals
	_ = mo.WaitForSignals(3, 2*time.Second) // Example - timeout acceptable

	// Check signal counts per topic
	fmt.Printf("Technical signals: %d\n", mo.GetSignalCount("agents.analysis.technical"))
	fmt.Printf("Order book signals: %d\n", mo.GetSignalCount("agents.analysis.orderbook"))
	fmt.Printf("Sentiment signals: %d\n", mo.GetSignalCount("agents.analysis.sentiment"))

	// Get decision based on all signals
	decision, _ := mo.WaitForDecision(2 * time.Second)
	fmt.Printf("Final decision: %s (confidence: %.2f)\n",
		decision.Action, decision.Confidence)

	// Example output when NATS is available:
	// Technical signals: 1
	// Order book signals: 1
	// Sentiment signals: 1
	// Final decision: BUY (confidence: 0.80)
}
Example (Testing)

ExampleMockOrchestrator_testing demonstrates using MockOrchestrator in tests

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"

	"github.com/ajitpratap0/cryptofunk/internal/agents/testing"
)

func main() {
	// This example shows how to use MockOrchestrator to test that agents
	// properly publish signals to NATS

	nc, _ := nats.Connect(nats.DefaultURL)
	defer nc.Close()

	mo, _ := testing.NewMockOrchestrator(testing.MockOrchestratorConfig{
		NATSConn: nc,
		Topics:   []string{"agents.analysis.technical"},
	})

	ctx := context.Background()
	_ = mo.Start(ctx)                // Example - error logged
	defer func() { _ = mo.Stop() }() // Example cleanup

	// Simulate agent publishing
	signal := struct {
		Symbol     string  `json:"symbol"`
		Signal     string  `json:"signal"`
		Confidence float64 `json:"confidence"`
	}{Symbol: "BTC/USDT", Signal: "BUY", Confidence: 0.90}

	data, _ := json.Marshal(signal)
	_ = nc.Publish("agents.analysis.technical", data) // Example - error acceptable

	// Verify signal was received
	if err := mo.WaitForSignals(1, 2*time.Second); err != nil {
		fmt.Printf("Test failed: signal not received\n")
		return
	}

	// Verify signal content
	lastSignal := mo.GetLastSignal("agents.analysis.technical")
	if lastSignal != nil {
		fmt.Println("Test passed: signal received and recorded")
	}

	// Verify decision was made
	decision := mo.GetLastDecision()
	if decision != nil && decision.Action == "BUY" {
		fmt.Println("Test passed: correct decision made")
	}

	// Example output when NATS is available:
	// Test passed: signal received and recorded
	// Test passed: correct decision made
}

func NewMockOrchestrator

func NewMockOrchestrator(config MockOrchestratorConfig) (*MockOrchestrator, error)

NewMockOrchestrator creates a new mock orchestrator

func (*MockOrchestrator) GetDecisions

func (m *MockOrchestrator) GetDecisions() []Decision

GetDecisions returns all simulated decisions

func (*MockOrchestrator) GetLastDecision

func (m *MockOrchestrator) GetLastDecision() *Decision

GetLastDecision returns the most recent decision

func (*MockOrchestrator) GetLastSignal

func (m *MockOrchestrator) GetLastSignal(topic string) *ReceivedSignal

GetLastSignal returns the last signal received from a specific topic

func (*MockOrchestrator) GetReceivedSignals

func (m *MockOrchestrator) GetReceivedSignals() []ReceivedSignal

GetReceivedSignals returns all recorded signals

func (*MockOrchestrator) GetSignalCount

func (m *MockOrchestrator) GetSignalCount(topic string) int

GetSignalCount returns the number of signals received from a specific topic

func (*MockOrchestrator) Reset

func (m *MockOrchestrator) Reset()

Reset clears all recorded signals and decisions

func (*MockOrchestrator) Start

func (m *MockOrchestrator) Start(ctx context.Context) error

Start begins listening for agent signals

func (*MockOrchestrator) Stop

func (m *MockOrchestrator) Stop() error

Stop unsubscribes from all topics and cleans up

func (*MockOrchestrator) WaitForDecision

func (m *MockOrchestrator) WaitForDecision(timeout time.Duration) (*Decision, error)

WaitForDecision waits for at least one decision to be made or timeout

func (*MockOrchestrator) WaitForSignals

func (m *MockOrchestrator) WaitForSignals(n int, timeout time.Duration) error

WaitForSignals waits for at least n signals to be received or timeout

type MockOrchestratorConfig

type MockOrchestratorConfig struct {
	NATSConn       *nats.Conn
	Topics         []string
	DecisionPolicy DecisionPolicy
}

MockOrchestratorConfig configures the mock orchestrator

type NewsFixtures

type NewsFixtures struct{}

NewsFixtures provides sentiment/news data for testing

func (NewsFixtures) SampleFearGreedIndex

func (NewsFixtures) SampleFearGreedIndex() map[string]interface{}

SampleFearGreedIndex returns sample Fear & Greed Index data

func (NewsFixtures) SampleNews

func (NewsFixtures) SampleNews() map[string]interface{}

SampleNews returns sample news articles

type OrderExecutionFixtures

type OrderExecutionFixtures struct{}

OrderExecutionFixtures provides order execution data for testing

func (OrderExecutionFixtures) SampleLimitOrder

func (OrderExecutionFixtures) SampleLimitOrder() map[string]interface{}

SampleLimitOrder returns sample limit order result

func (OrderExecutionFixtures) SampleMarketOrder

func (OrderExecutionFixtures) SampleMarketOrder() map[string]interface{}

SampleMarketOrder returns sample market order result

type ReceivedSignal

type ReceivedSignal struct {
	Topic     string
	Signal    interface{} // TechnicalSignal, OrderBookSignal, or SentimentSignal
	RawData   []byte
	Timestamp time.Time
}

ReceivedSignal represents a signal received from an agent

type RiskFixtures

type RiskFixtures struct{}

RiskFixtures provides risk management data for testing

func (RiskFixtures) SamplePortfolio

func (RiskFixtures) SamplePortfolio() map[string]interface{}

SamplePortfolio returns sample portfolio data

func (RiskFixtures) SampleRiskLimits

func (RiskFixtures) SampleRiskLimits() map[string]interface{}

SampleRiskLimits returns sample risk limits

type TechnicalIndicatorFixtures

type TechnicalIndicatorFixtures struct{}

TechnicalIndicatorFixtures provides technical indicator data for testing

func (TechnicalIndicatorFixtures) SampleBollinger

func (TechnicalIndicatorFixtures) SampleBollinger() map[string]interface{}

SampleBollinger returns sample Bollinger Bands data

func (TechnicalIndicatorFixtures) SampleMACD

func (TechnicalIndicatorFixtures) SampleMACD() map[string]interface{}

SampleMACD returns sample MACD data

func (TechnicalIndicatorFixtures) SampleRSI

func (TechnicalIndicatorFixtures) SampleRSI() map[string]interface{}

SampleRSI returns sample RSI data

type ToolCall

type ToolCall struct {
	ToolName  string
	Arguments map[string]interface{}
	Result    interface{}
	Error     error
}

ToolCall represents a recorded tool call for testing

type ToolHandler

type ToolHandler func(ctx context.Context, arguments map[string]interface{}) (interface{}, error)

ToolHandler is a function that handles a tool call

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL