Skip to content

Commit 992fc2e

Browse files
Copilotjeduden
andauthored
Cover release-trigger guard 100%
Agent-Logs-Url: https://github.com/jeduden/mdsmith/sessions/e63ff353-75c0-40a1-a466-0603d1b4868a Co-authored-by: jeduden <1117699+jeduden@users.noreply.github.com>
1 parent aa688e4 commit 992fc2e

3 files changed

Lines changed: 162 additions & 4 deletions

File tree

cmd/mdsmith-release/triggerguard_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88
"testing"
99

10+
"github.com/jeduden/mdsmith/internal/release"
1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213
)
@@ -32,3 +33,44 @@ func TestRunCheckReleaseTriggerWritesGitHubOutput(t *testing.T) {
3233
require.NoError(t, err)
3334
assert.Equal(t, "should_run=true\ncreate_release_is_draft=true\n", string(body))
3435
}
36+
37+
// TestRunCheckReleaseTriggerReportsCheckError covers the
38+
// reportError branch when CheckReleaseTrigger itself fails
39+
// (here: create-event tag without a token).
40+
func TestRunCheckReleaseTriggerReportsCheckError(t *testing.T) {
41+
t.Setenv("EVENT_NAME", "create")
42+
t.Setenv("CREATE_REF_TYPE", "tag")
43+
t.Setenv("RELEASE_TAG", "v1.2.3")
44+
t.Setenv("GITHUB_REPOSITORY", "jeduden/mdsmith")
45+
t.Setenv("GITHUB_TOKEN", "")
46+
t.Setenv("GITHUB_OUTPUT", "")
47+
48+
assert.Equal(t, 1, run([]string{"check-release-trigger"}))
49+
}
50+
51+
// TestRunCheckReleaseTriggerReportsWriteError covers the
52+
// reportError branch when writeReleaseTriggerGuardOutput fails
53+
// (here: GITHUB_OUTPUT points at a directory).
54+
func TestRunCheckReleaseTriggerReportsWriteError(t *testing.T) {
55+
dir := t.TempDir()
56+
t.Setenv("EVENT_NAME", "push")
57+
t.Setenv("GITHUB_OUTPUT", dir)
58+
59+
assert.Equal(t, 1, run([]string{"check-release-trigger"}))
60+
}
61+
62+
// TestWriteReleaseTriggerGuardOutputStdout covers the path=="" branch
63+
// (no GITHUB_OUTPUT set) which prints to stdout instead of opening
64+
// a file.
65+
func TestWriteReleaseTriggerGuardOutputStdout(t *testing.T) {
66+
err := writeReleaseTriggerGuardOutput("", release.TriggerGuardResult{ShouldRun: true})
67+
require.NoError(t, err)
68+
}
69+
70+
// TestWriteReleaseTriggerGuardOutputOpenError covers the
71+
// os.OpenFile error branch.
72+
func TestWriteReleaseTriggerGuardOutputOpenError(t *testing.T) {
73+
dir := t.TempDir()
74+
err := writeReleaseTriggerGuardOutput(dir, release.TriggerGuardResult{})
75+
require.Error(t, err)
76+
}

internal/release/triggerguard.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,10 @@ func CheckReleaseTrigger(opts TriggerGuardOptions) (TriggerGuardResult, error) {
9494
CreateReleaseIsDraft: draft,
9595
}, nil
9696
}
97-
if attempt == attempts {
98-
return TriggerGuardResult{}, nil
97+
if attempt < attempts {
98+
sleep(delay)
9999
}
100-
sleep(delay)
101100
}
102-
103101
return TriggerGuardResult{}, nil
104102
}
105103

internal/release/triggerguard_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package release
22

