Changeset 0.28.2 (#183)
This commit is contained in:
288
server/notifications/sources_test.go
Normal file
288
server/notifications/sources_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
)
|
||||
|
||||
// mockNotifStore implements store.NotificationStore for testing.
|
||||
type mockNotifStore struct {
|
||||
items []models.Notification
|
||||
}
|
||||
|
||||
func (m *mockNotifStore) Create(_ context.Context, n *models.Notification) error {
|
||||
n.ID = "mock-id"
|
||||
n.CreatedAt = time.Now()
|
||||
m.items = append(m.items, *n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockNotifStore) ListByUser(_ context.Context, _ string, _, _ int, _ bool) ([]models.Notification, int, error) {
|
||||
return m.items, len(m.items), nil
|
||||
}
|
||||
func (m *mockNotifStore) MarkRead(_ context.Context, _, _ string) error { return nil }
|
||||
func (m *mockNotifStore) MarkAllRead(_ context.Context, _ string) error { return nil }
|
||||
func (m *mockNotifStore) Delete(_ context.Context, _, _ string) error { return nil }
|
||||
func (m *mockNotifStore) UnreadCount(_ context.Context, _ string) (int, error) {
|
||||
return len(m.items), nil
|
||||
}
|
||||
func (m *mockNotifStore) DeleteOlderThan(_ context.Context, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// newTestService creates a Service with a mock store for testing sources.
|
||||
func newTestService() (*Service, *mockNotifStore) {
|
||||
ms := &mockNotifStore{}
|
||||
svc := NewService(ms, nil) // no hub
|
||||
return svc, ms
|
||||
}
|
||||
|
||||
// ── NotifyKBReady ─────────────────────────
|
||||
|
||||
func TestNotifyKBReady(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyKBReady(svc, "user1", "kb1", "My KB", 42)
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.UserID != "user1" {
|
||||
t.Errorf("user_id: got %q, want %q", n.UserID, "user1")
|
||||
}
|
||||
if n.Type != models.NotifTypeKBReady {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeKBReady)
|
||||
}
|
||||
if n.ResourceType != models.ResourceTypeKnowledgeBase {
|
||||
t.Errorf("resource_type: got %q, want %q", n.ResourceType, models.ResourceTypeKnowledgeBase)
|
||||
}
|
||||
if n.ResourceID != "kb1" {
|
||||
t.Errorf("resource_id: got %q, want %q", n.ResourceID, "kb1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyKBReady_NilService(t *testing.T) {
|
||||
// Should not panic
|
||||
NotifyKBReady(nil, "user1", "kb1", "My KB", 10)
|
||||
}
|
||||
|
||||
// ── NotifyKBError ─────────────────────────
|
||||
|
||||
func TestNotifyKBError(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyKBError(svc, "user1", "kb2", "Bad KB", "chunk failed")
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.Type != models.NotifTypeKBError {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeKBError)
|
||||
}
|
||||
if n.Body != "chunk failed" {
|
||||
t.Errorf("body: got %q, want %q", n.Body, "chunk failed")
|
||||
}
|
||||
}
|
||||
|
||||
// ── NotifyGroupMemberAdded ────────────────
|
||||
|
||||
func TestNotifyGroupMemberAdded(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyGroupMemberAdded(svc, "user1", "grp1", "Engineering")
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.Type != models.NotifTypeGrantChanged {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeGrantChanged)
|
||||
}
|
||||
if n.ResourceType != models.ResourceTypeGroup {
|
||||
t.Errorf("resource_type: got %q, want %q", n.ResourceType, models.ResourceTypeGroup)
|
||||
}
|
||||
if n.ResourceID != "grp1" {
|
||||
t.Errorf("resource_id: got %q, want %q", n.ResourceID, "grp1")
|
||||
}
|
||||
}
|
||||
|
||||
// ── NotifyGroupMemberRemoved ──────────────
|
||||
|
||||
func TestNotifyGroupMemberRemoved(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyGroupMemberRemoved(svc, "user1", "grp1", "Engineering")
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.Type != models.NotifTypeGrantChanged {
|
||||
t.Errorf("type: got %q", n.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ── NotifyMemoryExtracted ─────────────────
|
||||
|
||||
func TestNotifyMemoryExtracted(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyMemoryExtracted(svc, "user1", "chan1", 5)
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.Type != models.NotifTypeMemoryExtracted {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeMemoryExtracted)
|
||||
}
|
||||
if n.Title != "5 new memories extracted" {
|
||||
t.Errorf("title: got %q", n.Title)
|
||||
}
|
||||
if n.ResourceType != models.ResourceTypeChannel {
|
||||
t.Errorf("resource_type: got %q", n.ResourceType)
|
||||
}
|
||||
if n.ResourceID != "chan1" {
|
||||
t.Errorf("resource_id: got %q", n.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyMemoryExtracted_Singular(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyMemoryExtracted(svc, "user1", "chan1", 1)
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
if ms.items[0].Title != "1 new memory extracted" {
|
||||
t.Errorf("singular title: got %q", ms.items[0].Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyMemoryExtracted_NilService(t *testing.T) {
|
||||
NotifyMemoryExtracted(nil, "user1", "chan1", 3) // should not panic
|
||||
}
|
||||
|
||||
// ── NotifyUserMentioned ───────────────────
|
||||
|
||||
func TestNotifyUserMentioned(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyUserMentioned(svc, "mentioned-user", "chan1", "from-user", "hey @you check this")
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.UserID != "mentioned-user" {
|
||||
t.Errorf("user_id: got %q", n.UserID)
|
||||
}
|
||||
if n.Type != models.NotifTypeUserMentioned {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeUserMentioned)
|
||||
}
|
||||
if n.Body != "hey @you check this" {
|
||||
t.Errorf("body: got %q", n.Body)
|
||||
}
|
||||
if n.ResourceType != models.ResourceTypeChannel {
|
||||
t.Errorf("resource_type: got %q", n.ResourceType)
|
||||
}
|
||||
if n.ResourceID != "chan1" {
|
||||
t.Errorf("resource_id: got %q", n.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyUserMentioned_NilService(t *testing.T) {
|
||||
NotifyUserMentioned(nil, "u1", "c1", "u2", "hello") // should not panic
|
||||
}
|
||||
|
||||
// ── NotifyWorkflowClaimed ─────────────────
|
||||
|
||||
func TestNotifyWorkflowClaimed(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
NotifyWorkflowClaimed(svc, "claimer1", "assign1", "chan1")
|
||||
|
||||
if len(ms.items) != 1 {
|
||||
t.Fatalf("expected 1 notification, got %d", len(ms.items))
|
||||
}
|
||||
n := ms.items[0]
|
||||
if n.UserID != "claimer1" {
|
||||
t.Errorf("user_id: got %q", n.UserID)
|
||||
}
|
||||
if n.Type != models.NotifTypeWorkflowClaimed {
|
||||
t.Errorf("type: got %q, want %q", n.Type, models.NotifTypeWorkflowClaimed)
|
||||
}
|
||||
if n.ResourceType != models.ResourceTypeChannel {
|
||||
t.Errorf("resource_type: got %q", n.ResourceType)
|
||||
}
|
||||
if n.ResourceID != "chan1" {
|
||||
t.Errorf("resource_id: got %q", n.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyWorkflowClaimed_NilService(t *testing.T) {
|
||||
NotifyWorkflowClaimed(nil, "u1", "a1", "c1") // should not panic
|
||||
}
|
||||
|
||||
// ── NotifyMany ────────────────────────────
|
||||
|
||||
func TestNotifyMany(t *testing.T) {
|
||||
svc, ms := newTestService()
|
||||
|
||||
template := models.Notification{
|
||||
Type: "test.broadcast",
|
||||
Title: "Hello everyone",
|
||||
}
|
||||
|
||||
svc.NotifyMany(context.Background(), []string{"u1", "u2", "u3"}, template)
|
||||
|
||||
if len(ms.items) != 3 {
|
||||
t.Fatalf("expected 3 notifications, got %d", len(ms.items))
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
for _, n := range ms.items {
|
||||
seen[n.UserID] = true
|
||||
if n.Type != "test.broadcast" {
|
||||
t.Errorf("type: got %q", n.Type)
|
||||
}
|
||||
}
|
||||
for _, uid := range []string{"u1", "u2", "u3"} {
|
||||
if !seen[uid] {
|
||||
t.Errorf("missing notification for %s", uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Email subject lines for new types ─────
|
||||
|
||||
func TestSubjectForNotification_NewTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
notifType string
|
||||
title string
|
||||
wantPfx string
|
||||
}{
|
||||
{"memory.extracted", "3 new memories", "[Test] "},
|
||||
{"user.mentioned", "You were mentioned", "[Test] "},
|
||||
{"workflow.claimed", "Assignment claimed", "[Test] "},
|
||||
{"task.completed", "Task done", "[Test] "},
|
||||
{"task.budget_exceeded", "Budget hit", "[Test] "},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
n := &models.Notification{Type: tt.notifType, Title: tt.title}
|
||||
got := SubjectForNotification(n, "Test")
|
||||
if got == "" {
|
||||
t.Errorf("SubjectForNotification(%q) returned empty", tt.notifType)
|
||||
}
|
||||
// All should at least have the instance prefix
|
||||
if len(got) < len(tt.wantPfx) {
|
||||
t.Errorf("SubjectForNotification(%q) too short: %q", tt.notifType, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user