josie / simplegit

package web

import (
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"git.josie-c.com/josie/simplegit/internal/db"
)

func TestNewRepoFormRequiresLogin(t *testing.T) {
	httpServer, _, _ := newTestServer(t)
	client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
		return http.ErrUseLastResponse
	}}
	resp, err := client.Get(httpServer.URL + "/new")
	if err != nil {
		t.Fatalf("GET /new: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusSeeOther {
		t.Errorf("status = %d, want 303", resp.StatusCode)
	}
	if resp.Header.Get("Location") != "/login" {
		t.Errorf("Location = %q, want /login", resp.Header.Get("Location"))
	}

	loggedIn := newLoggedInClient(t, httpServer)
	resp, err = loggedIn.Get(httpServer.URL + "/new")
	if err != nil {
		t.Fatalf("GET /new signed in: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusOK || !strings.Contains(body, `name="name"`) {
		t.Errorf("signed-in GET /new = %d, form missing: %q", resp.StatusCode, body)
	}
}

func TestCreateRepoFlow(t *testing.T) {
	httpServer, database, dataDir := newTestServer(t)
	client := newLoggedInClient(t, httpServer)

	resp, err := client.PostForm(httpServer.URL+"/new", url.Values{
		"name": {"my-repo"}, "description": {"a test repo"}, "visibility": {"public"},
	})
	if err != nil {
		t.Fatalf("POST /new: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusOK {
		t.Errorf("status = %d, want 200", resp.StatusCode)
	}
	if !strings.Contains(body, "git remote add origin") || !strings.Contains(body, "/josie/my-repo.git") {
		t.Errorf("no push instructions: %q", body)
	}

	repo, err := db.GetRepoByName(database, "josie", "my-repo")
	if err != nil {
		t.Fatalf("GetRepoByName: %v", err)
	}
	if repo.Visibility != "public" || repo.Description != "a test repo" {
		t.Errorf("repo = %+v, want public / %q stored", repo, "a test repo")
	}
	if repo.PushedAt.Valid {
		t.Errorf("PushedAt = %d, want NULL before first push", repo.PushedAt.Int64)
	}

	head, err := os.ReadFile(filepath.Join(dataDir, "repos", "josie", "my-repo.git", "HEAD"))
	if err != nil {
		t.Fatalf("bare repo HEAD missing: %v", err)
	}
	if !strings.Contains(string(head), "ref: refs/heads/"+repo.DefaultBranch) {
		t.Errorf("HEAD = %q, want default branch %q", head, repo.DefaultBranch)
	}
}

func TestCreateRepoRejectsBadNames(t *testing.T) {
	httpServer, _, _ := newTestServer(t)
	client := newLoggedInClient(t, httpServer)
	for _, name := range []string{"bad name", "ünicode", "", strings.Repeat("a", maxNameLen+1)} {
		resp, err := client.PostForm(httpServer.URL+"/new", url.Values{"name": {name}})
		if err != nil {
			t.Fatalf("POST /new %q: %v", name, err)
		}
		body := readAll(t, resp)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Errorf("name %q: status = %d, want 422", name, resp.StatusCode)
		}
		if !strings.Contains(body, "invalid repository name") {
			t.Errorf("name %q: no error shown: %q", name, body)
		}
	}
}

func TestCreateRepoRejectsDuplicate(t *testing.T) {
	httpServer, database, _ := newTestServer(t)
	client := newLoggedInClient(t, httpServer)
	resp, err := client.PostForm(httpServer.URL+"/new", url.Values{"name": {"dup"}})
	if err != nil {
		t.Fatalf("first POST /new: %v", err)
	}
	readAll(t, resp)

	resp, err = client.PostForm(httpServer.URL+"/new", url.Values{"name": {"dup"}})
	if err != nil {
		t.Fatalf("second POST /new: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusUnprocessableEntity {
		t.Errorf("status = %d, want 422", resp.StatusCode)
	}
	if !strings.Contains(body, "already have a repository") {
		t.Errorf("no duplicate error shown: %q", body)
	}

	var count int
	if err := database.QueryRow(
		`SELECT count(*) FROM repos WHERE name = ?`, "dup",
	).Scan(&count); err != nil {
		t.Fatalf("count repos: %v", err)
	}
	if count != 1 {
		t.Errorf("repos named dup = %d, want 1", count)
	}
}