package mesh import ( "context" "errors" "net/http" "net/http/httptest" "testing" "time" ) func TestHTTPPeerTransportDirectSyntheticProbe(t *testing.T) { nodeA := newLiveSyntheticNode(t, PeerIdentity{ClusterID: "cluster-1", NodeID: "node-a"}) defer nodeA.Close() nodeB := newLiveSyntheticNode(t, PeerIdentity{ClusterID: "cluster-1", NodeID: "node-b"}) defer nodeB.Close() route := liveSyntheticRoute("route-direct", []string{"node-a", "node-b"}) routes := []SyntheticRoute{route} nodeA.Runtime = newLiveRuntime(nodeA.Local, routes, map[string]string{"node-b": nodeB.URL}) nodeB.Runtime = newLiveRuntime(nodeB.Local, routes, map[string]string{}) ack, err := nodeA.Runtime.SendProbe(context.Background(), route.RouteID, SyntheticChannelFabricControl, "probe-live-direct") if err != nil { t.Fatalf("send live direct probe: %v", err) } if ack.MessageType != SyntheticMessageProbeAck { t.Fatalf("MessageType = %q, want %q", ack.MessageType, SyntheticMessageProbeAck) } payload := decodeAckPayload(t, ack) if got, want := payload.Path, []string{"node-a", "node-b"}; !sameStrings(got, want) { t.Fatalf("path = %v, want %v", got, want) } } func TestHTTPPeerTransportSingleRelaySyntheticProbe(t *testing.T) { nodeA := newLiveSyntheticNode(t, PeerIdentity{ClusterID: "cluster-1", NodeID: "node-a"}) defer nodeA.Close() nodeR := newLiveSyntheticNode(t, PeerIdentity{ClusterID: "cluster-1", NodeID: "node-r"}) defer nodeR.Close() nodeB := newLiveSyntheticNode(t, PeerIdentity{ClusterID: "cluster-1", NodeID: "node-b"}) defer nodeB.Close() route := liveSyntheticRoute("route-relay", []string{"node-a", "node-r", "node-b"}) routes := []SyntheticRoute{route} nodeA.Runtime = newLiveRuntime(nodeA.Local, routes, map[string]string{"node-r": nodeR.URL}) nodeR.Runtime = newLiveRuntime(nodeR.Local, routes, map[string]string{"node-b": nodeB.URL}) nodeB.Runtime = newLiveRuntime(nodeB.Local, routes, map[string]string{}) ack, err := nodeA.Runtime.SendProbe(context.Background(), route.RouteID, SyntheticChannelFabricControl, "probe-live-relay") if err != nil { t.Fatalf("send live relay probe: %v", err) } if ack.MessageType != SyntheticMessageProbeAck { t.Fatalf("MessageType = %q, want %q", ack.MessageType, SyntheticMessageProbeAck) } payload := decodeAckPayload(t, ack) if got, want := payload.Path, []string{"node-a", "node-r", "node-b"}; !sameStrings(got, want) { t.Fatalf("path = %v, want %v", got, want) } } func TestHTTPPeerTransportMissingPeer(t *testing.T) { transport := NewHTTPPeerTransport(map[string]string{}) _, err := transport.SendSynthetic(context.Background(), "node-missing", SyntheticEnvelope{}) if !errors.Is(err, ErrSyntheticPeerUnavailable) { t.Fatalf("err = %v, want ErrSyntheticPeerUnavailable", err) } } type liveSyntheticNode struct { Local PeerIdentity Runtime *SyntheticRuntime URL string server *httptest.Server } func newLiveSyntheticNode(t *testing.T, local PeerIdentity) *liveSyntheticNode { t.Helper() node := &liveSyntheticNode{Local: local} node.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Server{Local: node.Local, SyntheticRuntime: node.Runtime}.Handler().ServeHTTP(w, r) })) node.URL = node.server.URL return node } func (n *liveSyntheticNode) Close() { if n.server != nil { n.server.Close() } } func newLiveRuntime(local PeerIdentity, routes []SyntheticRoute, peers map[string]string) *SyntheticRuntime { return NewSyntheticRuntime(SyntheticRuntimeConfig{ Enabled: true, Local: local, Routes: routes, Transport: NewHTTPPeerTransport(peers), }) } func liveSyntheticRoute(routeID string, hops []string) SyntheticRoute { return SyntheticRoute{ RouteID: routeID, ClusterID: "cluster-1", SourceNodeID: hops[0], DestinationNodeID: hops[len(hops)-1], Hops: hops, AllowedChannels: []string{SyntheticChannelFabricControl}, MaxTTL: 8, MaxHops: 8, ExpiresAt: time.Now().UTC().Add(time.Hour), RouteVersion: "route-v1", PolicyVersion: "policy-v1", PeerDirectoryVersion: "peers-v1", } } func sameStrings(left, right []string) bool { if len(left) != len(right) { return false } for i := range left { if left[i] != right[i] { return false } } return true }