forgejo-mcp/operation/user/notification_test.go
Byte Flavour 78916f6ef1
feat(notifications): implement complete notification API
- added get_notification_thread
- added mark_notification_read
- added mark_all_notifications_read
- added list_repo_notifications
- added mark_repo_notifications_read
- added mock tests for all notification endpoints
- updated README.md and added demo documentation
2026-03-08 10:25:22 +01:00

210 lines
5.2 KiB
Go

package user
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo"
forgejo_sdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
"github.com/mark3labs/mcp-go/mcp"
)
func newCallToolRequest(args map[string]interface{}) mcp.CallToolRequest {
return mcp.CallToolRequest{
Params: mcp.CallToolParams{
Arguments: args,
},
}
}
// mockNotificationServer sets up a test HTTP server that responds with
// a predetermined set of notifications for testing all notification endpoints.
func mockNotificationServer(t *testing.T) *httptest.Server {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.URL.Path == "/api/v1/notifications":
if r.Method == "GET" {
w.WriteHeader(http.StatusOK)
notifications := []map[string]interface{}{
{
"id": 1,
"unread": true,
"updated_at": time.Now().Format(time.RFC3339),
"subject": map[string]interface{}{
"title": "New Issue",
"type": "Issue",
},
"repository": map[string]interface{}{
"full_name": "synapse/neural-net",
},
},
}
_ = json.NewEncoder(w).Encode(notifications)
} else if r.Method == "PUT" {
w.WriteHeader(http.StatusResetContent)
w.Write([]byte("[]"))
}
case strings.HasPrefix(r.URL.Path, "/api/v1/notifications/threads/"):
if r.Method == "GET" {
w.WriteHeader(http.StatusOK)
thread := map[string]interface{}{
"id": 1,
"unread": true,
"updated_at": time.Now().Format(time.RFC3339),
}
_ = json.NewEncoder(w).Encode(thread)
} else if r.Method == "PATCH" {
w.WriteHeader(http.StatusResetContent)
w.Write([]byte("{}"))
}
case strings.HasPrefix(r.URL.Path, "/api/v1/repos/") && strings.HasSuffix(r.URL.Path, "/notifications"):
if r.Method == "GET" {
w.WriteHeader(http.StatusOK)
notifications := []map[string]interface{}{
{
"id": 3,
"unread": true,
},
}
_ = json.NewEncoder(w).Encode(notifications)
} else if r.Method == "PUT" {
w.WriteHeader(http.StatusResetContent)
w.Write([]byte("[]"))
}
default:
t.Errorf("unexpected path %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
}))
client, err := forgejo_sdk.NewClient(srv.URL, forgejo_sdk.SetForgejoVersion("7.0.0"))
if err != nil {
t.Fatalf("creating test client: %v", err)
}
forgejo.SetClientForTesting(client)
return srv
}
func TestCheckNotificationsFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"all": true,
"limit": 10.0,
"page": 1.0,
})
result, err := CheckNotificationsFn(context.Background(), req)
if err != nil {
t.Fatalf("CheckNotificationsFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("CheckNotificationsFn returned tool error")
}
if len(result.Content) == 0 {
t.Fatalf("expected some content in result, got none")
}
}
func TestGetNotificationThreadFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"id": 1.0,
})
result, err := GetNotificationThreadFn(context.Background(), req)
if err != nil {
t.Fatalf("GetNotificationThreadFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("GetNotificationThreadFn returned tool error")
}
}
func TestMarkNotificationReadFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"id": 1.0,
})
result, err := MarkNotificationReadFn(context.Background(), req)
if err != nil {
t.Fatalf("MarkNotificationReadFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("MarkNotificationReadFn returned tool error")
}
}
func TestMarkAllNotificationsReadFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"last_read_at": time.Now().Format(time.RFC3339),
})
result, err := MarkAllNotificationsReadFn(context.Background(), req)
if err != nil {
t.Fatalf("MarkAllNotificationsReadFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("MarkAllNotificationsReadFn returned tool error")
}
}
func TestListRepoNotificationsFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"owner": "synapse",
"repo": "neural-net",
"limit": 10.0,
"page": 1.0,
})
result, err := ListRepoNotificationsFn(context.Background(), req)
if err != nil {
t.Fatalf("ListRepoNotificationsFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("ListRepoNotificationsFn returned tool error")
}
}
func TestMarkRepoNotificationsReadFn(t *testing.T) {
srv := mockNotificationServer(t)
defer srv.Close()
req := newCallToolRequest(map[string]interface{}{
"owner": "synapse",
"repo": "neural-net",
})
result, err := MarkRepoNotificationsReadFn(context.Background(), req)
if err != nil {
t.Fatalf("MarkRepoNotificationsReadFn returned error: %v", err)
}
if result.IsError {
t.Fatalf("MarkRepoNotificationsReadFn returned tool error")
}
}