gitmote

gitmote / internal / bootstrap / bootstrap_test.go  ·  history  ·  branches

5043 bytes · raw

package bootstrap

import (
	"context"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"

	"github.com/atmin/gitmote/internal/auth"
	"github.com/atmin/gitmote/internal/meta"
)

func openMeta(t *testing.T) *meta.Metadata {
	t.Helper()
	m, err := meta.Open(context.Background(), meta.Config{
		LocalPath: filepath.Join(t.TempDir(), "meta.sqlite3"),
	})
	if err != nil {
		t.Fatalf("meta.Open: %v", err)
	}
	t.Cleanup(func() { _ = m.Close() })
	return m
}

// verifyToken resolves raw to its owner — the token-only check, no repo needed.
func verifyToken(t *testing.T, m *meta.Metadata, raw string) (*meta.User, error) {
	t.Helper()
	return auth.NewGuard(m).VerifyToken(context.Background(), raw)
}

func TestRunCreatesUsableInstanceWithRepo(t *testing.T) {
	ctx := context.Background()
	m := openMeta(t)

	res, err := Run(ctx, m, Options{AdminHandle: "atmin", RepoName: "gitmote"})
	if err != nil {
		t.Fatalf("Run: %v", err)
	}
	if res.AlreadyBootstrapped {
		t.Fatal("fresh instance reported AlreadyBootstrapped")
	}
	if res.Admin == nil || !res.Admin.IsAdmin {
		t.Fatalf("admin = %+v, want a global admin", res.Admin)
	}
	if res.Repo == nil || res.Repo.Name != "gitmote" {
		t.Fatalf("repo = %+v, want gitmote", res.Repo)
	}
	if res.RawToken == "" {
		t.Fatal("no token returned")
	}

	// The printed token authenticates as the admin and is authorized to write the
	// initial repo (the admin holds a repo-admin ACL).
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set("Authorization", "Bearer "+res.RawToken)
	user, err := auth.NewGuard(m).Authorize(req, "gitmote", meta.PermWrite)
	if err != nil {
		t.Fatalf("Authorize with bootstrap token: %v", err)
	}
	if user.ID != res.Admin.ID {
		t.Errorf("authorized user = %d, want admin %d", user.ID, res.Admin.ID)
	}
}

func TestRunDefaultsHandleAndSkipsRepo(t *testing.T) {
	ctx := context.Background()
	m := openMeta(t)

	// No handle and no repo: the admin defaults to "admin" and no repo is made.
	res, err := Run(ctx, m, Options{})
	if err != nil {
		t.Fatalf("Run: %v", err)
	}
	if res.Admin == nil || res.Admin.Handle != DefaultAdminHandle {
		t.Fatalf("admin = %+v, want default handle %q", res.Admin, DefaultAdminHandle)
	}
	if res.Repo != nil {
		t.Errorf("repo = %+v, want none created", res.Repo)
	}
	if res.RawToken == "" {
		t.Fatal("no token returned")
	}
	// The token authenticates as the (global-admin) user with no repo present.
	if _, err := verifyToken(t, m, res.RawToken); err != nil {
		t.Fatalf("VerifyToken with bootstrap token: %v", err)
	}
}

func TestRunIsIdempotentAndRefusesToClobber(t *testing.T) {
	ctx := context.Background()
	m := openMeta(t)

	first, err := Run(ctx, m, Options{AdminHandle: "atmin"})
	if err != nil {
		t.Fatalf("first Run: %v", err)
	}

	// A second run must not create a new admin or mint a new token.
	second, err := Run(ctx, m, Options{AdminHandle: "someone-else"})
	if err != nil {
		t.Fatalf("second Run: %v", err)
	}
	if !second.AlreadyBootstrapped {
		t.Error("second Run did not report AlreadyBootstrapped")
	}
	if second.RawToken != "" {
		t.Error("second Run minted a token")
	}

	// The original admin still authenticates; the would-be second admin was never
	// created.
	if _, err := verifyToken(t, m, first.RawToken); err != nil {
		t.Errorf("original admin no longer authorized: %v", err)
	}
	if _, err := m.GetUser(ctx, "someone-else"); err == nil {
		t.Error("second Run created a user despite an existing admin")
	}
}

func TestReissueMintsFreshTokenForExistingAdmin(t *testing.T) {
	ctx := context.Background()
	m := openMeta(t)

	first, err := Run(ctx, m, Options{AdminHandle: "atmin"})
	if err != nil {
		t.Fatalf("Run: %v", err)
	}

	res, err := Reissue(ctx, m, Options{AdminHandle: "atmin"})
	if err != nil {
		t.Fatalf("Reissue: %v", err)
	}
	if res.RawToken == "" || res.RawToken == first.RawToken {
		t.Fatalf("reissued token = %q, want a fresh non-empty token", res.RawToken)
	}
	if res.Admin.ID != first.Admin.ID {
		t.Errorf("reissued for user %d, want the existing admin %d", res.Admin.ID, first.Admin.ID)
	}

	// Both the old and the freshly-reissued token authenticate as the admin (the
	// old one isn't revoked — reissue only adds a new token).
	if _, err := verifyToken(t, m, res.RawToken); err != nil {
		t.Errorf("reissued token not authorized: %v", err)
	}
	if _, err := verifyToken(t, m, first.RawToken); err != nil {
		t.Errorf("original token no longer authorized after reissue: %v", err)
	}
}

func TestReissueRequiresAnExistingAdmin(t *testing.T) {
	ctx := context.Background()
	m := openMeta(t)

	// No admin yet: reissue has nothing to reissue for.
	if _, err := Reissue(ctx, m, Options{}); err == nil {
		t.Error("Reissue on an empty instance succeeded, want an error")
	}

	// A non-admin user must not be reissued for either.
	if _, err := m.CreateUser(ctx, "plain"); err != nil {
		t.Fatalf("CreateUser: %v", err)
	}
	if _, err := Reissue(ctx, m, Options{AdminHandle: "plain"}); err == nil {
		t.Error("Reissue for a non-admin succeeded, want an error")
	}
}