Skip to content

Commit 121eef5

Browse files
committed
first semi test
1 parent 44b1bba commit 121eef5

File tree

2 files changed

+141
-16
lines changed

2 files changed

+141
-16
lines changed

coderd/notifications/reports/generator.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,13 @@ func (i *reportGenerator) Close() error {
101101
const failedWorkspaceBuildsReportFrequencyDays = 7
102102

103103
func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db database.Store, enqueuer notifications.Enqueuer, clk quartz.Clock) error {
104-
statsRows, err := db.GetWorkspaceBuildStatsByTemplates(ctx, dbtime.Time(clk.Now()).UTC())
104+
statsRows, err := db.GetWorkspaceBuildStatsByTemplates(ctx, dbtime.Time(clk.Now().Add(-failedWorkspaceBuildsReportFrequencyDays*24*time.Hour)).UTC())
105105
if err != nil {
106106
return xerrors.Errorf("unable to fetch failed workspace builds: %w", err)
107107
}
108+
sort.Slice(statsRows, func(i, j int) bool {
109+
return statsRows[i].TemplateName < statsRows[j].TemplateName
110+
})
108111

109112
for _, stats := range statsRows {
110113
var failedBuilds []database.GetFailedWorkspaceBuildsByTemplateIDRow
@@ -161,8 +164,8 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat
161164
})
162165
if err != nil {
163166
logger.Error(ctx, "unable to update report generator logs", slog.F("template_id", stats.TemplateID), slog.F("user_id", templateAdmin.ID), slog.F("failed_builds", len(failedBuilds)), slog.Error(err))
164-
continue
165167
}
168+
continue
166169
}
167170

