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  }