Choose the hub: Settings' choice, else the install's channel's; state kept per hub
CI / build (push) Successful in 18s

The client's Settings > T5DE chooses the hub from the next start on (POST /hub/server, kept in server.json);
without a choice, -channel (which the client passes) decides: staging for nightly and dev, T5DE's hub for
releases. $T5DE_HUB still wins. Each hub's sign-in, product IDs and products are kept in their own folder
(T5DE's hub in the data folder as before, others in hubs/<host>), so a hub never gets another's token and
product IDs from two hubs never clash; a sign-in saved for another hub is ignored. Signing in always goes to
this run's hub.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-11-02 09:09:34 -08:00
1 parent 8daf91ede3
commit 8ae45fc07a
6 files changed
+292 -53

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
package control
import (
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"git.t5de.dev/t5de/t5de-proxy/internal/hub"
"git.t5de.dev/t5de/t5de-proxy/internal/proxy"
)
func TestServer(t *testing.T) {
choice := filepath.Join(t.TempDir(), "server.json")
for channel, want := range map[string]string{"release": Production, "": Production, "nightly": Staging, "dev": Staging} {
if got := Server(choice, channel, ""); got != want {
t.Errorf("channel %q: %s", channel, got)
}
}
if err := SaveChoice(choice, "https://hub.example.org"); err != nil {
t.Fatal(err)
}
if got := Server(choice, "nightly", ""); got != "https://hub.example.org" {
t.Errorf("chosen: %s", got)
}
if got := Server(choice, "nightly", "http://localhost:4101/"); got != "http://localhost:4101" {
t.Errorf("$T5DE_HUB: %s", got)
}
if err := SaveChoice(choice, ""); err != nil {
t.Fatal(err)
}
if got := Server(choice, "release", ""); got != Production {
t.Errorf("default again: %s", got)
}
// A choice that isn't a hub address is ignored.
os.WriteFile(choice, []byte(`{"hub": "http://evil.test"}`), 0o600)
if got := Server(choice, "release", ""); got != Production {
t.Errorf("bad choice: %s", got)
}
data := `C:\data`
if got := StateDir(data, Production); got != data {
t.Errorf("production state %s", got)
}
if got := StateDir(data, Staging); got != filepath.Join(data, "hubs", "hub.staging.t5de.dev") {
t.Errorf("staging state %s", got)
}
if got := StateDir(data, "http://LocalHost:4101/x"); got != filepath.Join(data, "hubs", "localhost_4101_x") {
t.Errorf("local state %s", got)
}
for _, bad := range []string{"http://evil.test", "ftp://x", "hub.t5de.dev", "https://", "https://x/?a=1"} {
if _, err := CheckHub(bad); err == nil {
t.Errorf("accepted %q", bad)
}
}
}
func TestChoosingServer(t *testing.T) {
dir := t.TempDir()
m := &Manager{Path: filepath.Join(dir, "hub.json"), Hub: Staging, Default: Staging, ChoicePath: filepath.Join(dir, "server.json"),
Log: log.New(io.Discard, "", 0)}
if s := ask(t, m, "GET", "/hub", ""); s.Hub != Staging || s.Default != Staging || s.Choice != "" || s.Restart {
t.Fatalf("status %+v", s)
}
if s := ask(t, m, "POST", "/hub/server", "hub="+url.QueryEscape(Production+"/")); s.Choice != Production || !s.Restart || s.Hub != Staging {
t.Fatalf("chose production: %+v", s)
}
// Choosing the default again forgets the choice.
if s := ask(t, m, "POST", "/hub/server", "hub="+url.QueryEscape(Staging)); s.Choice != "" || s.Restart {
t.Fatalf("back to the default: %+v", s)
}
if _, err := os.Stat(m.ChoicePath); !os.IsNotExist(err) {
t.Fatalf("server.json kept: %v", err)
}
u, _ := url.Parse("http://" + Host + "/hub/server")
body := "hub=http://evil.test"
req, _ := http.NewRequest("POST", u.String(), strings.NewReader(body))
x := &proxy.Exchange{URL: u, Request: req, Body: []byte(body)}
m.Intercept(x)
if x.Response.StatusCode != 400 {
t.Fatalf("plain http hub: %d", x.Response.StatusCode)
}
// Set by $T5DE_HUB: never a restart to suggest.
m.Fixed = true
ask(t, m, "POST", "/hub/server", "hub="+url.QueryEscape(Production))
if s := ask(t, m, "GET", "/hub", ""); s.Restart || !s.Fixed {
t.Fatalf("fixed: %+v", s)
}
}
func TestSignInForAnotherHub(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "hub.json")
if err := (&hub.Saved{URL: "http://localhost:4101", Token: "old"}).Save(path); err != nil {
t.Fatal(err)
}
attached := 0
m := &Manager{Path: path, Hub: Production, Log: log.New(io.Discard, "", 0), Attach: func(*hub.Client) { attached++ }}
m.Start()
if s := ask(t, m, "GET", "/hub", ""); s.SignedIn || attached != 0 {
t.Fatalf("used another hub's sign-in: %+v", s)
}
}