Full web : fichiers restants

This commit is contained in:
cedric committed 2026-10-23 17:43:20 +02:00
1 parent ec3b7c9a01
commit 4826fa5b82
26 files changed
+2537 -793

No files matched your search

+109 -2
View File
@@ -10,11 +10,13 @@ import (
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"testing/fstest"
"time"
"pve-confsync/internal/demo"
"pve-confsync/internal/pve/pvetest"
"pve-confsync/internal/store"
)
@@ -117,8 +119,8 @@ func TestAuthAndRoles(t *testing.T) {
t.Fatalf("clusters: %d", code)
}
for _, c := range d["clusters"].([]any) {
if s, ok := c.(map[string]any)["secret"]; ok && s != "" {
t.Fatalf("secret returned: %v", c)
if s, ok := c.(map[string]any)["password"]; ok && s != "" {
t.Fatalf("password returned: %v", c)
}
}
}
@@ -147,6 +149,9 @@ func TestDryRunAndApply(t *testing.T) {
if code != http.StatusOK {
t.Fatalf("dry-run site-a: %d %v", code, d)
}
if b, _ := json.Marshal(d); strings.Contains(string(b), "HashOnlyForTheDemo") || !strings.Contains(string(b), "pve-password") {
t.Fatalf("password hashes must be planned but never shown: %s", b)
}
sel["digests"] = digests(d)
if code, d := op.do("POST", "/api/apply", sel, true); code != http.StatusBadRequest || d["code"] != "confirm" {
t.Fatalf("SDN apply without confirmation: %d %v", code, d)
@@ -188,4 +193,106 @@ func TestDryRunAndApply(t *testing.T) {
}
}
// TestFromScratch: empty database, first administrator from the web, clusters
// added with their root password, certificate and SSH host key pinned by the
// first test, primary chosen, replication of the accounts to one target.
func TestFromScratch(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
st, err := store.Open(filepath.Join(dir, "t.db"), bytes.Repeat([]byte{7}, 32))
if err != nil {
t.Fatal(err)
}
srv, err := New(Options{Store: st, Version: "test",
Static: fstest.MapFS{"index.html": {Data: []byte("<html>")}, "login.html": {Data: []byte("<html>")}},
Auth: AuthConfig{Local: true, SessionTTL: time.Hour, DataDir: dir}})
if err != nil {
t.Fatal(err)
}
hs := httptest.NewServer(srv.Handler())
ref, tgt := pvetest.New("9.1.4"), pvetest.New("9.0.10")
t.Cleanup(func() { hs.Close(); srv.Wait(); ref.Close(); tgt.Close(); st.Close() })
ref.Groups["ops"] = map[string]any{"comment": "Ops"}
ref.Users["alice@pve"] = map[string]any{"enable": 1, "groups": "ops"}
ref.Shadow["alice"] = "$5$s$aliceSecretHash"
anon := &client{t: t, url: hs.URL, c: http.DefaultClient}
if _, d := anon.do("GET", "/auth/config", nil, false); d["setup"] != true {
t.Fatalf("setup expected: %v", d)
}
jar, _ := cookiejar.New(nil)
adm := &client{t: t, url: hs.URL, c: &http.Client{Jar: jar, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}}
r, err := adm.c.PostForm(hs.URL+"/auth/setup", url.Values{"user": {"cedric"}, "pass": {"a-long-password"}, "pass2": {"a-long-password"}})
if err != nil {
t.Fatal(err)
}
r.Body.Close()
if r.StatusCode != http.StatusSeeOther || r.Header.Get("Location") != "/#clusters" {
t.Fatalf("setup: %d %s", r.StatusCode, r.Header.Get("Location"))
}
if r, _ := anon.c.PostForm(hs.URL+"/auth/setup", url.Values{"user": {"intrus"}, "pass": {"a-long-password"}, "pass2": {"a-long-password"}}); r != nil {
r.Body.Close()
}
if n, _ := st.CountUsers(ctx); n != 1 {
t.Fatal("a second setup must be refused")
}
if _, d := adm.do("GET", "/api/state", nil, false); d["setup"] != "clusters" {
t.Fatalf("state on an empty database: %v", d)
}
for _, c := range []struct {
name string
s *pvetest.Server
}{{"ref", ref}, {"tgt", tgt}} {
_, port := c.s.SSHAddr()
code, d := adm.do("PUT", "/api/clusters/-", map[string]any{"name": c.name, "url": c.s.URL, "password": pvetest.Password,
"tls": "fp", "sshPort": port}, true)
if code != http.StatusOK {
t.Fatalf("save %s: %d %v", c.name, code, d)
}
code, d = adm.do("POST", "/api/clusters/"+c.name+"/check", nil, true)
if code != http.StatusOK || d["ok"] != true || d["tlsFingerprint"] == "" || d["sshFingerprint"] == "" {
t.Fatalf("check %s: %d %v", c.name, code, d)
}
if code, d = adm.do("POST", "/api/clusters/"+c.name+"/check", nil, true); d["ok"] != true || d["sshFingerprint"] != nil {
t.Fatalf("second check %s (pinned): %d %v", c.name, code, d)
}
}
if _, d := adm.do("GET", "/api/state", nil, false); d["setup"] != "primary" {
t.Fatalf("state without primary: %v", d)
}
if code, d := adm.do("POST", "/api/clusters/ref/role", map[string]any{"role": "primary"}, true); code != http.StatusOK {
t.Fatalf("role: %d %v", code, d)
}
sel := map[string]any{"scope": "global", "targets": []string{"tgt"}}
code, d := adm.do("POST", "/api/dryrun", sel, true)
if code != http.StatusOK {
t.Fatalf("dry-run: %d %v", code, d)
}
sel["digests"] = digests(d)
if code, d = adm.do("POST", "/api/apply", sel, true); code != http.StatusAccepted {
t.Fatalf("apply: %d %v", code, d)
}
id := int(d["run"].(float64))
var run map[string]any
for i := 0; i < 100; i++ {
_, run = adm.do("GET", "/api/runs/"+itoa(id), nil, false)
if run["status"] != "running" {
break
}
time.Sleep(50 * time.Millisecond)
}
if run["status"] != "ok" {
t.Fatalf("run: %v", run)
}
if tgt.Shadow["alice"] != "$5$s$aliceSecretHash" || tgt.Users["alice@pve"]["groups"] != "ops" {
t.Fatalf("target: %v %v", tgt.Shadow, tgt.Users)
}
if b, _ := json.Marshal(run); strings.Contains(string(b), "aliceSecretHash") {
t.Fatal("hash in the journal")
}
if ref.Calls != nil {
t.Fatalf("the primary must never be written: %v", ref.Calls)
}
}
func itoa(i int) string { b, _ := json.Marshal(i); return string(b) }