mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	In this PR, we extract the in-process LocalAPI client/server implementation from ipn/ipnserver/server_test.go into a new ipntest package to be used in high‑level black‑box tests, such as those for the tailscale CLI. Updates #15575 Signed-off-by: Nick Khyl <nickk@tailscale.com>
		
			
				
	
	
		
			72 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			72 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package lapitest
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"testing"
 | |
| 
 | |
| 	"tailscale.com/client/local"
 | |
| 	"tailscale.com/ipn"
 | |
| 	"tailscale.com/ipn/ipnauth"
 | |
| )
 | |
| 
 | |
| // Client wraps a [local.Client] for testing purposes.
 | |
| // It can be created using [Server.Client], [Server.ClientWithName],
 | |
| // or [Server.ClientFor] and sends requests as the specified actor
 | |
| // to the associated [Server].
 | |
| type Client struct {
 | |
| 	tb testing.TB
 | |
| 	// Client is the underlying [local.Client] wrapped by the test client.
 | |
| 	// It is configured to send requests to the test server on behalf of the actor.
 | |
| 	*local.Client
 | |
| 	// Actor represents the user on whose behalf this client is making requests.
 | |
| 	// The server uses it to determine the client's identity and permissions.
 | |
| 	// The test can mutate the user to alter the actor's identity or permissions
 | |
| 	// before making a new request. It is typically an [ipnauth.TestActor],
 | |
| 	// unless the [Client] was created with s specific actor using [Server.ClientFor].
 | |
| 	Actor ipnauth.Actor
 | |
| }
 | |
| 
 | |
| // Username returns username of the client's owner.
 | |
| func (c *Client) Username() string {
 | |
| 	c.tb.Helper()
 | |
| 	name, err := c.Actor.Username()
 | |
| 	if err != nil {
 | |
| 		c.tb.Fatalf("Client.Username: %v", err)
 | |
| 	}
 | |
| 	return name
 | |
| }
 | |
| 
 | |
| // WatchIPNBus is like [local.Client.WatchIPNBus] but returns a [local.IPNBusWatcher]
 | |
| // that is closed when the test ends and a cancel function that stops the watcher.
 | |
| // It fails the test if the underlying WatchIPNBus returns an error.
 | |
| func (c *Client) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*local.IPNBusWatcher, context.CancelFunc) {
 | |
| 	c.tb.Helper()
 | |
| 	ctx, cancelWatcher := context.WithCancel(ctx)
 | |
| 	c.tb.Cleanup(cancelWatcher)
 | |
| 	watcher, err := c.Client.WatchIPNBus(ctx, mask)
 | |
| 	name, _ := c.Actor.Username()
 | |
| 	if err != nil {
 | |
| 		c.tb.Fatalf("Client.WatchIPNBus(%q): %v", name, err)
 | |
| 	}
 | |
| 	c.tb.Cleanup(func() { watcher.Close() })
 | |
| 	return watcher, cancelWatcher
 | |
| }
 | |
| 
 | |
| // generateSequentialName generates a unique sequential name based on the given prefix and number n.
 | |
| // It uses a base-26 encoding to create names like "User-A", "User-B", ..., "User-Z", "User-AA", etc.
 | |
| func generateSequentialName(prefix string, n int) string {
 | |
| 	n++
 | |
| 	name := ""
 | |
| 	const numLetters = 'Z' - 'A' + 1
 | |
| 	for n > 0 {
 | |
| 		n--
 | |
| 		remainder := byte(n % numLetters)
 | |
| 		name = string([]byte{'A' + remainder}) + name
 | |
| 		n = n / numLetters
 | |
| 	}
 | |
| 	return prefix + "-" + name
 | |
| }
 |