Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions internal/config/config_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"

"github.com/BurntSushi/toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -352,3 +353,137 @@ args = ["run", "--rm", "-i", "ghcr.io/github/github-mcp-server:latest"]
assert.Nil(t, cfg)
assert.Contains(t, err.Error(), "payload_size_threshold must be a positive integer")
}

// TestIsDynamicTOMLPath verifies the branching logic of isDynamicTOMLPath,
// which guards the unknown-field check by exempting map-valued sections
// whose keys are not known at decode time.
func TestIsDynamicTOMLPath(t *testing.T) {
tests := []struct {
name string
key toml.Key
expected bool
}{
// ── servers.<name>.guard_policies.<policy>.<key> (len ≥ 5) ─────────
{
name: "servers guard_policies at minimum length 5",
key: toml.Key{"servers", "github", "guard_policies", "mypolicy", "repos"},
expected: true,
},
{
name: "servers guard_policies longer than minimum",
key: toml.Key{"servers", "github", "guard_policies", "mypolicy", "nested", "key"},
expected: true,
},
{
name: "servers guard_policies different server name still true",
key: toml.Key{"servers", "slack", "guard_policies", "p1", "field"},
expected: true,
},
{
name: "servers guard_policies exactly 4 elements is too short",
key: toml.Key{"servers", "github", "guard_policies", "mypolicy"},
expected: false,
},
{
name: "servers guard_policies 3 elements is too short",
key: toml.Key{"servers", "github", "guard_policies"},
expected: false,
},
{
name: "servers with wrong key[2] (not guard_policies)",
key: toml.Key{"servers", "github", "command", "whatever", "extra"},
expected: false,
},
{
name: "wrong key[0] for servers path (guards instead)",
key: toml.Key{"guards", "github", "guard_policies", "mypolicy", "repos"},
expected: false,
},
{
name: "gateway prefix with guard_policies shape",
key: toml.Key{"gateway", "x", "guard_policies", "p", "k"},
expected: false,
},

// ── guards.<name>.config.<key> (len ≥ 4) ───────────────────────────
{
name: "guards config at minimum length 4",
key: toml.Key{"guards", "myfence", "config", "somekey"},
expected: true,
},
{
name: "guards config longer than minimum",
key: toml.Key{"guards", "myfence", "config", "somekey", "nested"},
expected: true,
},
{
name: "guards config different guard name still true",
key: toml.Key{"guards", "allowonly", "config", "level"},
expected: true,
},
{
name: "guards config exactly 3 elements is too short",
key: toml.Key{"guards", "myfence", "config"},
expected: false,
},
{
name: "guards config 2 elements is too short",
key: toml.Key{"guards", "myfence"},
expected: false,
},
{
name: "guards with wrong key[2] (not config)",
key: toml.Key{"guards", "myfence", "command", "somekey"},
expected: false,
},
{
name: "wrong key[0] for guards path (servers instead)",
key: toml.Key{"servers", "myfence", "config", "somekey"},
expected: false,
},

// ── Non-dynamic / ordinary TOML paths ────────────────────────────────
{
name: "nil key",
key: nil,
expected: false,
},
{
name: "empty key",
key: toml.Key{},
expected: false,
},
{
name: "single element key",
key: toml.Key{"servers"},
expected: false,
},
{
name: "gateway port path",
key: toml.Key{"gateway", "port"},
expected: false,
},
{
name: "servers command path",
key: toml.Key{"servers", "github", "command"},
expected: false,
},
{
name: "servers args path",
key: toml.Key{"servers", "github", "args"},
expected: false,
},
{
name: "servers env path",
key: toml.Key{"servers", "github", "env", "TOKEN"},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isDynamicTOMLPath(tt.key)
assert.Equal(t, tt.expected, got, "isDynamicTOMLPath(%v)", tt.key)
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.Equal does not interpret the provided string as a format string; the %v will not be substituted, which makes failures harder to read. Use assert.Equalf(...) for formatted messages or pass a plain message without formatting tokens.

Suggested change
assert.Equal(t, tt.expected, got, "isDynamicTOMLPath(%v)", tt.key)
assert.Equalf(t, tt.expected, got, "isDynamicTOMLPath(%v)", tt.key)

Copilot uses AI. Check for mistakes.
})
}
}
134 changes: 134 additions & 0 deletions internal/proxy/graphql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package proxy

import (
"testing"

"github.com/stretchr/testify/assert"
)

// TestTruncateForLog verifies all three branches of truncateForLog:
// the early-exit for non-positive maxRunes, the no-op for short strings,
// and the actual truncation path — including correct Unicode rune handling.
func TestTruncateForLog(t *testing.T) {
tests := []struct {
name string
s string
maxRunes int
expected string
}{
// ── maxRunes ≤ 0 always returns "" ───────────────────────────────────
{
name: "maxRunes zero returns empty string",
s: "hello",
maxRunes: 0,
expected: "",
},
{
name: "maxRunes negative returns empty string",
s: "hello",
maxRunes: -1,
expected: "",
},
{
name: "maxRunes very negative returns empty string",
s: "hello",
maxRunes: -100,
expected: "",
},
{
name: "empty string with zero maxRunes returns empty string",
s: "",
maxRunes: 0,
expected: "",
},

// ── string fits within maxRunes (returned unchanged) ─────────────────
{
name: "empty string with positive maxRunes returns empty string",
s: "",
maxRunes: 10,
expected: "",
},
{
name: "string shorter than maxRunes returned unchanged",
s: "hello",
maxRunes: 10,
expected: "hello",
},
{
name: "string exactly equal to maxRunes returned unchanged",
s: "hello",
maxRunes: 5,
expected: "hello",
},
{
name: "single character string within limit",
s: "a",
maxRunes: 1,
expected: "a",
},
{
name: "unicode string within limit unchanged",
s: "日本語",
maxRunes: 10,
expected: "日本語",
},
{
name: "unicode string exactly at limit unchanged",
s: "日本語",
maxRunes: 3,
expected: "日本語",
},

// ── truncation path ───────────────────────────────────────────────────
{
name: "ASCII string truncated to first N chars",
s: "hello world",
maxRunes: 5,
expected: "hello",
},
{
name: "truncate to single rune",
s: "hello",
maxRunes: 1,
expected: "h",
},
{
name: "unicode string truncated by rune count not byte count",
s: "日本語テスト",
maxRunes: 3,
expected: "日本語",
},
{
name: "mixed ASCII and unicode truncated at rune boundary",
s: "hello日本語",
maxRunes: 7,
expected: "hello日本",
},
{
name: "multibyte euro sign truncated correctly",
s: "€€€€€",
maxRunes: 3,
expected: "€€€",
},
{
name: "4-byte emoji runes truncated correctly",
s: "😀😃😄😁",
maxRunes: 2,
expected: "😀😃",
},
{
name: "long string truncated at exactly maxRunes runes",
s: "abcdefghij",
maxRunes: 7,
expected: "abcdefg",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := truncateForLog(tt.s, tt.maxRunes)
assert.Equal(t, tt.expected, got)
})
}
}
Loading