Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
112 lines
3.5 KiB
Go
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:
|
|
}
|
|
}
|