mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-16 13:00:44 +09:00
servertest: add real-listener option and node/key helpers
Add WithRealListener for tests needing a real loopback port, plus CreateAPIKey and CreateRegisteredNode for the v2 roundtrip.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
package servertest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -43,6 +44,7 @@ type serverConfig struct {
|
||||
nodeExpiry time.Duration
|
||||
batcherWorkers int
|
||||
taildropEnabled bool
|
||||
realListener bool
|
||||
}
|
||||
|
||||
func defaultServerConfig() *serverConfig {
|
||||
@@ -82,6 +84,14 @@ func WithNodeExpiry(d time.Duration) ServerOption {
|
||||
return func(c *serverConfig) { c.nodeExpiry = d }
|
||||
}
|
||||
|
||||
// WithRealListener binds the HTTP API to a real loopback TCP port instead of
|
||||
// the in-process memnet, so external processes — the Tailscale SDK over a real
|
||||
// socket, tscli, OpenTofu — can reach the API. The Noise/[TestClient] control
|
||||
// path still uses memnet, so this is for REST/API tests only.
|
||||
func WithRealListener() ServerOption {
|
||||
return func(c *serverConfig) { c.realListener = true }
|
||||
}
|
||||
|
||||
// WithTaildropEnabled toggles the Taildrop file-sharing feature.
|
||||
// Defaults to true to match production. Pass false to verify
|
||||
// behaviour when an operator has switched the toggle off — e.g.
|
||||
@@ -164,13 +174,24 @@ func NewServer(tb testing.TB, opts ...ServerOption) *TestServer {
|
||||
app.StartBatcherForTest(tb)
|
||||
app.StartEphemeralGCForTest(tb)
|
||||
|
||||
// Start the HTTP server over an in-memory network so that all
|
||||
// TCP connections stay in-process.
|
||||
var memNetwork memnet.Network
|
||||
// Start the HTTP server. By default it binds an in-memory network so all
|
||||
// TCP connections stay in-process; WithRealListener swaps in a real loopback
|
||||
// port so external binaries can connect.
|
||||
var (
|
||||
memNetwork memnet.Network
|
||||
ln net.Listener
|
||||
)
|
||||
|
||||
if sc.realListener {
|
||||
var lc net.ListenConfig
|
||||
|
||||
ln, err = lc.Listen(context.Background(), "tcp", "127.0.0.1:0")
|
||||
} else {
|
||||
ln, err = memNetwork.Listen("tcp", "127.0.0.1:443")
|
||||
}
|
||||
|
||||
ln, err := memNetwork.Listen("tcp", "127.0.0.1:443")
|
||||
if err != nil {
|
||||
tb.Fatalf("servertest: memnet Listen: %v", err)
|
||||
tb.Fatalf("servertest: Listen: %v", err)
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
@@ -233,6 +254,42 @@ func (s *TestServer) CreateUser(tb testing.TB, name string) *types.User {
|
||||
return u
|
||||
}
|
||||
|
||||
// CreateAPIKey mints an API key string (the Bearer/Basic credential). When
|
||||
// owner is non-nil the key is owned by that user, so the v2 API mints
|
||||
// user-owned (untagged) auth keys on its behalf.
|
||||
func (s *TestServer) CreateAPIKey(tb testing.TB, owner *types.User) string {
|
||||
tb.Helper()
|
||||
|
||||
keyStr, key, err := s.st.CreateAPIKey(nil)
|
||||
if err != nil {
|
||||
tb.Fatalf("servertest: CreateAPIKey: %v", err)
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
err := s.st.SetAPIKeyUser(key.ID, types.UserID(owner.ID))
|
||||
if err != nil {
|
||||
tb.Fatalf("servertest: SetAPIKeyUser: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return keyStr
|
||||
}
|
||||
|
||||
// CreateRegisteredNode mints a registered node (allocated IPs, registered
|
||||
// method) in BOTH the database and the in-memory NodeStore, then returns its
|
||||
// view. The v2 device endpoints resolve nodes via State.GetNodeByID, which reads
|
||||
// the NodeStore, so a DB-only node would be invisible to them.
|
||||
func (s *TestServer) CreateRegisteredNode(tb testing.TB, owner *types.User, hostname ...string) types.NodeView {
|
||||
tb.Helper()
|
||||
|
||||
node := s.st.CreateRegisteredNodeForTest(owner, hostname...)
|
||||
// CreateRegisteredNodeForTest sets UserID but leaves the User association
|
||||
// unloaded; the device response renders the owner login, so attach it.
|
||||
node.User = owner
|
||||
|
||||
return s.st.PutNodeInStoreForTest(*node)
|
||||
}
|
||||
|
||||
// CreatePreAuthKey creates a reusable pre-auth key for the given user.
|
||||
func (s *TestServer) CreatePreAuthKey(tb testing.TB, userID types.UserID) string {
|
||||
tb.Helper()
|
||||
|
||||
Reference in New Issue
Block a user