a73x

cmd/filewatch-mcp/integration_test.go

Ref:   Size: 2.4 KiB

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// TestEndToEnd boots the binary as a subprocess, simulates a save, and
// verifies a notifications/claude/channel frame appears on stdout AND the
// file gets rewritten with [fw-XXXXXXXX].
func TestEndToEnd(t *testing.T) {
	if testing.Short() {
		t.Skip("integration")
	}
	if _, err := exec.LookPath("go"); err != nil {
		t.Skip("go toolchain not on PATH")
	}

	dir := t.TempDir()
	target := filepath.Join(dir, "foo.go")
	if err := os.WriteFile(target, []byte("package foo\n"), 0o644); err != nil {
		t.Fatal(err)
	}

	cmd := exec.Command("go", "run", ".", "--root", dir, "--debounce", "100ms")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		t.Fatal(err)
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		t.Fatal(err)
	}
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}
	defer func() {
		stdin.Close()
		_ = cmd.Process.Kill()
		_ = cmd.Wait()
	}()

	// Send initialize so srv.Run completes the handshake.
	fmt.Fprintln(stdin, `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{}}}`)

	sc := bufio.NewScanner(stdout)
	sc.Buffer(make([]byte, 1<<16), 1<<16)
	if !sc.Scan() {
		t.Fatal("no init response")
	}

	// Trigger an atomic save (tmpfile + rename) to be realistic.
	tmp := target + ".tmp"
	if err := os.WriteFile(tmp, []byte("// @claude please review\npackage foo\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.Rename(tmp, target); err != nil {
		t.Fatal(err)
	}

	// Look for the channel notification in subsequent frames.
	deadline := time.Now().Add(5 * time.Second)
	var sawChannel bool
	for time.Now().Before(deadline) {
		if !sc.Scan() {
			break
		}
		var f map[string]any
		if err := json.Unmarshal(sc.Bytes(), &f); err != nil {
			continue
		}
		if f["method"] == "notifications/claude/channel" {
			sawChannel = true
			break
		}
	}
	if !sawChannel {
		t.Fatal("no notifications/claude/channel frame on stdout")
	}

	// Verify the file got stamped. Allow a brief moment for the rewriter
	// to run after the notification was emitted.
	deadline = time.Now().Add(2 * time.Second)
	var got []byte
	for time.Now().Before(deadline) {
		got, _ = os.ReadFile(target)
		if strings.Contains(string(got), "@claude[fw-") {
			return
		}
		time.Sleep(50 * time.Millisecond)
	}
	t.Errorf("file not stamped:\n%s", got)
}