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_dev_test.go

849 lines
26 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
import (
"bytes"
"context"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn"
"tailscale.com/types/logger"
)
func TestServeDevConfigMutations(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
debugBreak func()
}
var steps []step
add := func(s step) {
_, _, s.line, _ = runtime.Caller(1)
steps = append(steps, s)
}
// using port number
add(step{reset: true})
add(step{
command: cmd("funnel --bg 3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
},
})
// funnel background
add(step{reset: true})
add(step{
command: cmd("funnel --bg localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
},
})
// serve background
add(step{reset: true})
add(step{
command: cmd("serve --bg localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
// --set-path runs in background
add(step{reset: true})
add(step{
command: cmd("serve --set-path=/ localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
// using http listener
add(step{reset: true})
add(step{
command: cmd("serve --bg --http=80 localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
// using https listener with a valid port
add(step{reset: true})
add(step{
command: cmd("serve --bg --https=8443 localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
// https
add(step{reset: true})
add(step{ // allow omitting port (default to 80)
command: cmd("serve --http=80 --bg http://localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // support non Funnel port
command: cmd("serve --http=9999 --set-path=/abc http://localhost:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --http=9999 --set-path=/abc off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{
command: cmd("serve --http=8080 --set-path=/abc http://127.0.0.1:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8080": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
// // https
add(step{reset: true})
add(step{
command: cmd("serve --https=443 --bg http://localhost:0"), // invalid port, too low
wantErr: anyErr(),
})
add(step{
command: cmd("serve --https=443 --bg http://localhost:65536"), // invalid port, too high
wantErr: anyErr(),
})
add(step{
command: cmd("serve --https=443 --bg http://somehost:3000"), // invalid host
wantErr: anyErr(),
})
add(step{
command: cmd("serve --https=443 --bg httpz://127.0.0.1"), // invalid scheme
wantErr: anyErr(),
})
add(step{ // allow omitting port (default to 443)
command: cmd("serve --https=443 --bg http://localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // support non Funnel port
command: cmd("serve --https=9999 --set-path=/abc http://localhost:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 9999: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --https=9999 --set-path=/abc off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{
command: cmd("serve --https=8443 --set-path=/abc http://127.0.0.1:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --https=10000 --bg text:hi"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {HTTPS: true}, 8443: {HTTPS: true}, 10000: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
"foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Text: "hi"},
}},
},
},
})
add(step{
command: cmd("serve --https=443 --set-path=/foo off"),
want: nil, // nothing to save
wantErr: anyErr(),
}) // handler doesn't exist, so we get an error
add(step{
command: cmd("serve --https=10000 off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --https=443 off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --https=8443 --set-path=/abc off"),
want: &ipn.ServeConfig{},
})
add(step{ // clean mount: "bar" becomes "/bar"
command: cmd("serve --https=443 --set-path=bar https://127.0.0.1:8443"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/bar": {Proxy: "https://127.0.0.1:8443"},
}},
},
},
})
// add(step{
// command: cmd("serve --https=443 --set-path=bar https://127.0.0.1:8443"),
// want: nil, // nothing to save
// })
add(step{ // try resetting using reset command
command: cmd("serve reset"),
want: &ipn.ServeConfig{},
})
add(step{
command: cmd("serve --https=443 --bg https+insecure://127.0.0.1:3001"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "https+insecure://127.0.0.1:3001"},
}},
},
},
})
add(step{reset: true})
add(step{
command: cmd("serve --https=443 --set-path=/foo localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/foo": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // test a second handler on the same port
command: cmd("serve --https=8443 --set-path=/foo localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/foo": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/foo": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{reset: true})
add(step{ // support path in proxy
command: cmd("serve --https=443 --bg http://127.0.0.1:3000/foo/bar"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000/foo/bar"},
}},
},
},
})
// // tcp
add(step{reset: true})
add(step{ // must include scheme for tcp
command: cmd("serve --tls-terminated-tcp=443 --bg localhost:5432"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{ // !somehost, must be localhost or 127.0.0.1
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:5432"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{ // bad target port, too low
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:0"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{ // bad target port, too high
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:65536"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:5432",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:8443",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
// add(step{
// command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"),
// want: nil, // nothing to save
// })
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:8444"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:8444",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8445"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:8445",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{reset: true})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:123"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:123",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{ // handler doesn't exist, so we get an error
command: cmd("serve --tls-terminated-tcp=8443 off"),
wantErr: anyErr(),
})
add(step{
command: cmd("serve --tls-terminated-tcp=443 off"),
want: &ipn.ServeConfig{},
})
// // text
add(step{reset: true})
add(step{
command: cmd("serve --https=443 --bg text:hello"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Text: "hello"},
}},
},
},
})
// path
td := t.TempDir()
writeFile := func(suffix, contents string) {
if err := os.WriteFile(filepath.Join(td, suffix), []byte(contents), 0600); err != nil {
t.Fatal(err)
}
}
add(step{reset: true})
writeFile("foo", "this is foo")
add(step{
command: cmd("serve --https=443 --bg " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Path: filepath.Join(td, "foo")},
}},
},
},
})
os.MkdirAll(filepath.Join(td, "subdir"), 0700)
writeFile("subdir/file-a", "this is A")
add(step{
command: cmd("serve --https=443 --set-path=/some/where " + filepath.Join(td, "subdir/file-a")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Path: filepath.Join(td, "foo")},
"/some/where": {Path: filepath.Join(td, "subdir/file-a")},
}},
},
},
})
add(step{ // bad path
command: cmd("serve --https=443 --bg bad/path"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{reset: true})
add(step{
command: cmd("serve --https=443 --bg " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Path: filepath.Join(td, "subdir/")},
}},
},
},
})
add(step{
command: cmd("serve --https=443 off"),
want: &ipn.ServeConfig{},
})
// // combos
add(step{reset: true})
add(step{
command: cmd("serve --bg localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // enable funnel for primary port
command: cmd("funnel --bg localhost:3000"),
want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // serving on secondary port doesn't change funnel on primary port
command: cmd("serve --https=8443 --set-path=/bar localhost:3001"),
want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/bar": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{ // turn funnel on for secondary port
command: cmd("funnel --https=8443 --set-path=/bar localhost:3001"),
want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true, "foo.test.ts.net:8443": true},
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/bar": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
// TODO(tylersmalley) resolve these failures
// add(step{ // turn funnel off for primary port 443
// command: cmd("serve --https=443 --set-path=/bar localhost:3001"),
// want: &ipn.ServeConfig{
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
// "/": {Proxy: "http://127.0.0.1:3000"},
// }},
// "foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
// "/bar": {Proxy: "http://127.0.0.1:3001"},
// }},
// },
// },
// })
// add(step{ // remove secondary port
// command: cmd("https:8443 /bar off"),
// want: &ipn.ServeConfig{
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
// "/": {Proxy: "http://127.0.0.1:3000"},
// }},
// },
// },
// })
// add(step{ // start a tcp forwarder on 8443
// command: cmd("tcp:8443 tcp://localhost:5432"),
// want: &ipn.ServeConfig{
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
// TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {TCPForward: "127.0.0.1:5432"}},
// Web: map[ipn.HostPort]*ipn.WebServerConfig{
// "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
// "/": {Proxy: "http://127.0.0.1:3000"},
// }},
// },
// },
// })
// add(step{ // remove primary port http handler
// command: cmd("https:443 / off"),
// want: &ipn.ServeConfig{
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
// TCP: map[uint16]*ipn.TCPPortHandler{8443: {TCPForward: "127.0.0.1:5432"}},
// },
// })
// add(step{ // remove tcp forwarder
// command: cmd("tls-terminated-tcp:8443 off"),
// want: &ipn.ServeConfig{
// AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
// },
// })
// add(step{ // turn off funnel
// command: cmd("funnel 8443 off"),
// want: &ipn.ServeConfig{},
// })
// // tricky steps
add(step{reset: true})
add(step{ // a directory with a trailing slash mount point
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/dir/": {Path: filepath.Join(td, "subdir/")},
}},
},
},
})
add(step{ // this should overwrite the previous one
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/dir": {Path: filepath.Join(td, "foo")},
}},
},
},
})
add(step{reset: true}) // reset and do the opposite
add(step{ // a file without a trailing slash mount point
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/dir": {Path: filepath.Join(td, "foo")},
}},
},
},
})
add(step{ // this should overwrite the previous one
command: cmd("serve --https=443 --set-path=/dir " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/dir/": {Path: filepath.Join(td, "subdir/")},
}},
},
},
})
// // error states
add(step{reset: true})
add(step{ // tcp forward 5432 on serve port 443
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:5432",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{ // try to start a web handler on the same port
command: cmd("serve --https=443 --bg localhost:3000"),
wantErr: exactErr(errHelp, "errHelp"),
})
add(step{reset: true})
add(step{ // start a web handler on port 443
command: cmd("serve --https=443 --bg localhost:3000"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
},
},
})
add(step{ // try to start a tcp forwarder on the same serve port
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
wantErr: anyErr(),
})
lc := &fakeLocalServeClient{}
// And now run the steps above.
for i, st := range steps {
if st.debugBreak != nil {
st.debugBreak()
}
if st.reset {
t.Logf("Executing step #%d, line %v: [reset]", i, st.line)
lc.config = 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
e := &serveEnv{
lc: lc,
testFlagOut: &flagOut,
testStdout: &stdout,
}
lastCount := lc.setCount
var cmd *ffcli.Command
var args []string
mode := serve
if st.command[0] == "funnel" {
mode = funnel
}
cmd = newServeDevCommand(e, mode)
args = st.command[1:]
err := cmd.ParseAndRun(context.Background(), args)
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, lc.config != nil)
}
var got *ipn.ServeConfig = nil
if lc.setCount > lastCount {
got = lc.config
}
if !reflect.DeepEqual(got, st.want) {
t.Fatalf("[%d] %v: bad state. got:\n%v\n\nwant:\n%v\n",
i, st.command, logger.AsJSON(got), logger.AsJSON(st.want))
// NOTE: asJSON will omit empty fields, which might make
// result in bad state got/want diffs being the same, even
// though the actual state is different. Use below to debug:
// t.Fatalf("[%d] %v: bad state. got:\n%+v\n\nwant:\n%+v\n",
// i, st.command, got, st.want)
}
}
}
func TestSrcTypeFromFlags(t *testing.T) {
tests := []struct {
name string
env *serveEnv
expectedType string
expectedPort uint16
expectedErr bool
}{
{
name: "only http set",
env: &serveEnv{http: "80"},
expectedType: "http",
expectedPort: 80,
expectedErr: false,
},
{
name: "only https set",
env: &serveEnv{https: "10000"},
expectedType: "https",
expectedPort: 10000,
expectedErr: false,
},
{
name: "only tcp set",
env: &serveEnv{tcp: "8000"},
expectedType: "tcp",
expectedPort: 8000,
expectedErr: false,
},
{
name: "only tls-terminated-tcp set",
env: &serveEnv{tlsTerminatedTcp: "8080"},
expectedType: "tls-terminated-tcp",
expectedPort: 8080,
expectedErr: false,
},
{
name: "defaults to https, port 443",
env: &serveEnv{},
expectedType: "https",
expectedPort: 443,
expectedErr: false,
},
{
name: "multiple types set",
env: &serveEnv{http: "80", https: "443"},
expectedType: "",
expectedPort: 0,
expectedErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcType, srcPort, err := srvTypeAndPortFromFlags(tt.env)
if (err != nil) != tt.expectedErr {
t.Errorf("Expected error: %v, got: %v", tt.expectedErr, err)
}
if srcType != tt.expectedType {
t.Errorf("Expected srcType: %s, got: %s", tt.expectedType, srcType)
}
if srcPort != tt.expectedPort {
t.Errorf("Expected srcPort: %d, got: %d", tt.expectedPort, srcPort)
}
})
}
}