verae-mcp-timestamping-serv.../internal/server/server_test.go
George Lambert 3ec1f0516b
Some checks failed
test-and-deploy / test (push) Failing after 12m55s
test-and-deploy / deploy-138 (push) Has been cancelled
feat(mcp): optional receipt_format on stamp/verify (text/PDF/JSON/MD/CSV/TSV)
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.
2026-10-20 16:02:20 -04:00

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)
}
}