Skip to content

Commit 61fc755

Browse files
Merge pull request #94 from salesforce/slack-api-interface-coverage
Add slackAPI interface and inject dependencies for coverage
2 parents 6c538ad + d9c2fd0 commit 61fc755

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

internal/slack.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,27 @@ type slackClient interface {
2525
postWebhookMessage(buildInfo BuildInfo) error
2626
}
2727

28+
type slackAPI interface {
29+
PostMessage(channelID string, options ...slack.MsgOption) (string, string, error)
30+
}
31+
32+
var _ slackAPI = (*slack.Client)(nil)
33+
2834
type productionSlackClientWorker struct {
35+
apiFactory func(token string) slackAPI
36+
webhookPoster func(url string, msg *slack.WebhookMessage) error
2937
}
3038

3139
func (client *productionSlackClientWorker) postChannelMessage(buildInfo BuildInfo) error {
32-
api := slack.New(buildInfo.OauthToken)
40+
api := client.apiFactory(buildInfo.OauthToken)
3341
postMessage := getPostMessage(buildInfo, buildInfo.GetContextualStatus())
3442
_, _, err := api.PostMessage(buildInfo.DestChannelId, postMessage...)
3543
return err
3644
}
3745

3846
func (client *productionSlackClientWorker) postWebhookMessage(buildInfo BuildInfo) error {
3947
message := getWebhookMessage(buildInfo, buildInfo.GetContextualStatus())
40-
err := slack.PostWebhook(buildInfo.HookURL, &message)
48+
err := client.webhookPoster(buildInfo.HookURL, &message)
4149
return err
4250
}
4351

@@ -87,7 +95,10 @@ func (client *SlackClient) PostToSlack(buildInfo BuildInfo) error {
8795
}
8896

8997
func NewSlackClient() SlackClient {
90-
return SlackClient{&productionSlackClientWorker{}}
98+
return SlackClient{&productionSlackClientWorker{
99+
apiFactory: func(token string) slackAPI { return slack.New(token) },
100+
webhookPoster: slack.PostWebhook,
101+
}}
91102
}
92103

93104
func getPostMessage(buildInfo BuildInfo, buildStatus Status) []slack.MsgOption {

internal/slack_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,107 @@ func Test_getPostMessage(t *testing.T) {
318318
// This ensures the function executes and returns the expected slice structure
319319
}
320320

321+
// fakeSlackAPI is a test double for the slackAPI interface.
322+
type fakeSlackAPI struct {
323+
capturedChannelID string
324+
capturedOptions []slack.MsgOption
325+
err error
326+
}
327+
328+
func (f *fakeSlackAPI) PostMessage(channelID string, options ...slack.MsgOption) (string, string, error) {
329+
f.capturedChannelID = channelID
330+
f.capturedOptions = options
331+
return "", "", f.err
332+
}
333+
334+
func Test_productionSlackClientWorker_postChannelMessage(t *testing.T) {
335+
t.Run("calls PostMessage with correct channelID and non-empty options", func(t *testing.T) {
336+
fakeAPI := &fakeSlackAPI{}
337+
worker := &productionSlackClientWorker{
338+
apiFactory: func(token string) slackAPI { return fakeAPI },
339+
}
340+
buildInfo := BuildInfo{
341+
JobName: "test-job",
342+
BuildURL: "https://example.com",
343+
BuildStatus: successKey,
344+
OauthToken: "token",
345+
DestChannelId: "C12345",
346+
}
347+
err := worker.postChannelMessage(buildInfo)
348+
if err != nil {
349+
t.Errorf("unexpected error: %v", err)
350+
}
351+
if fakeAPI.capturedChannelID != "C12345" {
352+
t.Errorf("expected channelID %q, got %q", "C12345", fakeAPI.capturedChannelID)
353+
}
354+
if len(fakeAPI.capturedOptions) == 0 {
355+
t.Error("expected non-empty message options")
356+
}
357+
})
358+
359+
t.Run("propagates error from PostMessage", func(t *testing.T) {
360+
fakeAPI := &fakeSlackAPI{err: fmt.Errorf("api error")}
361+
worker := &productionSlackClientWorker{
362+
apiFactory: func(token string) slackAPI { return fakeAPI },
363+
}
364+
buildInfo := BuildInfo{
365+
OauthToken: "token",
366+
DestChannelId: "C12345",
367+
BuildStatus: successKey,
368+
}
369+
err := worker.postChannelMessage(buildInfo)
370+
if err == nil || err.Error() != "api error" {
371+
t.Errorf("expected 'api error', got %v", err)
372+
}
373+
})
374+
}
375+
376+
func Test_productionSlackClientWorker_postWebhookMessage(t *testing.T) {
377+
t.Run("calls webhookPoster with correct URL and non-nil message", func(t *testing.T) {
378+
var capturedURL string
379+
var capturedMsg *slack.WebhookMessage
380+
worker := &productionSlackClientWorker{
381+
webhookPoster: func(url string, msg *slack.WebhookMessage) error {
382+
capturedURL = url
383+
capturedMsg = msg
384+
return nil
385+
},
386+
}
387+
buildInfo := BuildInfo{
388+
JobName: "test-job",
389+
BuildURL: "https://example.com",
390+
BuildStatus: successKey,
391+
HookURL: "https://hooks.slack.com/test",
392+
}
393+
err := worker.postWebhookMessage(buildInfo)
394+
if err != nil {
395+
t.Errorf("unexpected error: %v", err)
396+
}
397+
if capturedURL != "https://hooks.slack.com/test" {
398+
t.Errorf("expected URL %q, got %q", "https://hooks.slack.com/test", capturedURL)
399+
}
400+
if capturedMsg == nil {
401+
t.Error("expected non-nil webhook message")
402+
}
403+
})
404+
405+
t.Run("propagates error from webhookPoster", func(t *testing.T) {
406+
worker := &productionSlackClientWorker{
407+
webhookPoster: func(url string, msg *slack.WebhookMessage) error {
408+
return fmt.Errorf("webhook error")
409+
},
410+
}
411+
buildInfo := BuildInfo{
412+
HookURL: "https://hooks.slack.com/test",
413+
BuildStatus: successKey,
414+
}
415+
err := worker.postWebhookMessage(buildInfo)
416+
if err == nil || err.Error() != "webhook error" {
417+
t.Errorf("expected 'webhook error', got %v", err)
418+
}
419+
})
420+
}
421+
321422
func Test_NewSlackClient(t *testing.T) {
322423
client := NewSlackClient()
323424
if client.slackClient == nil {
@@ -385,6 +486,31 @@ func Test_PostToSlack_EdgeCases(t *testing.T) {
385486
true,
386487
PickRunModeErrorMessage,
387488
},
489+
{
490+
"channel message client returns error - PostToSlack propagates it",
491+
BuildInfo{
492+
JobName: "job",
493+
BuildURL: "url",
494+
BuildStatus: "SUCCESS",
495+
OauthToken: "token",
496+
DestChannelId: "channel",
497+
},
498+
NewTestClient(true, false),
499+
true,
500+
ChannelMessageTestErr,
501+
},
502+
{
503+
"webhook client returns error - PostToSlack propagates it",
504+
BuildInfo{
505+
JobName: "job",
506+
BuildURL: "url",
507+
BuildStatus: "SUCCESS",
508+
HookURL: "https://hooks.slack.com/test",
509+
},
510+
NewTestClient(false, true),
511+
true,
512+
WebhookMessageTestErr,
513+
},
388514
}
389515
for _, tt := range tests {
390516
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)