Skip to content

Commit 229f1fd

Browse files
authored
Merge pull request #334 from bndr/add-tests
Add tests
2 parents 91e79e3 + e2e9482 commit 229f1fd

39 files changed

+5263
-137
lines changed

.github/workflows/go.yml

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,82 @@ name: Go
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66
pull_request:
7-
branches: [ master ]
7+
branches: [master]
8+
9+
permissions:
10+
contents: read
811

912
jobs:
13+
test:
14+
name: Test (Go ${{ matrix.go-version }})
15+
runs-on: ubuntu-latest
16+
strategy:
17+
matrix:
18+
go-version: ['1.21', '1.22', '1.23']
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Go ${{ matrix.go-version }}
25+
uses: actions/setup-go@v5
26+
with:
27+
go-version: ${{ matrix.go-version }}
28+
cache: true
29+
30+
- name: Verify dependencies
31+
run: go mod verify
32+
33+
- name: Build
34+
run: go build -v ./...
35+
36+
- name: Run tests
37+
run: go test -v -race -shuffle=on ./...
1038

11-
build:
39+
coverage:
40+
name: Coverage
1241
runs-on: ubuntu-latest
1342
steps:
14-
- uses: actions/checkout@v2
43+
- name: Checkout code
44+
uses: actions/checkout@v4
1545

16-
- name: Set up Go
17-
uses: actions/setup-go@v2
18-
with:
19-
go-version: 1.17
46+
- name: Set up Go
47+
uses: actions/setup-go@v5
48+
with:
49+
go-version: '1.23'
50+
cache: true
51+
52+
- name: Run tests with coverage
53+
run: go test -v -coverprofile=coverage.out -covermode=atomic ./...
54+
55+
- name: Display coverage
56+
run: go tool cover -func=coverage.out
57+
58+
- name: Upload coverage to Codecov
59+
uses: codecov/codecov-action@v4
60+
with:
61+
files: ./coverage.out
62+
fail_ci_if_error: false
63+
verbose: true
64+
env:
65+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
66+
67+
lint:
68+
name: Lint
69+
runs-on: ubuntu-latest
70+
steps:
71+
- name: Checkout code
72+
uses: actions/checkout@v4
2073

21-
- name: Build
22-
run: go build -v ./...
74+
- name: Set up Go
75+
uses: actions/setup-go@v5
76+
with:
77+
go-version: '1.23'
78+
cache: true
2379

24-
- name: Test
25-
run: go test -v ./...
80+
- name: Run golangci-lint
81+
uses: golangci/golangci-lint-action@v6
82+
with:
83+
version: latest

apitoken.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (j *Jenkins) RevokeAllAPITokens(ctx context.Context) error {
8585
return nil
8686
}
8787

88-
// Revoke revokes an API token
88+
// Revoke revokes the API token.
8989
func (a *APIToken) Revoke() error {
9090
return a.Jenkins.RevokeAPIToken(context.Background(), a.UUID)
9191
}

apitoken_test.go

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
package gojenkins
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"io"
8+
"net/http"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
// TestErrAPIToken verifies ErrAPIToken implements error interface
15+
func TestErrAPIToken(t *testing.T) {
16+
err := &ErrAPIToken{Message: "token creation failed"}
17+
18+
assert.Error(t, err)
19+
assert.Equal(t, "token creation failed", err.Error())
20+
}
21+
22+
// TestGenerateAPITokenSuccess tests successful token generation
23+
func TestGenerateAPITokenSuccess(t *testing.T) {
24+
mock := &MockRequester{
25+
response: &http.Response{StatusCode: http.StatusOK},
26+
PostFunc: func(ctx context.Context, endpoint string, payload io.Reader, response interface{}, query map[string]string) (*http.Response, error) {
27+
if apiResp, ok := response.(*APITokenGenerateResponse); ok {
28+
apiResp.Status = "ok"
29+
apiResp.Data = APIToken{
30+
Name: "my-token",
31+
UUID: "generated-uuid",
32+
Value: "generated-value",
33+
}
34+
}
35+
return &http.Response{StatusCode: http.StatusOK}, nil
36+
},
37+
}
38+
39+
jenkins := &Jenkins{
40+
Server: "http://jenkins.local",
41+
Requester: mock,
42+
}
43+
44+
ctx := context.Background()
45+
token, err := jenkins.GenerateAPIToken(ctx, "my-token")
46+
47+
assert.NoError(t, err)
48+
assert.Equal(t, "my-token", token.Name)
49+
assert.Equal(t, "generated-uuid", token.UUID)
50+
assert.Equal(t, "generated-value", token.Value)
51+
assert.Equal(t, jenkins, token.Jenkins)
52+
}
53+
54+
// TestGenerateAPITokenError tests token generation with HTTP error
55+
func TestGenerateAPITokenError(t *testing.T) {
56+
mock := &MockRequester{
57+
response: &http.Response{StatusCode: http.StatusForbidden},
58+
}
59+
60+
jenkins := &Jenkins{
61+
Server: "http://jenkins.local",
62+
Requester: mock,
63+
}
64+
65+
ctx := context.Background()
66+
_, err := jenkins.GenerateAPIToken(ctx, "my-token")
67+
68+
assert.Error(t, err)
69+
errToken, ok := err.(*ErrAPIToken)
70+
assert.True(t, ok)
71+
assert.Contains(t, errToken.Message, "403")
72+
}
73+
74+
// TestGenerateAPITokenNetworkError tests token generation with network error
75+
func TestGenerateAPITokenNetworkError(t *testing.T) {
76+
mock := &MockRequester{
77+
err: errors.New("connection refused"),
78+
}
79+
80+
jenkins := &Jenkins{
81+
Server: "http://jenkins.local",
82+
Requester: mock,
83+
}
84+
85+
ctx := context.Background()
86+
_, err := jenkins.GenerateAPIToken(ctx, "my-token")
87+
88+
assert.Error(t, err)
89+
assert.Contains(t, err.Error(), "connection refused")
90+
}
91+
92+
// TestRevokeAPITokenSuccess tests successful token revocation
93+
func TestRevokeAPITokenSuccess(t *testing.T) {
94+
mock := &MockRequester{
95+
response: &http.Response{StatusCode: http.StatusOK},
96+
}
97+
98+
jenkins := &Jenkins{
99+
Server: "http://jenkins.local",
100+
Requester: mock,
101+
}
102+
103+
ctx := context.Background()
104+
err := jenkins.RevokeAPIToken(ctx, "token-uuid-123")
105+
106+
assert.NoError(t, err)
107+
assert.Contains(t, mock.lastEndpoint, "/revoke")
108+
}
109+
110+
// TestRevokeAPITokenError tests token revocation with HTTP error
111+
func TestRevokeAPITokenError(t *testing.T) {
112+
mock := &MockRequester{
113+
response: &http.Response{StatusCode: http.StatusNotFound},
114+
}
115+
116+
jenkins := &Jenkins{
117+
Server: "http://jenkins.local",
118+
Requester: mock,
119+
}
120+
121+
ctx := context.Background()
122+
err := jenkins.RevokeAPIToken(ctx, "nonexistent-uuid")
123+
124+
assert.Error(t, err)
125+
errToken, ok := err.(*ErrAPIToken)
126+
assert.True(t, ok)
127+
assert.Contains(t, errToken.Message, "404")
128+
}
129+
130+
// TestRevokeAPITokenNetworkError tests token revocation with network error
131+
func TestRevokeAPITokenNetworkError(t *testing.T) {
132+
mock := &MockRequester{
133+
err: errors.New("timeout"),
134+
}
135+
136+
jenkins := &Jenkins{
137+
Server: "http://jenkins.local",
138+
Requester: mock,
139+
}
140+
141+
ctx := context.Background()
142+
err := jenkins.RevokeAPIToken(ctx, "token-uuid")
143+
144+
assert.Error(t, err)
145+
assert.Contains(t, err.Error(), "timeout")
146+
}
147+
148+
// TestRevokeAllAPITokensSuccess tests revoking all tokens
149+
func TestRevokeAllAPITokensSuccess(t *testing.T) {
150+
mock := &MockRequester{
151+
response: &http.Response{StatusCode: http.StatusOK},
152+
}
153+
154+
jenkins := &Jenkins{
155+
Server: "http://jenkins.local",
156+
Requester: mock,
157+
}
158+
159+
ctx := context.Background()
160+
err := jenkins.RevokeAllAPITokens(ctx)
161+
162+
assert.NoError(t, err)
163+
assert.Contains(t, mock.lastEndpoint, "/revokeAll")
164+
}
165+
166+
// TestRevokeAllAPITokensError tests revoking all tokens with error
167+
func TestRevokeAllAPITokensError(t *testing.T) {
168+
mock := &MockRequester{
169+
response: &http.Response{StatusCode: http.StatusForbidden},
170+
}
171+
172+
jenkins := &Jenkins{
173+
Server: "http://jenkins.local",
174+
Requester: mock,
175+
}
176+
177+
ctx := context.Background()
178+
err := jenkins.RevokeAllAPITokens(ctx)
179+
180+
assert.Error(t, err)
181+
errToken, ok := err.(*ErrAPIToken)
182+
assert.True(t, ok)
183+
assert.Contains(t, errToken.Message, "403")
184+
}
185+
186+
// TestRevokeAllAPITokensNetworkError tests revoking all tokens with network error
187+
func TestRevokeAllAPITokensNetworkError(t *testing.T) {
188+
mock := &MockRequester{
189+
err: errors.New("network error"),
190+
}
191+
192+
jenkins := &Jenkins{
193+
Server: "http://jenkins.local",
194+
Requester: mock,
195+
}
196+
197+
ctx := context.Background()
198+
err := jenkins.RevokeAllAPITokens(ctx)
199+
200+
assert.Error(t, err)
201+
assert.Contains(t, err.Error(), "network error")
202+
}
203+
204+
// TestAPITokenRevokeMethod tests the Revoke method on APIToken struct
205+
func TestAPITokenRevokeMethod(t *testing.T) {
206+
mock := &MockRequester{
207+
response: &http.Response{StatusCode: http.StatusOK},
208+
}
209+
210+
jenkins := &Jenkins{
211+
Server: "http://jenkins.local",
212+
Requester: mock,
213+
}
214+
215+
token := APIToken{
216+
Jenkins: jenkins,
217+
Name: "test-token",
218+
UUID: "token-uuid-456",
219+
Value: "token-value",
220+
}
221+
222+
err := token.Revoke()
223+
224+
assert.NoError(t, err)
225+
assert.Contains(t, mock.lastEndpoint, "/revoke")
226+
}
227+
228+
// TestAPITokenRevokeMethodError tests the Revoke method with error
229+
func TestAPITokenRevokeMethodError(t *testing.T) {
230+
mock := &MockRequester{
231+
response: &http.Response{StatusCode: http.StatusInternalServerError},
232+
}
233+
234+
jenkins := &Jenkins{
235+
Server: "http://jenkins.local",
236+
Requester: mock,
237+
}
238+
239+
token := APIToken{
240+
Jenkins: jenkins,
241+
UUID: "token-uuid-456",
242+
}
243+
244+
err := token.Revoke()
245+
246+
assert.Error(t, err)
247+
}
248+
249+
// TestAPITokenConstants tests the API token URL constants
250+
func TestAPITokenConstants(t *testing.T) {
251+
assert.Equal(t, "/me/descriptorByName/jenkins.security.ApiTokenProperty", apiTokenBaseContext)
252+
assert.Equal(t, apiTokenBaseContext+"/generateNewToken", generateAPITokenURL)
253+
assert.Equal(t, apiTokenBaseContext+"/revoke", revokeAPITokenURL)
254+
assert.Equal(t, apiTokenBaseContext+"/revokeAll", revokeAllAPITokensURL)
255+
}
256+
257+
// TestAPITokenJSONMarshaling tests JSON field tags
258+
func TestAPITokenJSONMarshaling(t *testing.T) {
259+
token := APIToken{
260+
Name: "test-token",
261+
UUID: "uuid-123",
262+
Value: "value-456",
263+
}
264+
265+
data, err := json.Marshal(token)
266+
assert.NoError(t, err)
267+
268+
jsonStr := string(data)
269+
assert.Contains(t, jsonStr, "tokenName")
270+
assert.Contains(t, jsonStr, "tokenUuid")
271+
assert.Contains(t, jsonStr, "tokenValue")
272+
}
273+
274+
// TestAPITokenGenerateResponseJSONUnmarshaling tests JSON unmarshaling
275+
func TestAPITokenGenerateResponseJSONUnmarshaling(t *testing.T) {
276+
jsonData := `{
277+
"status": "ok",
278+
"data": {
279+
"tokenName": "my-api-token",
280+
"tokenUuid": "abc-123",
281+
"tokenValue": "secret-value"
282+
}
283+
}`
284+
285+
var response APITokenGenerateResponse
286+
err := json.Unmarshal([]byte(jsonData), &response)
287+
288+
assert.NoError(t, err)
289+
assert.Equal(t, "ok", response.Status)
290+
assert.Equal(t, "my-api-token", response.Data.Name)
291+
assert.Equal(t, "abc-123", response.Data.UUID)
292+
assert.Equal(t, "secret-value", response.Data.Value)
293+
}

0 commit comments

Comments
 (0)