package tests import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "testing" "time" "github.com/xeipuuv/gojsonschema" ) // MessageTypes defines the three core BACKBEAT interfaces const ( BeatFrameType = "backbeat.beatframe.v1" StatusClaimType = "backbeat.statusclaim.v1" BarReportType = "backbeat.barreport.v1" ) // BeatFrame represents INT-A: Pulse → All Services type BeatFrame struct { Type string `json:"type"` ClusterID string `json:"cluster_id"` BeatIndex int64 `json:"beat_index"` Downbeat bool `json:"downbeat"` Phase string `json:"phase"` HLC string `json:"hlc"` DeadlineAt time.Time `json:"deadline_at"` TempoBPM float64 `json:"tempo_bpm"` WindowID string `json:"window_id"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // StatusClaim represents INT-B: Agents → Reverb type StatusClaim struct { Type string `json:"type"` AgentID string `json:"agent_id"` TaskID string `json:"task_id,omitempty"` BeatIndex int64 `json:"beat_index"` State string `json:"state"` BeatsLeft int `json:"beats_left,omitempty"` Progress float64 `json:"progress,omitempty"` Notes string `json:"notes,omitempty"` HLC string `json:"hlc"` Resources map[string]interface{} `json:"resources,omitempty"` Dependencies []string `json:"dependencies,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // BarReport represents INT-C: Reverb → All Services type BarReport struct { Type string `json:"type"` WindowID string `json:"window_id"` FromBeat int64 `json:"from_beat"` ToBeat int64 `json:"to_beat"` AgentsReporting int `json:"agents_reporting"` OnTimeReviews int `json:"on_time_reviews"` HelpPromisesFulfilled int `json:"help_promises_fulfilled"` SecretRotationsOK bool `json:"secret_rotations_ok"` TempoDriftMS float64 `json:"tempo_drift_ms"` Issues []map[string]interface{} `json:"issues,omitempty"` Performance map[string]interface{} `json:"performance,omitempty"` HealthIndicators map[string]interface{} `json:"health_indicators,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // TestSchemaValidation tests that all JSON schemas are valid and messages conform func TestSchemaValidation(t *testing.T) { schemaDir := "../schemas" tests := []struct { name string schemaFile string validMsgs []interface{} invalidMsgs []map[string]interface{} }{ { name: "BeatFrame Schema Validation", schemaFile: "beatframe-v1.schema.json", validMsgs: []interface{}{ BeatFrame{ Type: BeatFrameType, ClusterID: "test-cluster", BeatIndex: 100, Downbeat: false, Phase: "execute", HLC: "7ffd:0001:abcd", DeadlineAt: time.Now().Add(30 * time.Second), TempoBPM: 2.0, WindowID: "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", }, BeatFrame{ Type: BeatFrameType, ClusterID: "prod", BeatIndex: 0, Downbeat: true, Phase: "plan", HLC: "0001:0000:cafe", DeadlineAt: time.Now().Add(15 * time.Second), TempoBPM: 4.0, WindowID: "a1b2c3d4e5f6789012345678901234ab", Metadata: map[string]interface{}{ "pulse_version": "1.0.0", "cluster_health": "healthy", }, }, }, invalidMsgs: []map[string]interface{}{ // Missing required fields { "type": BeatFrameType, "cluster_id": "test", // missing beat_index, downbeat, phase, etc. }, // Invalid phase { "type": BeatFrameType, "cluster_id": "test", "beat_index": 0, "downbeat": false, "phase": "invalid_phase", "hlc": "7ffd:0001:abcd", "deadline_at": "2025-09-05T12:00:00Z", "tempo_bpm": 2.0, "window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", }, // Invalid HLC format { "type": BeatFrameType, "cluster_id": "test", "beat_index": 0, "downbeat": false, "phase": "plan", "hlc": "invalid-hlc-format", "deadline_at": "2025-09-05T12:00:00Z", "tempo_bpm": 2.0, "window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", }, }, }, { name: "StatusClaim Schema Validation", schemaFile: "statusclaim-v1.schema.json", validMsgs: []interface{}{ StatusClaim{ Type: StatusClaimType, AgentID: "worker:test-01", TaskID: "task:123", BeatIndex: 100, State: "executing", BeatsLeft: 3, Progress: 0.5, Notes: "processing batch", HLC: "7ffd:0001:beef", }, StatusClaim{ Type: StatusClaimType, AgentID: "agent:backup", BeatIndex: 101, State: "idle", HLC: "7ffe:0002:dead", Resources: map[string]interface{}{ "cpu_percent": 25.0, "memory_mb": 512, }, }, }, invalidMsgs: []map[string]interface{}{ // Missing required fields { "type": StatusClaimType, "agent_id": "test", // missing beat_index, state, hlc }, // Invalid state { "type": StatusClaimType, "agent_id": "test", "beat_index": 0, "state": "invalid_state", "hlc": "7ffd:0001:abcd", }, // Negative progress { "type": StatusClaimType, "agent_id": "test", "beat_index": 0, "state": "executing", "progress": -0.1, "hlc": "7ffd:0001:abcd", }, }, }, { name: "BarReport Schema Validation", schemaFile: "barreport-v1.schema.json", validMsgs: []interface{}{ BarReport{ Type: BarReportType, WindowID: "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", FromBeat: 0, ToBeat: 119, AgentsReporting: 150, OnTimeReviews: 147, HelpPromisesFulfilled: 12, SecretRotationsOK: true, TempoDriftMS: -2.1, }, BarReport{ Type: BarReportType, WindowID: "a1b2c3d4e5f6789012345678901234ab", FromBeat: 120, ToBeat: 239, AgentsReporting: 200, OnTimeReviews: 195, HelpPromisesFulfilled: 25, SecretRotationsOK: false, TempoDriftMS: 15.7, Issues: []map[string]interface{}{ { "severity": "warning", "category": "timing", "count": 5, "description": "Some agents running late", }, }, }, }, invalidMsgs: []map[string]interface{}{ // Missing required fields { "type": BarReportType, "window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", // missing from_beat, to_beat, etc. }, // Invalid window_id format { "type": BarReportType, "window_id": "invalid-window-id", "from_beat": 0, "to_beat": 119, "agents_reporting": 150, "on_time_reviews": 147, "help_promises_fulfilled": 12, "secret_rotations_ok": true, "tempo_drift_ms": 0.0, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Load schema schemaPath := filepath.Join(schemaDir, tt.schemaFile) schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath) // Test valid messages for i, validMsg := range tt.validMsgs { t.Run(fmt.Sprintf("Valid_%d", i), func(t *testing.T) { msgBytes, err := json.Marshal(validMsg) if err != nil { t.Fatalf("Failed to marshal valid message: %v", err) } docLoader := gojsonschema.NewBytesLoader(msgBytes) result, err := gojsonschema.Validate(schemaLoader, docLoader) if err != nil { t.Fatalf("Schema validation failed: %v", err) } if !result.Valid() { t.Errorf("Valid message failed validation: %v", result.Errors()) } }) } // Test invalid messages for i, invalidMsg := range tt.invalidMsgs { t.Run(fmt.Sprintf("Invalid_%d", i), func(t *testing.T) { msgBytes, err := json.Marshal(invalidMsg) if err != nil { t.Fatalf("Failed to marshal invalid message: %v", err) } docLoader := gojsonschema.NewBytesLoader(msgBytes) result, err := gojsonschema.Validate(schemaLoader, docLoader) if err != nil { t.Fatalf("Schema validation failed: %v", err) } if result.Valid() { t.Errorf("Invalid message passed validation when it should have failed") } }) } }) } } // TestMessageParsing tests that messages can be correctly parsed from JSON func TestMessageParsing(t *testing.T) { tests := []struct { name string jsonStr string expected interface{} }{ { name: "Parse BeatFrame", jsonStr: `{ "type": "backbeat.beatframe.v1", "cluster_id": "test", "beat_index": 123, "downbeat": true, "phase": "review", "hlc": "7ffd:0001:abcd", "deadline_at": "2025-09-05T12:00:00Z", "tempo_bpm": 2.5, "window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5" }`, expected: BeatFrame{ Type: BeatFrameType, ClusterID: "test", BeatIndex: 123, Downbeat: true, Phase: "review", HLC: "7ffd:0001:abcd", TempoBPM: 2.5, WindowID: "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", }, }, { name: "Parse StatusClaim", jsonStr: `{ "type": "backbeat.statusclaim.v1", "agent_id": "worker:01", "beat_index": 456, "state": "completed", "progress": 1.0, "hlc": "7ffe:0002:beef" }`, expected: StatusClaim{ Type: StatusClaimType, AgentID: "worker:01", BeatIndex: 456, State: "completed", Progress: 1.0, HLC: "7ffe:0002:beef", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { switch expected := tt.expected.(type) { case BeatFrame: var parsed BeatFrame err := json.Unmarshal([]byte(tt.jsonStr), &parsed) if err != nil { t.Fatalf("Failed to parse BeatFrame: %v", err) } if parsed.Type != expected.Type || parsed.ClusterID != expected.ClusterID || parsed.BeatIndex != expected.BeatIndex { t.Errorf("Parsed BeatFrame doesn't match expected") } case StatusClaim: var parsed StatusClaim err := json.Unmarshal([]byte(tt.jsonStr), &parsed) if err != nil { t.Fatalf("Failed to parse StatusClaim: %v", err) } if parsed.Type != expected.Type || parsed.AgentID != expected.AgentID || parsed.State != expected.State { t.Errorf("Parsed StatusClaim doesn't match expected") } } }) } } // TestHLCValidation tests Hybrid Logical Clock format validation func TestHLCValidation(t *testing.T) { validHLCs := []string{ "0000:0000:0000", "7ffd:0001:abcd", "FFFF:FFFF:FFFF", "1234:5678:90ab", } invalidHLCs := []string{ "invalid", "7ffd:0001", // too short "7ffd:0001:abcd:ef", // too long "gggg:0001:abcd", // invalid hex "7ffd:0001:abcdz", // invalid hex } for _, hlc := range validHLCs { t.Run(fmt.Sprintf("Valid_%s", hlc), func(t *testing.T) { if !isValidHLC(hlc) { t.Errorf("Valid HLC %s was rejected", hlc) } }) } for _, hlc := range invalidHLCs { t.Run(fmt.Sprintf("Invalid_%s", hlc), func(t *testing.T) { if isValidHLC(hlc) { t.Errorf("Invalid HLC %s was accepted", hlc) } }) } } // TestWindowIDValidation tests window ID format validation func TestWindowIDValidation(t *testing.T) { validWindowIDs := []string{ "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", "a1b2c3d4e5f6789012345678901234ab", "00000000000000000000000000000000", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", } invalidWindowIDs := []string{ "invalid", "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d", // too short "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d55", // too long "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4g5", // invalid hex } for _, windowID := range validWindowIDs { t.Run(fmt.Sprintf("Valid_%s", windowID), func(t *testing.T) { if !isValidWindowID(windowID) { t.Errorf("Valid window ID %s was rejected", windowID) } }) } for _, windowID := range invalidWindowIDs { t.Run(fmt.Sprintf("Invalid_%s", windowID), func(t *testing.T) { if isValidWindowID(windowID) { t.Errorf("Invalid window ID %s was accepted", windowID) } }) } } // Helper functions for validation func isValidHLC(hlc string) bool { parts := strings.Split(hlc, ":") if len(parts) != 3 { return false } for _, part := range parts { if len(part) != 4 { return false } for _, char := range part { if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) { return false } } } return true } func isValidWindowID(windowID string) bool { if len(windowID) != 32 { return false } for _, char := range windowID { if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) { return false } } return true } // BenchmarkSchemaValidation benchmarks schema validation performance func BenchmarkSchemaValidation(b *testing.B) { schemaDir := "../schemas" schemaPath := filepath.Join(schemaDir, "beatframe-v1.schema.json") schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath) beatFrame := BeatFrame{ Type: BeatFrameType, ClusterID: "benchmark", BeatIndex: 1000, Downbeat: false, Phase: "execute", HLC: "7ffd:0001:abcd", DeadlineAt: time.Now().Add(30 * time.Second), TempoBPM: 2.0, WindowID: "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5", } msgBytes, _ := json.Marshal(beatFrame) docLoader := gojsonschema.NewBytesLoader(msgBytes) b.ResetTimer() for i := 0; i < b.N; i++ { result, err := gojsonschema.Validate(schemaLoader, docLoader) if err != nil || !result.Valid() { b.Fatal("Validation failed") } } } // Helper function to check if schema files exist func TestSchemaFilesExist(t *testing.T) { schemaDir := "../schemas" requiredSchemas := []string{ "beatframe-v1.schema.json", "statusclaim-v1.schema.json", "barreport-v1.schema.json", } for _, schema := range requiredSchemas { schemaPath := filepath.Join(schemaDir, schema) if _, err := os.Stat(schemaPath); os.IsNotExist(err) { t.Errorf("Required schema file %s does not exist", schemaPath) } } }