package track1 import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strconv" "testing" "time" "github.com/explorer/backend/api/freshness" "github.com/stretchr/testify/require" ) func TestFetchCCIPRelayHealthFromURL(t *testing.T) { relay := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"ok":true,"status":"operational","destination":{"chain_name":"Ethereum Mainnet"},"queue":{"size":0}}`)) })) defer relay.Close() t.Setenv("CCIP_RELAY_HEALTH_URL", relay.URL+"/healthz") t.Setenv("CCIP_RELAY_HEALTH_URLS", "") t.Setenv("MISSION_CONTROL_CCIP_JSON", "") got := FetchCCIPRelayHealth(context.Background()) require.NotNil(t, got) probe, ok := got["url_probe"].(map[string]interface{}) require.True(t, ok) require.Equal(t, true, probe["ok"]) body, ok := probe["body"].(map[string]interface{}) require.True(t, ok) require.Equal(t, "operational", body["status"]) dest, ok := body["destination"].(map[string]interface{}) require.True(t, ok) require.Equal(t, "Ethereum Mainnet", dest["chain_name"]) } func TestFetchCCIPRelayHealthsFromNamedURLs(t *testing.T) { mainnet := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"status":"operational","destination":{"chain_name":"Ethereum Mainnet"},"queue":{"size":0}}`)) })) defer mainnet.Close() bsc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"status":"operational","destination":{"chain_name":"BSC"},"queue":{"size":1}}`)) })) defer bsc.Close() t.Setenv("CCIP_RELAY_HEALTH_URL", "") t.Setenv("MISSION_CONTROL_CCIP_JSON", "") t.Setenv("CCIP_RELAY_HEALTH_URLS", "mainnet="+mainnet.URL+"/healthz,bsc="+bsc.URL+"/healthz") got := FetchCCIPRelayHealths(context.Background()) require.NotNil(t, got) mainnetRelay, ok := got["mainnet"].(map[string]interface{}) require.True(t, ok) mainnetProbe, ok := mainnetRelay["url_probe"].(map[string]interface{}) require.True(t, ok) require.Equal(t, true, mainnetProbe["ok"]) bscRelay, ok := got["bsc"].(map[string]interface{}) require.True(t, ok) bscProbe, ok := bscRelay["url_probe"].(map[string]interface{}) require.True(t, ok) body, ok := bscProbe["body"].(map[string]interface{}) require.True(t, ok) dest, ok := body["destination"].(map[string]interface{}) require.True(t, ok) require.Equal(t, "BSC", dest["chain_name"]) } func TestFetchCCIPRelayHealthPrefersMainnetCW(t *testing.T) { relays := map[string]interface{}{ "mainnet_weth": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true}}, "mainnet_cw": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true, "body": map[string]interface{}{"status": "operational"}}}, "bsc": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true}}, } got := primaryRelayHealth(relays) require.NotNil(t, got) require.Equal(t, relays["mainnet_cw"], got) } func TestFetchCCIPRelayHealthFromFileSnapshot(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "relay-health.json") require.NoError(t, os.WriteFile(path, []byte(`{"status":"paused","queue":{"size":3}}`), 0o644)) t.Setenv("CCIP_RELAY_HEALTH_URL", "") t.Setenv("CCIP_RELAY_HEALTH_URLS", "") t.Setenv("MISSION_CONTROL_CCIP_JSON", path) got := FetchCCIPRelayHealth(context.Background()) require.NotNil(t, got) snapshot, ok := got["file_snapshot"].(map[string]interface{}) require.True(t, ok) require.Equal(t, "paused", snapshot["status"]) queue, ok := snapshot["queue"].(map[string]interface{}) require.True(t, ok) require.Equal(t, float64(3), queue["size"]) } func TestBuildBridgeStatusDataIncludesCCIPRelay(t *testing.T) { rpc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Method string `json:"method"` } require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) w.Header().Set("Content-Type", "application/json") switch req.Method { case "eth_blockNumber": _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x10"}`)) case "eth_getBlockByNumber": ts := strconv.FormatInt(time.Now().Add(-2*time.Second).Unix(), 16) _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"timestamp":"0x` + ts + `"}}`)) default: http.Error(w, `{"jsonrpc":"2.0","id":1,"error":{"message":"unsupported"}}`, http.StatusBadRequest) } })) defer rpc.Close() relay := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"ok":true,"status":"operational","queue":{"size":0}}`)) })) defer relay.Close() t.Setenv("RPC_URL", rpc.URL) t.Setenv("ETH_MAINNET_RPC_URL", "") t.Setenv("MISSION_CONTROL_EXTRA_RPCS", "") t.Setenv("MISSION_CONTROL_VERIFY_JSON", "") t.Setenv("CCIP_RELAY_HEALTH_URL", relay.URL+"/healthz") t.Setenv("CCIP_RELAY_HEALTH_URLS", "") t.Setenv("MISSION_CONTROL_CCIP_JSON", "") s := &Server{ freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, *freshness.Diagnostics, error) { now := time.Now().UTC().Format(time.RFC3339) head := int64(16) txBlock := int64(12) distance := int64(4) return &freshness.Snapshot{ ChainHead: freshness.Reference{ BlockNumber: &head, Timestamp: &now, AgeSeconds: func() *int64 { v := int64(1); return &v }(), Source: freshness.SourceReported, Confidence: freshness.ConfidenceHigh, Provenance: freshness.ProvenanceRPC, Completeness: freshness.CompletenessComplete, }, LatestIndexedTransaction: freshness.Reference{ BlockNumber: &txBlock, Timestamp: &now, AgeSeconds: func() *int64 { v := int64(120); return &v }(), Source: freshness.SourceReported, Confidence: freshness.ConfidenceHigh, Provenance: freshness.ProvenanceTxIndex, Completeness: freshness.CompletenessPartial, }, LatestNonEmptyBlock: freshness.Reference{ BlockNumber: &txBlock, Timestamp: &now, AgeSeconds: func() *int64 { v := int64(120); return &v }(), DistanceFromHead: &distance, Source: freshness.SourceReported, Confidence: freshness.ConfidenceHigh, Provenance: freshness.ProvenanceTxIndex, Completeness: freshness.CompletenessPartial, }, }, &freshness.SummaryCompleteness{ TransactionsFeed: freshness.CompletenessPartial, BlocksFeed: freshness.CompletenessComplete, }, &freshness.Sampling{StatsGeneratedAt: &now}, &freshness.Diagnostics{ TxVisibilityState: "lagging", ActivityState: "fresh_head_stale_transaction_visibility", Source: freshness.SourceReported, Confidence: freshness.ConfidenceMedium, Provenance: freshness.ProvenanceComposite, Completeness: freshness.CompletenessPartial, }, nil }, } got := s.BuildBridgeStatusData(context.Background()) ccip, ok := got["ccip_relay"].(map[string]interface{}) require.True(t, ok) relays, ok := got["ccip_relays"].(map[string]interface{}) require.True(t, ok) require.Contains(t, relays, "mainnet") probe, ok := ccip["url_probe"].(map[string]interface{}) require.True(t, ok) require.Equal(t, true, probe["ok"]) require.Contains(t, got, "freshness") require.Contains(t, got, "diagnostics") require.Contains(t, got, "subsystems") require.Contains(t, got, "mode") } func TestBuildBridgeStatusDataDegradesWhenNamedRelayFails(t *testing.T) { rpc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Method string `json:"method"` } require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) w.Header().Set("Content-Type", "application/json") switch req.Method { case "eth_blockNumber": _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x10"}`)) case "eth_getBlockByNumber": ts := strconv.FormatInt(time.Now().Add(-2*time.Second).Unix(), 16) _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"timestamp":"0x` + ts + `"}}`)) default: http.Error(w, `{"jsonrpc":"2.0","id":1,"error":{"message":"unsupported"}}`, http.StatusBadRequest) } })) defer rpc.Close() mainnet := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"status":"operational","queue":{"size":0}}`)) })) defer mainnet.Close() bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"status":"degraded"}`, http.StatusBadGateway) })) defer bad.Close() t.Setenv("RPC_URL", rpc.URL) t.Setenv("ETH_MAINNET_RPC_URL", "") t.Setenv("MISSION_CONTROL_EXTRA_RPCS", "") t.Setenv("MISSION_CONTROL_VERIFY_JSON", "") t.Setenv("CCIP_RELAY_HEALTH_URL", "") t.Setenv("MISSION_CONTROL_CCIP_JSON", "") t.Setenv("CCIP_RELAY_HEALTH_URLS", "mainnet="+mainnet.URL+"/healthz,bsc="+bad.URL+"/healthz") s := &Server{ freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, *freshness.Diagnostics, error) { return nil, nil, nil, nil, nil }, } got := s.BuildBridgeStatusData(context.Background()) require.Equal(t, "degraded", got["status"]) }