mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
notification commands required a repository unless --mine was given.
They now act on the account's inbox (D17); -R, TEA_REPO or the new
--this-repo limit them to one repository, and --mine is a hidden no-op.
notification list is a table with ID, TYPE, REPOSITORY, SUBJECT ("#17
Improve greeting", bold when unread), STATE and UPDATED, a "Showing N of
M" line and an empty message on a terminal; piped rows add the status
and the URL. The ID links to the subject. JSON gains the subject number.
notification view shows the subject, its state and the latest comment,
then marks an unread notification as read. mark-read, mark-unread, pin
and unpin report each change ("Pinned notification 12
(alice/project#17)"), and --all confirms on a terminal and reports the
count. notification watch --json printed one array when it exited; it
now prints each change as one JSON object per line, prints a line per
change on a terminal, and runs until Ctrl-C there instead of stopping
after a minute.
Status and type filters complete and validate their values, notification
IDs complete from the inbox, and every command has examples.
Signed-off-by: bircni <bircni@icloud.com>
141 lines
7.2 KiB
Go
141 lines
7.2 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNotificationBoundsShareClockAndReachServer(t *testing.T) {
|
|
now := time.Date(2028, 2, 1, 12, 0, 0, 0, time.UTC)
|
|
calls := 0
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
require.Equal(t, "/api/v1/notifications", r.URL.Path)
|
|
require.Equal(t, "2028-01-31T12:00:00Z", r.URL.Query().Get("since"))
|
|
require.Equal(t, "2028-02-01T11:00:00Z", r.URL.Query().Get("before"))
|
|
require.Equal(t, []string{"unread", "pinned"}, r.URL.Query()["status-types"])
|
|
_, _ = w.Write([]byte(`[]`))
|
|
}))
|
|
defer server.Close()
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
reads := 0
|
|
deps.Now = func() time.Time { reads++; return now }
|
|
err := Execute(context.Background(), deps, []string{"notification", "list", "--mine", "--since=24h", "--before=1h", "--json"})
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, reads)
|
|
require.Equal(t, 1, calls)
|
|
require.JSONEq(t, `[]`, out.String())
|
|
}
|
|
|
|
// notificationThreads is a page of two notification threads as Gitea sends them.
|
|
const notificationThreads = `[
|
|
{"id":12,"unread":true,"repository":{"full_name":"alice/project"},"updated_at":"2026-10-11T18:00:00Z",
|
|
"subject":{"title":"Improve greeting","type":"Pull","state":"open","html_url":"https://gitea.test/alice/project/pulls/17","url":"https://gitea.test/api/v1/repos/alice/project/issues/17"}},
|
|
{"id":3,"unread":false,"pinned":true,"repository":{"full_name":"alice/project"},"updated_at":"2026-10-11T17:00:00Z",
|
|
"subject":{"title":"Improve error messages","type":"Issue","state":"closed","html_url":"https://gitea.test/alice/project/issues/8","url":"https://gitea.test/api/v1/repos/alice/project/issues/8"}}
|
|
]`
|
|
|
|
func TestNotificationsListTheInboxByDefault(t *testing.T) {
|
|
var path atomic.Value
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path.Store(r.URL.Path)
|
|
w.Header().Set("X-Total-Count", "2")
|
|
_, _ = w.Write([]byte(notificationThreads))
|
|
}))
|
|
defer server.Close()
|
|
for _, tc := range []struct {
|
|
args []string
|
|
git map[string]string
|
|
path string
|
|
}{
|
|
{nil, nil, "/api/v1/notifications"},
|
|
{nil, map[string]string{"remote": "origin", "remote get-url origin": server.URL + "/alice/project.git"}, "/api/v1/notifications"},
|
|
{[]string{"-R", "alice/project"}, nil, "/api/v1/repos/alice/project/notifications"},
|
|
{[]string{"--this-repo"}, map[string]string{"remote": "origin", "remote get-url origin": server.URL + "/bob/fork.git"}, "/api/v1/repos/bob/fork/notifications"},
|
|
} {
|
|
deps, _, _ := serverDeps(t, server.URL)
|
|
deps.Git = &fakeGit{results: tc.git}
|
|
require.NoError(t, Execute(context.Background(), deps, append([]string{"notification", "list"}, tc.args...)), tc.args)
|
|
require.Equal(t, tc.path, path.Load(), tc.args)
|
|
}
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
deps.Streams.OutTTY, deps.Streams.Width = true, 100
|
|
deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) }
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"notification", "list"}))
|
|
require.Equal(t, `Showing 2 of 2 unread or pinned notifications in your inbox
|
|
|
|
ID TYPE REPOSITORY SUBJECT STATE UPDATED
|
|
12 pull alice/project #17 Improve greeting open about 2 hours ago
|
|
3 issue alice/project #8 Improve error messages closed about 3 hours ago
|
|
`, stripLinks(withoutANSI(out.String())))
|
|
deps, out, _ = serverDeps(t, server.URL)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"notification", "list"}))
|
|
require.Equal(t, "12\tpull\talice/project\t#17 Improve greeting\topen\tunread\t2026-10-11T18:00:00Z\thttps://gitea.test/alice/project/pulls/17\n"+
|
|
"3\tissue\talice/project\t#8 Improve error messages\tclosed\tpinned\t2026-10-11T17:00:00Z\thttps://gitea.test/alice/project/issues/8\n", out.String())
|
|
}
|
|
|
|
func TestNotificationWatchStreamsOneJSONObjectPerLine(t *testing.T) {
|
|
var polls atomic.Int32
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
if polls.Add(1) == 1 {
|
|
_, _ = w.Write([]byte(`[{"id":1,"subject":{"title":"First"}}]`))
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`[{"id":1,"subject":{"title":"First"}},{"id":2,"subject":{"title":"Second"}}]`))
|
|
}))
|
|
defer server.Close()
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"notification", "watch", "--json", "id,title", "--interval", "20ms", "--timeout", "500ms"}))
|
|
require.Equal(t, "{\"id\":1,\"title\":\"First\"}\n{\"id\":2,\"title\":\"Second\"}\n", out.String())
|
|
}
|
|
|
|
func TestNotificationMutationsReportWhatChanged(t *testing.T) {
|
|
var marks []string
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/notifications/threads/12":
|
|
marks = append(marks, r.URL.Query().Get("to-status"))
|
|
_, _ = w.Write([]byte(`{"id":12,"repository":{"full_name":"alice/project"},"subject":{"type":"Pull","url":"https://gitea.test/api/v1/repos/alice/project/issues/17"}}`))
|
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/notifications/threads/12":
|
|
_, _ = w.Write([]byte(`{"id":12,"unread":true,"repository":{"full_name":"alice/project"},"subject":{"title":"Improve greeting","type":"Pull","state":"open","html_url":"https://gitea.test/alice/project/pulls/17","url":"https://gitea.test/api/v1/repos/alice/project/issues/17","latest_comment_url":"https://gitea.test/api/v1/repos/alice/project/issues/comments/9"}}`))
|
|
case r.URL.Path == "/api/v1/repos/alice/project/issues/comments/9":
|
|
_, _ = w.Write([]byte(`{"id":9,"body":"Looks good to me","user":{"login":"bob"}}`))
|
|
case r.Method == http.MethodPut && r.URL.Path == "/api/v1/notifications":
|
|
marks = append(marks, "all "+r.URL.Query().Get("to-status"))
|
|
_, _ = w.Write([]byte(`[{"id":12},{"id":3}]`))
|
|
default:
|
|
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, out, diagnostics := serverDeps(t, server.URL)
|
|
deps.Streams.OutTTY = true
|
|
for _, tc := range []struct {
|
|
args []string
|
|
want string
|
|
}{
|
|
{[]string{"pin", "12"}, "✓ Pinned notification 12 (alice/project#17)\n"},
|
|
{[]string{"unpin", "#12"}, "✓ Unpinned notification 12 (alice/project#17)\n"},
|
|
{[]string{"mark-read", "12"}, "✓ Marked notification 12 (alice/project#17) as read\n"},
|
|
{[]string{"mark-read", "--all", "--yes"}, "✓ Marked 2 notifications as read\n"},
|
|
} {
|
|
diagnostics.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, append([]string{"notification"}, tc.args...)), tc.args)
|
|
require.Equal(t, tc.want, withoutANSI(diagnostics.String()), tc.args)
|
|
}
|
|
require.Equal(t, []string{"pinned", "read", "read", "all read"}, marks)
|
|
marks = nil
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"notification", "view", "12"}))
|
|
require.Contains(t, withoutANSI(out.String()), "Latest comment: bob\n\n Looks good to me\n\nView this pull request on Gitea: https://gitea.test/alice/project/pulls/17\n")
|
|
require.Equal(t, []string{"read"}, marks, "viewing marks an unread notification as read")
|
|
}
|