Add receipt_format to stamp and verify. Omitted → text; receipt|PDF-Receipt → PDF; JSON-Receipt → JSON; MD|MD-Receipt; CSV|CSV-Receipt; TSV|TSV-Receipt. Stamp always returns receipts[] and adds artifacts[] in the chosen format. Stdio writes ~/.verae/receipts (0600); public MCP returns content_text/content_base64 only. Verify accepts inline receipt or receipt_path (stdio), parses id, and re-emits an artifact. Vendors pdfcert (+ slim statuslang, gofpdf) for CERT-01 PDF. Also lands welcome tool and email-username helpers already wired into register/public paths.
107 lines
2.8 KiB
Go
107 lines
2.8 KiB
Go
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", "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)
|
|
}
|
|
}
|