strapi-plugin-notifier 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +130 -20
- package/dist/_chunks/{Index-DOQrGurB.mjs → Index-1ChU1M3b.mjs} +22 -4
- package/dist/_chunks/{Index-C5mgbISF.js → Index-cFnGw4rg.js} +22 -4
- package/dist/_chunks/{SettingsPage-Cft7agRa.js → SettingsPage-DKTIHVrI.js} +103 -2
- package/dist/_chunks/{SettingsPage-CRsuB4cw.mjs → SettingsPage-DVaHqffU.mjs} +105 -4
- package/dist/_chunks/{index-DrwLcZBZ.js → index-BMiYJk-5.js} +2 -2
- package/dist/_chunks/{index-7hrPEwa_.mjs → index-BauNAWBC.mjs} +2 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +433 -106
- package/dist/server/index.mjs +433 -106
- package/package.json +5 -2
- package/tests/helpers.ts +145 -0
- package/tests/unit/config.test.ts +64 -0
- package/tests/unit/notification.test.ts +263 -0
- package/tests/unit/notifier.test.ts +348 -0
- package/tests/unit/preference.test.ts +122 -0
- package/tests/unit/rules.test.ts +470 -0
- package/vitest.config.ts +9 -0
package/tests/helpers.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import type { NotificationType } from '../server/src/config';
|
|
3
|
+
import type { UserPreference } from '../server/src/services/preference';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// DB query mock
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
export const makeDbQuery = (overrides: Record<string, unknown> = {}) => ({
|
|
10
|
+
findOne: vi.fn().mockResolvedValue(null),
|
|
11
|
+
findMany: vi.fn().mockResolvedValue([]),
|
|
12
|
+
create: vi.fn().mockImplementation(({ data }: { data: Record<string, unknown> }) =>
|
|
13
|
+
Promise.resolve({ id: 1, ...data })
|
|
14
|
+
),
|
|
15
|
+
update: vi.fn().mockImplementation(({ data }: { data: Record<string, unknown> }) =>
|
|
16
|
+
Promise.resolve({ id: 1, ...data })
|
|
17
|
+
),
|
|
18
|
+
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
|
19
|
+
delete: vi.fn().mockResolvedValue({ id: 1 }),
|
|
20
|
+
deleteMany: vi.fn().mockResolvedValue({ count: 0 }),
|
|
21
|
+
count: vi.fn().mockResolvedValue(0),
|
|
22
|
+
...overrides,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Preference helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export const makeDefaultPref = (overrides: Partial<UserPreference> = {}): UserPreference => ({
|
|
30
|
+
userId: 1,
|
|
31
|
+
globalOptOut: false,
|
|
32
|
+
mutedTypes: [],
|
|
33
|
+
...overrides,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const makePrefService = (prefOverrides: Partial<UserPreference> = {}) => {
|
|
37
|
+
const pref = makeDefaultPref(prefOverrides);
|
|
38
|
+
return {
|
|
39
|
+
get: vi.fn().mockResolvedValue(pref),
|
|
40
|
+
isOptedOut: (p: UserPreference, type: NotificationType) =>
|
|
41
|
+
p.globalOptOut || p.mutedTypes.includes(type),
|
|
42
|
+
upsert: vi.fn().mockResolvedValue(pref),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Notification service mock (for notifier tests)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export const makeNotificationService = (overrides: Record<string, unknown> = {}) => ({
|
|
51
|
+
create: vi.fn().mockResolvedValue({ id: 1, mergeCount: 1 }),
|
|
52
|
+
createMany: vi.fn().mockResolvedValue([{ id: 1 }]),
|
|
53
|
+
findMergeCandidate: vi.fn().mockResolvedValue(null),
|
|
54
|
+
mergeInto: vi.fn().mockResolvedValue({ id: 1, mergeCount: 2 }),
|
|
55
|
+
findByRecipient: vi.fn().mockResolvedValue([]),
|
|
56
|
+
countByRecipient: vi.fn().mockResolvedValue(0),
|
|
57
|
+
countUnread: vi.fn().mockResolvedValue(0),
|
|
58
|
+
markAsRead: vi.fn().mockResolvedValue({ id: 1, read: true }),
|
|
59
|
+
markAllAsRead: vi.fn().mockResolvedValue({}),
|
|
60
|
+
delete: vi.fn().mockResolvedValue({ id: 1 }),
|
|
61
|
+
clearAll: vi.fn().mockResolvedValue({}),
|
|
62
|
+
...overrides,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Settings mock
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
export const makeSettingsService = (overrides: Record<string, unknown> = {}) => ({
|
|
70
|
+
getEffective: vi.fn().mockResolvedValue({
|
|
71
|
+
retention: { maxDays: 90, maxPerUser: 500 },
|
|
72
|
+
delivery: { defaultRecipient: 'broadcast', allowedRoles: [] },
|
|
73
|
+
merge: {
|
|
74
|
+
enabled: false,
|
|
75
|
+
windowMinutes: 60,
|
|
76
|
+
keyFields: ['title', 'type'],
|
|
77
|
+
countBadge: true,
|
|
78
|
+
rewriteMessage: false,
|
|
79
|
+
},
|
|
80
|
+
ui: {
|
|
81
|
+
pollInterval: 30000,
|
|
82
|
+
pageSize: 20,
|
|
83
|
+
defaultFilter: 'all',
|
|
84
|
+
badge: { enabled: true, color: '#ee5e52' },
|
|
85
|
+
theme: {
|
|
86
|
+
bellSize: '1.5em',
|
|
87
|
+
accent: { info: '#4945ff', success: '#328048', warning: '#d97706', error: '#d02b20' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
...overrides,
|
|
91
|
+
}),
|
|
92
|
+
get: vi.fn(),
|
|
93
|
+
update: vi.fn(),
|
|
94
|
+
reset: vi.fn(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Strapi instance mocks
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/** Minimal strapi mock for notification service tests. */
|
|
102
|
+
export const makeNotificationStrapi = (
|
|
103
|
+
dbOverrides: Record<string, unknown> = {},
|
|
104
|
+
prefOverrides: Partial<UserPreference> = {}
|
|
105
|
+
) => {
|
|
106
|
+
const db = makeDbQuery(dbOverrides);
|
|
107
|
+
const prefSvc = makePrefService(prefOverrides);
|
|
108
|
+
const strapi = {
|
|
109
|
+
db: { query: vi.fn().mockReturnValue(db) },
|
|
110
|
+
plugin: vi.fn().mockReturnValue({ service: vi.fn().mockReturnValue(prefSvc) }),
|
|
111
|
+
};
|
|
112
|
+
return { strapi, _db: db, _prefSvc: prefSvc };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Minimal strapi mock for preference service tests. */
|
|
116
|
+
export const makePreferenceStrapi = (dbOverrides: Record<string, unknown> = {}) => {
|
|
117
|
+
const db = makeDbQuery(dbOverrides);
|
|
118
|
+
const strapi = { db: { query: vi.fn().mockReturnValue(db) } };
|
|
119
|
+
return { strapi, _db: db };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Minimal strapi mock for notifier service tests. */
|
|
123
|
+
export const makeNotifierStrapi = (
|
|
124
|
+
notifOverrides: Record<string, unknown> = {},
|
|
125
|
+
prefOverrides: Partial<UserPreference> = {},
|
|
126
|
+
settingsOverrides: Record<string, unknown> = {}
|
|
127
|
+
) => {
|
|
128
|
+
const notifSvc = makeNotificationService(notifOverrides);
|
|
129
|
+
const prefSvc = makePrefService(prefOverrides);
|
|
130
|
+
const settSvc = makeSettingsService(settingsOverrides);
|
|
131
|
+
|
|
132
|
+
const serviceMap: Record<string, unknown> = {
|
|
133
|
+
notification: notifSvc,
|
|
134
|
+
preference: prefSvc,
|
|
135
|
+
settings: settSvc,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const strapi = {
|
|
139
|
+
plugin: vi.fn().mockReturnValue({
|
|
140
|
+
service: vi.fn().mockImplementation((name: string) => serviceMap[name]),
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return { strapi, _notifSvc: notifSvc, _prefSvc: prefSvc, _settSvc: settSvc };
|
|
145
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DEFAULT_SETTINGS, mergeWithDefaults } from '../../server/src/config';
|
|
3
|
+
|
|
4
|
+
describe('mergeWithDefaults', () => {
|
|
5
|
+
it('returns all default fields when called with no overrides', () => {
|
|
6
|
+
const result = mergeWithDefaults();
|
|
7
|
+
expect(result).toEqual(DEFAULT_SETTINGS);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('includes default merge config', () => {
|
|
11
|
+
const result = mergeWithDefaults();
|
|
12
|
+
expect(result.merge).toEqual({
|
|
13
|
+
enabled: false,
|
|
14
|
+
windowMinutes: 60,
|
|
15
|
+
keyFields: ['title', 'type'],
|
|
16
|
+
countBadge: true,
|
|
17
|
+
rewriteMessage: false,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('enables merge when overridden', () => {
|
|
22
|
+
const result = mergeWithDefaults({ merge: { enabled: true } });
|
|
23
|
+
expect(result.merge.enabled).toBe(true);
|
|
24
|
+
expect(result.merge.windowMinutes).toBe(60);
|
|
25
|
+
expect(result.merge.keyFields).toEqual(['title', 'type']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('overrides windowMinutes while preserving other merge defaults', () => {
|
|
29
|
+
const result = mergeWithDefaults({ merge: { windowMinutes: 30 } });
|
|
30
|
+
expect(result.merge.windowMinutes).toBe(30);
|
|
31
|
+
expect(result.merge.enabled).toBe(false);
|
|
32
|
+
expect(result.merge.countBadge).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('replaces keyFields entirely when overridden', () => {
|
|
36
|
+
const result = mergeWithDefaults({ merge: { keyFields: ['url'] } });
|
|
37
|
+
expect(result.merge.keyFields).toEqual(['url']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('applies rewriteMessage override', () => {
|
|
41
|
+
const result = mergeWithDefaults({ merge: { rewriteMessage: true } });
|
|
42
|
+
expect(result.merge.rewriteMessage).toBe(true);
|
|
43
|
+
expect(result.merge.countBadge).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not bleed merge config into other sections', () => {
|
|
47
|
+
const result = mergeWithDefaults({ merge: { enabled: true } });
|
|
48
|
+
expect(result.retention).toEqual(DEFAULT_SETTINGS.retention);
|
|
49
|
+
expect(result.delivery).toEqual(DEFAULT_SETTINGS.delivery);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('overrides retention fields independently', () => {
|
|
53
|
+
const result = mergeWithDefaults({ retention: { maxDays: 30 } });
|
|
54
|
+
expect(result.retention.maxDays).toBe(30);
|
|
55
|
+
expect(result.retention.maxPerUser).toBe(DEFAULT_SETTINGS.retention.maxPerUser);
|
|
56
|
+
expect(result.merge).toEqual(DEFAULT_SETTINGS.merge);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('deeply merges ui accent colours', () => {
|
|
60
|
+
const result = mergeWithDefaults({ ui: { theme: { accent: { info: '#000' } } } });
|
|
61
|
+
expect(result.ui.theme.accent.info).toBe('#000');
|
|
62
|
+
expect(result.ui.theme.accent.error).toBe(DEFAULT_SETTINGS.ui.theme.accent.error);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import makeNotificationService from '../../server/src/services/notification';
|
|
3
|
+
import { makeNotificationStrapi, makeDbQuery } from '../helpers';
|
|
4
|
+
import type { MergeConfig } from '../../server/src/config';
|
|
5
|
+
|
|
6
|
+
const DISABLED_MERGE: MergeConfig = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
windowMinutes: 60,
|
|
9
|
+
keyFields: ['title', 'type'],
|
|
10
|
+
countBadge: true,
|
|
11
|
+
rewriteMessage: false,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// findByRecipient — opt-out filtering
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
describe('notification.findByRecipient', () => {
|
|
19
|
+
it('returns empty array without querying DB when globalOptOut is true', async () => {
|
|
20
|
+
const { strapi, _db } = makeNotificationStrapi({}, { globalOptOut: true });
|
|
21
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
22
|
+
|
|
23
|
+
const result = await svc.findByRecipient(1, []);
|
|
24
|
+
|
|
25
|
+
expect(result).toEqual([]);
|
|
26
|
+
expect(_db.findMany).not.toHaveBeenCalled();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('queries DB normally when user has no restrictions', async () => {
|
|
30
|
+
const rows = [{ id: 1, title: 'Hello', type: 'info' }];
|
|
31
|
+
const { strapi, _db } = makeNotificationStrapi(
|
|
32
|
+
{ findMany: vi.fn().mockResolvedValue(rows) },
|
|
33
|
+
{ globalOptOut: false, mutedTypes: [] }
|
|
34
|
+
);
|
|
35
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
36
|
+
|
|
37
|
+
const result = await svc.findByRecipient(1, []);
|
|
38
|
+
|
|
39
|
+
expect(result).toBe(rows);
|
|
40
|
+
expect(_db.findMany).toHaveBeenCalledOnce();
|
|
41
|
+
const call = _db.findMany.mock.calls[0][0];
|
|
42
|
+
expect(call.where).not.toHaveProperty('type');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('adds $notIn filter for mutedTypes', async () => {
|
|
46
|
+
const { strapi, _db } = makeNotificationStrapi(
|
|
47
|
+
{},
|
|
48
|
+
{ globalOptOut: false, mutedTypes: ['info', 'warning'] as any }
|
|
49
|
+
);
|
|
50
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
51
|
+
|
|
52
|
+
await svc.findByRecipient(1, []);
|
|
53
|
+
|
|
54
|
+
const where = _db.findMany.mock.calls[0][0].where;
|
|
55
|
+
expect(where.type).toEqual({ $notIn: ['info', 'warning'] });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('respects page and pageSize', async () => {
|
|
59
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
60
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
61
|
+
|
|
62
|
+
await svc.findByRecipient(1, [], { page: 3, pageSize: 10 });
|
|
63
|
+
|
|
64
|
+
const call = _db.findMany.mock.calls[0][0];
|
|
65
|
+
expect(call.limit).toBe(10);
|
|
66
|
+
expect(call.offset).toBe(20);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// countByRecipient / countUnread — opt-out
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
describe('notification.countByRecipient', () => {
|
|
75
|
+
it('returns 0 without querying DB when globalOptOut is true', async () => {
|
|
76
|
+
const { strapi, _db } = makeNotificationStrapi({}, { globalOptOut: true });
|
|
77
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
78
|
+
|
|
79
|
+
const count = await svc.countByRecipient(1, []);
|
|
80
|
+
|
|
81
|
+
expect(count).toBe(0);
|
|
82
|
+
expect(_db.count).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('queries DB normally when not opted out', async () => {
|
|
86
|
+
const { strapi, _db } = makeNotificationStrapi({ count: vi.fn().mockResolvedValue(5) });
|
|
87
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
88
|
+
|
|
89
|
+
const count = await svc.countByRecipient(1, []);
|
|
90
|
+
|
|
91
|
+
expect(count).toBe(5);
|
|
92
|
+
expect(_db.count).toHaveBeenCalledOnce();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('notification.countUnread', () => {
|
|
97
|
+
it('returns 0 without querying DB when globalOptOut is true', async () => {
|
|
98
|
+
const { strapi, _db } = makeNotificationStrapi({}, { globalOptOut: true });
|
|
99
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
100
|
+
|
|
101
|
+
const count = await svc.countUnread(1, []);
|
|
102
|
+
|
|
103
|
+
expect(count).toBe(0);
|
|
104
|
+
expect(_db.count).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('passes read: false filter when not opted out', async () => {
|
|
108
|
+
const { strapi, _db } = makeNotificationStrapi({ count: vi.fn().mockResolvedValue(3) });
|
|
109
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
110
|
+
|
|
111
|
+
await svc.countUnread(1, []);
|
|
112
|
+
|
|
113
|
+
const where = _db.count.mock.calls[0][0].where;
|
|
114
|
+
expect(where.read).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// create
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
describe('notification.create', () => {
|
|
123
|
+
it('stores mergeKey and mergeCount: 1', async () => {
|
|
124
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
125
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
126
|
+
|
|
127
|
+
await svc.create({ title: 'Test', type: 'info', mergeKey: 'Test\x00info' });
|
|
128
|
+
|
|
129
|
+
const data = _db.create.mock.calls[0][0].data;
|
|
130
|
+
expect(data.mergeKey).toBe('Test\x00info');
|
|
131
|
+
expect(data.mergeCount).toBe(1);
|
|
132
|
+
expect(data.read).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('defaults type to info', async () => {
|
|
136
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
137
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
138
|
+
|
|
139
|
+
await svc.create({ title: 'Hi' });
|
|
140
|
+
|
|
141
|
+
expect(_db.create.mock.calls[0][0].data.type).toBe('info');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// findMergeCandidate
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('notification.findMergeCandidate', () => {
|
|
150
|
+
it('queries DB with mergeKey, recipientId, and cutoff', async () => {
|
|
151
|
+
const candidate = { id: 5, mergeCount: 1 };
|
|
152
|
+
const { strapi, _db } = makeNotificationStrapi({
|
|
153
|
+
findOne: vi.fn().mockResolvedValue(candidate),
|
|
154
|
+
});
|
|
155
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
156
|
+
|
|
157
|
+
const before = Date.now();
|
|
158
|
+
const result = await svc.findMergeCandidate('Test\x00info', 1, null, 60 * 60 * 1000);
|
|
159
|
+
const after = Date.now();
|
|
160
|
+
|
|
161
|
+
expect(result).toBe(candidate);
|
|
162
|
+
const where = _db.findOne.mock.calls[0][0].where;
|
|
163
|
+
expect(where.mergeKey).toBe('Test\x00info');
|
|
164
|
+
expect(where.recipientId).toBe(1);
|
|
165
|
+
expect(where.recipientRole).toBeNull();
|
|
166
|
+
const cutoffMs = new Date(where.createdAt.$gt).getTime();
|
|
167
|
+
expect(cutoffMs).toBeGreaterThanOrEqual(before - 60 * 60 * 1000);
|
|
168
|
+
expect(cutoffMs).toBeLessThanOrEqual(after);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('returns null when no candidate found', async () => {
|
|
172
|
+
const { strapi } = makeNotificationStrapi({ findOne: vi.fn().mockResolvedValue(null) });
|
|
173
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
174
|
+
|
|
175
|
+
const result = await svc.findMergeCandidate('key', null, null, 3600000);
|
|
176
|
+
expect(result).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('uses recipientRole when provided', async () => {
|
|
180
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
181
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
182
|
+
|
|
183
|
+
await svc.findMergeCandidate('key', null, 'strapi-editor', 3600000);
|
|
184
|
+
|
|
185
|
+
const where = _db.findOne.mock.calls[0][0].where;
|
|
186
|
+
expect(where.recipientRole).toBe('strapi-editor');
|
|
187
|
+
expect(where.recipientId).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// mergeInto
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
describe('notification.mergeInto', () => {
|
|
196
|
+
it('updates mergeCount without rewriting message when rewriteMessage=false', async () => {
|
|
197
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
198
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
199
|
+
|
|
200
|
+
await svc.mergeInto(5, 3, { ...DISABLED_MERGE, rewriteMessage: false }, 'Test');
|
|
201
|
+
|
|
202
|
+
const data = _db.update.mock.calls[0][0].data;
|
|
203
|
+
expect(data.mergeCount).toBe(3);
|
|
204
|
+
expect(data.message).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('rewrites message when rewriteMessage=true', async () => {
|
|
208
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
209
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
210
|
+
|
|
211
|
+
await svc.mergeInto(5, 4, { ...DISABLED_MERGE, rewriteMessage: true }, 'Test');
|
|
212
|
+
|
|
213
|
+
const data = _db.update.mock.calls[0][0].data;
|
|
214
|
+
expect(data.mergeCount).toBe(4);
|
|
215
|
+
expect(data.message).toBe('4× Test');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('targets the correct notification id', async () => {
|
|
219
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
220
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
221
|
+
|
|
222
|
+
await svc.mergeInto(99, 2, DISABLED_MERGE, 'X');
|
|
223
|
+
|
|
224
|
+
expect(_db.update.mock.calls[0][0].where).toEqual({ id: 99 });
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// createMany
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
describe('notification.createMany', () => {
|
|
233
|
+
it('calls create for each item', async () => {
|
|
234
|
+
const { strapi, _db } = makeNotificationStrapi();
|
|
235
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
236
|
+
|
|
237
|
+
await svc.createMany([
|
|
238
|
+
{ title: 'A', type: 'info' },
|
|
239
|
+
{ title: 'B', type: 'error' },
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
expect(_db.create).toHaveBeenCalledTimes(2);
|
|
243
|
+
const titles = _db.create.mock.calls.map((c: any) => c[0].data.title);
|
|
244
|
+
expect(titles).toContain('A');
|
|
245
|
+
expect(titles).toContain('B');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('returns an array of created items', async () => {
|
|
249
|
+
const { strapi } = makeNotificationStrapi({
|
|
250
|
+
create: vi
|
|
251
|
+
.fn()
|
|
252
|
+
.mockResolvedValueOnce({ id: 1, title: 'A' })
|
|
253
|
+
.mockResolvedValueOnce({ id: 2, title: 'B' }),
|
|
254
|
+
});
|
|
255
|
+
const svc = makeNotificationService({ strapi: strapi as any });
|
|
256
|
+
|
|
257
|
+
const results = await svc.createMany([{ title: 'A' }, { title: 'B' }]);
|
|
258
|
+
|
|
259
|
+
expect(results).toHaveLength(2);
|
|
260
|
+
expect(results[0]).toMatchObject({ id: 1 });
|
|
261
|
+
expect(results[1]).toMatchObject({ id: 2 });
|
|
262
|
+
});
|
|
263
|
+
});
|