github.com/windmeup/goreleaser@v1.21.95/internal/pipe/slack/slack_test.go (about) 1 package slack 2 3 import ( 4 "bytes" 5 "testing" 6 7 "github.com/slack-go/slack" 8 "github.com/stretchr/testify/require" 9 "github.com/windmeup/goreleaser/internal/testctx" 10 "github.com/windmeup/goreleaser/internal/testlib" 11 "github.com/windmeup/goreleaser/internal/yaml" 12 "github.com/windmeup/goreleaser/pkg/config" 13 ) 14 15 func TestStringer(t *testing.T) { 16 require.Equal(t, Pipe{}.String(), "slack") 17 } 18 19 func TestDefault(t *testing.T) { 20 ctx := testctx.New() 21 require.NoError(t, Pipe{}.Default(ctx)) 22 require.Equal(t, ctx.Config.Announce.Slack.MessageTemplate, defaultMessageTemplate) 23 } 24 25 func TestAnnounceInvalidTemplate(t *testing.T) { 26 ctx := testctx.NewWithCfg(config.Project{ 27 Announce: config.Announce{ 28 Slack: config.Slack{ 29 MessageTemplate: "{{ .Foo }", 30 }, 31 }, 32 }) 33 testlib.RequireTemplateError(t, Pipe{}.Announce(ctx)) 34 } 35 36 func TestAnnounceMissingEnv(t *testing.T) { 37 ctx := testctx.NewWithCfg(config.Project{ 38 Announce: config.Announce{ 39 Slack: config.Slack{}, 40 }, 41 }) 42 require.NoError(t, Pipe{}.Default(ctx)) 43 require.EqualError(t, Pipe{}.Announce(ctx), `slack: env: environment variable "SLACK_WEBHOOK" should not be empty`) 44 } 45 46 func TestSkip(t *testing.T) { 47 t.Run("skip", func(t *testing.T) { 48 require.True(t, Pipe{}.Skip(testctx.New())) 49 }) 50 51 t.Run("dont skip", func(t *testing.T) { 52 ctx := testctx.NewWithCfg(config.Project{ 53 Announce: config.Announce{ 54 Slack: config.Slack{ 55 Enabled: true, 56 }, 57 }, 58 }) 59 require.False(t, Pipe{}.Skip(ctx)) 60 }) 61 } 62 63 const testVersion = "v1.2.3" 64 65 func TestParseRichText(t *testing.T) { 66 t.Parallel() 67 68 t.Run("parse only - full slack config with blocks and attachments", func(t *testing.T) { 69 t.Parallel() 70 var project config.Project 71 require.NoError(t, yaml.Unmarshal(goodRichSlackConf(), &project)) 72 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 73 blocks, attachments, err := parseAdvancedFormatting(ctx) 74 require.NoError(t, err) 75 require.Len(t, blocks.BlockSet, 4) 76 require.Len(t, attachments, 2) 77 }) 78 79 t.Run("parse only - slack config with bad blocks", func(t *testing.T) { 80 t.Parallel() 81 var project config.Project 82 require.NoError(t, yaml.Unmarshal(badBlocksSlackConf(), &project)) 83 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 84 _, _, err := parseAdvancedFormatting(ctx) 85 require.Error(t, err) 86 require.ErrorContains(t, err, "json") 87 }) 88 89 t.Run("parse only - slack config with bad attachments", func(t *testing.T) { 90 t.Parallel() 91 var project config.Project 92 require.NoError(t, yaml.Unmarshal(badAttachmentsSlackConf(), &project)) 93 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 94 _, _, err := parseAdvancedFormatting(ctx) 95 require.Error(t, err) 96 require.ErrorContains(t, err, "json") 97 }) 98 } 99 100 func TestRichText(t *testing.T) { 101 t.Setenv("SLACK_WEBHOOK", slackTestHook()) 102 103 t.Run("e2e - full slack config with blocks and attachments", func(t *testing.T) { 104 t.SkipNow() // requires a valid webhook for integration testing 105 var project config.Project 106 require.NoError(t, yaml.Unmarshal(goodRichSlackConf(), &project)) 107 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 108 require.NoError(t, Pipe{}.Announce(ctx)) 109 }) 110 111 t.Run("slack config with bad blocks", func(t *testing.T) { 112 var project config.Project 113 require.NoError(t, yaml.Unmarshal(badBlocksSlackConf(), &project)) 114 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 115 err := Pipe{}.Announce(ctx) 116 require.Error(t, err) 117 require.ErrorContains(t, err, "json") 118 }) 119 } 120 121 func TestUnmarshall(t *testing.T) { 122 t.Parallel() 123 124 t.Run("happy unmarshal", func(t *testing.T) { 125 t.Parallel() 126 ctx := testctx.New(testctx.WithVersion(testVersion)) 127 var blocks slack.Blocks 128 require.NoError(t, unmarshal(ctx, []interface{}{map[string]interface{}{"type": "divider"}}, &blocks)) 129 }) 130 131 t.Run("unmarshal fails on MarshalJSON", func(t *testing.T) { 132 t.Parallel() 133 ctx := testctx.New(testctx.WithVersion(testVersion)) 134 var blocks slack.Blocks 135 require.Error(t, unmarshal(ctx, []interface{}{map[string]interface{}{"type": func() {}}}, &blocks)) 136 }) 137 138 t.Run("unmarshal happy to resolve template", func(t *testing.T) { 139 t.Parallel() 140 var project config.Project 141 require.NoError(t, yaml.Unmarshal(goodTemplateSlackConf(), &project)) 142 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 143 var blocks slack.Blocks 144 require.NoError(t, unmarshal(ctx, ctx.Config.Announce.Slack.Blocks, &blocks)) 145 require.Len(t, blocks.BlockSet, 1) 146 header, ok := blocks.BlockSet[0].(*slack.HeaderBlock) 147 require.True(t, ok) 148 require.Contains(t, header.Text.Text, testVersion) 149 }) 150 151 t.Run("unmarshal fails on resolve template", func(t *testing.T) { 152 t.Parallel() 153 var project config.Project 154 require.NoError(t, yaml.Unmarshal(badTemplateSlackConf(), &project)) 155 ctx := testctx.NewWithCfg(project, testctx.WithVersion(testVersion)) 156 var blocks slack.Blocks 157 require.Error(t, unmarshal(ctx, ctx.Config.Announce.Slack.Blocks, &blocks)) 158 }) 159 } 160 161 func slackTestHook() string { 162 // redacted: replace this by a real Slack Web Incoming Hook to test the feature end to end. 163 const hook = "https://hooks.slack.com/services/*********/***********/************************" 164 165 return hook 166 } 167 168 func goodRichSlackConf() []byte { 169 const conf = ` 170 project_name: test 171 announce: 172 slack: 173 enabled: true 174 message_template: fallback 175 channel: my_channel 176 blocks: 177 - type: header 178 text: 179 type: plain_text 180 text: '{{ .Version }}' 181 - type: section 182 text: 183 type: mrkdwn 184 text: | 185 Heading 186 ======= 187 188 # Other Heading 189 190 *Bold* 191 _italic_ 192 ~Strikethrough~ 193 194 ## Heading 2 195 ### Heading 3 196 * List item 1 197 * List item 2 198 199 - List item 3 200 - List item 4 201 202 [link](https://example.com) 203 <https://example.com|link> 204 205 :) 206 207 :star: 208 209 - type: divider 210 - type: section 211 text: 212 type: mrkdwn 213 text: | 214 my release 215 attachments: 216 - 217 title: Release artifacts 218 color: '#2eb886' 219 text: | 220 *Helm chart packages* 221 - fallback: full changelog 222 color: '#2eb886' 223 title: Full Change Log 224 text: | 225 * this link 226 * that link 227 ` 228 229 buf := bytes.NewBufferString(conf) 230 231 return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")) 232 } 233 234 func badBlocksSlackConf() []byte { 235 const conf = ` 236 project_name: test 237 announce: 238 slack: 239 enabled: true 240 message_template: fallback 241 channel: my_channel 242 blocks: 243 - type: header 244 text: invalid # <- wrong type for Slack API 245 ` 246 247 buf := bytes.NewBufferString(conf) 248 249 return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")) 250 } 251 252 func badAttachmentsSlackConf() []byte { 253 const conf = ` 254 project_name: test 255 announce: 256 slack: 257 enabled: true 258 message_template: fallback 259 channel: my_channel 260 attachments: 261 - 262 title: 263 - Release artifacts 264 - wrong # <- title is not an array 265 color: '#2eb886' 266 text: | 267 *Helm chart packages* 268 ` 269 270 buf := bytes.NewBufferString(conf) 271 272 return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")) 273 } 274 275 func goodTemplateSlackConf() []byte { 276 const conf = ` 277 project_name: test 278 announce: 279 slack: 280 enabled: true 281 message_template: '{{ .Version }}' 282 channel: my_channel 283 blocks: 284 - type: header 285 text: 286 type: plain_text 287 text: '{{ .Version }}' 288 ` 289 290 buf := bytes.NewBufferString(conf) 291 292 return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")) 293 } 294 295 func badTemplateSlackConf() []byte { 296 const conf = ` 297 project_name: test 298 announce: 299 slack: 300 enabled: true 301 message_template: fallback 302 channel: my_channel 303 blocks: 304 - type: header 305 text: 306 type: plain_text 307 text: '{{ .Wrong }}' 308 ` 309 310 buf := bytes.NewBufferString(conf) 311 312 return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")) 313 }