Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions cli/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ func (r *RootCmd) notifications() *serpent.Command {
Short: "Manage Coder notifications",
Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples(
Example{
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).",
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding)",
Command: "coder notifications pause",
},
Example{
Description: "Resume Coder notifications",
Command: "coder notifications resume",
},
Example{
Description: "Send a test notification. Administrators can use this to verify the notification target settings.",
Description: "Send a test notification. Administrators can use this to verify the notification target settings",
Command: "coder notifications test",
},
Example{
Description: "Send a custom notification to the requesting user. Sending notifications targeting other users or groups is currently not supported",
Command: "coder notifications custom \"Custom Title\" \"Custom Message\"",
},
),
Aliases: []string{"notification"},
Handler: func(inv *serpent.Invocation) error {
Expand All @@ -36,6 +40,7 @@ func (r *RootCmd) notifications() *serpent.Command {
r.pauseNotifications(),
r.resumeNotifications(),
r.testNotifications(),
r.customNotifications(),
},
}
return cmd
Expand Down Expand Up @@ -109,3 +114,30 @@ func (r *RootCmd) testNotifications() *serpent.Command {
}
return cmd
}

func (r *RootCmd) customNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "custom <title> <message>",
Short: "Send a custom notification",
Middleware: serpent.Chain(
serpent.RequireNArgs(2),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PostCustomNotification(inv.Context(), codersdk.CustomNotificationRequest{
Content: &codersdk.CustomNotificationContent{
Title: inv.Args[0],
Message: inv.Args[1],
},
})
if err != nil {
return xerrors.Errorf("unable to post custom notification: %w", err)
}

_, _ = fmt.Fprintln(inv.Stderr, "A custom notification has been sent.")
return nil
},
}
return cmd
}
101 changes: 101 additions & 0 deletions cli/notifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
"github.com/coder/coder/v2/codersdk"
Expand Down Expand Up @@ -166,3 +168,102 @@ func TestNotificationsTest(t *testing.T) {
require.Len(t, sent, 0)
})
}

func TestCustomNotifications(t *testing.T) {
t.Parallel()

t.Run("BadRequest", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A member user
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)

// When: The member user attempts to send a custom notification with empty title and message
inv, root := clitest.New(t, "notifications", "custom", "", "")
clitest.SetupConfig(t, memberClient, root)

// Then: an error is expected with no notifications sent
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
require.Equal(t, http.StatusBadRequest, sdkError.StatusCode())
require.Equal(t, "Invalid request body", sdkError.Message)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTestNotification))
require.Len(t, sent, 0)
})

t.Run("SystemUserNotAllowed", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A system user (prebuilds system user)
_, token := dbgen.APIKey(t, db, database.APIKey{
UserID: database.PrebuildsSystemUserID,
LoginType: database.LoginTypeNone,
})
systemUserClient := codersdk.New(ownerClient.URL)
systemUserClient.SetSessionToken(token)

// When: The system user attempts to send a custom notification
inv, root := clitest.New(t, "notifications", "custom", "Custom Title", "Custom Message")
clitest.SetupConfig(t, systemUserClient, root)

// Then: an error is expected with no notifications sent
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
require.Equal(t, "Forbidden", sdkError.Message)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTestNotification))
require.Len(t, sent, 0)
})

t.Run("Success", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A member user
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
memberClient, memberUser := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)

// When: The member user attempts to send a custom notification
inv, root := clitest.New(t, "notifications", "custom", "Custom Title", "Custom Message")
clitest.SetupConfig(t, memberClient, root)

// Then: we expect a custom notification to be sent to the member user
err := inv.Run()
require.NoError(t, err)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateCustomNotification))
require.Len(t, sent, 1)
require.Equal(t, memberUser.ID, sent[0].UserID)
require.Len(t, sent[0].Labels, 2)
require.Equal(t, "Custom Title", sent[0].Labels["custom_title"])
require.Equal(t, "Custom Message", sent[0].Labels["custom_message"])
require.Equal(t, memberUser.ID.String(), sent[0].CreatedBy)
})
}
10 changes: 8 additions & 2 deletions cli/testdata/coder_notifications_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ USAGE:
from
dispatching messages in case of the target outage (for example: unavailable
SMTP
server or Webhook not responding).:
server or Webhook not responding):

$ coder notifications pause

Expand All @@ -22,11 +22,17 @@ USAGE:

- Send a test notification. Administrators can use this to verify the
notification
target settings.:
target settings:

$ coder notifications test

- Send a custom notification to the requesting user. Sending notifications
targeting other users or groups is currently not supported:

$ coder notifications custom "Custom Title" "Custom Message"

SUBCOMMANDS:
custom Send a custom notification
pause Pause notifications
resume Resume notifications
test Send a test notification
Expand Down
9 changes: 9 additions & 0 deletions cli/testdata/coder_notifications_custom_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
coder v0.0.0-devel

USAGE:
coder notifications custom <title> <message>

Send a custom notification

———
Run `coder --help` for a list of global options.
113 changes: 113 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading