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: // skipped under -short. func TestUserMCPGuardClientIP(t *testing.T) { if testing.Short() { t.Skip("Postgres") } 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) } }) }