Files

112 lines
3.5 KiB
Go

package join
import (
"context"
"strings"
"testing"
"time"
)
func listen(t *testing.T) *Listener {
t.Helper()
l, err := Listen("127.0.0.1:0", "", time.Hour)
if err != nil {
t.Fatal(err)
}
l.addr = l.ln.Addr().String()
t.Cleanup(func() { _ = l.Close() })
return l
}
func TestAJoinedMachineRunsWhatTheSetupSendsUntilItIsDone(t *testing.T) {
l := listen(t)
code, arrived := l.Code("database")
parsed, err := Parse(code.String())
if err != nil || parsed.Role != "database" {
t.Fatalf("%v %+v", err, parsed)
}
var said []string
served := make(chan error, 1)
go func() { served <- Serve(context.Background(), parsed, func(s string) { said = append(said, s) }) }()
var m *Remote
select {
case m = <-arrived:
case err := <-served:
t.Fatalf("join ended before arriving: %v", err)
case <-time.After(5 * time.Second):
t.Fatal("the machine never arrived")
}
if m.Hostname == "" || m.Addr != "127.0.0.1" {
t.Errorf("arrived as %q from %q", m.Hostname, m.Addr)
}
out, err := m.Run(context.Background(), "set -e\ntr a-z A-Z", []byte("hello\n"))
if err != nil || string(out) != "HELLO\n" {
t.Fatalf("%q, %v", out, err)
}
if out, err := m.Run(context.Background(), "echo nope >&2; exit 3", nil); err == nil || !strings.Contains(string(out), "nope") {
t.Errorf("a failing script: %q, %v", out, err)
}
_ = m.Close()
if err := <-served; err != nil {
t.Errorf("join did not end cleanly when the setup was done: %v", err)
}
if len(said) < 2 || !strings.Contains(strings.Join(said, "\n"), "tr a-z A-Z") {
t.Errorf("the machine's operator was not told what ran: %q", said)
}
// The code was for one machine.
if err := Serve(context.Background(), parsed, func(string) {}); err == nil || !strings.Contains(err.Error(), "used already") {
t.Errorf("a code used twice: %v", err)
}
}
func TestAJoinRefusesAListenerThatIsNotTheOneInTheCode(t *testing.T) {
l := listen(t)
code, _ := l.Code("node")
other := listen(t)
code.Addr = other.addr // what the code names is elsewhere
err := Serve(context.Background(), code, func(string) {})
if err == nil || !strings.Contains(err.Error(), "not the setup") {
t.Errorf("joined a stranger: %v", err)
}
}
func TestAnExpiredOrDamagedCodeIsRefusedBeforeDialling(t *testing.T) {
c := Code{Addr: "10.0.0.5:7444", Role: "node", Expires: time.Now().Add(-time.Minute).Unix()}
if _, err := Parse(c.String()); err == nil || !strings.Contains(err.Error(), "expired") {
t.Errorf("expired: %v", err)
}
if _, err := Parse(c.String()[:30]); err == nil {
t.Error("a truncated code was read")
}
if _, err := Parse("ssh-rsa AAAA"); err == nil {
t.Error("something else was read as a code")
}
// Short enough to copy from a screen in one line.
c.Expires = time.Now().Add(time.Hour).Unix()
if n := len(c.String()); n > 100 {
t.Errorf("a code is %d characters", n)
}
if back, err := Parse(c.String()); err != nil || back.Addr != c.Addr || back.Role != "node" {
t.Errorf("%+v, %v", back, err)
}
}
func TestTheSetupRefusesAnExpiredCodeEvenFromAJoinerThatDidNotLook(t *testing.T) {
l := listen(t)
code, arrived := l.Code("node")
// Time moves on past the code's life on the setup's side; the joiner is
// handed the code as it was, without checking it again.
l.now = func() time.Time { return time.Now().Add(2 * time.Hour) }
err := Serve(context.Background(), code, func(string) {})
if err == nil || !strings.Contains(err.Error(), "expired") {
t.Fatalf("an expired code was taken: %v", err)
}
select {
case m := <-arrived:
t.Fatalf("a machine arrived on an expired code: %+v", m)
default:
}
}