diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index a9748c2d19ea0..b384068e831f2 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -41,4 +41,6 @@ extend-exclude = [ "tailnet/testdata/**", "site/src/pages/SetupPage/countries.tsx", "provisioner/terraform/testdata/**", + # notifications' golden files confuse the detector because of quoted-printable encoding + "coderd/notifications/testdata/**" ] diff --git a/coderd/database/migrations/000263_consistent_notification_initiator_naming.down.sql b/coderd/database/migrations/000263_consistent_notification_initiator_naming.down.sql new file mode 100644 index 0000000000000..0e7823a3383dd --- /dev/null +++ b/coderd/database/migrations/000263_consistent_notification_initiator_naming.down.sql @@ -0,0 +1,55 @@ +-- UserAccountCreated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'New user account **{{.Labels.created_account_name}}** has been created.\n\n' || + -- Mention the real name of the user who created the account: + E'This new user account was created for **{{.Labels.created_account_user_name}}** by **{{.Labels.account_creator}}**.' +WHERE + id = '4e19c0ac-94e1-4532-9515-d1801aa283b2'; + +-- UserAccountDeleted +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'User account **{{.Labels.deleted_account_name}}** has been deleted.\n\n' || + -- Mention the real name of the user who deleted the account: + E'The deleted account belonged to **{{.Labels.deleted_account_user_name}}** and was deleted by **{{.Labels.account_deleter_user_name}}**.' +WHERE + id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6'; + +-- UserAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.suspended_account_name}}** has been suspended.\n\n' || + -- Mention the real name of the user who suspended the account: + E'The newly suspended account belongs to **{{.Labels.suspended_account_user_name}}** and was suspended by **{{.Labels.account_suspender_user_name}}**.' +WHERE + id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d'; + +-- YourAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'Your account **{{.Labels.suspended_account_name}}** has been suspended by **{{.Labels.account_suspender_user_name}}**.' +WHERE + id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff'; + + +-- UserAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.activated_account_name}}** has been activated.\n\n' || + E'The newly activated account belongs to **{{.Labels.activated_account_user_name}}** and was activated by **{{.Labels.account_activator_user_name}}**.' +WHERE + id = '9f5af851-8408-4e73-a7a1-c6502ba46689'; + +-- YourAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'Your account **{{.Labels.activated_account_name}}** has been activated by **{{.Labels.account_activator_user_name}}**.' +WHERE + id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4'; diff --git a/coderd/database/migrations/000263_consistent_notification_initiator_naming.up.sql b/coderd/database/migrations/000263_consistent_notification_initiator_naming.up.sql new file mode 100644 index 0000000000000..1357e7a1ef287 --- /dev/null +++ b/coderd/database/migrations/000263_consistent_notification_initiator_naming.up.sql @@ -0,0 +1,57 @@ +-- UserAccountCreated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'New user account **{{.Labels.created_account_name}}** has been created.\n\n' || + -- Use the conventional initiator label: + E'This new user account was created for **{{.Labels.created_account_user_name}}** by **{{.Labels.initiator}}**.' +WHERE + id = '4e19c0ac-94e1-4532-9515-d1801aa283b2'; + +-- UserAccountDeleted +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'User account **{{.Labels.deleted_account_name}}** has been deleted.\n\n' || + -- Use the conventional initiator label: + E'The deleted account belonged to **{{.Labels.deleted_account_user_name}}** and was deleted by **{{.Labels.initiator}}**.' +WHERE + id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6'; + +-- UserAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.suspended_account_name}}** has been suspended.\n\n' || + -- Use the conventional initiator label: + E'The newly suspended account belongs to **{{.Labels.suspended_account_user_name}}** and was suspended by **{{.Labels.initiator}}**.' +WHERE + id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d'; + +-- YourAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + -- Use the conventional initiator label: + E'Your account **{{.Labels.suspended_account_name}}** has been suspended by **{{.Labels.initiator}}**.' +WHERE + id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff'; + +-- UserAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.activated_account_name}}** has been activated.\n\n' || + -- Use the conventional initiator label: + E'The newly activated account belongs to **{{.Labels.activated_account_user_name}}** and was activated by **{{.Labels.initiator}}**.' +WHERE + id = '9f5af851-8408-4e73-a7a1-c6502ba46689'; + +-- YourAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + -- Use the conventional initiator label: + E'Your account **{{.Labels.activated_account_name}}** has been activated by **{{.Labels.initiator}}**.' +WHERE + id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4'; diff --git a/coderd/notifications/dispatch/smtp_test.go b/coderd/notifications/dispatch/smtp_test.go index eb12f05ad46c7..2687e0d82bb26 100644 --- a/coderd/notifications/dispatch/smtp_test.go +++ b/coderd/notifications/dispatch/smtp_test.go @@ -2,11 +2,8 @@ package dispatch_test import ( "bytes" - "crypto/tls" - _ "embed" "fmt" "log" - "net" "sync" "testing" @@ -22,6 +19,7 @@ import ( "github.com/coder/serpent" "github.com/coder/coder/v2/coderd/notifications/dispatch" + "github.com/coder/coder/v2/coderd/notifications/dispatch/smtptest" "github.com/coder/coder/v2/coderd/notifications/types" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/testutil" @@ -47,9 +45,9 @@ func TestSMTP(t *testing.T) { subject = "This is the subject" body = "This is the body" - caFile = "fixtures/ca.crt" - certFile = "fixtures/server.crt" - keyFile = "fixtures/server.key" + caFile = "smtptest/fixtures/ca.crt" + certFile = "smtptest/fixtures/server.crt" + keyFile = "smtptest/fixtures/server.key" ) logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true, IgnoredErrorIs: []error{}}).Leveled(slog.LevelDebug) @@ -125,7 +123,7 @@ func TestSMTP(t *testing.T) { Auth: codersdk.NotificationsEmailAuthConfig{ Username: username, - PasswordFile: "fixtures/password.txt", + PasswordFile: "smtptest/fixtures/password.txt", }, }, toAddrs: []string{to}, @@ -341,14 +339,14 @@ func TestSMTP(t *testing.T) { cfg: codersdk.NotificationsEmailConfig{ TLS: codersdk.NotificationsEmailTLSConfig{ CAFile: caFile, - CertFile: "fixtures/nope.cert", + CertFile: "smtptest/fixtures/nope.cert", KeyFile: keyFile, }, }, // not using full error message here since it differs on *nix and Windows: // *nix: no such file or directory // Windows: The system cannot find the file specified. - expectedErr: "open fixtures/nope.cert:", + expectedErr: "open smtptest/fixtures/nope.cert:", retryable: true, }, { @@ -358,13 +356,13 @@ func TestSMTP(t *testing.T) { TLS: codersdk.NotificationsEmailTLSConfig{ CAFile: caFile, CertFile: certFile, - KeyFile: "fixtures/nope.key", + KeyFile: "smtptest/fixtures/nope.key", }, }, // not using full error message here since it differs on *nix and Windows: // *nix: no such file or directory // Windows: The system cannot find the file specified. - expectedErr: "open fixtures/nope.key:", + expectedErr: "open smtptest/fixtures/nope.key:", retryable: true, }, /** @@ -417,7 +415,7 @@ func TestSMTP(t *testing.T) { tc.cfg.ForceTLS = serpent.Bool(tc.useTLS) - backend := NewBackend(Config{ + backend := smtptest.NewBackend(smtptest.Config{ AuthMechanisms: tc.authMechs, AcceptedIdentity: tc.cfg.Auth.Identity.String(), @@ -428,7 +426,7 @@ func TestSMTP(t *testing.T) { }) // Create a mock SMTP server which conditionally listens for plain or TLS connections. - srv, listen, err := createMockSMTPServer(backend, tc.useTLS) + srv, listen, err := smtptest.CreateMockSMTPServer(backend, tc.useTLS) require.NoError(t, err) t.Cleanup(func() { // We expect that the server has already been closed in the test @@ -460,7 +458,7 @@ func TestSMTP(t *testing.T) { // Wait for the server to become pingable. require.Eventually(t, func() bool { - cl, err := pingClient(listen, tc.useTLS, tc.cfg.TLS.StartTLS.Value()) + cl, err := smtptest.PingClient(listen, tc.useTLS, tc.cfg.TLS.StartTLS.Value()) if err != nil { t.Logf("smtp not yet dialable: %s", err) return false @@ -522,19 +520,3 @@ func TestSMTP(t *testing.T) { }) } } - -func pingClient(listen net.Listener, useTLS bool, startTLS bool) (*smtp.Client, error) { - tlsCfg := &tls.Config{ - // nolint:gosec // It's a test. - InsecureSkipVerify: true, - } - - switch { - case useTLS: - return smtp.DialTLS(listen.Addr().String(), tlsCfg) - case startTLS: - return smtp.DialStartTLS(listen.Addr().String(), tlsCfg) - default: - return smtp.Dial(listen.Addr().String()) - } -} diff --git a/coderd/notifications/dispatch/fixtures/ca.conf b/coderd/notifications/dispatch/smtptest/fixtures/ca.conf similarity index 100% rename from coderd/notifications/dispatch/fixtures/ca.conf rename to coderd/notifications/dispatch/smtptest/fixtures/ca.conf diff --git a/coderd/notifications/dispatch/fixtures/ca.crt b/coderd/notifications/dispatch/smtptest/fixtures/ca.crt similarity index 100% rename from coderd/notifications/dispatch/fixtures/ca.crt rename to coderd/notifications/dispatch/smtptest/fixtures/ca.crt diff --git a/coderd/notifications/dispatch/fixtures/ca.key b/coderd/notifications/dispatch/smtptest/fixtures/ca.key similarity index 100% rename from coderd/notifications/dispatch/fixtures/ca.key rename to coderd/notifications/dispatch/smtptest/fixtures/ca.key diff --git a/coderd/notifications/dispatch/fixtures/ca.srl b/coderd/notifications/dispatch/smtptest/fixtures/ca.srl similarity index 100% rename from coderd/notifications/dispatch/fixtures/ca.srl rename to coderd/notifications/dispatch/smtptest/fixtures/ca.srl diff --git a/coderd/notifications/dispatch/fixtures/generate.sh b/coderd/notifications/dispatch/smtptest/fixtures/generate.sh similarity index 100% rename from coderd/notifications/dispatch/fixtures/generate.sh rename to coderd/notifications/dispatch/smtptest/fixtures/generate.sh diff --git a/coderd/notifications/dispatch/fixtures/password.txt b/coderd/notifications/dispatch/smtptest/fixtures/password.txt similarity index 100% rename from coderd/notifications/dispatch/fixtures/password.txt rename to coderd/notifications/dispatch/smtptest/fixtures/password.txt diff --git a/coderd/notifications/dispatch/fixtures/server.conf b/coderd/notifications/dispatch/smtptest/fixtures/server.conf similarity index 100% rename from coderd/notifications/dispatch/fixtures/server.conf rename to coderd/notifications/dispatch/smtptest/fixtures/server.conf diff --git a/coderd/notifications/dispatch/fixtures/server.crt b/coderd/notifications/dispatch/smtptest/fixtures/server.crt similarity index 100% rename from coderd/notifications/dispatch/fixtures/server.crt rename to coderd/notifications/dispatch/smtptest/fixtures/server.crt diff --git a/coderd/notifications/dispatch/fixtures/server.csr b/coderd/notifications/dispatch/smtptest/fixtures/server.csr similarity index 100% rename from coderd/notifications/dispatch/fixtures/server.csr rename to coderd/notifications/dispatch/smtptest/fixtures/server.csr diff --git a/coderd/notifications/dispatch/fixtures/server.key b/coderd/notifications/dispatch/smtptest/fixtures/server.key similarity index 100% rename from coderd/notifications/dispatch/fixtures/server.key rename to coderd/notifications/dispatch/smtptest/fixtures/server.key diff --git a/coderd/notifications/dispatch/fixtures/v3_ext.conf b/coderd/notifications/dispatch/smtptest/fixtures/v3_ext.conf similarity index 100% rename from coderd/notifications/dispatch/fixtures/v3_ext.conf rename to coderd/notifications/dispatch/smtptest/fixtures/v3_ext.conf diff --git a/coderd/notifications/dispatch/smtp_util_test.go b/coderd/notifications/dispatch/smtptest/server.go similarity index 90% rename from coderd/notifications/dispatch/smtp_util_test.go rename to coderd/notifications/dispatch/smtptest/server.go index 44cb8725c5d8c..689b4d384036d 100644 --- a/coderd/notifications/dispatch/smtp_util_test.go +++ b/coderd/notifications/dispatch/smtptest/server.go @@ -1,4 +1,4 @@ -package dispatch_test +package smtptest import ( "crypto/tls" @@ -162,7 +162,7 @@ func (*Session) Reset() {} func (*Session) Logout() error { return nil } // nolint:revive // Yes, useTLS is a control flag. -func createMockSMTPServer(be *Backend, useTLS bool) (*smtp.Server, net.Listener, error) { +func CreateMockSMTPServer(be *Backend, useTLS bool) (*smtp.Server, net.Listener, error) { // nolint:gosec tlsCfg := &tls.Config{ GetCertificate: readCert, @@ -203,3 +203,19 @@ func readCert(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { return &crt, nil } + +func PingClient(listen net.Listener, useTLS bool, startTLS bool) (*smtp.Client, error) { + tlsCfg := &tls.Config{ + // nolint:gosec // It's a test. + InsecureSkipVerify: true, + } + + switch { + case useTLS: + return smtp.DialTLS(listen.Addr().String(), tlsCfg) + case startTLS: + return smtp.DialStartTLS(listen.Addr().String(), tlsCfg) + default: + return smtp.Dial(listen.Addr().String()) + } +} diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 28bd8b5190c71..d6eb480f75c9b 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -10,11 +10,13 @@ import ( "go/ast" "go/parser" "go/token" + "io" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" + "regexp" "slices" "sort" "strings" @@ -23,18 +25,16 @@ import ( "testing" "time" - "golang.org/x/xerrors" - - "github.com/coder/quartz" - + "github.com/emersion/go-sasl" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" smtpmock "github.com/mocktools/go-smtp-mock/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" + "golang.org/x/xerrors" - "github.com/coder/serpent" - + "cdr.dev/slog" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" @@ -42,12 +42,14 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/notifications/dispatch" - "github.com/coder/coder/v2/coderd/notifications/render" + "github.com/coder/coder/v2/coderd/notifications/dispatch/smtptest" "github.com/coder/coder/v2/coderd/notifications/types" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/util/syncmap" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/testutil" + "github.com/coder/quartz" + "github.com/coder/serpent" ) // updateGoldenFiles is a flag that can be set to update golden files. @@ -691,6 +693,16 @@ func TestNotificationTemplates_Golden(t *testing.T) { t.Skip("This test requires postgres; it relies on the notification templates added by migrations in the database") } + const ( + username = "bob" + password = "🤫" + + hello = "localhost" + + from = "system@coder.com" + hint = "run \"DB=ci make update-golden-files\" and commit the changes" + ) + tests := []struct { name string id uuid.UUID @@ -700,7 +712,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceDeleted", id: notifications.TemplateWorkspaceDeleted, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "reason": "autodeleted due to dormancy", @@ -712,7 +726,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceAutobuildFailed", id: notifications.TemplateWorkspaceAutobuildFailed, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "reason": "autostart", @@ -723,7 +739,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceDormant", id: notifications.TemplateWorkspaceDormant, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "reason": "breached the template's threshold for inactivity", @@ -737,7 +755,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceAutoUpdated", id: notifications.TemplateWorkspaceAutoUpdated, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "template_version_name": "1.0", @@ -749,7 +769,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceMarkedForDeletion", id: notifications.TemplateWorkspaceMarkedForDeletion, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "reason": "template updated to new dormancy policy", @@ -762,11 +784,13 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateUserAccountCreated", id: notifications.TemplateUserAccountCreated, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "created_account_name": "bobby", "created_account_user_name": "William Tables", - "account_creator": "rob", + "initiator": "rob", }, }, }, @@ -774,11 +798,13 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateUserAccountDeleted", id: notifications.TemplateUserAccountDeleted, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "deleted_account_name": "bobby", - "deleted_account_user_name": "william tables", - "account_deleter_user_name": "rob", + "deleted_account_user_name": "William Tables", + "initiator": "rob", }, }, }, @@ -786,11 +812,13 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateUserAccountSuspended", id: notifications.TemplateUserAccountSuspended, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "suspended_account_name": "bobby", - "suspended_account_user_name": "william tables", - "account_suspender_user_name": "rob", + "suspended_account_user_name": "William Tables", + "initiator": "rob", }, }, }, @@ -798,11 +826,13 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateUserAccountActivated", id: notifications.TemplateUserAccountActivated, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "activated_account_name": "bobby", - "activated_account_user_name": "william tables", - "account_activator_user_name": "rob", + "activated_account_user_name": "William Tables", + "initiator": "rob", }, }, }, @@ -810,10 +840,12 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateYourAccountSuspended", id: notifications.TemplateYourAccountSuspended, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ - "suspended_account_name": "bobby", - "account_suspender_user_name": "rob", + "suspended_account_name": "bobby", + "initiator": "rob", }, }, }, @@ -821,10 +853,12 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateYourAccountActivated", id: notifications.TemplateYourAccountActivated, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ - "activated_account_name": "bobby", - "account_activator_user_name": "rob", + "activated_account_name": "bobby", + "initiator": "rob", }, }, }, @@ -832,7 +866,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateTemplateDeleted", id: notifications.TemplateTemplateDeleted, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-template", "display_name": "Bobby's Template", @@ -844,7 +880,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceManualBuildFailed", id: notifications.TemplateWorkspaceManualBuildFailed, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "name": "bobby-workspace", "template_name": "bobby-template", @@ -860,7 +898,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateWorkspaceBuildsFailedReport", id: notifications.TemplateWorkspaceBuildsFailedReport, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "template_name": "bobby-first-template", "template_display_name": "Bobby First Template", @@ -911,7 +951,9 @@ func TestNotificationTemplates_Golden(t *testing.T) { name: "TemplateUserRequestedOneTimePasscode", id: notifications.TemplateUserRequestedOneTimePasscode, payload: types.MessagePayload{ - UserName: "Bobby", + UserName: "Bobby", + UserEmail: "bobby@coder.com", + UserUsername: "bobby", Labels: map[string]string{ "one_time_passcode": "fad9020b-6562-4cdb-87f1-0486f1bea415", }, @@ -919,6 +961,7 @@ func TestNotificationTemplates_Golden(t *testing.T) { }, } + // We must have a test case for every notification_template. This is enforced below: allTemplates, err := enumerateAllTemplates(t) require.NoError(t, err) for _, name := range allTemplates { @@ -938,53 +981,303 @@ func TestNotificationTemplates_Golden(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - _, _, sql := dbtestutil.NewDBWithSQLDB(t) - - var ( - titleTmpl string - bodyTmpl string - ) - err := sql. - QueryRow("SELECT title_template, body_template FROM notification_templates WHERE id = $1 LIMIT 1", tc.id). - Scan(&titleTmpl, &bodyTmpl) - require.NoError(t, err, "failed to query body template for template:", tc.id) - - title, err := render.GoTemplate(titleTmpl, tc.payload, defaultHelpers()) - require.NotContainsf(t, title, render.NoValue, "template %q is missing a label value", tc.name) - require.NoError(t, err, "failed to render notification title template") - require.NotEmpty(t, title, "title should not be empty") - - body, err := render.GoTemplate(bodyTmpl, tc.payload, defaultHelpers()) - require.NoError(t, err, "failed to render notification body template") - require.NotEmpty(t, body, "body should not be empty") - - partialName := strings.Split(t.Name(), "/")[1] - bodyGoldenFile := filepath.Join("testdata", "rendered-templates", partialName+"-body.md.golden") - titleGoldenFile := filepath.Join("testdata", "rendered-templates", partialName+"-title.md.golden") - - if *updateGoldenFiles { - err = os.MkdirAll(filepath.Dir(bodyGoldenFile), 0o755) - require.NoError(t, err, "want no error creating golden file directory") - err = os.WriteFile(bodyGoldenFile, []byte(body), 0o600) - require.NoError(t, err, "want no error writing body golden file") - err = os.WriteFile(titleGoldenFile, []byte(title), 0o600) - require.NoError(t, err, "want no error writing title golden file") - return - } + t.Run("smtp", func(t *testing.T) { + t.Parallel() + + // Spin up the DB + db, logger, user := func() (*database.Store, *slog.Logger, *codersdk.User) { + adminClient, _, api := coderdtest.NewWithAPI(t, nil) + db := api.Database + firstUser := coderdtest.CreateFirstUser(t, adminClient) + + _, user := coderdtest.CreateAnotherUserMutators( + t, + adminClient, + firstUser.OrganizationID, + []rbac.RoleIdentifier{rbac.RoleUserAdmin()}, + func(r *codersdk.CreateUserRequestWithOrgs) { + r.Username = tc.payload.UserUsername + r.Email = tc.payload.UserEmail + r.Name = tc.payload.UserName + }, + ) + return &db, &api.Logger, &user + }() - const hint = "run \"DB=ci make update-golden-files\" and commit the changes" + // nolint:gocritic // Unit test. + ctx := dbauthz.AsSystemRestricted(testutil.Context(t, testutil.WaitSuperLong)) - wantBody, err := os.ReadFile(bodyGoldenFile) - require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint)) - wantTitle, err := os.ReadFile(titleGoldenFile) - require.NoError(t, err, fmt.Sprintf("missing golden notification title file. %s", hint)) + // smtp config shared between client and server + smtpConfig := codersdk.NotificationsEmailConfig{ + Hello: hello, + From: from, - require.Equal(t, string(wantBody), body, fmt.Sprintf("rendered template body does not match golden file. If this is expected, %s", hint)) - require.Equal(t, string(wantTitle), title, fmt.Sprintf("rendered template title does not match golden file. If this is expected, %s", hint)) + Auth: codersdk.NotificationsEmailAuthConfig{ + Username: username, + Password: password, + }, + } + + // Spin up the mock SMTP server + backend := smtptest.NewBackend(smtptest.Config{ + AuthMechanisms: []string{sasl.Login}, + + AcceptedIdentity: smtpConfig.Auth.Identity.String(), + AcceptedUsername: username, + AcceptedPassword: password, + }) + + // Create a mock SMTP server which conditionally listens for plain or TLS connections. + srv, listen, err := smtptest.CreateMockSMTPServer(backend, false) + require.NoError(t, err) + t.Cleanup(func() { + err := srv.Shutdown(ctx) + require.NoError(t, err) + }) + + var hp serpent.HostPort + require.NoError(t, hp.Set(listen.Addr().String())) + smtpConfig.Smarthost = hp + + // Start mock SMTP server in the background. + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + assert.NoError(t, srv.Serve(listen)) + }() + + // Wait for the server to become pingable. + require.Eventually(t, func() bool { + cl, err := smtptest.PingClient(listen, false, smtpConfig.TLS.StartTLS.Value()) + if err != nil { + t.Logf("smtp not yet dialable: %s", err) + return false + } + + if err = cl.Noop(); err != nil { + t.Logf("smtp not yet noopable: %s", err) + return false + } + + if err = cl.Close(); err != nil { + t.Logf("smtp didn't close properly: %s", err) + return false + } + + return true + }, testutil.WaitShort, testutil.IntervalFast) + + smtpCfg := defaultNotificationsConfig(database.NotificationMethodSmtp) + smtpCfg.SMTP = smtpConfig + + smtpManager, err := notifications.NewManager( + smtpCfg, + *db, + defaultHelpers(), + createMetrics(), + logger.Named("manager"), + ) + require.NoError(t, err) + + smtpManager.Run(ctx) + + notificationCfg := defaultNotificationsConfig(database.NotificationMethodSmtp) + + smtpEnqueuer, err := notifications.NewStoreEnqueuer( + notificationCfg, + *db, + defaultHelpers(), + logger.Named("enqueuer"), + quartz.NewReal(), + ) + require.NoError(t, err) + + _, err = smtpEnqueuer.EnqueueWithData( + ctx, + user.ID, + tc.id, + tc.payload.Labels, + tc.payload.Data, + user.Username, + user.ID, + ) + require.NoError(t, err) + + // Wait for the message to be fetched + var msg *smtptest.Message + require.Eventually(t, func() bool { + msg = backend.LastMessage() + return msg != nil && len(msg.Contents) > 0 + }, testutil.WaitShort, testutil.IntervalFast) + + body := normalizeGoldenEmail([]byte(msg.Contents)) + + err = smtpManager.Stop(ctx) + require.NoError(t, err) + + partialName := strings.Split(t.Name(), "/")[1] + goldenFile := filepath.Join("testdata", "rendered-templates", "smtp", partialName+".html.golden") + if *updateGoldenFiles { + err = os.MkdirAll(filepath.Dir(goldenFile), 0o755) + require.NoError(t, err, "want no error creating golden file directory") + err = os.WriteFile(goldenFile, body, 0o600) + require.NoError(t, err, "want no error writing body golden file") + return + } + + wantBody, err := os.ReadFile(goldenFile) + require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint)) + require.Empty( + t, + cmp.Diff(wantBody, body), + fmt.Sprintf("golden file mismatch: %s. If this is expected, %s. (-want +got). ", goldenFile, hint), + ) + }) + + t.Run("webhook", func(t *testing.T) { + t.Parallel() + + // Spin up the DB + db, logger, user := func() (*database.Store, *slog.Logger, *codersdk.User) { + adminClient, _, api := coderdtest.NewWithAPI(t, nil) + db := api.Database + firstUser := coderdtest.CreateFirstUser(t, adminClient) + + _, user := coderdtest.CreateAnotherUserMutators( + t, + adminClient, + firstUser.OrganizationID, + []rbac.RoleIdentifier{rbac.RoleUserAdmin()}, + func(r *codersdk.CreateUserRequestWithOrgs) { + r.Username = tc.payload.UserUsername + r.Email = tc.payload.UserEmail + r.Name = tc.payload.UserName + }, + ) + return &db, &api.Logger, &user + }() + + // nolint:gocritic // Unit test. + ctx := dbauthz.AsSystemRestricted(testutil.Context(t, testutil.WaitSuperLong)) + + // Spin up the mock webhook server + var body []byte + var readErr error + var webhookReceived bool + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + + body, readErr = io.ReadAll(r.Body) + webhookReceived = true + })) + t.Cleanup(server.Close) + + endpoint, err := url.Parse(server.URL) + require.NoError(t, err) + + webhookCfg := defaultNotificationsConfig(database.NotificationMethodWebhook) + + webhookCfg.Webhook = codersdk.NotificationsWebhookConfig{ + Endpoint: *serpent.URLOf(endpoint), + } + + webhookManager, err := notifications.NewManager( + webhookCfg, + *db, + defaultHelpers(), + createMetrics(), + logger.Named("manager"), + ) + require.NoError(t, err) + + webhookManager.Run(ctx) + + httpEnqueuer, err := notifications.NewStoreEnqueuer( + defaultNotificationsConfig(database.NotificationMethodWebhook), + *db, + defaultHelpers(), + logger.Named("enqueuer"), + quartz.NewReal(), + ) + require.NoError(t, err) + + _, err = httpEnqueuer.EnqueueWithData( + ctx, + user.ID, + tc.id, + tc.payload.Labels, + tc.payload.Data, + user.Username, + user.ID, + ) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return webhookReceived + }, testutil.WaitShort, testutil.IntervalFast) + + require.NoError(t, err) + + // Handle the body that was read in the http server here. + // We need to do it here because we can't call require.* in a separate goroutine, such as the http server handler + require.NoError(t, readErr) + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, body, "", " ") + require.NoError(t, err) + + content := normalizeGoldenWebhook(prettyJSON.Bytes()) + + partialName := strings.Split(t.Name(), "/")[1] + goldenFile := filepath.Join("testdata", "rendered-templates", "webhook", partialName+".json.golden") + if *updateGoldenFiles { + err = os.MkdirAll(filepath.Dir(goldenFile), 0o755) + require.NoError(t, err, "want no error creating golden file directory") + err = os.WriteFile(goldenFile, content, 0o600) + require.NoError(t, err, "want no error writing body golden file") + return + } + + wantBody, err := os.ReadFile(goldenFile) + require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint)) + require.Equal(t, wantBody, content, fmt.Sprintf("smtp notification does not match golden file. If this is expected, %s", hint)) + }) }) } } +func normalizeGoldenEmail(content []byte) []byte { + const ( + constantDate = "Fri, 11 Oct 2024 09:03:06 +0000" + constantMessageID = "02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48" + constantBoundary = "bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4" + ) + + dateRegex := regexp.MustCompile(`Date: .+`) + messageIDRegex := regexp.MustCompile(`Message-Id: .+`) + boundaryRegex := regexp.MustCompile(`boundary=([0-9a-zA-Z]+)`) + submatches := boundaryRegex.FindSubmatch(content) + if len(submatches) == 0 { + return content + } + + boundary := submatches[1] + + content = dateRegex.ReplaceAll(content, []byte("Date: "+constantDate)) + content = messageIDRegex.ReplaceAll(content, []byte("Message-Id: "+constantMessageID)) + content = bytes.ReplaceAll(content, boundary, []byte(constantBoundary)) + + return content +} + +func normalizeGoldenWebhook(content []byte) []byte { + const constantUUID = "00000000-0000-0000-0000-000000000000" + uuidRegex := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`) + content = uuidRegex.ReplaceAll(content, []byte(constantUUID)) + + return content +} + // TestDisabledBeforeEnqueue ensures that notifications cannot be enqueued once a user has disabled that notification template func TestDisabledBeforeEnqueue(t *testing.T) { t.Parallel() diff --git a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden deleted file mode 100644 index ade9c87ff791a..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -The template **bobby-template** was deleted by **rob**. - -The template's display name was **Bobby's Template**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-title.md.golden deleted file mode 100644 index c3f3db7645422..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Template "bobby-template" deleted \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden deleted file mode 100644 index 5a773a51dc181..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -User account **bobby** has been activated. - -The newly activated account belongs to **william tables** and was activated by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-title.md.golden deleted file mode 100644 index ebf8e9da36934..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -User account "bobby" activated \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden deleted file mode 100644 index 88a46735f0483..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -New user account **bobby** has been created. - -This new user account was created for **William Tables** by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-title.md.golden deleted file mode 100644 index bfcdf6826f772..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -User account "bobby" created \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden deleted file mode 100644 index 4ca8bedd0ca52..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -User account **bobby** has been deleted. - -The deleted account belonged to **william tables** and was deleted by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-title.md.golden deleted file mode 100644 index 199d4ddd66d12..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -User account "bobby" deleted \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden deleted file mode 100644 index ecf276504ebc9..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -User account **bobby** has been suspended. - -The newly suspended account belongs to **william tables** and was suspended by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-title.md.golden deleted file mode 100644 index f2be8e201f0af..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -User account "bobby" suspended \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-body.md.golden deleted file mode 100644 index 6288af33f867e..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-body.md.golden +++ /dev/null @@ -1,7 +0,0 @@ -Hi Bobby, - -A request to reset the password for your Coder account has been made. Your one-time passcode is: - -**fad9020b-6562-4cdb-87f1-0486f1bea415** - -If you did not request to reset your password, you can ignore this message. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-title.md.golden deleted file mode 100644 index ecf7383911053..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserRequestedOneTimePasscode-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Your One-Time Passcode for Coder. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden deleted file mode 100644 index d9f4e27cb4c6e..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -Your workspace **bobby-workspace** has been updated automatically to the latest template version (1.0). - -Reason for update: **template now includes catnip**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-title.md.golden deleted file mode 100644 index fb62dcd0d3692..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" updated automatically \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden deleted file mode 100644 index cddf2149d0d46..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -Automatic build of your workspace **bobby-workspace** failed. - -The specified reason was "**autostart**". \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-title.md.golden deleted file mode 100644 index 9cf98bc9e546a..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" autobuild failed \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-body.md.golden deleted file mode 100644 index e896a0a8c9e51..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-body.md.golden +++ /dev/null @@ -1,17 +0,0 @@ -Hi Bobby, - -Template **Bobby First Template** has failed to build 4/55 times over the last week. - -**Report:** - -**bobby-template-version-1** failed 3 times: - -* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234) -* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678) -* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774) - -**bobby-template-version-2** failed 1 time: - -* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888) - -We recommend reviewing these issues to ensure future builds are successful. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-title.md.golden deleted file mode 100644 index f03f8fca96c7c..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceBuildsFailedReport-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace builds failed for template "Bobby First Template" \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden deleted file mode 100644 index a5c71fb3e0170..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -Your workspace **bobby-workspace** was deleted. - -The specified reason was "**autodeleted due to dormancy (autobuild)**". \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-title.md.golden deleted file mode 100644 index 6806624053eb9..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" deleted \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden deleted file mode 100644 index 35bfe8d2c19b6..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -Your workspace **bobby-workspace** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of breached the template's threshold for inactivity. -Dormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivity. -To prevent deletion, use your workspace with the link below. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-title.md.golden deleted file mode 100644 index ce34a2a029ab4..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" marked as dormant \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden deleted file mode 100644 index e1091b2888830..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden +++ /dev/null @@ -1,5 +0,0 @@ -Hi Bobby, - -A manual build of the workspace **bobby-workspace** using the template **bobby-template** failed (version: **bobby-template-version**). - -The template's display name was **William's Template**. The workspace build was initiated by **joe**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-title.md.golden deleted file mode 100644 index e786626b74672..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" manual build failed \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden deleted file mode 100644 index 21defa7c1d500..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden +++ /dev/null @@ -1,4 +0,0 @@ -Hi Bobby, - -Your workspace **bobby-workspace** has been marked for **deletion** after 24 hours of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of template updated to new dormancy policy. -To prevent deletion, use your workspace with the link below. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-title.md.golden deleted file mode 100644 index 1b561a73678de..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Workspace "bobby-workspace" marked for deletion \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden deleted file mode 100644 index a3a8710de4673..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden +++ /dev/null @@ -1,3 +0,0 @@ -Hi Bobby, - -Your account **bobby** has been activated by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-title.md.golden deleted file mode 100644 index 90be1ef2dd63c..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Your account "bobby" has been activated \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden deleted file mode 100644 index 86fc40401774b..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden +++ /dev/null @@ -1,3 +0,0 @@ -Hi Bobby, - -Your account **bobby** has been suspended by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-title.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-title.md.golden deleted file mode 100644 index 3a4cb57c8aac0..0000000000000 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-title.md.golden +++ /dev/null @@ -1 +0,0 @@ -Your account "bobby" has been suspended \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTemplateDeleted.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTemplateDeleted.html.golden new file mode 100644 index 0000000000000..ec7c3cfdce485 --- /dev/null +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTemplateDeleted.html.golden @@ -0,0 +1,83 @@ +From: system@coder.com +To: bobby@coder.com +Subject: Template "bobby-template" deleted +Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48 +Date: Fri, 11 Oct 2024 09:03:06 +0000 +Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 +MIME-Version: 1.0 + +--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8 + +Hi Bobby, + +The template bobby-template was deleted by rob. + +The template's display name was Bobby's Template. + + +View templates: http://test.com/templates + +--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/html; charset=UTF-8 + + + +
+ + +Hi Bobby,
+ +The template bobby-template was deleted by rob<= +/strong>.
+ +The template’s display name was Bobby’s Template.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +User account bobby has been activated.
+ +The newly activated account belongs to William Tables a= +nd was activated by rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +New user account bobby has been created.
+ +This new user account was created for William Tables by= + rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +User account bobby has been deleted.
+ +The deleted account belonged to William Tables and was = +deleted by rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +User account bobby has been suspended.
+ +The newly suspended account belongs to William Tables a= +nd was suspended by rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +A request to reset the password for your Coder account has been made. Yo= +ur one-time passcode is:
+ +fad9020b-6562-4cdb-87f1-0486f1bea415
+ +If you did not request to reset your password, you can ignore this messa= +ge.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your workspace bobby-workspace has been updated automat= +ically to the latest template version (1.0).
+ +Reason for update: template now includes catnip.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Automatic build of your workspace bobby-workspace faile= +d.
+ +The specified reason was “autostart”.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your workspace bobby-workspace was deleted.
+ +The specified reason was “autodeleted due to dormancy (aut= +obuild)”.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your workspace bobby-workspace has been marked as dormant because of breached the template’s t=
+hreshold for inactivity.
+Dormant workspaces are automatically deleted after 24 hour=
+s of inactivity.
+To prevent deletion, use your workspace with the link below.
© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +A manual build of the workspace bobby-workspace using t= +he template bobby-template failed (version: bobby-= +template-version).
+ +The template’s display name was William’s Template= +strong>. The workspace build was initiated by joe.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your workspace bobby-workspace has been marked for
+To prevent deletion, use your workspace with the link below.
© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your account bobby has been activated by rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +Hi Bobby,
+ +Your account bobby has been suspended by rob.
+© 2024 Coder. All rights reserved - h= +ttp://test.com
+ + +