Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

99 lines
3.7 KiB
Go

package mitm
import (
"fmt"
"net"
"net/http"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// noopHandler is the simplest InterceptHandler that satisfies NewServer.
// We only exercise Start/Stop lifecycle here — no requests go through.
func noopHandler(_ http.ResponseWriter, _ *http.Request, _ string) {}
func newTestServer(addr string, hosts []string) *Server {
ca, err := NewInMemoryCA()
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "NewInMemoryCA")
srv, err := NewServer(Config{
Addr: addr,
CA: ca,
InterceptHosts: hosts,
Handler: noopHandler,
})
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "NewServer")
return srv
}
// Server_StopIdempotent: calling Stop twice (and Stop without
// Start) must not panic or deadlock. The application's RestartMITM
// path is sensitive to this — it always calls Stop before Start, even
// when the server is already stopped.
var _ = Describe("Server", func() {
It("Stop is idempotent", func() {
srv := newTestServer("127.0.0.1:0", nil)
srv.Stop() // never started
srv.Stop() // double-stop after never-started
srv2 := newTestServer("127.0.0.1:0", nil)
Expect(srv2.Start()).To(Succeed())
srv2.Stop()
srv2.Stop() // second Stop after Start+Stop
})
// Server_RestartCycle: two sequential Server lifecycles on the
// same address must rebind cleanly, the new listener must accept
// connections, and the new allowlist must take effect — the shape
// RestartMITM relies on.
It("restart cycle rebinds and swaps allowlist", func() {
// First, find a free port we can rebind to.
probe, err := net.Listen("tcp", "127.0.0.1:0")
Expect(err).NotTo(HaveOccurred(), "probe listen")
addr := probe.Addr().String()
_ = probe.Close()
srv1 := newTestServer(addr, []string{"first.example.com"})
if err := srv1.Start(); err != nil {
// Port could have been recycled between probe close and Start.
// Skip rather than flake — the production path uses dynamic
// addrs anyway.
Skip(fmt.Sprintf("could not bind probed addr: %v", err))
}
Expect(strings.HasPrefix(srv1.Addr(), "127.0.0.1:")).To(BeTrue(), "Addr() = %q, want 127.0.0.1:* prefix", srv1.Addr())
srv1.Stop()
// Now bring up a second server on the same addr with a different
// allowlist — mirrors the RestartMITM-with-edited-hosts path.
srv2 := newTestServer(addr, []string{"second.example.com"})
if err := srv2.Start(); err != nil {
// SO_REUSEADDR is not set; brief TIME_WAIT collisions are
// possible on slow CI runners. Retry once on a fresh port so
// the test still exercises the "different hosts" property.
srv2 = newTestServer("127.0.0.1:0", []string{"second.example.com"})
Expect(srv2.Start()).To(Succeed(), "second Start (fresh port fallback)")
}
defer srv2.Stop()
// Smoke: the new listener accepts a TCP connection.
conn, err := net.Dial("tcp", srv2.Addr())
Expect(err).NotTo(HaveOccurred(), "dial restarted listener")
_ = conn.Close()
// Allowlist swap took effect: the new server intercepts
// "second.example.com" (and not the old "first.example.com").
Expect(srv2.shouldIntercept("second.example.com")).To(BeTrue(), "second server did not pick up the new InterceptHosts")
Expect(srv2.shouldIntercept("first.example.com")).To(BeFalse(), "second server still has the first server's allowlist")
})
// Server_AddrBeforeStart: Addr() pre-Start returns the configured
// address rather than panicking on a nil listener. The admin status
// endpoint reads it under MITMServer() — when an admin queries between
// configuration and Start, the response should still render cleanly.
It("Addr before start returns configured address", func() {
srv := newTestServer(":12345", nil)
Expect(srv.Addr()).To(Equal(":12345"))
})
})