Add the mock provider with a real CONNECT proxy per port (Step 3)
An in-memory provider.Provider whose state (Provisioning -> Running -> Terminated -> purged) is a pure function of an injectable clock, not background timers, so it's deterministic under tests and correct under real time with no goroutine lifecycle to leak. Once an instance is observed Running, it lazily acquires a real HTTP CONNECT proxy listener so the health engine's through-the-proxy probe (later steps) genuinely tunnels a request end to end, instead of the healthcheck being simulated or bypassed for local development. Redesigned the listener sharing model from what the plan assumed: the plan's "one loopback IP per instance" doesn't work on macOS (only 127.0.0.1 binds without a privileged ifconfig alias, unlike Linux where the whole 127.0.0.0/8 routes to loopback by default), and there's no channel for a provider to report a port back to the reconciler anyway (EffectivePort() is spec-only). Instances now share one real listener per port, reference-counted at the package level rather than per Provider instance, since a bound TCP port is a genuinely process-global OS resource -- two separately configured mock-typed provider entries must not both try to bind the same default port. Fault injection wired both ways: MockConfig.FailNextCreates/FailWith for demos, InjectCreateFailures(n, class) for tests. Create is idempotent by name. Caught and fixed a real test flake (not a logic bug): the freePort test helper asked the OS for a free port via bind-then-close, a TOCTOU race under t.Parallel() that let two tests collide on the same "free" port. Replaced it with a monotonic counter, since these tests only need uniqueness within the test run. internal/provider/mock at 91.1% coverage, including an end-to-end test that opens real sockets: Create -> Get past provisionDelay -> a real http.Client tunnelling a CONNECT through the mock to a real TLS origin. make test green across the whole repo. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
51
internal/provider/mock/proxy_test.go
Normal file
51
internal/provider/mock/proxy_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAcquireProxy_sharedAcrossAcquires(t *testing.T) {
|
||||
t.Parallel()
|
||||
port := freePort(t)
|
||||
addr := net.JoinHostPort(mockProxyIP, strconv.Itoa(int(port)))
|
||||
|
||||
release1, err := acquireProxy(port)
|
||||
if err != nil {
|
||||
t.Fatalf("acquireProxy() #1 error = %v", err)
|
||||
}
|
||||
// A second acquire for the same port must join the existing listener
|
||||
// rather than fail trying to bind it again — this is the whole point
|
||||
// of sharing by port instead of by IP.
|
||||
release2, err := acquireProxy(port)
|
||||
if err != nil {
|
||||
t.Fatalf("acquireProxy() #2 error = %v, want nil (should share the existing listener)", err)
|
||||
}
|
||||
|
||||
release1()
|
||||
// One reference remains; the port must still be in use.
|
||||
if ln, err := net.Listen("tcp", addr); err == nil {
|
||||
ln.Close()
|
||||
t.Fatal("port became bindable after releasing only one of two references")
|
||||
}
|
||||
|
||||
release2()
|
||||
// Last reference released: the port must now be free.
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("port not released after last reference: %v", err)
|
||||
}
|
||||
ln.Close()
|
||||
}
|
||||
|
||||
func TestAcquireProxy_releaseIsIdempotent(t *testing.T) {
|
||||
t.Parallel()
|
||||
port := freePort(t)
|
||||
release, err := acquireProxy(port)
|
||||
if err != nil {
|
||||
t.Fatalf("acquireProxy() error = %v", err)
|
||||
}
|
||||
release()
|
||||
release() // must not panic or double-decrement into a negative refcount
|
||||
}
|
||||
Reference in New Issue
Block a user