168171
templateDisplayName := stats.TemplateDisplayName
@@ -234,10 +237,12 @@ func buildDataForReportFailedWorkspaceBuilds(frequencyDays int, stats database.G
234237
templateVersions = append(templateVersions, map[string]any{
235238
"template_version_name": failedBuild.TemplateVersionName,
236239
"failed_count": 1,
237-
"failed_builds": map[string]any{
238-
"workspace_owner_username": failedBuild.WorkspaceOwnerUsername,
239-
"workspace_name": failedBuild.WorkspaceName,
240-
"build_number": failedBuild.WorkspaceBuildNumber,
240+
"failed_builds": []map[string]any{
241+
{
242+
"workspace_owner_username": failedBuild.WorkspaceOwnerUsername,
243+
"workspace_name": failedBuild.WorkspaceName,
244+
"build_number": failedBuild.WorkspaceBuildNumber,
245+
},
241246
},
242247
})
243248
continue

coderd/notifications/reports/generator_internal_test.go

Lines changed: 130 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,42 @@ package reports
22

33
import (
44
"context"
5+
"database/sql"
56
"testing"
7+
"time"
68

7-
"github.com/prometheus/client_golang/prometheus"
9+
"github.com/google/uuid"
810
"github.com/stretchr/testify/require"
911

1012
"cdr.dev/slog"
1113
"cdr.dev/slog/sloggers/slogtest"
1214
"github.com/coder/quartz"
1315

14-
"github.com/coder/coder/v2/coderd/coderdtest"
1516
"github.com/coder/coder/v2/coderd/database"
1617
"github.com/coder/coder/v2/coderd/database/dbauthz"
18+
"github.com/coder/coder/v2/coderd/database/dbgen"
1719
"github.com/coder/coder/v2/coderd/database/dbtestutil"
20+
"github.com/coder/coder/v2/coderd/database/pubsub"
1821
"github.com/coder/coder/v2/coderd/notifications"
1922
"github.com/coder/coder/v2/coderd/rbac"
2023
"github.com/coder/coder/v2/testutil"
2124
)
2225

26+
const dayDuration = 24 * time.Hour
27+
28+
var (
29+
jobError = sql.NullString{String: "badness", Valid: true}
30+
jobErrorCode = sql.NullString{String: "ERR-42", Valid: true}
31+
)
32+
2333
func TestReportFailedWorkspaceBuilds(t *testing.T) {
2434
t.Parallel()
2535

2636
t.Run("InitialState_NoBuilds_NoReport", func(t *testing.T) {
2737
t.Parallel()
2838

2939
// Setup
30-
logger, db, notifEnq, clk := setup(t)
31-
// nolint:gocritic // reportFailedWorkspaceBuilds is called by system.
32-
ctx := dbauthz.AsSystemRestricted(context.Background())
40+
ctx, logger, db, _, notifEnq, clk := setup(t)
3341

3442
// When
3543
err := reportFailedWorkspaceBuilds(ctx, logger, db, notifEnq, clk)
@@ -40,7 +48,116 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) {
4048

4149
t.Run("FailedBuilds_TemplateAdminOptIn_FirstRun_Report_SecondRunTooEarly_NoReport_ThirdRun_Report", func(t *testing.T) {
4250
t.Parallel()
43-
// TODO
51+
52+
// Setup
53+
ctx, logger, db, ps, notifEnq, clk := setup(t)
54+
55+
// Given
56+
// Organization
57+
org := dbgen.Organization(t, db, database.Organization{})
58+
59+
// Template admins
60+
templateAdmin1 := dbgen.User(t, db, database.User{Username: "template-admin-1", RBACRoles: []string{rbac.RoleTemplateAdmin().Name}})
61+
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: templateAdmin1.ID, OrganizationID: org.ID})
62+
templateAdmin2 := dbgen.User(t, db, database.User{Username: "template-admin-2", RBACRoles: []string{rbac.RoleTemplateAdmin().Name}})
63+
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: templateAdmin2.ID, OrganizationID: org.ID})
64+
_ = dbgen.User(t, db, database.User{Name: "template-admin-3", RBACRoles: []string{rbac.RoleTemplateAdmin().Name}})
65+
// template admin in some other org
66+
67+
// Regular users
68+
user1 := dbgen.User(t, db, database.User{})
69+
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: user1.ID, OrganizationID: org.ID})
70+
user2 := dbgen.User(t, db, database.User{})
71+
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: user2.ID, OrganizationID: org.ID})
72+
user3 := dbgen.User(t, db, database.User{})
73+
// user in some other org
74+
75+
// Templates
76+
t1 := dbgen.Template(t, db, database.Template{Name: "template-1", DisplayName: "First Template", OrganizationID: org.ID})
77+
t2 := dbgen.Template(t, db, database.Template{Name: "template-2", OrganizationID: org.ID})
78+
79+
// Template versions
80+
t1v1 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-1", OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true}, JobID: uuid.New()})
81+
t1v2 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-2", OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true}, JobID: uuid.New()})
82+
t2v1 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-2-version-1", OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true}, JobID: uuid.New()})
83+
t2v2 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-1", OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true}, JobID: uuid.New()})
84+
85+
// Workspaces
86+
w1 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID})
87+
w2 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID})
88+
w3 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user3.ID, OrganizationID: org.ID})
89+
w4 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID})
90+
91+
now := clk.Now()
92+
93+
// Workspace builds
94+
w1wb1pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-6 * dayDuration), Valid: true}})
95+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w1.ID, TemplateVersionID: t1v1.ID, JobID: w1wb1pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
96+
w1wb2pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, CompletedAt: sql.NullTime{Time: now.Add(-5 * dayDuration), Valid: true}})
97+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w1.ID, TemplateVersionID: t1v2.ID, JobID: w1wb2pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
98+
w1wb3pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-4 * dayDuration), Valid: true}})
99+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w1.ID, TemplateVersionID: t1v2.ID, JobID: w1wb3pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
100+
101+
w2wb1pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, CompletedAt: sql.NullTime{Time: now.Add(-5 * dayDuration), Valid: true}})
102+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w2.ID, TemplateVersionID: t2v1.ID, JobID: w2wb1pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
103+
w2wb2pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-4 * dayDuration), Valid: true}})
104+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w2.ID, TemplateVersionID: t2v2.ID, JobID: w2wb2pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
105+
w2wb3pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-3 * dayDuration), Valid: true}})
106+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w2.ID, TemplateVersionID: t2v2.ID, JobID: w2wb3pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
107+
108+
w3wb1pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-3 * dayDuration), Valid: true}})
109+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w3.ID, TemplateVersionID: t1v1.ID, JobID: w3wb1pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
110+
111+
w4wb1pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-6 * dayDuration), Valid: true}})
112+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w4.ID, TemplateVersionID: t2v1.ID, JobID: w4wb1pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
113+
w4wb2pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, CompletedAt: sql.NullTime{Time: now.Add(-1 * dayDuration), Valid: true}})
114+
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w4.ID, TemplateVersionID: t2v2.ID, JobID: w4wb2pj.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator})
115+
116+
// Database is ready, so we can clear notifications queue
117+
notifEnq.Clear()
118+
119+
// When
120+
err := reportFailedWorkspaceBuilds(ctx, logger, db, notifEnq, clk)
121+
122+
// Then
123+
require.NoError(t, err)
124+
125+
require.Len(t, notifEnq.Sent, 4) // 2 templates, 2 template admins
126+
require.Equal(t, notifEnq.Sent[0].UserID, templateAdmin1.ID)
127+
require.Equal(t, notifEnq.Sent[0].TemplateID, notifications.TemplateWorkspaceBuildsFailedReport)
128+
require.Equal(t, notifEnq.Sent[0].Labels["template_name"], t1.Name)
129+
require.Equal(t, notifEnq.Sent[0].Labels["template_display_name"], t1.DisplayName)
130+
require.Equal(t, notifEnq.Sent[0].Data["failed_builds"], int64(3))
131+
require.Equal(t, notifEnq.Sent[0].Data["total_builds"], int64(4))
132+
require.Equal(t, notifEnq.Sent[0].Data["report_frequency"], "week")
133+
// require.Contains(t, notifEnq.Sent[0].Data["template_versions"], "?")
134+
135+
require.Equal(t, notifEnq.Sent[1].UserID, templateAdmin2.ID)
136+
require.Equal(t, notifEnq.Sent[1].TemplateID, notifications.TemplateWorkspaceBuildsFailedReport)
137+
require.Equal(t, notifEnq.Sent[1].Labels["template_name"], t1.Name)
138+
require.Equal(t, notifEnq.Sent[1].Labels["template_display_name"], t1.DisplayName)
139+
require.Equal(t, notifEnq.Sent[1].Data["failed_builds"], int64(3))
140+
require.Equal(t, notifEnq.Sent[1].Data["total_builds"], int64(4))
141+
require.Equal(t, notifEnq.Sent[1].Data["report_frequency"], "week")
142+
// require.Contains(t, notifEnq.Sent[1].Data["template_versions"], "?")
143+
144+
require.Equal(t, notifEnq.Sent[2].UserID, templateAdmin1.ID)
145+
require.Equal(t, notifEnq.Sent[2].TemplateID, notifications.TemplateWorkspaceBuildsFailedReport)
146+
require.Equal(t, notifEnq.Sent[2].Labels["template_name"], t2.Name)
147+
require.Equal(t, notifEnq.Sent[2].Labels["template_display_name"], t2.DisplayName)
148+
require.Equal(t, notifEnq.Sent[2].Data["failed_builds"], int64(3))
149+
require.Equal(t, notifEnq.Sent[2].Data["total_builds"], int64(5))
150+
require.Equal(t, notifEnq.Sent[2].Data["report_frequency"], "week")
151+
// require.Contains(t, notifEnq.Sent[0].Data["template_versions"], "?")
152+
153+
require.Equal(t, notifEnq.Sent[3].UserID, templateAdmin2.ID)
154+
require.Equal(t, notifEnq.Sent[3].TemplateID, notifications.TemplateWorkspaceBuildsFailedReport)
155+
require.Equal(t, notifEnq.Sent[3].Labels["template_name"], t2.Name)
156+
require.Equal(t, notifEnq.Sent[3].Labels["template_display_name"], t2.DisplayName)
157+
require.Equal(t, notifEnq.Sent[3].Data["failed_builds"], int64(3))
158+
require.Equal(t, notifEnq.Sent[3].Data["total_builds"], int64(5))
159+
require.Equal(t, notifEnq.Sent[3].Data["report_frequency"], "week")
160+
// require.Contains(t, notifEnq.Sent[0].Data["template_versions"], "?")
44161
})
45162

