Files
explorer-monorepo/backend/api/track1/ccip_health_test.go

255 lines
9.0 KiB
Go

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, 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},
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, "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, error) {
return nil, nil, nil, nil
},
}
got := s.BuildBridgeStatusData(context.Background())
require.Equal(t, "degraded", got["status"])
}