package web
import (
"net/http/httptest"
"net/url"
"sync"
"testing"
"time"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ops"
)
// memMirrorUV is a mirror table in memory, with the import's half.
type memMirrorUV struct {
mu sync.Mutex
m map[string]api.Mirror
pw map[string]string
}
func (s *memMirrorUV) Mirrors() ([]api.Mirror, error) {
s.mu.Lock()
defer s.mu.Unlock()
var out []api.Mirror
for _, m := range s.m {
out = append(out, m)
}
return out, nil
}
func (s *memMirrorUV) MirrorFor(repo string) (api.Mirror, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
m, ok := s.m[repo]
return m, ok, nil
}
func (s *memMirrorUV) SetMirror(repo, u string, every int) error {
s.mu.Lock()
defer s.mu.Unlock()
m := s.m[repo]
if m.URL != u {
m.UVUser, m.UVCredential = "", false
}
m.Repo, m.URL, m.Every, m.Enabled = repo, u, every, true
s.m[repo] = m
return nil
}
func (s *memMirrorUV) DetachMirror(string) error { return nil }
func (s *memMirrorUV) SetMirrorWrites(string, bool) error { return nil }
func (s *memMirrorUV) RecordMirrorRun(string, time.Duration, bool, error) (int, error) { return 0, nil }
func (s *memMirrorUV) MirrorPulls(string, int) ([]api.MirrorPull, error) { return nil, nil }
func (s *memMirrorUV) MirrorDiverged(string, string, time.Time) error { return nil }
func (s *memMirrorUV) SetMirrorUV(repo string, on bool) error {
s.mu.Lock()
defer s.mu.Unlock()
m := s.m[repo]
m.UV = on
s.m[repo] = m
return nil
}
func (s *memMirrorUV) SetMirrorUVCredential(repo, user, pw string) error {
s.mu.Lock()
defer s.mu.Unlock()
m := s.m[repo]
m.UVUser, m.UVCredential = user, user != ""
s.m[repo], s.pw[repo] = m, pw
return nil
}
func (s *memMirrorUV) MirrorUVCredential(repo string) (string, string, bool, error) {
m, _, _ := s.MirrorFor(repo)
return m.UVUser, s.pw[repo], m.UVCredential, nil
}
func (s *memMirrorUV) RecordMirrorUV(string, string, int, string) error { return nil }
// The fleet form saves the mirror and its import as one: a field the
// import refuses leaves the address and the credential as they were.
func TestFleetMirrorFormNeverHalfApplies(t *testing.T) {
store := &memMirrorUV{m: map[string]api.Mirror{"test": {Repo: "test", URL: "https://a.example/x", Every: 3600, Enabled: true,
UVUser: "puller", UVCredential: true}}, pw: map[string]string{"test": "secret"}}
accts := newBetaAccts()
_ = accts.AddAccount("root", "root-pw", true)
r := ops.NewRegistry()
// No UV catalog: this forge keeps no unversioned files, so the
// switch is blocked for every repository.
if err := api.RegisterAll(r, api.Deps{Version: "v", Accounts: accts, Mirrors: store, MirrorUV: store,
Repos: api.NewRepos(map[string]*api.Repo{"test": {}})}); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(HandlerWith(r, nil, nil,
[]Option{WithSessions(&betaSessions{accts: accts, admin: "root"}), WithCaps(betaCaps{admin: "root"})},
Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })))
defer srv.Close()
post := func(f url.Values) int {
res := as(t, srv, "root", "POST", "/fleet/mirror", f)
body(res)
return res.StatusCode
}
form := url.Values{"repo": {"test"}, "url": {"https://b.example/x"}, "every": {"3600"}, "uvform": {"1"},
"uvuser": {"puller"}, "uvpassword": {""}}
code := post(form)
m, _, _ := store.MirrorFor("test")
check := func(ok bool, pass, fail string, args ...any) {
t.Helper()
if ok {
t.Log("ok: " + pass)
} else {
t.Errorf("FAIL: "+fail, args...)
}
}
check(code >= 400 && m.URL == "https://a.example/x" && m.UVCredential && store.pw["test"] == "secret",
"a new address with the old login and no password is refused before either is saved",
"status %d, mirror %+v", code, m)
form.Set("url", "https://a.example/x")
form.Set("every", "7200")
form.Set("uv", "1")
code = post(form)
m, _, _ = store.MirrorFor("test")
check(code >= 400 && m.Every == 3600 && !m.UV,
"the switch on where the store cannot import is refused before the interval is saved",
"status %d, mirror %+v", code, m)
form.Del("uv")
code = post(form)
m, _, _ = store.MirrorFor("test")
check(code == 303 && m.Every == 7200 && m.UVCredential && store.pw["test"] == "secret",
"the same form with the switch off saves, keeping the held password",
"status %d, mirror %+v", code, m)
}