Skip to content

Commit 60ce461

Browse files
committed
.
1 parent 53c3a25 commit 60ce461

File tree

3 files changed

+134
-45
lines changed

3 files changed

+134
-45
lines changed

pkg/github/issues_test.go

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package github
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"fmt"
8+
"io"
79
"net/http"
810
"strings"
911
"testing"
@@ -20,9 +22,103 @@ import (
2022
"github.com/stretchr/testify/require"
2123
)
2224

23-
var defaultGQLClient *githubv4.Client = githubv4.NewClient(githubv4mock.NewMockedHTTPClient())
25+
var defaultGQLClient *githubv4.Client = githubv4.NewClient(newRepoAccessHTTPClient())
2426
var repoAccessCache *lockdown.RepoAccessCache = stubRepoAccessCache(defaultGQLClient, 15*time.Minute)
2527

28+
type repoAccessKey struct {
29+
owner string
30+
repo string
31+
username string
32+
}
33+
34+
type repoAccessValue struct {
35+
isPrivate bool
36+
permission string
37+
}
38+
39+
type repoAccessMockTransport struct {
40+
responses map[repoAccessKey]repoAccessValue
41+
}
42+
43+
func newRepoAccessHTTPClient() *http.Client {
44+
responses := map[repoAccessKey]repoAccessValue{
45+
{owner: "owner2", repo: "repo2", username: "testuser2"}: {isPrivate: true},
46+
{owner: "owner", repo: "repo", username: "testuser"}: {isPrivate: false, permission: "READ"},
47+
}
48+
49+
return &http.Client{Transport: &repoAccessMockTransport{responses: responses}}
50+
}
51+
52+
func (rt *repoAccessMockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
53+
if req.Body == nil {
54+
return nil, fmt.Errorf("missing request body")
55+
}
56+
57+
var payload struct {
58+
Query string `json:"query"`
59+
Variables map[string]any `json:"variables"`
60+
}
61+
62+
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
63+
return nil, err
64+
}
65+
_ = req.Body.Close()
66+
67+
owner := toString(payload.Variables["owner"])
68+
repo := toString(payload.Variables["name"])
69+
username := toString(payload.Variables["username"])
70+
71+
value, ok := rt.responses[repoAccessKey{owner: owner, repo: repo, username: username}]
72+
if !ok {
73+
value = repoAccessValue{isPrivate: false, permission: "WRITE"}
74+
}
75+
76+
edges := []any{}
77+
if value.permission != "" {
78+
edges = append(edges, map[string]any{
79+
"permission": value.permission,
80+
"node": map[string]any{
81+
"login": username,
82+
},
83+
})
84+
}
85+
86+
responseBody, err := json.Marshal(map[string]any{
87+
"data": map[string]any{
88+
"repository": map[string]any{
89+
"isPrivate": value.isPrivate,
90+
"collaborators": map[string]any{
91+
"edges": edges,
92+
},
93+
},
94+
},
95+
})
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
resp := &http.Response{
101+
StatusCode: http.StatusOK,
102+
Header: make(http.Header),
103+
Body: io.NopCloser(bytes.NewReader(responseBody)),
104+
}
105+
resp.Header.Set("Content-Type", "application/json")
106+
return resp, nil
107+
}
108+
109+
func toString(v any) string {
110+
switch value := v.(type) {
111+
case string:
112+
return value
113+
case fmt.Stringer:
114+
return value.String()
115+
case nil:
116+
return ""
117+
default:
118+
return fmt.Sprintf("%v", value)
119+
}
120+
}
121+
26122
func Test_GetIssue(t *testing.T) {
27123
// Verify tool definition once
28124
mockClient := github.NewClient(nil)
@@ -55,6 +151,22 @@ func Test_GetIssue(t *testing.T) {
55151
},
56152
},
57153
}
154+
mockIssue2 := &github.Issue{
155+
Number: github.Ptr(422),
156+
Title: github.Ptr("Test Issue 2"),
157+
Body: github.Ptr("This is a test issue 2"),
158+
State: github.Ptr("open"),
159+
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
160+
User: &github.User{
161+
Login: github.Ptr("testuser2"),
162+
},
163+
Repository: &github.Repository{
164+
Name: github.Ptr("repo2"),
165+
Owner: &github.User{
166+
Login: github.Ptr("owner2"),
167+
},
168+
},
169+
}
58170

