test(mcp): pin session concurrency, document error codes, assert id round-trip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,27 @@ package mcp
|
|||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
|
// JSON-RPC application-defined error codes (range -32000 to -32099 per spec).
|
||||||
|
// Tool handlers return one of these from tools/call to signal a typed failure.
|
||||||
const (
|
const (
|
||||||
|
// CodePermissionDenied: caller authenticated but lacks permission for this
|
||||||
|
// resource (e.g. owner not in the allowlist).
|
||||||
CodePermissionDenied = -32001
|
CodePermissionDenied = -32001
|
||||||
|
|
||||||
|
// CodeNotFound: target repo, file, branch, PR, issue, or workflow run does
|
||||||
|
// not exist.
|
||||||
CodeNotFound = -32002
|
CodeNotFound = -32002
|
||||||
|
|
||||||
|
// CodeConflict: write attempted on stale state (branch already exists,
|
||||||
|
// non-fast-forward push, file modified concurrently).
|
||||||
CodeConflict = -32003
|
CodeConflict = -32003
|
||||||
|
|
||||||
|
// CodeValidation: arguments failed input validation (bad regex, oversized
|
||||||
|
// payload, missing required field).
|
||||||
CodeValidation = -32004
|
CodeValidation = -32004
|
||||||
|
|
||||||
|
// CodeUpstreamGitea: Gitea API returned an error this server could not map
|
||||||
|
// to one of the codes above. The original status is in error.data.
|
||||||
CodeUpstreamGitea = -32005
|
CodeUpstreamGitea = -32005
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ func TestRequestUnmarshal(t *testing.T) {
|
|||||||
require.NoError(t, json.Unmarshal(raw, &req))
|
require.NoError(t, json.Unmarshal(raw, &req))
|
||||||
assert.Equal(t, "2.0", req.JSONRPC)
|
assert.Equal(t, "2.0", req.JSONRPC)
|
||||||
assert.Equal(t, "initialize", req.Method)
|
assert.Equal(t, "initialize", req.Method)
|
||||||
|
// ID is opaque; encoding/json decodes JSON numbers into float64 by default.
|
||||||
|
// We don't type-assert in the server — we echo it back unchanged.
|
||||||
|
assert.Equal(t, float64(1), req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorResponseShape(t *testing.T) {
|
func TestErrorResponseShape(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mcp_test
|
package mcp_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.d-ma.be/mathias/gitea-mcp/internal/mcp"
|
"gitea.d-ma.be/mathias/gitea-mcp/internal/mcp"
|
||||||
@@ -20,3 +21,26 @@ func TestSessionStoreIssueAndCheck(t *testing.T) {
|
|||||||
s.Drop(id)
|
s.Drop(id)
|
||||||
assert.False(t, s.Valid(id))
|
assert.False(t, s.Valid(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionStoreConcurrency(t *testing.T) {
|
||||||
|
s := mcp.NewSessionStore()
|
||||||
|
|
||||||
|
const goroutines = 32
|
||||||
|
const perGoroutine = 100
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(goroutines)
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < perGoroutine; j++ {
|
||||||
|
id := s.Issue()
|
||||||
|
if !s.Valid(id) {
|
||||||
|
t.Errorf("issued id %s reported invalid", id)
|
||||||
|
}
|
||||||
|
s.Drop(id)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user