Vectrum
Sign In
277 lines · 7.6 KB · Go
package herdr

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

	"codey/internal/skills"
)

// realSkill is the shape of the file herdr publishes: frontmatter naming the
// skill and describing it, then the instructions.
const realSkill = `---
name: herdr
description: "Control Herdr, a terminal multiplexer for coding agents. Requires HERDR_ENV=1."
---

# Herdr

Before issuing any control command, verify that this agent is running inside a
Herdr-managed pane.
`

// serve points the installer at a test server for the duration of one test.
func serve(t *testing.T, h http.HandlerFunc) {
	t.Helper()
	srv := httptest.NewServer(h)
	t.Cleanup(srv.Close)

	was := skillURL
	skillURL = srv.URL
	t.Cleanup(func() { skillURL = was })
}

// serveSkill serves the real file's shape.
func serveSkill(t *testing.T, body string) {
	t.Helper()
	serve(t, func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte(body))
	})
}

func TestInstallSkillWritesWhereCodeyLooks(t *testing.T) {
	serveSkill(t, realSkill)
	home := t.TempDir()

	path, err := InstallSkill(context.Background(), home)
	if err != nil {
		t.Fatalf("InstallSkill: %v", err)
	}

	want := filepath.Join(home, skills.Dirname, SkillName, "SKILL.md")
	if path != want {
		t.Errorf("installed at %q, want %q", path, want)
	}
	got, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("reading what was installed: %v", err)
	}
	if string(got) != realSkill {
		t.Error("what was written is not what was served")
	}
}

// The install is only worth anything if the next launch finds it, so the test
// that matters is the loader's own.
func TestAnInstalledSkillIsDiscovered(t *testing.T) {
	serveSkill(t, realSkill)
	home := t.TempDir()

	if _, err := InstallSkill(context.Background(), home); err != nil {
		t.Fatalf("InstallSkill: %v", err)
	}

	found, problems := skills.Discover(home, t.TempDir())
	if len(problems) != 0 {
		t.Fatalf("discovery complained about the installed skill: %v", problems)
	}
	if len(found) != 1 {
		t.Fatalf("discovered %d skills, want the one just installed", len(found))
	}
	if found[0].Name != SkillName {
		t.Errorf("discovered %q, want %q", found[0].Name, SkillName)
	}
	if found[0].Description == "" {
		t.Error("the discovered skill has no description, which is all the model sees")
	}
}

// What comes back may be an error page, a login redirect, or a file whose
// shape has changed. None of those should reach the disk as a skill that only
// fails at the next launch.
func TestNothingIsWrittenWhenTheDownloadIsNotASkill(t *testing.T) {
	tests := []struct {
		name    string
		handler http.HandlerFunc
		wantErr string
	}{
		{
			name:    "not found",
			handler: func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "nope", http.StatusNotFound) },
			wantErr: "404",
		},
		{
			name:    "server error",
			handler: func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "nope", http.StatusInternalServerError) },
			wantErr: "500",
		},
		{
			name: "an HTML page",
			handler: func(w http.ResponseWriter, _ *http.Request) {
				w.Write([]byte("<!doctype html><title>Sign in</title>"))
			},
			wantErr: "not a skill",
		},
		{
			name: "frontmatter naming something else",
			handler: func(w http.ResponseWriter, _ *http.Request) {
				w.Write([]byte("---\nname: something-else\ndescription: x\n---\n\nbody\n"))
			},
			wantErr: "not a skill",
		},
		{
			name: "no description",
			handler: func(w http.ResponseWriter, _ *http.Request) {
				w.Write([]byte("---\nname: herdr\n---\n\nbody\n"))
			},
			wantErr: "not a skill",
		},
		{
			name: "more bytes than any skill has",
			handler: func(w http.ResponseWriter, _ *http.Request) {
				w.Write([]byte(strings.Repeat("x", maxSkillBytes+1)))
			},
			wantErr: "not the skill file",
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			serve(t, tc.handler)
			home := t.TempDir()

			path, err := InstallSkill(context.Background(), home)
			if err == nil {
				t.Fatalf("InstallSkill accepted %s and wrote %s", tc.name, path)
			}
			if !strings.Contains(err.Error(), tc.wantErr) {
				t.Errorf("error is %v, want it to mention %q", err, tc.wantErr)
			}
			if _, statErr := os.Stat(filepath.Join(home, skills.Dirname)); !os.IsNotExist(statErr) {
				t.Error("something was written into the home directory anyway")
			}
		})
	}
}

