josie / simplegit

package web

import (
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

// seedFiles gives a repo created via /new one commit on main.
func seedFiles(t *testing.T, dataDir, name string) {
	t.Helper()
	bare := filepath.Join(dataDir, "repos", "josie", name+".git")
	work := filepath.Join(t.TempDir(), "work")
	cmd := exec.Command("git", "clone", "-q", bare, work)
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("clone seed: %v: %s", err, out)
	}
	write := func(rel, content string) {
		full := filepath.Join(work, rel)
		if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
			t.Fatalf("mkdir: %v", err)
		}
		if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
			t.Fatalf("write %s: %v", rel, err)
		}
	}
	write("README.md", "# demo repo\n\nthe **readme** body\n")
	write("LICENSE", "MIT License\n\nPermission is hereby granted, free of charge, to any person...\n")
	write("hello.c", "int main(void) { return 0; }\n")
	write("sub/note.txt", "note\n")
	for _, args := range [][]string{
		{"add", "."},
		{"-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "seed"},
		{"push", "-q", "origin", "main"},
	} {
		cmd := exec.Command("git", args...)
		cmd.Dir = work
		if out, err := cmd.CombinedOutput(); err != nil {
			t.Fatalf("git %v: %v: %s", args, err, out)
		}
	}
}

func TestRepoHomePublic(t *testing.T) {
	httpServer, _, dataDir := newTestServer(t)
	createRepo(t, newLoggedInClient(t, httpServer), httpServer, "pub", "public")
	seedFiles(t, dataDir, "pub")

	resp, err := (&http.Client{}).Get(httpServer.URL + "/josie/pub")
	if err != nil {
		t.Fatalf("GET /josie/pub: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, want 200: %q", resp.StatusCode, body)
	}
	for _, want := range []string{
		"josie/pub", "README.md", "hello.c", "sub/",
		"<strong>readme</strong>", "MIT", "/josie/pub.git",
		"/josie/pub/blob/main/hello.c", "/josie/pub/blob/main/sub/note.txt",
		`class="file-tree`, "✓ main", `value="http`,
	} {
		if !strings.Contains(body, want) {
			t.Errorf("body lacks %q", want)
		}
	}
}

func TestRepoHomePrivate(t *testing.T) {
	httpServer, _, dataDir := newTestServer(t)
	loggedIn := newLoggedInClient(t, httpServer)
	createRepo(t, loggedIn, httpServer, "sec", "private")
	seedFiles(t, dataDir, "sec")

	noFollow := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
		return http.ErrUseLastResponse
	}}
	resp, err := noFollow.Get(httpServer.URL + "/josie/sec")
	if err != nil {
		t.Fatalf("anonymous GET private home: %v", err)
	}
	readAll(t, resp)
	if resp.StatusCode != http.StatusNotFound {
		t.Errorf("anonymous status = %d, want 404 (no existence oracle)", resp.StatusCode)
	}

	resp, err = loggedIn.Get(httpServer.URL + "/josie/sec")
	if err != nil {
		t.Fatalf("signed-in GET private home: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusOK || !strings.Contains(body, "README.md") {
		t.Errorf("signed-in status = %d, want 200 with listing", resp.StatusCode)
	}
}

func TestRepoHomeLowercaseReadme(t *testing.T) {
	httpServer, _, dataDir := newTestServer(t)
	createRepo(t, newLoggedInClient(t, httpServer), httpServer, "lc", "public")
	seedFiles(t, dataDir, "lc")

	work := filepath.Join(t.TempDir(), "work")
	cmd := exec.Command("git", "clone", "-q", filepath.Join(dataDir, "repos", "josie", "lc.git"), work)
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("clone: %v: %s", err, out)
	}
	for _, args := range [][]string{
		{"mv", "README.md", "readme.md"},
		{"-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "lowercase readme"},
		{"push", "-q", "origin", "main"},
	} {
		cmd := exec.Command("git", args...)
		cmd.Dir = work
		if out, err := cmd.CombinedOutput(); err != nil {
			t.Fatalf("git %v: %v: %s", args, err, out)
		}
	}

	resp, err := (&http.Client{}).Get(httpServer.URL + "/josie/lc")
	if err != nil {
		t.Fatalf("GET /josie/lc: %v", err)
	}
	body := readAll(t, resp)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, want 200: %q", resp.StatusCode, body)
	}
	for _, want := range []string{"readme.md", "<strong>readme</strong>"} {
		if !strings.Contains(body, want) {
			t.Errorf("body lacks %q", want)
		}
	}
}

func TestRepoHomeEmptyRepoShowsInstructions(t *testing.T) {
	httpServer, _, _ := newTestServer(t)
	loggedIn := newLoggedInClient(t, httpServer)
	createRepo(t, loggedIn, httpServer, "fresh", "private")

	resp, err := loggedIn.Get(httpServer.URL + "/josie/fresh")
	if err != nil {
		t.Fatalf("GET empty home: %v", err)
	}
	body := readAll(t, resp)
	if !strings.Contains(body, "The repository is empty") || !strings.Contains(body, "git remote add origin") {
		t.Errorf("empty repo page lacks push instructions: %q", body)
	}
}

func TestRepoHomePrivateOwnerOnly(t *testing.T) {
	httpServer, database, dataDir := newTestServer(t)
	createRepo(t, newLoggedInClient(t, httpServer), httpServer, "sec", "private")
	seedFiles(t, dataDir, "sec")
	addUser(t, database, "mallory", "pw")

	mallory := loginAs(t, httpServer, "mallory", "pw")
	for _, path := range []string{"/josie/sec", "/josie/sec/tree/main", "/josie/sec/blob/main/README.md"} {
		resp, err := mallory.Get(httpServer.URL + path)
		if err != nil {
			t.Fatalf("GET %s as non-owner: %v", path, err)
		}
		readAll(t, resp)
		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("non-owner GET %s = %d, want 404", path, resp.StatusCode)
		}
	}
}

func TestRepoHomeUnknown(t *testing.T) {
	httpServer, _, _ := newTestServer(t)
	resp, err := (&http.Client{}).Get(httpServer.URL + "/josie/nope")
	if err != nil {
		t.Fatalf("GET unknown: %v", err)
	}
	readAll(t, resp)
	if resp.StatusCode != http.StatusNotFound {
		t.Errorf("status = %d, want 404", resp.StatusCode)
	}
}