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) } }