Skip to content

Commit bdfd53e

Browse files
committed
Test npipe URL parsing and discovery health
TestParseNamedPipeURL covers the success case plus every rejection branch (missing scheme, wrong scheme, empty name, forward slash, backslash, ".."). A non-Windows guard test asserts CheckHealth("npipe://...") surfaces a clear "Windows" error rather than a misleading dial syscall failure. The Windows-only health_windows_test.go spins up a winio listener with an http.Server.Serve goroutine and asserts CheckHealth("npipe://<name>", expectedNonce) succeeds with a matching nonce, plus a not-found case that expects "health check failed". An atomic counter disambiguates parallel pipe names, since the Windows pipe namespace is global.
1 parent 9eeceab commit bdfd53e

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

pkg/server/discovery/health_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http/httptest"
1111
"os"
1212
"path/filepath"
13+
"runtime"
1314
"testing"
1415

1516
"github.com/stretchr/testify/assert"
@@ -44,6 +45,49 @@ func TestParseUnixSocketPath_Empty(t *testing.T) {
4445
assert.Contains(t, err.Error(), "empty")
4546
}
4647

48+
func TestParseNamedPipeURL(t *testing.T) {
49+
t.Parallel()
50+
tests := []struct {
51+
name string
52+
raw string
53+
expect string
54+
wantErr bool
55+
errSubstr string
56+
}{
57+
{name: "valid", raw: "npipe://thv-api", expect: `\\.\pipe\thv-api`},
58+
{name: "valid with hyphen and digits", raw: "npipe://thv-api-1", expect: `\\.\pipe\thv-api-1`},
59+
{name: "missing scheme", raw: "thv-api", wantErr: true, errSubstr: "must start with npipe://"},
60+
{name: "wrong scheme", raw: "unix://thv-api", wantErr: true, errSubstr: "must start with npipe://"},
61+
{name: "empty name", raw: "npipe://", wantErr: true, errSubstr: "empty"},
62+
{name: "forward slash", raw: "npipe://thv/api", wantErr: true, errSubstr: "path separators"},
63+
{name: "backslash", raw: `npipe://thv\api`, wantErr: true, errSubstr: "path separators"},
64+
{name: "dot dot", raw: "npipe://..thv", wantErr: true, errSubstr: "'..'"},
65+
}
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
t.Parallel()
69+
got, err := ParseNamedPipeURL(tt.raw)
70+
if tt.wantErr {
71+
require.Error(t, err)
72+
assert.Contains(t, err.Error(), tt.errSubstr)
73+
return
74+
}
75+
require.NoError(t, err)
76+
assert.Equal(t, tt.expect, got)
77+
})
78+
}
79+
}
80+
81+
func TestCheckHealth_NamedPipe_Unsupported_OnNonWindows(t *testing.T) {
82+
if runtime.GOOS == "windows" {
83+
t.Skip("non-Windows guard test")
84+
}
85+
t.Parallel()
86+
err := CheckHealth(context.Background(), "npipe://thv-api", "")
87+
require.Error(t, err)
88+
assert.Contains(t, err.Error(), "Windows")
89+
}
90+
4791
func TestCheckHealth_TCP_Success(t *testing.T) {
4892
t.Parallel()
4993
expectedNonce := "test-nonce-123"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:build windows
5+
6+
package discovery
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"net/http"
12+
"sync/atomic"
13+
"testing"
14+
15+
"github.com/Microsoft/go-winio"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
// pipeNameSeq disambiguates concurrent test pipes so parallel runs don't
21+
// collide on the global pipe namespace.
22+
var pipeNameSeq atomic.Uint64
23+
24+
func TestCheckHealth_NamedPipe_Success(t *testing.T) {
25+
t.Parallel()
26+
27+
pipeName := fmt.Sprintf("thv-test-%d", pipeNameSeq.Add(1))
28+
pipePath := `\\.\pipe\` + pipeName
29+
30+
listener, err := winio.ListenPipe(pipePath, &winio.PipeConfig{})
31+
require.NoError(t, err)
32+
t.Cleanup(func() { _ = listener.Close() })
33+
34+
expectedNonce := "pipe-nonce"
35+
mux := http.NewServeMux()
36+
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
37+
w.Header().Set(NonceHeader, expectedNonce)
38+
w.WriteHeader(http.StatusNoContent)
39+
})
40+
srv := &http.Server{Handler: mux} //nolint:gosec // test server, ReadHeaderTimeout not relevant
41+
go func() { _ = srv.Serve(listener) }()
42+
t.Cleanup(func() { _ = srv.Close() })
43+
44+
err = CheckHealth(context.Background(), "npipe://"+pipeName, expectedNonce)
45+
require.NoError(t, err)
46+
}
47+
48+
func TestCheckHealth_NamedPipe_NotFound(t *testing.T) {
49+
t.Parallel()
50+
err := CheckHealth(context.Background(), "npipe://nonexistent-pipe-thv-test", "")
51+
require.Error(t, err)
52+
assert.Contains(t, err.Error(), "health check failed")
53+
}

0 commit comments

Comments
 (0)