From 1552255e02c54385bd79273a1fd6ab62e67c92c3 Mon Sep 17 00:00:00 2001 From: Alexandre Bontems Date: Mon, 4 May 2026 18:26:04 +0200 Subject: [PATCH] feat: add tests for watch options --- models/activities/notification_test.go | 68 +++++++++++++++++++++ models/fixtures/watch.yml | 43 ++++++++----- models/repo/watch_test.go | 29 ++++++++- services/mailer/mail_issue.go | 2 +- services/mailer/mail_issue_test.go | 83 ++++++++++++++++++++++++++ services/mailer/mail_release_test.go | 52 ++++++++++++++++ services/repository/repository_test.go | 4 +- 7 files changed, 261 insertions(+), 20 deletions(-) create mode 100644 services/mailer/mail_issue_test.go diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go index 6f2253c815..ac29dd34dd 100644 --- a/models/activities/notification_test.go +++ b/models/activities/notification_test.go @@ -7,9 +7,11 @@ import ( "context" "testing" + "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -138,3 +140,69 @@ func TestSetIssueReadBy(t *testing.T) { assert.NoError(t, err) assert.Equal(t, activities_model.NotificationStatusRead, nt.Status) } + +func TestIssueNotificationWithWatchOptions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + watcher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + iss := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + + assert.NoError(t, issues_model.RemoveIssueWatchersByRepoID(t.Context(), watcher.ID, repo.ID)) + assert.NoError(t, repo_model.WatchRepo(t.Context(), watcher, repo, true)) + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: false, + Releases: true, + })) + + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(t.Context(), iss.ID, 0, doer.ID, 0)) + notification, err := activities.GetIssueNotification(t.Context(), watcher.ID, iss.ID) + assert.NoError(t, err) + assert.Equal(t, int64(0), notification.IssueID) // No notification found + + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: true, + Releases: true, + })) + + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(t.Context(), iss.ID, 0, doer.ID, 0)) + notification, err = activities.GetIssueNotification(t.Context(), watcher.ID, iss.ID) + assert.NoError(t, err) + assert.Equal(t, activities.NotificationStatusUnread, notification.Status) +} + +func TestPullRequestNotificationWithWatchOptions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + watcher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 8}) + + assert.NoError(t, repo_model.WatchRepo(t.Context(), watcher, repo2, true)) + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo2, repo_model.WatchOptions{ + PullRequests: false, + Issues: true, + Releases: true, + })) + + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(t.Context(), pr.ID, 0, doer.ID, 0)) + notification, err := activities.GetIssueNotification(t.Context(), watcher.ID, pr.ID) + assert.NoError(t, err) + assert.Equal(t, int64(0), notification.IssueID) // No notification found + + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo2, repo_model.WatchOptions{ + PullRequests: true, + Issues: true, + Releases: true, + })) + + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(t.Context(), pr.ID, 0, doer.ID, 0)) + notification, err = activities.GetIssueNotification(t.Context(), watcher.ID, pr.ID) + assert.NoError(t, err) + assert.Equal(t, activities.NotificationStatusUnread, notification.Status) +} diff --git a/models/fixtures/watch.yml b/models/fixtures/watch.yml index b7ee121f24..e676149ec1 100644 --- a/models/fixtures/watch.yml +++ b/models/fixtures/watch.yml @@ -1,43 +1,56 @@ -- - id: 1 +- id: 1 user_id: 1 repo_id: 1 mode: 1 # normal + pull_requests: true + issues: true + releases: true -- - id: 2 +- id: 2 user_id: 4 repo_id: 1 mode: 1 # normal + pull_requests: true + issues: true + releases: true -- - id: 3 +- id: 3 user_id: 9 repo_id: 1 mode: 1 # normal + pull_requests: true + issues: true + releases: true -- - id: 4 +- id: 4 user_id: 8 repo_id: 1 mode: 2 # don't watch + pull_requests: true + issues: true + releases: true -- - id: 5 +- id: 5 user_id: 11 repo_id: 1 mode: 3 # auto + pull_requests: true + issues: true + releases: true -- - id: 6 +- id: 6 user_id: 10 repo_id: 21 mode: 1 # normal + pull_requests: true + issues: true + releases: true -- - id: 7 +- id: 7 user_id: 10 repo_id: 32 mode: 1 # normal - + pull_requests: true + issues: true + releases: true # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index d3dae90e54..52c029066b 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -125,16 +125,41 @@ func TestClearRepoWatches(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) const repoID int64 = 1 - watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repoID) + watchers, err := repo_model.GetRepoWatchers(t.Context(), repoID, db.ListOptions{Page: 1}) require.NoError(t, err) require.NotEmpty(t, watchers) assert.NoError(t, repo_model.ClearRepoWatches(t.Context(), repoID)) - watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repoID) + watchers, err = repo_model.GetRepoWatchers(t.Context(), repoID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Empty(t, watchers) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) assert.Zero(t, repo.NumWatches) } + +func TestWatchOptions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + assert.NoError(t, repo_model.WatchRepo(t.Context(), user5, repo, true)) + watch, err := repo_model.GetWatch(t.Context(), user5.ID, repo.ID) + assert.NoError(t, err) + assert.True(t, watch.PullRequests) + assert.True(t, watch.Issues) + assert.True(t, watch.Releases) + + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), user5, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: false, + Releases: true, + })) + watch, err = repo_model.GetWatch(t.Context(), user5.ID, repo.ID) + assert.NoError(t, err) + assert.True(t, watch.PullRequests) + assert.False(t, watch.Issues) + assert.True(t, watch.Releases) +} diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index 4ade79f133..fbe617b8d9 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -66,7 +66,7 @@ func mailIssueCommentToParticipants(ctx context.Context, comment *mailComment, m // =========== Repo watchers =========== // Make repo watchers last, since it's likely the list with the most users if !(comment.Issue.IsPull && comment.Issue.PullRequest.IsWorkInProgress(ctx) && comment.ActionType != activities_model.ActionCreatePullRequest) { - var watchType repo_model.WatchType = repo_model.WatchIssues + watchType := repo_model.WatchIssues if comment.Issue.IsPull { watchType = repo_model.WatchPullRequests } diff --git a/services/mailer/mail_issue_test.go b/services/mailer/mail_issue_test.go new file mode 100644 index 0000000000..b789b63ab5 --- /dev/null +++ b/services/mailer/mail_issue_test.go @@ -0,0 +1,83 @@ +package mailer + +import ( + "testing" + + "code.gitea.io/gitea/models/activities" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + sender_service "code.gitea.io/gitea/services/mailer/sender" + + "github.com/stretchr/testify/assert" +) + +func TestMailNewIssueAndPullRequest(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + defer test.MockVariableValue(&setting.MailService)() + defer test.MockVariableValue(&setting.Domain)() + defer test.MockVariableValue(&setting.AppName)() + defer test.MockVariableValue(&setting.AppURL)() + + setting.MailService = &setting.Mailer{ + From: "Gitea", + FromEmail: "noreply@example.com", + } + setting.Domain = "example.com" + setting.AppName = "Gitea" + setting.AppURL = "https://example.com/" + defer mockMailTemplates(string("repo/issue/new"), "{{.Subject}}", "

Issue

")() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + watcher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + var didSend = false + origSend := SendAsync + SendAsync = func(msgs ...*sender_service.Message) { + for _, msg := range msgs { + if msg.To == watcher.Email { + didSend = true + } + } + } + defer func() { + SendAsync = origSend + }() + + iss := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + + assert.NoError(t, issues_model.RemoveIssueWatchersByRepoID(t.Context(), watcher.ID, repo.ID)) + assert.NoError(t, repo_model.WatchRepo(t.Context(), watcher, repo, true)) + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo, repo_model.WatchOptions{ + PullRequests: false, + Issues: false, + Releases: true, + })) + + var mentions []*user_model.User + assert.NoError(t, MailParticipants(t.Context(), iss, doer, activities.ActionCreateIssue, mentions)) + assert.False(t, didSend) + + didSend = false + assert.NoError(t, MailParticipants(t.Context(), pr, doer, activities.ActionCreatePullRequest, mentions)) + assert.False(t, didSend) + + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), watcher, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: true, + Releases: true, + })) + didSend = false + assert.NoError(t, MailParticipants(t.Context(), pr, doer, activities.ActionCreatePullRequest, mentions)) + assert.True(t, didSend) + + didSend = false + assert.NoError(t, MailParticipants(t.Context(), iss, doer, activities.ActionCreateIssue, mentions)) + assert.True(t, didSend) +} diff --git a/services/mailer/mail_release_test.go b/services/mailer/mail_release_test.go index 6fc8587f98..052843426b 100644 --- a/services/mailer/mail_release_test.go +++ b/services/mailer/mail_release_test.go @@ -62,3 +62,55 @@ func TestMailNewReleaseFiltersUnauthorizedWatchers(t *testing.T) { assert.Equal(t, admin.EmailTo(), sent[0].To) assert.NotEqual(t, unauthorized.EmailTo(), sent[0].To) } + +func TestMailNewReleaseWithWatchOptions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + defer test.MockVariableValue(&setting.MailService)() + defer test.MockVariableValue(&setting.Domain)() + defer test.MockVariableValue(&setting.AppName)() + defer test.MockVariableValue(&setting.AppURL)() + + setting.MailService = &setting.Mailer{ + From: "Gitea", + FromEmail: "noreply@example.com", + } + setting.Domain = "example.com" + setting.AppName = "Gitea" + setting.AppURL = "https://example.com/" + defer mockMailTemplates(string(tplNewReleaseMail), "{{.Subject}}", "

{{.Release.TagName}}

")() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + assert.NoError(t, repo_model.WatchRepo(t.Context(), user, repo, true)) + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), user, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: true, + Releases: false, + })) + + var didSend = false + origSend := SendAsync + SendAsync = func(msgs ...*sender_service.Message) { + didSend = true + } + defer func() { + SendAsync = origSend + }() + + rel := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 11}) + rel.Publisher = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: rel.PublisherID}) + MailNewRelease(t.Context(), rel) + assert.False(t, didSend) + + assert.NoError(t, repo_model.WatchRepoOptions(t.Context(), user, repo, repo_model.WatchOptions{ + PullRequests: true, + Issues: true, + Releases: true, + })) + + didSend = false + MailNewRelease(t.Context(), rel) + assert.True(t, didSend) +} diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go index 5a879a6aa6..169fb21856 100644 --- a/services/repository/repository_test.go +++ b/services/repository/repository_test.go @@ -76,13 +76,13 @@ func TestMakeRepoPrivateClearsWatches(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.False(t, repo.IsPrivate) - watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID) + watchers, err := repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) require.NoError(t, err) require.NotEmpty(t, watchers) assert.NoError(t, MakeRepoPrivate(t.Context(), repo, true)) - watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID) + watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Empty(t, watchers)