mirror of
https://github.com/hanzo-fi/gateway.git
synced 2026-10-30 20:34:40 +00:00
199 lines
4.8 KiB
Go
199 lines
4.8 KiB
Go
//go:build it
|
|
|
|
package e2e_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
|
_ "github.com/hanzo-fi/gateway/pkg/plugins"
|
|
|
|
logging "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/testing/deferred"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/testing/platform/natstesting"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/audit"
|
|
"github.com/nats-io/nats.go"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
func TestE2E(t *testing.T) {
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Gateway E2E Suite")
|
|
}
|
|
|
|
var (
|
|
natsServer = deferred.New[*natstesting.NatsServer]()
|
|
debug = os.Getenv("DEBUG") == "true"
|
|
logger = logging.NewDefaultLogger(GinkgoWriter, debug, false, false)
|
|
)
|
|
|
|
var _ = SynchronizedBeforeSuite(func(specContext SpecContext) []byte {
|
|
deferred.RegisterRecoverHandler(GinkgoRecover)
|
|
|
|
natsServer.LoadAsync(func() (*natstesting.NatsServer, error) {
|
|
By("Initializing NATS server")
|
|
ret := natstesting.CreateServer(GinkgoT(), debug, logger)
|
|
By("NATS address: " + ret.ClientURL())
|
|
return ret, nil
|
|
})
|
|
|
|
By("Waiting for NATS")
|
|
_, err := natsServer.Wait(specContext)
|
|
Expect(err).To(BeNil())
|
|
|
|
data, err := json.Marshal(natsServer.GetValue())
|
|
Expect(err).To(BeNil())
|
|
return data
|
|
}, func(data []byte) {
|
|
select {
|
|
case <-natsServer.Done():
|
|
return
|
|
default:
|
|
}
|
|
ns := &natstesting.NatsServer{}
|
|
Expect(json.Unmarshal(data, ns)).To(Succeed())
|
|
natsServer.SetValue(ns)
|
|
})
|
|
|
|
// freePort finds an available TCP port.
|
|
func freePort() int {
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
ExpectWithOffset(1, err).To(BeNil())
|
|
port := l.Addr().(*net.TCPAddr).Port
|
|
ExpectWithOffset(1, l.Close()).To(Succeed())
|
|
return port
|
|
}
|
|
|
|
// backendRecorder holds the mock backend server and the last request it received.
|
|
type backendRecorder struct {
|
|
Server *httptest.Server
|
|
mu sync.Mutex
|
|
lastReq *http.Request
|
|
}
|
|
|
|
func (b *backendRecorder) LastRequest() *http.Request {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.lastReq
|
|
}
|
|
|
|
func (b *backendRecorder) recordRequest(r *http.Request) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.lastReq = r.Clone(r.Context())
|
|
}
|
|
|
|
// startBackend creates a mock HTTP backend that records incoming requests.
|
|
func startBackend() *backendRecorder {
|
|
rec := &backendRecorder{}
|
|
rec.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
rec.recordRequest(r)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
|
}))
|
|
DeferCleanup(rec.Server.Close)
|
|
return rec
|
|
}
|
|
|
|
// startGateway starts a Caddy server with the audit plugin configured to
|
|
// publish to the given NATS URL, proxying to the given backend address.
|
|
// It returns the gateway base URL (e.g. "http://127.0.0.1:PORT").
|
|
func startGateway(natsURL, backendAddr, topicName string) string {
|
|
port := freePort()
|
|
|
|
caddyfileContent := fmt.Sprintf(`
|
|
{
|
|
order audit after encode
|
|
admin off
|
|
}
|
|
|
|
:%d {
|
|
audit {
|
|
topic_name %s
|
|
organization_id test-org
|
|
stack_id test-stack
|
|
auto_provision true
|
|
|
|
publisher_kafka_enabled false
|
|
|
|
publisher_nats_enabled true
|
|
publisher_nats_url %s
|
|
publisher_nats_client_id gateway-test
|
|
}
|
|
|
|
handle /api/* {
|
|
reverse_proxy %s
|
|
}
|
|
|
|
handle /health {
|
|
respond "OK" 200
|
|
}
|
|
}
|
|
`, port, topicName, natsURL, backendAddr)
|
|
|
|
adapter := caddyconfig.GetAdapter("caddyfile")
|
|
Expect(adapter).NotTo(BeNil(), "caddyfile adapter not registered")
|
|
|
|
cfgJSON, _, err := adapter.Adapt([]byte(caddyfileContent), nil)
|
|
Expect(err).To(BeNil())
|
|
|
|
Expect(caddy.Load(cfgJSON, false)).To(Succeed())
|
|
|
|
DeferCleanup(func() {
|
|
Expect(caddy.Stop()).To(Succeed())
|
|
})
|
|
|
|
baseURL := fmt.Sprintf("http://127.0.0.1:%d", port)
|
|
|
|
// Wait for the server to be ready.
|
|
Eventually(func() error {
|
|
resp, err := http.Get(baseURL + "/health")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = resp.Body.Close()
|
|
return nil
|
|
}).WithTimeout(5 * time.Second).WithPolling(100 * time.Millisecond).Should(Succeed())
|
|
|
|
return baseURL
|
|
}
|
|
|
|
// subscribeToAuditTopic subscribes to the NATS topic and returns a channel of raw messages.
|
|
func subscribeToAuditTopic(natsURL, topicName string) chan *nats.Msg {
|
|
conn, err := nats.Connect(natsURL)
|
|
Expect(err).To(BeNil())
|
|
DeferCleanup(conn.Close)
|
|
|
|
ch := make(chan *nats.Msg, 64)
|
|
sub, err := conn.Subscribe(topicName, func(msg *nats.Msg) {
|
|
ch <- msg
|
|
})
|
|
Expect(err).To(BeNil())
|
|
DeferCleanup(sub.Unsubscribe)
|
|
|
|
return ch
|
|
}
|
|
|
|
// parseAuditPayload extracts the audit.Payload from a raw NATS message.
|
|
// The message payload is a Watermill EventMessage JSON containing a nested Payload.
|
|
func parseAuditPayload(msg *nats.Msg) audit.Payload {
|
|
var envelope struct {
|
|
Payload audit.Payload `json:"payload"`
|
|
}
|
|
ExpectWithOffset(1, json.Unmarshal(msg.Data, &envelope)).To(Succeed())
|
|
return envelope.Payload
|
|
}
|