Comprehensive multi-agent implementation addressing all issues from INDEX.md: ## Core Architecture & Validation - ✅ Issue 001: UCXL address validation at all system boundaries - ✅ Issue 002: Fixed search parsing bug in encrypted storage - ✅ Issue 003: Wired UCXI P2P announce and discover functionality - ✅ Issue 011: Aligned temporal grammar and documentation - ✅ Issue 012: SLURP idempotency, backpressure, and DLQ implementation - ✅ Issue 013: Linked SLURP events to UCXL decisions and DHT ## API Standardization & Configuration - ✅ Issue 004: Standardized UCXI payloads to UCXL codes - ✅ Issue 010: Status endpoints and configuration surface ## Infrastructure & Operations - ✅ Issue 005: Election heartbeat on admin transition - ✅ Issue 006: Active health checks for PubSub and DHT - ✅ Issue 007: DHT replication and provider records - ✅ Issue 014: SLURP leadership lifecycle and health probes - ✅ Issue 015: Comprehensive monitoring, SLOs, and alerts ## Security & Access Control - ✅ Issue 008: Key rotation and role-based access policies ## Testing & Quality Assurance - ✅ Issue 009: Integration tests for UCXI + DHT encryption + search - ✅ Issue 016: E2E tests for HMMM → SLURP → UCXL workflow ## HMMM Integration - ✅ Issue 017: HMMM adapter wiring and comprehensive testing ## Key Features Delivered: - Enterprise-grade security with automated key rotation - Comprehensive monitoring with Prometheus/Grafana stack - Role-based collaboration with HMMM integration - Complete API standardization with UCXL response formats - Full test coverage with integration and E2E testing - Production-ready infrastructure monitoring and alerting All solutions include comprehensive testing, documentation, and production-ready implementations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
359 lines
11 KiB
Go
359 lines
11 KiB
Go
package hmmm_adapter
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestAdapter_Publish_OK(t *testing.T) {
|
|
var joined, published bool
|
|
a := NewAdapter(
|
|
func(topic string) error { joined = (topic == "bzzz/meta/issue/42"); return nil },
|
|
func(topic string, payload []byte) error { published = (topic == "bzzz/meta/issue/42" && len(payload) > 0); return nil },
|
|
)
|
|
if err := a.Publish(context.Background(), "bzzz/meta/issue/42", []byte(`{"ok":true}`)); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !joined || !published {
|
|
t.Fatalf("expected join and publish to be called")
|
|
}
|
|
|
|
// Verify metrics
|
|
metrics := a.GetMetrics()
|
|
if metrics.PublishCount != 1 {
|
|
t.Fatalf("expected publish count 1, got %d", metrics.PublishCount)
|
|
}
|
|
if metrics.JoinCount != 1 {
|
|
t.Fatalf("expected join count 1, got %d", metrics.JoinCount)
|
|
}
|
|
if metrics.ErrorCount != 0 {
|
|
t.Fatalf("expected error count 0, got %d", metrics.ErrorCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_JoinError(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return errors.New("join failed") },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
if err := a.Publish(context.Background(), "t", []byte("{}")); err == nil {
|
|
t.Fatalf("expected join error")
|
|
}
|
|
|
|
// Verify error was tracked
|
|
metrics := a.GetMetrics()
|
|
if metrics.ErrorCount != 1 {
|
|
t.Fatalf("expected error count 1, got %d", metrics.ErrorCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_PublishError(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return errors.New("publish failed") },
|
|
)
|
|
if err := a.Publish(context.Background(), "test-topic", []byte(`{"test":true}`)); err == nil {
|
|
t.Fatalf("expected publish error")
|
|
}
|
|
|
|
// Verify error was tracked
|
|
metrics := a.GetMetrics()
|
|
if metrics.ErrorCount != 1 {
|
|
t.Fatalf("expected error count 1, got %d", metrics.ErrorCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_EmptyTopic(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
err := a.Publish(context.Background(), "", []byte(`{"test":true}`))
|
|
if err == nil {
|
|
t.Fatalf("expected error for empty topic")
|
|
}
|
|
if !strings.Contains(err.Error(), "topic cannot be empty") {
|
|
t.Fatalf("expected empty topic error, got: %v", err)
|
|
}
|
|
|
|
metrics := a.GetMetrics()
|
|
if metrics.ErrorCount != 1 {
|
|
t.Fatalf("expected error count 1, got %d", metrics.ErrorCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_EmptyPayload(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
err := a.Publish(context.Background(), "test-topic", []byte{})
|
|
if err == nil {
|
|
t.Fatalf("expected error for empty payload")
|
|
}
|
|
if !strings.Contains(err.Error(), "payload cannot be empty") {
|
|
t.Fatalf("expected empty payload error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_PayloadTooLarge(t *testing.T) {
|
|
config := DefaultAdapterConfig()
|
|
config.MaxPayloadSize = 10 // Very small limit for testing
|
|
|
|
a := NewAdapterWithConfig(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
config,
|
|
)
|
|
|
|
largePayload := make([]byte, 20) // Larger than limit
|
|
err := a.Publish(context.Background(), "test-topic", largePayload)
|
|
if err == nil {
|
|
t.Fatalf("expected error for payload too large")
|
|
}
|
|
if !strings.Contains(err.Error(), "exceeds maximum") {
|
|
t.Fatalf("expected payload size error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_TopicCaching(t *testing.T) {
|
|
joinCallCount := 0
|
|
a := NewAdapter(
|
|
func(topic string) error { joinCallCount++; return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
topic := "bzzz/meta/issue/123"
|
|
|
|
// First publish should join
|
|
err := a.Publish(context.Background(), topic, []byte(`{"msg1":true}`))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if joinCallCount != 1 {
|
|
t.Fatalf("expected 1 join call, got %d", joinCallCount)
|
|
}
|
|
|
|
// Second publish to same topic should not join again
|
|
err = a.Publish(context.Background(), topic, []byte(`{"msg2":true}`))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if joinCallCount != 1 {
|
|
t.Fatalf("expected 1 join call total, got %d", joinCallCount)
|
|
}
|
|
|
|
// Verify metrics
|
|
metrics := a.GetMetrics()
|
|
if metrics.JoinCount != 1 {
|
|
t.Fatalf("expected join count 1, got %d", metrics.JoinCount)
|
|
}
|
|
if metrics.PublishCount != 2 {
|
|
t.Fatalf("expected publish count 2, got %d", metrics.PublishCount)
|
|
}
|
|
|
|
// Verify topic is cached
|
|
joinedTopics := a.GetJoinedTopics()
|
|
if len(joinedTopics) != 1 || joinedTopics[0] != topic {
|
|
t.Fatalf("expected topic to be cached: %v", joinedTopics)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_Timeout(t *testing.T) {
|
|
config := DefaultAdapterConfig()
|
|
config.PublishTimeout = 10 * time.Millisecond // Very short timeout
|
|
|
|
a := NewAdapterWithConfig(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error {
|
|
time.Sleep(50 * time.Millisecond) // Longer than timeout
|
|
return nil
|
|
},
|
|
config,
|
|
)
|
|
|
|
err := a.Publish(context.Background(), "test-topic", []byte(`{"test":true}`))
|
|
if err == nil {
|
|
t.Fatalf("expected timeout error")
|
|
}
|
|
if !strings.Contains(err.Error(), "timed out") {
|
|
t.Fatalf("expected timeout error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Publish_JoinTimeout(t *testing.T) {
|
|
config := DefaultAdapterConfig()
|
|
config.JoinTimeout = 10 * time.Millisecond // Very short timeout
|
|
|
|
a := NewAdapterWithConfig(
|
|
func(topic string) error {
|
|
time.Sleep(50 * time.Millisecond) // Longer than timeout
|
|
return nil
|
|
},
|
|
func(topic string, payload []byte) error { return nil },
|
|
config,
|
|
)
|
|
|
|
err := a.Publish(context.Background(), "test-topic", []byte(`{"test":true}`))
|
|
if err == nil {
|
|
t.Fatalf("expected join timeout error")
|
|
}
|
|
if !strings.Contains(err.Error(), "failed to join topic") {
|
|
t.Fatalf("expected join timeout error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ConcurrentPublish(t *testing.T) {
|
|
joinCalls := make(map[string]int)
|
|
var joinMutex sync.Mutex
|
|
|
|
a := NewAdapter(
|
|
func(topic string) error {
|
|
joinMutex.Lock()
|
|
joinCalls[topic]++
|
|
joinMutex.Unlock()
|
|
return nil
|
|
},
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
const numGoroutines = 10
|
|
const numTopics = 3
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
topic := fmt.Sprintf("bzzz/meta/issue/%d", id%numTopics)
|
|
payload := fmt.Sprintf(`{"id":%d}`, id)
|
|
|
|
err := a.Publish(context.Background(), topic, []byte(payload))
|
|
if err != nil {
|
|
t.Errorf("unexpected error from goroutine %d: %v", id, err)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify each topic was joined exactly once
|
|
joinMutex.Lock()
|
|
for topic, count := range joinCalls {
|
|
if count != 1 {
|
|
t.Errorf("topic %s was joined %d times, expected 1", topic, count)
|
|
}
|
|
}
|
|
joinMutex.Unlock()
|
|
|
|
// Verify metrics
|
|
metrics := a.GetMetrics()
|
|
if metrics.JoinCount != numTopics {
|
|
t.Fatalf("expected join count %d, got %d", numTopics, metrics.JoinCount)
|
|
}
|
|
if metrics.PublishCount != numGoroutines {
|
|
t.Fatalf("expected publish count %d, got %d", numGoroutines, metrics.PublishCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ResetMetrics(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
// Generate some metrics
|
|
a.Publish(context.Background(), "topic1", []byte(`{"test":true}`))
|
|
a.Publish(context.Background(), "topic2", []byte(`{"test":true}`))
|
|
|
|
metrics := a.GetMetrics()
|
|
if metrics.PublishCount == 0 {
|
|
t.Fatalf("expected non-zero publish count")
|
|
}
|
|
|
|
// Reset metrics
|
|
a.ResetMetrics()
|
|
|
|
metrics = a.GetMetrics()
|
|
if metrics.PublishCount != 0 {
|
|
t.Fatalf("expected publish count to be reset to 0, got %d", metrics.PublishCount)
|
|
}
|
|
if metrics.JoinCount != 0 {
|
|
t.Fatalf("expected join count to be reset to 0, got %d", metrics.JoinCount)
|
|
}
|
|
if metrics.ErrorCount != 0 {
|
|
t.Fatalf("expected error count to be reset to 0, got %d", metrics.ErrorCount)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ClearTopicCache(t *testing.T) {
|
|
a := NewAdapter(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
)
|
|
|
|
// Publish to create cached topics
|
|
a.Publish(context.Background(), "topic1", []byte(`{"test":true}`))
|
|
a.Publish(context.Background(), "topic2", []byte(`{"test":true}`))
|
|
|
|
joinedTopics := a.GetJoinedTopics()
|
|
if len(joinedTopics) != 2 {
|
|
t.Fatalf("expected 2 joined topics, got %d", len(joinedTopics))
|
|
}
|
|
|
|
// Clear cache
|
|
a.ClearTopicCache()
|
|
|
|
joinedTopics = a.GetJoinedTopics()
|
|
if len(joinedTopics) != 0 {
|
|
t.Fatalf("expected 0 joined topics after cache clear, got %d", len(joinedTopics))
|
|
}
|
|
}
|
|
|
|
func TestAdapter_DefaultConfig(t *testing.T) {
|
|
config := DefaultAdapterConfig()
|
|
|
|
if config.MaxPayloadSize <= 0 {
|
|
t.Fatalf("expected positive max payload size, got %d", config.MaxPayloadSize)
|
|
}
|
|
if config.JoinTimeout <= 0 {
|
|
t.Fatalf("expected positive join timeout, got %v", config.JoinTimeout)
|
|
}
|
|
if config.PublishTimeout <= 0 {
|
|
t.Fatalf("expected positive publish timeout, got %v", config.PublishTimeout)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_CustomConfig(t *testing.T) {
|
|
config := AdapterConfig{
|
|
MaxPayloadSize: 1000,
|
|
JoinTimeout: 5 * time.Second,
|
|
PublishTimeout: 2 * time.Second,
|
|
}
|
|
|
|
a := NewAdapterWithConfig(
|
|
func(topic string) error { return nil },
|
|
func(topic string, payload []byte) error { return nil },
|
|
config,
|
|
)
|
|
|
|
if a.maxPayloadSize != 1000 {
|
|
t.Fatalf("expected max payload size 1000, got %d", a.maxPayloadSize)
|
|
}
|
|
if a.joinTimeout != 5*time.Second {
|
|
t.Fatalf("expected join timeout 5s, got %v", a.joinTimeout)
|
|
}
|
|
if a.publishTimeout != 2*time.Second {
|
|
t.Fatalf("expected publish timeout 2s, got %v", a.publishTimeout)
|
|
}
|
|
}
|
|
|