diff --git a/internal/health/health.go b/internal/health/health.go new file mode 100644 index 0000000..b694c7a --- /dev/null +++ b/internal/health/health.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +// Package health performs post-apply connectivity checks. +package health + +import ( + "context" + "fmt" + "net" + "time" +) + +// DialTCP attempts a TCP connection to addr within timeout. +func DialTCP(ctx context.Context, addr string, timeout time.Duration) error { + d := net.Dialer{Timeout: timeout} + conn, err := d.DialContext(ctx, "tcp", addr) + if err != nil { + return fmt.Errorf("connect %s: %w", addr, err) + } + return conn.Close() +} diff --git a/internal/health/health_test.go b/internal/health/health_test.go new file mode 100644 index 0000000..0c20950 --- /dev/null +++ b/internal/health/health_test.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +package health + +import ( + "context" + "net" + "testing" + "time" +) + +func TestDialTCPSuccess(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen: %v", err) + } + defer ln.Close() + go func() { + for { + conn, err := ln.Accept() + if err != nil { + return + } + _ = conn.Close() + } + }() + + if err := DialTCP(context.Background(), ln.Addr().String(), time.Second); err != nil { + t.Fatalf("DialTCP to a live listener failed: %v", err) + } +} + +func TestDialTCPFailure(t *testing.T) { + // Reserve a port and close it so the dial is refused. + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen: %v", err) + } + addr := ln.Addr().String() + ln.Close() + + if err := DialTCP(context.Background(), addr, 500*time.Millisecond); err == nil { + t.Fatal("expected a dial error to a closed port") + } +}