You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/cmd/tailscale/cli/serve_test.go

127 lines
3.4 KiB
Go

// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"bytes"
"context"
"flag"
"fmt"
"reflect"
"runtime"
"strings"
"testing"
"tailscale.com/ipn"
)
func TestServeConfigMutations(t *testing.T) {
// Stateful mutations, starting from an empty config.
type step struct {
command []string // serve args; nil means no command to run (only reset)
reset bool // if true, reset all ServeConfig state
want *ipn.ServeConfig // non-nil means we want a save of this value
wantErr func(error) (badErrMsg string) // nil means no error is wanted
line int // line number of addStep call, for error messages
}
var steps []step
add := func(s step) {
_, _, s.line, _ = runtime.Caller(1)
steps = append(steps, s)
}
add(step{reset: true})
add(step{
command: cmd("ingress on"),
want: &ipn.ServeConfig{AllowIngress: map[ipn.HostPort]bool{"foo:123": true}},
})
add(step{
command: cmd("ingress on"),
want: nil, // nothing to save
})
add(step{
command: cmd("ingress off"),
want: &ipn.ServeConfig{AllowIngress: map[ipn.HostPort]bool{}},
})
add(step{
command: cmd("ingress off"),
want: nil, // nothing to save
})
add(step{
command: cmd("ingress"),
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
})
// And now run the steps above.
var current *ipn.ServeConfig
for i, st := range steps {
if st.reset {
t.Logf("Executing step #%d, line %v: [reset]", i, st.line)
current = nil
}
if st.command == nil {
continue
}
t.Logf("Executing step #%d, line %v: %q ... ", i, st.line, st.command)
var stdout bytes.Buffer
var flagOut bytes.Buffer
var newState *ipn.ServeConfig
e := &serveEnv{
testFlagOut: &flagOut,
testStdout: &stdout,
testGetServeConfig: func(context.Context) (*ipn.ServeConfig, error) {
return current, nil
},
testSetServeConfig: func(_ context.Context, c *ipn.ServeConfig) error {
newState = c
return nil
},
}
cmd := newServeCommand(e)
err := cmd.ParseAndRun(context.Background(), st.command)
if flagOut.Len() > 0 {
t.Logf("flag package output: %q", flagOut.Bytes())
}
if err != nil {
if st.wantErr == nil {
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, err)
}
if bad := st.wantErr(err); bad != "" {
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, bad)
}
continue
}
if st.wantErr != nil {
t.Fatalf("step #%d, line %v: got success (saved=%v), but wanted an error", i, st.line, newState != nil)
}
if !reflect.DeepEqual(newState, st.want) {
t.Fatalf("[%d] %v: bad state. got:\n%s\n\nwant:\n%s\n",
i, st.command, asJSON(newState), asJSON(st.want))
}
if newState != nil {
current = newState
}
}
}
// exactError returns an error checker that wants exactly the provided want error.
// If optName is non-empty, it's used in the error message.
func exactErr(want error, optName ...string) func(error) string {
return func(got error) string {
if got == want {
return ""
}
if len(optName) > 0 {
return fmt.Sprintf("got error %v, want %v", got, optName[0])
}
return fmt.Sprintf("got error %v, want %v", got, want)
}
}
func cmd(s string) []string {
return strings.Fields(s)
}