package server_test import ( "context" "encoding/json" "net/http/httptest" "os" "path/filepath" "strings" "testing" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/ledger" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/server" "github.com/modelcontextprotocol/go-sdk/mcp" ) const sha256Empty = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" func TestStreamableHTTP_InitializeListStampLocal(t *testing.T) { t.Parallel() dir := t.TempDir() led, err := ledger.Open(filepath.Join(dir, "ledger.json")) if err != nil { t.Fatal(err) } authPath := filepath.Join(dir, "auth.properties") if err := os.WriteFile(authPath, []byte("username=x\npassword=y\n"), 0o600); err != nil { t.Fatal(err) } srv, err := server.New(server.Options{ Ledger: led, AuthPath: authPath, GlobalURL: "http://127.0.0.1:1", // unused for local stamp RequireBearer: false, JSONResponse: true, Stateless: true, }) if err != nil { t.Fatal(err) } httpServer := httptest.NewServer(srv.Handler()) t.Cleanup(httpServer.Close) ctx := context.Background() client := mcp.NewClient(&mcp.Implementation{Name: "test-client", Version: "0.0.1"}, nil) session, err := client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: httpServer.URL + "/mcp"}, nil) if err != nil { t.Fatalf("connect: %v", err) } t.Cleanup(func() { _ = session.Close() }) tools, err := session.ListTools(ctx, nil) if err != nil { t.Fatalf("tools/list: %v", err) } wantTools := []string{"welcome", "stamp", "verify", "hash_report", "signup", "login", "oauth_start", "oauth_complete", "session", "create_user"} got := map[string]bool{} for _, tool := range tools.Tools { got[tool.Name] = true } for _, name := range wantTools { if !got[name] { t.Fatalf("tools/list missing %q; got %#v", name, got) } } res, err := session.CallTool(ctx, &mcp.CallToolParams{ Name: "stamp", Arguments: map[string]any{ "sha256": sha256Empty, "scope": "local", }, }) if err != nil { t.Fatalf("tools/call stamp: %v", err) } if res.IsError { t.Fatalf("stamp tool error: %+v", res.Content) } raw, err := json.Marshal(res.StructuredContent) if err != nil { t.Fatal(err) } var out struct { Receipts []struct { Hash string `json:"hash"` Scope string `json:"scope"` } `json:"receipts"` } if err := json.Unmarshal(raw, &out); err != nil { t.Fatalf("decode structured: %v raw=%s", err, raw) } if len(out.Receipts) != 1 { t.Fatalf("want 1 receipt, got %s", raw) } if out.Receipts[0].Hash != sha256Empty { t.Fatalf("hash=%q", out.Receipts[0].Hash) } if out.Receipts[0].Scope != "local" { t.Fatalf("scope=%q", out.Receipts[0].Scope) } if !strings.Contains(string(raw), "receipt_id") { t.Fatalf("missing receipt_id in %s", raw) } }