a73x

566ae22e

test: end-to-end save-to-stamp integration

a73x   2026-04-29 06:01

Commit message
test: end-to-end save-to-stamp integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

cmd/filewatch-mcp/integration_test.go
Old New
@@ -0,0 +1,101 @@
1 package main
2
3 import (
4 "bufio"
5 "encoding/json"
6 "fmt"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "testing"
12 "time"
13 )
14
15 // TestEndToEnd boots the binary as a subprocess, simulates a save, and
16 // verifies a notifications/claude/channel frame appears on stdout AND the
17 // file gets rewritten with [fw-XXXXXXXX].
18 func TestEndToEnd(t *testing.T) {
19 if testing.Short() {
20 t.Skip("integration")
21 }
22 if _, err := exec.LookPath("go"); err != nil {
23 t.Skip("go toolchain not on PATH")
24 }
25
26 dir := t.TempDir()
27 target := filepath.Join(dir, "foo.go")
28 if err := os.WriteFile(target, []byte("package foo\n"), 0o644); err != nil {
29 t.Fatal(err)
30 }
31
32 cmd := exec.Command("go", "run", ".", "--root", dir, "--debounce", "100ms")
33 stdin, err := cmd.StdinPipe()
34 if err != nil {
35 t.Fatal(err)
36 }
37 stdout, err := cmd.StdoutPipe()
38 if err != nil {
39 t.Fatal(err)
40 }
41 cmd.Stderr = os.Stderr
42 if err := cmd.Start(); err != nil {
43 t.Fatal(err)
44 }
45 defer func() {
46 stdin.Close()
47 _ = cmd.Process.Kill()
48 _ = cmd.Wait()
49 }()
50
51 // Send initialize so srv.Run completes the handshake.
52 fmt.Fprintln(stdin, `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{}}}`)
53
54 sc := bufio.NewScanner(stdout)
55 sc.Buffer(make([]byte, 1<<16), 1<<16)
56 if !sc.Scan() {
57 t.Fatal("no init response")
58 }
59
60 // Trigger an atomic save (tmpfile + rename) to be realistic.
61 tmp := target + ".tmp"
62 if err := os.WriteFile(tmp, []byte("// @claude please review\npackage foo\n"), 0o644); err != nil {
63 t.Fatal(err)
64 }
65 if err := os.Rename(tmp, target); err != nil {
66 t.Fatal(err)
67 }
68
69 // Look for the channel notification in subsequent frames.
70 deadline := time.Now().Add(5 * time.Second)
71 var sawChannel bool
72 for time.Now().Before(deadline) {
73 if !sc.Scan() {
74 break
75 }
76 var f map[string]any
77 if err := json.Unmarshal(sc.Bytes(), &f); err != nil {
78 continue
79 }
80 if f["method"] == "notifications/claude/channel" {
81 sawChannel = true
82 break
83 }
84 }
85 if !sawChannel {
86 t.Fatal("no notifications/claude/channel frame on stdout")
87 }
88
89 // Verify the file got stamped. Allow a brief moment for the rewriter
90 // to run after the notification was emitted.
91 deadline = time.Now().Add(2 * time.Second)
92 var got []byte
93 for time.Now().Before(deadline) {
94 got, _ = os.ReadFile(target)
95 if strings.Contains(string(got), "@claude[fw-") {
96 return
97 }
98 time.Sleep(50 * time.Millisecond)
99 }
100 t.Errorf("file not stamped:\n%s", got)
101 }