Files
sm-user-plugin/mcp_guard_client_ip_test.go
Jakub Zych e3c164fdf0 test: named tests for the account link proof, revocation, member token scopes and the organisation logo
- TestMemberTokenScopes walks E-2's acceptance list item by item, plus the
  per-application catalog and the legacy MintAPIToken fallback;
  TestMemberTokenScopesBoot (no Docker) the boot refusals
- TestBackendLinkAuthTime (C-CR-02), TestBackendLinkShowPost (C-WR-01,
  C-IN-06), TestAccessRevocation (C-CR-01), TestUserMCPGuardClientIP
  (C-IN-04), TestAPITokenRevokeEvent (C-IN-02), TestOrganisationLogo (E-1)
- README Testing section names them
2026-10-23 18:36:39 +02:00

62 lines
2.1 KiB
Go

package user
import (
"context"
"testing"
"git.golem15.com/golem15/sm-user-plugin/models"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/towel"
)
// TestUserMCPGuardClientIP pins plan 08's client address on the MCP
// personal-token path (C-IN-04): MCPResolvePersonalToken stamps
// last_used_ip from towel.ClientIP, as the api_token guard does, and leaves
// the column as it was when the context carries no address. Postgres only:
// under -short nothing runs.
func TestUserMCPGuardClientIP(t *testing.T) {
if testing.Short() {
return
}
env := newAdminEnv(t)
resolver, ok := env.app.Lookup[pact.MCPPersonalTokenResolver]()
if !ok {
t.Fatal("no personal token resolver published")
}
user := env.seedUser(t, "mcp-ip", nil)
secret, token := env.mintUserToken(t, user, "read")
lastIP := func(t *testing.T) *string {
t.Helper()
var row models.APIToken
if err := env.gdb.Where("id = ?", token.ID).First(&row).Error; err != nil {
t.Fatal(err)
}
return row.LastUsedIP
}
t.Run("an address on the context is stamped", func(t *testing.T) {
subject, matched, err := resolver.MCPResolvePersonalToken(towel.WithClientIP(context.Background(), "203.0.113.44"), secret)
if err != nil || !matched || subject.TokenID != token.ID {
t.Fatalf("resolve = %+v, %v, %v", subject, matched, err)
}
if ip := lastIP(t); ip == nil || *ip != "203.0.113.44" {
t.Fatalf("last_used_ip = %v, want 203.0.113.44", ip)
}
})
t.Run("no address leaves the column as it was", func(t *testing.T) {
if _, matched, err := resolver.MCPResolvePersonalToken(context.Background(), secret); err != nil || !matched {
t.Fatalf("resolve = %v, %v", matched, err)
}
if ip := lastIP(t); ip == nil || *ip != "203.0.113.44" {
t.Fatalf("last_used_ip after a resolve without an address = %v, want it unchanged", ip)
}
if _, _, err := resolver.MCPResolvePersonalToken(towel.WithClientIP(context.Background(), " "), secret); err != nil {
t.Fatal(err)
}
if ip := lastIP(t); ip == nil || *ip != "203.0.113.44" {
t.Fatalf("last_used_ip after a blank address = %v, want it unchanged", ip)
}
})
}