59171
tests := []struct {
60172
name string
@@ -77,8 +189,8 @@ func Test_GetIssue(t *testing.T) {
77189
),
78190
requestArgs: map[string]interface{}{
79191
"method": "get",
80-
"owner": "owner",
81-
"repo": "repo",
192+
"owner": "owner2",
193+
"repo": "repo2",
82194
"issue_number": float64(42),
83195
},
84196
expectedIssue: mockIssue,
@@ -105,7 +217,7 @@ func Test_GetIssue(t *testing.T) {
105217
mockedClient: mock.NewMockedHTTPClient(
106218
mock.WithRequestMatch(
107219
mock.GetReposIssuesByOwnerByRepoByIssueNumber,
108-
mockIssue,
220+
mockIssue2,
109221
),
110222
),
111223
gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
@@ -124,9 +236,9 @@ func Test_GetIssue(t *testing.T) {
124236
} `graphql:"repository(owner: $owner, name: $name)"`
125237
}{},
126238
map[string]any{
127-
"owner": githubv4.String("owner"),
128-
"name": githubv4.String("repo"),
129-
"username": githubv4.String("testuser"),
239+
"owner": githubv4.String("owner2"),
240+
"name": githubv4.String("repo2"),
241+
"username": githubv4.String("testuser2"),
130242
},
131243
githubv4mock.DataResponse(map[string]any{
132244
"repository": map[string]any{
@@ -140,11 +252,11 @@ func Test_GetIssue(t *testing.T) {
140252
),
141253
requestArgs: map[string]interface{}{
142254
"method": "get",
143-
"owner": "owner",
144-
"repo": "repo",
145-
"issue_number": float64(42),
255+
"owner": "owner2",
256+
"repo": "repo2",
257+
"issue_number": float64(422),
146258
},
147-
expectedIssue: mockIssue,
259+
expectedIssue: mockIssue2,
148260
lockdownEnabled: true,
149261
},
150262
{

pkg/github/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func stubGetGQLClientFn(client *githubv4.Client) GetGQLClientFn {
4242

4343
func stubRepoAccessCache(client *githubv4.Client, ttl time.Duration) *lockdown.RepoAccessCache {
4444
cacheName := fmt.Sprintf("repo-access-cache-test-%d", time.Now().UnixNano())
45-
return lockdown.NewRepoAccessCache(client, lockdown.WithTTL(ttl), lockdown.WithCacheName(cacheName))
45+
return lockdown.GetInstance(client, lockdown.WithTTL(ttl), lockdown.WithCacheName(cacheName))
4646
}
4747

4848
func stubFeatureFlags(enabledFlags map[string]bool) FeatureFlags {

pkg/lockdown/lockdown.go

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,18 @@ func GetInstance(client *githubv4.Client, opts ...RepoAccessOption) *RepoAccessC
7373
instanceMu.Lock()
7474
defer instanceMu.Unlock()
7575
if instance == nil {
76-
instance = newRepoAccessCache(client, opts...)
77-
}
78-
return instance
79-
}
80-
81-
// NewRepoAccessCache constructs a repo access cache without mutating the global singleton.
82-
// This helper is useful for tests that need isolated cache instances.
83-
func NewRepoAccessCache(client *githubv4.Client, opts ...RepoAccessOption) *RepoAccessCache {
84-
return newRepoAccessCache(client, opts...)
85-
}
86-
87-
// newRepoAccessCache creates a new cache instance. This is a private helper function
88-
// used by GetInstance.
89-
func newRepoAccessCache(client *githubv4.Client, opts ...RepoAccessOption) *RepoAccessCache {
90-
c := &RepoAccessCache{
91-
client: client,
92-
cache: cache2go.Cache(defaultRepoAccessCacheKey),
93-
ttl: defaultRepoAccessTTL,
94-
}
95-
for _, opt := range opts {
96-
if opt != nil {
97-
opt(c)
76+
instance = &RepoAccessCache{
77+
client: client,
78+
cache: cache2go.Cache(defaultRepoAccessCacheKey),
79+
ttl: defaultRepoAccessTTL,
80+
}
81+
for _, opt := range opts {
82+
if opt != nil {
83+
opt(instance)
84+
}
9885
}
9986
}
100-
if c.cache == nil {
101-
c.cache = cache2go.Cache(defaultRepoAccessCacheKey)
102-
}
103-
c.logInfo("repo access cache initialized", "ttl", c.ttl)
104-
return c
87+
return instance
10588
}
10689

10790
// SetLogger updates the logger used for cache diagnostics.
@@ -217,9 +200,3 @@ func (c *RepoAccessCache) logDebug(msg string, args ...any) {
217200
c.logger.Debug(msg, args...)
218201
}
219202
}
220-
221-
func (c *RepoAccessCache) logInfo(msg string, args ...any) {
222-
if c != nil && c.logger != nil {
223-
c.logger.Info(msg, args...)
224-
}
225-
}

0 commit comments

Comments
 (0)