Files
github--github-mcp-server/pkg/lockdown/lockdown_test.go
JoannaaKL 28b868dad4 Add in memory cache for lockdown mode (#1416)
* Apply lockdown mode to issues and pull requests

* Add cache

* Unlock in defer

* Add muesli/cache2go

* [WIP] Replace custom cache in lockdown.go with cache2go struct (#1425)

* Initial plan

* Replace custom cache with cache2go library

- Added github.com/muesli/cache2go dependency
- Replaced custom map-based cache with cache2go.CacheTable
- Removed manual timer management (scheduleExpiry, ensureEntry methods)
- Removed timer field from repoAccessCacheEntry struct
- Updated GetRepoAccessInfo to use cache2go's Value() and Add() methods
- Updated SetTTL to flush and re-add entries with new TTL
- Used unique cache names per instance to avoid test interference
- All existing tests pass with the new implementation

Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

* Final verification complete

Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

* Use muesli for cache

* Make RepoAccessCache a singleton (#1426)

* Initial plan

* Implement RepoAccessCache as a singleton pattern

Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

* Complete singleton implementation and verification

Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

* Remove cacheIDCounter as requested

Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>

* Update mutexes

* .

* Reuse cache

* .

* .

* Fix logic after vibe coding

* Update docs

* .

* Refactoring to make the code pretty

* Hide lockdown logic behind shouldFilter function

* .

* Tests

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-11-21 10:34:51 +01:00

113 lines
2.6 KiB
Go

package lockdown
import (
"net/http"
"sync"
"testing"
"time"
"github.com/github/github-mcp-server/internal/githubv4mock"
"github.com/shurcooL/githubv4"
"github.com/stretchr/testify/require"
)
const (
testOwner = "octo-org"
testRepo = "octo-repo"
testUser = "octocat"
)
type repoAccessQuery struct {
Viewer struct {
Login githubv4.String
}
Repository struct {
IsPrivate githubv4.Boolean
Collaborators struct {
Edges []struct {
Permission githubv4.String
Node struct {
Login githubv4.String
}
}
} `graphql:"collaborators(query: $username, first: 1)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
type countingTransport struct {
mu sync.Mutex
next http.RoundTripper
calls int
}
func (c *countingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
c.mu.Lock()
c.calls++
c.mu.Unlock()
return c.next.RoundTrip(req)
}
func (c *countingTransport) CallCount() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.calls
}
func newMockRepoAccessCache(t *testing.T, ttl time.Duration) (*RepoAccessCache, *countingTransport) {
t.Helper()
var query repoAccessQuery
variables := map[string]any{
"owner": githubv4.String(testOwner),
"name": githubv4.String(testRepo),
"username": githubv4.String(testUser),
}
response := githubv4mock.DataResponse(map[string]any{
"viewer": map[string]any{
"login": testUser,
},
"repository": map[string]any{
"isPrivate": false,
"collaborators": map[string]any{
"edges": []any{
map[string]any{
"permission": "WRITE",
"node": map[string]any{
"login": testUser,
},
},
},
},
},
})
httpClient := githubv4mock.NewMockedHTTPClient(githubv4mock.NewQueryMatcher(query, variables, response))
counting := &countingTransport{next: httpClient.Transport}
httpClient.Transport = counting
gqlClient := githubv4.NewClient(httpClient)
return GetInstance(gqlClient, WithTTL(ttl)), counting
}
func TestRepoAccessCacheEvictsAfterTTL(t *testing.T) {
ctx := t.Context()
cache, transport := newMockRepoAccessCache(t, 5*time.Millisecond)
info, err := cache.getRepoAccessInfo(ctx, testUser, testOwner, testRepo)
require.NoError(t, err)
require.Equal(t, testUser, info.ViewerLogin)
require.True(t, info.HasPushAccess)
require.EqualValues(t, 1, transport.CallCount())
time.Sleep(20 * time.Millisecond)
info, err = cache.getRepoAccessInfo(ctx, testUser, testOwner, testRepo)
require.NoError(t, err)
require.Equal(t, testUser, info.ViewerLogin)
require.True(t, info.HasPushAccess)
require.EqualValues(t, 2, transport.CallCount())
}