package web
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/xfer"
)
type webFossilUsers struct {
users []oltp.FossilUser
links map[string]string
fetched int
warn string
}
func (m *webFossilUsers) ReplaceFossilUsers(_ string, u []oltp.FossilUser) (int, int, error) {
m.users = u
return len(u), 0, nil
}
func (m *webFossilUsers) FossilUsers(string) ([]oltp.FossilUser, error) { return m.users, nil }
func (m *webFossilUsers) LinkFossilUser(_, _, login, account string) (string, error) {
m.links[login] = account
for i := range m.users {
if m.users[i].Login == login {
m.users[i].Linked, m.users[i].Suggest = account, false
}
}
return m.warn, nil
}
func fossilUsersServer(t *testing.T, plane *memCaps, store *webFossilUsers) *httptest.Server {
t.Helper()
r := ops.NewRegistry()
if err := api.RegisterAll(r, api.Deps{Version: "v",
Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: landingIndex(t, false)}}),
Roster: capsRoster{plane},
FossilUsers: store,
ProjectCode: func(string) (string, error) { return "code", nil },
FetchUsers: func(string, string, string, string) ([]xfer.RemoteUser, error) {
store.fetched++
return nil, nil
}}); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
WithCaps(plane)}))
t.Cleanup(srv.Close)
return srv
}
// Advanced lists the synced users; a matching name is suggested, a
// linked account shows on People as coming from fossil.
func TestSettingsListFossilUsersAndMarkLinkedPeople(t *testing.T) {
plane := &memCaps{stored: map[string]string{"dan": "a", "al": "i"}}
store := &webFossilUsers{links: map[string]string{}, users: []oltp.FossilUser{
{Login: "alice", Caps: "i", Linked: "al"},
{Login: "bob", Caps: "k", Suggest: true},
}}
body := asDan(t, fossilUsersServer(t, plane, store), "/r/alpha/settings")
for _, hook := range []string{`data-hook="fossil-users"`, `data-login="alice"`, `data-hook="fossil-suggest"`, `data-hook="from-fossil"`} {
if !strings.Contains(body, hook) {
t.Errorf("settings lack %s", hook)
}
}
if strings.Contains(body, `name="password" value=`) {
t.Error("the password field is prefilled")
}
}
// A repository admin cannot aim the server at another host; the refusal
// comes back to the block, and nothing is fetched.
func TestFossilUserSyncFormCarriesTheRefusal(t *testing.T) {
plane := &memCaps{stored: map[string]string{"dan": "a"}}
store := &webFossilUsers{links: map[string]string{}}
srv := fossilUsersServer(t, plane, store)
resp := postAsDan(t, srv, "/r/alpha/settings/fossil-users", url.Values{
"url": {"http://127.0.0.1:4222/"}, "login": {"root"}, "password": {"pw"}})
if resp.StatusCode != http.StatusSeeOther || !strings.Contains(resp.Header.Get("Location"), "fe=") {
t.Fatalf("status %d, location %q", resp.StatusCode, resp.Header.Get("Location"))
}
if strings.Contains(resp.Header.Get("Location"), "pw") {
t.Fatal("the password travelled into the redirect")
}
if store.fetched != 0 {
t.Fatal("a refused sync fetched")
}
resp = postAsDan(t, srv, "/r/alpha/settings/fossil-users/link", url.Values{"login": {"bob"}, "account": {"bob"}})
if resp.StatusCode != http.StatusSeeOther || store.links["bob"] != "bob" {
t.Fatalf("link: status %d, links %v", resp.StatusCode, store.links)
}
}
// A link the guard allows with a warning — a site admin linking away a
// repository's last admin — brings the warning back to the block.
func TestFossilUserLinkShowsTheGuardsWarning(t *testing.T) {
plane := &memCaps{stored: map[string]string{"dan": "a"}}
store := &webFossilUsers{links: map[string]string{}, warn: "alpha has no admin of its own now",
users: []oltp.FossilUser{{Login: "bob", Caps: "k"}}}
srv := fossilUsersServer(t, plane, store)
resp := postAsDan(t, srv, "/r/alpha/settings/fossil-users/link", url.Values{"login": {"bob"}, "account": {"bob"}})
loc := resp.Header.Get("Location")
if resp.StatusCode != http.StatusSeeOther || store.links["bob"] != "bob" {
t.Fatalf("FAIL: link: status %d, links %v", resp.StatusCode, store.links)
}
u, err := url.Parse(loc)
if err != nil || u.Query().Get("fw") != store.warn {
t.Fatalf("FAIL: the redirect %q does not carry the guard's warning", loc)
}
t.Log("ok: the redirect carries the warning")
body := asDan(t, srv, u.RequestURI())
if !strings.Contains(body, `data-hook="fossil-link-warn"`) {
t.Fatal("FAIL: the settings page does not show the link's warning")
}
t.Log("ok: the settings page shows it")
}
// A hand-pinned account that diverges from upstream says so.
func TestSettingsShowAPinnedLink(t *testing.T) {
plane := &memCaps{stored: map[string]string{"dan": "a", "al": "a"}}
store := &webFossilUsers{links: map[string]string{}, users: []oltp.FossilUser{
{Login: "alice", Caps: "k", Linked: "al", Held: "a", Pinned: true},
{Login: "carol", Caps: "i", Linked: "dan", Held: "i"},
}}
body := asDan(t, fossilUsersServer(t, plane, store), "/r/alpha/settings")
if n := strings.Count(body, `data-hook="fossil-pinned"`); n != 1 || !strings.Contains(body, `data-held="a"`) {
t.Fatalf("%d pinned marks", n)
}
}