33
import (
4+
"errors"
45
"fmt"
6+
"io"
57
"net/http"
68
"net/http/httptest"
9+
"strings"
710
"sync"
811
"testing"
912
"time"
@@ -191,3 +194,118 @@ type roundTripFunc func(*http.Request) (*http.Response, error)
191194
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
192195
return f(req)
193196
}
197+
198+
// TestCheckReleaseTriggerUsesDefaultAPIBase verifies that an
199+
// empty APIBaseURL falls back to api.github.com (line covered:
200+
// the apiBase == "" branch).
201+
func TestCheckReleaseTriggerUsesDefaultAPIBase(t *testing.T) {
202+
var gotURL string
203+
client := &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
204+
gotURL = r.URL.String()
205+
return &http.Response{
206+
StatusCode: http.StatusOK,
207+
Body: io.NopCloser(strings.NewReader(`{"draft":true}`)),
208+
Header: make(http.Header),
209+
}, nil
210+
})}
211+
res, err := CheckReleaseTrigger(TriggerGuardOptions{
212+
EventName: "create",
213+
Repository: "jeduden/mdsmith",
214+
RefType: "tag",
215+
RefName: "v1.2.3",
216+
Token: "t",
217+
Client: client,
218+
})
219+
require.NoError(t, err)
220+
assert.True(t, res.ShouldRun)
221+
assert.Equal(t, "https://api.github.com/repos/jeduden/mdsmith/releases/tags/v1.2.3", gotURL)
222+
}
223+
224+
// TestCheckReleaseTriggerNewRequestError trips http.NewRequest by
225+
// passing an APIBaseURL containing a control character so the URL
226+
// parser rejects it.
227+
func TestCheckReleaseTriggerNewRequestError(t *testing.T) {
228+
_, err := CheckReleaseTrigger(TriggerGuardOptions{
229+
EventName: "create",
230+
Repository: "jeduden/mdsmith",
231+
RefType: "tag",
232+
RefName: "v1.2.3",
233+
Token: "t",
234+
APIBaseURL: "http://example.com\x7f",
235+
})
236+
require.Error(t, err)
237+
}
238+
239+
// TestCheckReleaseTriggerClientDoError propagates a transport
240+
// error from client.Do.
241+
func TestCheckReleaseTriggerClientDoError(t *testing.T) {
242+
sentinel := errors.New("transport boom")
243+
client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
244+
return nil, sentinel
245+
})}
246+
_, err := CheckReleaseTrigger(TriggerGuardOptions{
247+
EventName: "create",
248+
Repository: "jeduden/mdsmith",
249+
RefType: "tag",
250+
RefName: "v1.2.3",
251+
Token: "t",
252+
APIBaseURL: "https://api.example.com",
253+
Client: client,
254+
})
255+
require.ErrorIs(t, err, sentinel)
256+
}
257+
258+
type errReadCloser struct{ err error }
259+
260+
func (e errReadCloser) Read([]byte) (int, error) { return 0, e.err }
261+
func (e errReadCloser) Close() error { return nil }
262+
263+
// TestCheckReleaseTriggerReadBodyError covers the io.ReadAll
264+
// error branch of lookupReleaseDraft.
265+
func TestCheckReleaseTriggerReadBodyError(t *testing.T) {
266+
sentinel := errors.New("read boom")
267+
client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
268+
return &http.Response{
269+
StatusCode: http.StatusOK,
270+
Body: errReadCloser{err: sentinel},
271+
Header: make(http.Header),
272+
}, nil
273+
})}
274+
_, err := CheckReleaseTrigger(TriggerGuardOptions{
275+
EventName: "create",
276+
Repository: "jeduden/mdsmith",
277+
RefType: "tag",
278+
RefName: "v1.2.3",
279+
Token: "t",
280+
APIBaseURL: "https://api.example.com",
281+
Client: client,
282+
})
283+
require.ErrorIs(t, err, sentinel)
284+
}
285+
286+
// TestCheckReleaseTriggerInvalidJSONErrors covers the
287+
// json.Unmarshal error branch of lookupReleaseDraft.
288+
func TestCheckReleaseTriggerInvalidJSONErrors(t *testing.T) {
289+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
290+
_, _ = fmt.Fprint(w, `{not json`)
291+
}))
292+
t.Cleanup(srv.Close)
293+
294+
_, err := CheckReleaseTrigger(TriggerGuardOptions{
295+
EventName: "create",
296+
Repository: "jeduden/mdsmith",
297+
RefType: "tag",
298+
RefName: "v1.2.3",
299+
Token: "t",
300+
APIBaseURL: srv.URL,
301+
})
302+
require.Error(t, err)
303+
assert.Contains(t, err.Error(), "parse ")
304+
}
305+
306+
// TestReleaseLookupErrorEmptyBody pins the empty-body formatting
307+
// branch of releaseLookupError.Error.
308+
func TestReleaseLookupErrorEmptyBody(t *testing.T) {
309+
e := &releaseLookupError{URL: "https://x/y", StatusCode: 503}
310+
assert.Equal(t, "lookup https://x/y: unexpected GitHub API status 503", e.Error())
311+
}

0 commit comments

Comments
 (0)