package mcpsrv
import (
"errors"
"fmt"
"testing"
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sourcecraft.dev/bigbes/sr-ht-dolt/db"
)
// The mapping of errors.go, tested from inside the package because it is the
// contract every address-taking tool is written against and none of them exists
// yet: list_databases addresses no single database, so the not-found arm has no
// caller until the tools of ch. 9.1 land. Pinning it here is what keeps the two
// arms from being decided again, differently, in the first handler that needs
// them.
func TestMissingOrDeniedSeparatesTheTwoArms(t *testing.T) {
const missing = "no database ~alice/notes"
t.Run("a miss is an answer the agent reads", func(t *testing.T) {
// db/ wraps its misses with what it looked up; the sentence the agent
// gets is written from the call's own arguments and never from that.
err := missingOrDenied(fmt.Errorf("get repo alice/notes: %w", db.ErrNotFound), "get_database", missing)
require.Error(t, err)
var protocol *jsonrpc.Error
assert.False(t, errors.As(err, &protocol), "a miss is a tool result, not a protocol failure")
assert.Equal(t, missing, err.Error())
})
t.Run("anything else is a protocol failure", func(t *testing.T) {
// An agent must not read "the metadata store could not answer" as "that
// database does not exist" and go rewrite its plan around a database that
// is perfectly real.
err := missingOrDenied(errors.New("dial tcp 127.0.0.1:5432: connection refused"), "get_database", missing)
var protocol *jsonrpc.Error
require.ErrorAs(t, err, &protocol)
assert.EqualValues(t, jsonrpc.CodeInternalError, protocol.Code)
assert.Equal(t, internalMessage, protocol.Message)
assert.NotContains(t, protocol.Message, "5432", "the cause is logged, never sent")
assert.NotContains(t, protocol.Message, missing,
"and a store outage does not borrow the sentence a miss gets")
})
}