// Re-running setup and saying yes again is how a stale skill gets updated.
func TestInstallingOverAnOlderCopyReplacesIt(t *testing.T) {
	home := t.TempDir()
	dir := filepath.Join(home, skills.Dirname, SkillName)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatal(err)
	}
	old := filepath.Join(dir, "SKILL.md")
	if err := os.WriteFile(old, []byte("---\nname: herdr\ndescription: an older copy\n---\n\nold\n"), 0o644); err != nil {
		t.Fatal(err)
	}

	serveSkill(t, realSkill)
	if _, err := InstallSkill(context.Background(), home); err != nil {
		t.Fatalf("InstallSkill: %v", err)
	}

	got, err := os.ReadFile(old)
	if err != nil {
		t.Fatal(err)
	}
	if string(got) != realSkill {
		t.Error("the older copy is still there")
	}
}

// A failed install must not leave the previous skill destroyed: the write goes
// through a temporary file for exactly this.
func TestAFailedInstallLeavesTheOldSkillAlone(t *testing.T) {
	home := t.TempDir()
	dir := filepath.Join(home, skills.Dirname, SkillName)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatal(err)
	}
	keep := "---\nname: herdr\ndescription: the copy that was already here\n---\n\nkeep\n"
	path := filepath.Join(dir, "SKILL.md")
	if err := os.WriteFile(path, []byte(keep), 0o644); err != nil {
		t.Fatal(err)
	}

	serve(t, func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "nope", http.StatusNotFound) })
	if _, err := InstallSkill(context.Background(), home); err == nil {
		t.Fatal("a 404 was accepted")
	}

	got, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("the old skill is gone: %v", err)
	}
	if string(got) != keep {
		t.Error("the old skill was overwritten by a failed install")
	}
}

func TestInstallSkillNeedsAHomeToInstallInto(t *testing.T) {
	if _, err := InstallSkill(context.Background(), ""); err == nil {
		t.Error("an install with nowhere to write reported success")
	}
}

func TestInstallSkillHonoursACancelledContext(t *testing.T) {
	serveSkill(t, realSkill)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	if _, err := InstallSkill(ctx, t.TempDir()); err == nil {
		t.Error("a cancelled install reported success")
	}
}

// The file is read by the person who installed it as often as by the agent.
func TestTheInstalledSkillIsReadable(t *testing.T) {
	serveSkill(t, realSkill)
	home := t.TempDir()

	path, err := InstallSkill(context.Background(), home)
	if err != nil {
		t.Fatalf("InstallSkill: %v", err)
	}
	info, err := os.Stat(path)
	if err != nil {
		t.Fatal(err)
	}
	if info.Mode().Perm()&0o044 == 0 {
		t.Errorf("mode is %v, want something an editor can open", info.Mode().Perm())
	}
}

func TestPresentFollowsTheEnvironment(t *testing.T) {
	// Inside a pane there is nothing to look up: the variable is the answer.
	t.Setenv("HERDR_ENV", "1")
	if !Present() {
		t.Error("Present is false inside a herdr pane")
	}

	t.Setenv("HERDR_ENV", "")
	t.Setenv("PATH", t.TempDir())
	if Present() {
		t.Error("Present is true with no herdr in the environment and none on PATH")
	}
}

// Someone with herdr installed but running Codey in an ordinary terminal still
// wants the skill, for the next time they are not.
func TestPresentFindsHerdrOnThePath(t *testing.T) {
	dir := t.TempDir()
	bin := filepath.Join(dir, "herdr")
	if err := os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o755); err != nil {
		t.Fatal(err)
	}
	t.Setenv("HERDR_ENV", "")
	t.Setenv("PATH", dir)

	if !Present() {
		t.Error("Present is false with herdr on PATH")
	}
}