package api
import (
"context"
"errors"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/xfer"
)
type memFossilUsers struct {
got []oltp.FossilUser
links map[string]string
}
func (m *memFossilUsers) ReplaceFossilUsers(_ string, u []oltp.FossilUser) (int, int, error) {
m.got = u
return len(u), 0, nil
}
func (m *memFossilUsers) FossilUsers(string) ([]oltp.FossilUser, error) { return m.got, nil }
func (m *memFossilUsers) LinkFossilUser(_, login, account string) error {
for _, u := range m.got {
if u.Login == login {
m.links[login] = account
return nil
}
}
return oltp.ErrNoFossilUser
}
type fetchCall struct{ peer, code, login string }
func fossilUserDeps(t *testing.T, calls *[]fetchCall) (*ops.Registry, *memFossilUsers) {
t.Helper()
store := &memFossilUsers{links: map[string]string{}}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(wikiIndex(t)),
FossilUsers: store,
ProjectCode: func(string) (string, error) { return "code-test", nil },
FetchUsers: func(peer, code, login, _ string) ([]xfer.RemoteUser, error) {
*calls = append(*calls, fetchCall{peer, code, login})
return []xfer.RemoteUser{{Login: "alice", Caps: "ei", PWHash: "secret-hash"}}, nil
}}); err != nil {
t.Fatal(err)
}
return r, store
}
func asCaps(caps string) context.Context {
return ops.WithCaps(ops.WithUser(context.Background(), "dan"), oltp.CapSet(caps))
}
func TestFossilUserSyncFetchesWithTheGivenLoginAndKeepsNoHash(t *testing.T) {
var calls []fetchCall
r, store := fossilUserDeps(t, &calls)
out, err := r.Invoke(asCaps("s"), "repo.fossil.users.sync", fossilUsersSyncIn{
Repo: "test", URL: "https://fossil.example.org/r", Login: "root", Password: "pw"})
if err != nil {
t.Fatal(err)
}
if len(calls) != 1 || calls[0] != (fetchCall{"https://fossil.example.org/r", "code-test", "root"}) {
t.Fatalf("fetch calls = %+v", calls)
}
if got := out.(fossilUsersSyncOut); got.Users != 1 || len(store.got) != 1 || store.got[0].Login != "alice" {
t.Fatalf("sync out %+v, stored %+v", got, store.got)
}
}
// The server makes the request, so only a site administrator may aim
// it somewhere other than the repository's own fossil server.
func TestFossilUserSyncRefusesAnotherServerToARepositoryAdmin(t *testing.T) {
var calls []fetchCall
r, _ := fossilUserDeps(t, &calls)
_, err := r.Invoke(asCaps("a"), "repo.fossil.users.sync", fossilUsersSyncIn{
Repo: "test", URL: "http://127.0.0.1:4222/", Login: "root", Password: "pw"})
if !errors.Is(err, ops.ErrForbidden) {
t.Fatalf("repository admin with a foreign URL: %v", err)
}
_, err = r.Invoke(asCaps("a"), "repo.fossil.users.sync", fossilUsersSyncIn{
Repo: "test", Login: "root", Password: "pw"})
if !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("no URL and no upstream: %v", err)
}
_, err = r.Invoke(asCaps("s"), "repo.fossil.users.sync", fossilUsersSyncIn{
Repo: "test", URL: "file:///etc/passwd", Login: "root", Password: "pw"})
if !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("a non-http URL: %v", err)
}
if len(calls) != 0 {
t.Fatalf("a refused sync still fetched: %+v", calls)
}
}
func TestFossilUserLinkNamesAMissingLogin(t *testing.T) {
var calls []fetchCall
r, store := fossilUserDeps(t, &calls)
store.got = []oltp.FossilUser{{Login: "alice", Caps: "i"}}
if _, err := r.Invoke(asCaps("a"), "repo.fossil.users.link", fossilUserLinkIn{Repo: "test", Login: "alice", Account: "al"}); err != nil {
t.Fatal(err)
}
if store.links["alice"] != "al" {
t.Fatalf("links = %v", store.links)
}
_, err := r.Invoke(asCaps("a"), "repo.fossil.users.link", fossilUserLinkIn{Repo: "test", Login: "zed", Account: "al"})
if !errors.Is(err, ops.ErrNotFound) {
t.Fatalf("link of a login that was never synced: %v", err)
}
}