Skip to content

Commit b5d566b

Browse files
authored
test: verify shamhub behavior compliance (#974)
Take advantage of the generalized integration tests which verify behavior of github and gitlab to also verify that our test shamhub implementation complies with the same behavior. This is necessary since we use shamhub for all of git-spice's own integration tests so any divergence in behavior would be problematic. This found minor discrepancies in ShamHub behavior that have been fixed.
1 parent 7c34f49 commit b5d566b

File tree

51 files changed

+2422
-64
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2422
-64
lines changed

internal/forge/shamhub/auth.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ func (sh *ShamHub) RegisterUser(username string) error {
6565
return nil
6666
}
6767

68+
// IssueToken issues an authentication token for the given username.
69+
// The user must already be registered.
70+
// This is a test helper method.
71+
func (sh *ShamHub) IssueToken(username string) (string, error) {
72+
var buf [8]byte
73+
_, _ = rand.Read(buf[:])
74+
token := hex.EncodeToString(buf[:])
75+
76+
sh.mu.Lock()
77+
defer sh.mu.Unlock()
78+
79+
for _, u := range sh.users {
80+
if u.Username == username {
81+
sh.tokens[token] = username
82+
return token, nil
83+
}
84+
}
85+
86+
return "", fmt.Errorf("user %q not found", username)
87+
}
88+
6889
// AuthenticationToken defines the token returned by the ShamHub forge.
6990
type AuthenticationToken struct {
7091
forge.AuthenticationToken

internal/forge/shamhub/comment.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,12 @@ func (sh *ShamHub) handleListChangeComments(_ context.Context, req *listChangeCo
217217
}, nil
218218
}
219219

220+
var _listChangeCommentsPageSize = 10 // var for testing
221+
220222
func (r *forgeRepository) ListChangeComments(
221223
ctx context.Context,
222224
id forge.ChangeID,
223-
_ *forge.ListChangeCommentsOptions,
225+
opts *forge.ListChangeCommentsOptions,
224226
) iter.Seq2[*forge.ListChangeCommentItem, error] {
225227
u := r.apiURL.JoinPath(r.owner, r.repo, "comments")
226228
q := u.Query()
@@ -231,7 +233,7 @@ func (r *forgeRepository) ListChangeComments(
231233
offset := 0
232234
for {
233235
q.Set("offset", strconv.Itoa(offset))
234-
q.Set("limit", "10")
236+
q.Set("limit", strconv.Itoa(_listChangeCommentsPageSize))
235237
u.RawQuery = q.Encode()
236238

237239
var res listChangeCommentsResponse
@@ -241,6 +243,23 @@ func (r *forgeRepository) ListChangeComments(
241243
}
242244

243245
for _, item := range res.Items {
246+
// Apply filtering if options are provided.
247+
if opts != nil {
248+
// Filter by BodyMatchesAll patterns.
249+
if len(opts.BodyMatchesAll) > 0 {
250+
matches := true
251+
for _, pattern := range opts.BodyMatchesAll {
252+
if !pattern.MatchString(item.Body) {
253+
matches = false
254+
break
255+
}
256+
}
257+
if !matches {
258+
continue
259+
}
260+
}
261+
}
262+
244263
if !yield(&forge.ListChangeCommentItem{
245264
ID: ChangeCommentID(item.ID),
246265
Body: item.Body,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package shamhub
2+
3+
import (
4+
"testing"
5+
6+
"go.abhg.dev/testing/stub"
7+
)
8+
9+
// SetListChangeCommentsPageSize sets the page size for listing change comments.
10+
// This is used to test pagination.
11+
func SetListChangeCommentsPageSize(t testing.TB, pageSize int) {
12+
t.Cleanup(stub.Value(&_listChangeCommentsPageSize, pageSize))
13+
}

internal/forge/shamhub/edit.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ func (sh *ShamHub) handleEditChange(_ context.Context, req *editChangeRequest) (
8181
}
8282

8383
if len(req.Assignees) > 0 {
84+
// Validate that all assignees are registered users.
85+
for _, assignee := range req.Assignees {
86+
found := false
87+
for _, u := range sh.users {
88+
if u.Username == assignee {
89+
found = true
90+
break
91+
}
92+
}
93+
if !found {
94+
return nil, badRequestErrorf("assignee %q is not a registered user", assignee)
95+
}
96+
}
97+
8498
assignees := sh.changes[changeIdx].Assignees
8599
for _, assignee := range req.Assignees {
86100
if !slices.Contains(assignees, assignee) {

internal/forge/shamhub/find.go

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -108,47 +108,58 @@ nextChange:
108108
return changes, nil
109109
}
110110

111-
func (r *forgeRepository) FindChangeByID(ctx context.Context, fid forge.ChangeID) (*forge.FindChangeItem, error) {
112-
id := fid.(ChangeID)
113-
u := r.apiURL.JoinPath(r.owner, r.repo, "change", strconv.Itoa(int(id)))
114-
var res Change
115-
if err := r.client.Get(ctx, u.String(), &res); err != nil {
116-
return nil, fmt.Errorf("find change by ID: %w", err)
117-
}
118-
111+
// toFindChangeItem converts a Change to a forge.FindChangeItem.
112+
func toFindChangeItem(c *Change) *forge.FindChangeItem {
119113
var state forge.ChangeState
120-
switch res.State {
114+
switch c.State {
121115
case "open":
122116
state = forge.ChangeOpen
123117
case "closed":
124-
if res.Merged {
118+
if c.Merged {
125119
state = forge.ChangeMerged
126120
} else {
127121
state = forge.ChangeClosed
128122
}
129123
}
130124

131-
labels := res.Labels
125+
labels := c.Labels
132126
if len(labels) == 0 {
133127
labels = nil
134128
}
135129

136-
reviewers := res.RequestedReviewers
130+
reviewers := c.RequestedReviewers
137131
if len(reviewers) == 0 {
138132
reviewers = nil
139133
}
140134

135+
assignees := c.Assignees
136+
if len(assignees) == 0 {
137+
assignees = nil
138+
}
139+
141140
return &forge.FindChangeItem{
142-
ID: ChangeID(res.Number),
143-
URL: res.URL,
144-
Subject: res.Subject,
145-
HeadHash: git.Hash(res.Head.Hash),
146-
BaseName: res.Base.Name,
147-
Draft: res.Draft,
141+
ID: ChangeID(c.Number),
142+
URL: c.URL,
143+
Subject: c.Subject,
144+
HeadHash: git.Hash(c.Head.Hash),
145+
BaseName: c.Base.Name,
146+
Draft: c.Draft,
148147
State: state,
149148
Labels: labels,
150149
Reviewers: reviewers,
151-
}, nil
150+
Assignees: assignees,
151+
}
152+
}
153+
154+
func (r *forgeRepository) FindChangeByID(ctx context.Context, fid forge.ChangeID) (*forge.FindChangeItem, error) {
155+
id := fid.(ChangeID)
156+
u := r.apiURL.JoinPath(r.owner, r.repo, "change", strconv.Itoa(int(id)))
157+
var res Change
158+
if err := r.client.Get(ctx, u.String(), &res); err != nil {
159+
return nil, fmt.Errorf("find change by ID: %w", err)
160+
}
161+
162+
return toFindChangeItem(&res), nil
152163
}
153164

154165
func (r *forgeRepository) FindChangesByBranch(ctx context.Context, branch string, opts forge.FindChangesOptions) ([]*forge.FindChangeItem, error) {
@@ -173,39 +184,7 @@ func (r *forgeRepository) FindChangesByBranch(ctx context.Context, branch string
173184

174185
changes := make([]*forge.FindChangeItem, len(res))
175186
for i, c := range res {
176-
var state forge.ChangeState
177-
switch c.State {
178-
case "open":
179-
state = forge.ChangeOpen
180-
case "closed":
181-
if c.Merged {
182-
state = forge.ChangeMerged
183-
} else {
184-
state = forge.ChangeClosed
185-
}
186-
}
187-
188-
labels := c.Labels
189-
if len(labels) == 0 {
190-
labels = nil
191-
}
192-
193-
reviewers := c.RequestedReviewers
194-
if len(reviewers) == 0 {
195-
reviewers = nil
196-
}
197-
198-
changes[i] = &forge.FindChangeItem{
199-
ID: ChangeID(c.Number),
200-
URL: c.URL,
201-
State: state,
202-
Subject: c.Subject,
203-
HeadHash: git.Hash(c.Head.Hash),
204-
BaseName: c.Base.Name,
205-
Draft: c.Draft,
206-
Labels: labels,
207-
Reviewers: reviewers,
208-
}
187+
changes[i] = toFindChangeItem(c)
209188
}
210189
return changes, nil
211190
}

internal/forge/shamhub/forge.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,20 @@ func (f *Forge) ParseRemoteURL(remoteURL string) (forge.RepositoryID, error) {
7171

7272
// OpenRepository opens the repository that this repository ID points to.
7373
func (f *Forge) OpenRepository(_ context.Context, token forge.AuthenticationToken, id forge.RepositoryID) (forge.Repository, error) {
74+
return newRepository(f, token.(*AuthenticationToken), id.(*RepositoryID), http.DefaultClient)
75+
}
76+
77+
// newRepository creates a new repository instance with the given HTTP client.
78+
func newRepository(f *Forge, token *AuthenticationToken, rid *RepositoryID, httpClient *http.Client) (forge.Repository, error) {
7479
must.NotBeBlankf(f.URL, "URL is required")
7580
must.NotBeBlankf(f.APIURL, "API URL is required")
7681

77-
rid := id.(*RepositoryID)
78-
tok := token.(*AuthenticationToken).tok
79-
client := f.jsonHTTPClient()
80-
client.headers = map[string]string{
81-
"Authentication-Token": tok,
82+
client := &jsonHTTPClient{
83+
log: f.Log,
84+
client: httpClient,
85+
headers: map[string]string{
86+
"Authentication-Token": token.tok,
87+
},
8288
}
8389

8490
apiURL, err := url.Parse(f.APIURL)

0 commit comments

Comments
 (0)