Files
Hammerhead/selfedge_test.go
T
VeillaxandClaude Sonnet 5.5 abf352e6c9
CI / test (push) Successful in 4m48s
Add a Check for updates button under Settings; keep the self edge when settings are saved
Saving settings replaced the whole Settings value, which would have dropped SelfEdgeID and made the
next start create a second self edge with a new token.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 16:02:27 -06:00

122 lines
3.6 KiB
Go

package hammerhead
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"git.veillax.com/Veillax/hammerhead/internal/secretbox"
"git.veillax.com/Veillax/reef/model"
)
func edgeConfigOf(t *testing.T, s *Server, token string) model.EdgeConfig {
t.Helper()
ts := httptest.NewServer(s.Handler())
defer ts.Close()
req, _ := http.NewRequest("GET", ts.URL+"/edge/config?v=0", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
var cfg model.EdgeConfig
if resp.StatusCode != 200 {
t.Fatalf("edge config: %s", resp.Status)
}
json.NewDecoder(resp.Body).Decode(&cfg)
return cfg
}
func tokenFile(t *testing.T, dir string) string {
b, err := os.ReadFile(filepath.Join(dir, "self-edge.env"))
if err != nil {
t.Fatal(err)
}
return strings.TrimSpace(strings.SplitN(strings.SplitN(string(b), "BLACKTIP_TOKEN=", 2)[1], "\n", 2)[0])
}
var key = secretbox.GenerateKey()
func TestSelfEdgeServesTheDashboardWithoutAProxy(t *testing.T) {
dir := t.TempDir()
opts := Options{MasterKey: key, SelfDomain: "Hammerhead.Example.com", SelfTarget: "127.0.0.1:8080"}
s, err := New(dir, opts)
if err != nil {
t.Fatal(err)
}
token := tokenFile(t, dir)
cfg := edgeConfigOf(t, s, token)
if len(cfg.Mappings) != 1 || len(cfg.Local) != 1 {
t.Fatalf("mappings=%d local=%d", len(cfg.Mappings), len(cfg.Local))
}
m := cfg.Mappings[0]
if m.Proto != model.ProtoHTTPS || m.Hostname != "hammerhead.example.com" || m.Port != 443 || !m.RedirectHTTP || !m.Enabled ||
len(m.Targets) != 1 || m.Targets[0].Host != "127.0.0.1" || m.Targets[0].Port != 8080 || m.Targets[0].SiteID != cfg.Local[0] {
t.Fatalf("unexpected dashboard route: %+v", m)
}
if m.Auth.Protected() {
t.Fatal("the dashboard has its own login and must not sit behind resource auth")
}
edgeID := s.st.Settings.SelfEdgeID
if info, _ := os.Stat(filepath.Join(dir, "self-edge.env")); info.Mode().Perm()&0o077 != 0 && os.PathSeparator == '/' {
t.Fatalf("token file is readable by others: %v", info.Mode())
}
s.Close()
// a restart keeps the same edge and token instead of making a new one
s2, err := New(dir, opts)
if err != nil {
t.Fatal(err)
}
defer s2.Close()
if s2.st.Settings.SelfEdgeID != edgeID || tokenFile(t, dir) != token || len(s2.st.Edges) != 1 {
t.Fatal("restart changed the self edge")
}
if len(edgeConfigOf(t, s2, token).Mappings) != 1 {
t.Fatal("route missing after restart")
}
}
func TestNoSelfEdgeUnlessAsked(t *testing.T) {
dir := t.TempDir()
s, err := New(dir, Options{MasterKey: key})
if err != nil {
t.Fatal(err)
}
defer s.Close()
if len(s.st.Edges) != 0 {
t.Fatal("an edge appeared without HAMMERHEAD_SELF_DOMAIN")
}
if _, err := os.Stat(filepath.Join(dir, "self-edge.env")); err == nil {
t.Fatal("token file written without HAMMERHEAD_SELF_DOMAIN")
}
}
func TestSavingSettingsKeepsTheSelfEdge(t *testing.T) {
dir := t.TempDir()
opts := Options{AdminEmail: "admin@test.local", AdminPassword: "password123", MasterKey: key, SelfDomain: "hh.example.com", SelfTarget: "127.0.0.1:8080"}
s, err := New(dir, opts)
if err != nil {
t.Fatal(err)
}
token := tokenFile(t, dir)
do := apiClient(t, s)
if code, b := do("PUT", "/api/admin/settings", map[string]any{"dashboardUrl": "https://hh.example.com", "acmeEmail": "me@example.com"}); code != 200 {
t.Fatalf("settings: %d %s", code, b)
}
s.Close()
s2, err := New(dir, opts)
if err != nil {
t.Fatal(err)
}
defer s2.Close()
if len(s2.st.Edges) != 1 || tokenFile(t, dir) != token {
t.Fatalf("saving settings lost the self edge: %d edges", len(s2.st.Edges))
}
}