 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 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 == "CHORUS/meta/issue/42"); return nil },
 | |
|         func(topic string, payload []byte) error { published = (topic == "CHORUS/meta/issue/42" && len(payload) > 0); return nil },
 | |
|     )
 | |
|     if err := a.Publish(context.Background(), "CHORUS/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 := "CHORUS/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("CHORUS/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)
 | |
|     }
 | |
| }
 | |
| 
 |