summaryrefslogtreecommitdiffhomepage
path: root/ipn/lapitest/client.go
blob: 6d22e938b210e594f58249b803ad94144c419e83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 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
}