46163
t.Run("NoFailedBuilds_TemplateAdminIn_NoReport", func(t *testing.T) {
@@ -59,13 +176,16 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) {
59176
})
60177
}
61178

62-
func setup(t *testing.T) (slog.Logger, database.Store, notifications.Enqueuer, quartz.Clock) {
179+
func setup(t *testing.T) (context.Context, slog.Logger, database.Store, pubsub.Pubsub, *testutil.FakeNotificationsEnqueuer, quartz.Clock) {
63180
t.Helper()
64181

182+
// nolint:gocritic // reportFailedWorkspaceBuilds is called by system.
183+
ctx := dbauthz.AsSystemRestricted(context.Background())
65184
logger := slogtest.Make(t, &slogtest.Options{})
66-
rdb, _ := dbtestutil.NewDB(t)
67-
db := dbauthz.New(rdb, rbac.NewAuthorizer(prometheus.NewRegistry()), logger, coderdtest.AccessControlStorePointer())
185+
db, ps := dbtestutil.NewDB(t)
186+
// does not work with works
187+
// db := dbauthz.New(rdb, rbac.NewAuthorizer(prometheus.NewRegistry()), logger, coderdtest.AccessControlStorePointer())
68188
notifyEnq := &testutil.FakeNotificationsEnqueuer{}
69189
clk := quartz.NewMock(t)
70-
return logger, db, notifyEnq, clk
190+
return ctx, logger, db, ps, notifyEnq, clk
71191
}

0 commit comments

Comments
 (0)