fix notifications
Build & Push / Pipeline Tests (push) Failing after 1m8s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 52s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Successful in 46s
Test / API Integration Tests (push) Successful in 1m7s
Build & Push / Pipeline Tests (push) Failing after 1m8s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 52s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Successful in 46s
Test / API Integration Tests (push) Successful in 1m7s
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
notification: {
|
||||
notificationRecipient: {
|
||||
findMany: vi.fn(),
|
||||
count: vi.fn(),
|
||||
updateMany: vi.fn(),
|
||||
},
|
||||
notificationDelivery: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
notificationPreference: {
|
||||
findMany: vi.fn(),
|
||||
upsert: vi.fn(),
|
||||
@@ -17,25 +20,35 @@ vi.mock('../../lib/prisma', () => ({ prisma: prismaMock }))
|
||||
import * as repo from './notification.repo'
|
||||
|
||||
describe('notification.repo query boundaries', () => {
|
||||
it('scopes company in-app notification lists and applies unread filtering only when requested', async () => {
|
||||
await repo.findCompany('company_1', 'true')
|
||||
it('scopes company in-app notification lists to the authenticated employee recipient', async () => {
|
||||
prismaMock.notificationRecipient.findMany.mockResolvedValue([])
|
||||
await repo.findCompany('employee_1', 'true')
|
||||
|
||||
expect(prismaMock.notification.findMany).toHaveBeenCalledWith({
|
||||
where: { companyId: 'company_1', channel: 'IN_APP', readAt: null },
|
||||
expect(prismaMock.notificationRecipient.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
employeeId: 'employee_1',
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
readAt: null,
|
||||
},
|
||||
include: {
|
||||
notificationEvent: true,
|
||||
deliveries: { where: { channel: 'IN_APP' } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
})
|
||||
})
|
||||
|
||||
it('marks a single company notification read with tenant scoping', async () => {
|
||||
it('marks a single company notification read through recipient state only', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-06-09T13:00:00.000Z'))
|
||||
|
||||
await repo.markRead('notification_1', 'company_1')
|
||||
await repo.markRead('recipient_1', 'employee_1')
|
||||
|
||||
expect(prismaMock.notification.updateMany).toHaveBeenCalledWith({
|
||||
where: { id: 'notification_1', companyId: 'company_1' },
|
||||
data: { readAt: new Date('2026-06-09T13:00:00.000Z'), status: 'READ' },
|
||||
expect(prismaMock.notificationRecipient.updateMany).toHaveBeenCalledWith({
|
||||
where: { id: 'recipient_1', employeeId: 'employee_1' },
|
||||
data: { readAt: new Date('2026-06-09T13:00:00.000Z') },
|
||||
})
|
||||
vi.useRealTimers()
|
||||
})
|
||||
@@ -43,17 +56,32 @@ describe('notification.repo query boundaries', () => {
|
||||
it('keeps renter bulk read updates constrained to renter in-app notifications', async () => {
|
||||
await repo.markAllRenterRead('renter_1')
|
||||
|
||||
expect(prismaMock.notification.updateMany).toHaveBeenCalledWith({
|
||||
where: { renterId: 'renter_1', channel: 'IN_APP', readAt: null },
|
||||
data: { readAt: expect.any(Date), status: 'READ' },
|
||||
expect(prismaMock.notificationRecipient.updateMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
renterId: 'renter_1',
|
||||
readAt: null,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
},
|
||||
data: { readAt: expect.any(Date) },
|
||||
})
|
||||
})
|
||||
|
||||
it('defaults company notification history to a capped newest-first query', async () => {
|
||||
prismaMock.notificationDelivery.findMany.mockResolvedValue([])
|
||||
await repo.findCompanyHistory('company_1')
|
||||
|
||||
expect(prismaMock.notification.findMany).toHaveBeenCalledWith({
|
||||
where: { companyId: 'company_1' },
|
||||
expect(prismaMock.notificationDelivery.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
notificationRecipient: {
|
||||
notificationEvent: { companyId: 'company_1' },
|
||||
},
|
||||
},
|
||||
include: {
|
||||
notificationRecipient: {
|
||||
include: { notificationEvent: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 200,
|
||||
})
|
||||
@@ -61,7 +89,7 @@ describe('notification.repo query boundaries', () => {
|
||||
|
||||
it('upserts employee preferences by composite key without cross-employee mutation', async () => {
|
||||
await repo.upsertEmployeePreferences('employee_1', [
|
||||
{ notificationType: 'RESERVATION_CREATED', channel: 'EMAIL', enabled: false },
|
||||
{ notificationType: 'NEW_BOOKING', channel: 'EMAIL', enabled: false },
|
||||
{ notificationType: 'PAYMENT_RECEIVED', channel: 'IN_APP', enabled: true },
|
||||
])
|
||||
|
||||
@@ -70,13 +98,13 @@ describe('notification.repo query boundaries', () => {
|
||||
where: {
|
||||
employeeId_notificationType_channel: {
|
||||
employeeId: 'employee_1',
|
||||
notificationType: 'RESERVATION_CREATED',
|
||||
notificationType: 'NEW_BOOKING',
|
||||
channel: 'EMAIL',
|
||||
},
|
||||
},
|
||||
create: {
|
||||
employeeId: 'employee_1',
|
||||
notificationType: 'RESERVATION_CREATED',
|
||||
notificationType: 'NEW_BOOKING',
|
||||
channel: 'EMAIL',
|
||||
enabled: false,
|
||||
},
|
||||
@@ -86,21 +114,21 @@ describe('notification.repo query boundaries', () => {
|
||||
|
||||
it('upserts renter preferences by renter composite key', async () => {
|
||||
await repo.upsertRenterPreferences('renter_1', [
|
||||
{ notificationType: 'RESERVATION_CONFIRMED', channel: 'PUSH', enabled: true },
|
||||
{ notificationType: 'BOOKING_CONFIRMED', channel: 'EMAIL', enabled: true },
|
||||
])
|
||||
|
||||
expect(prismaMock.notificationPreference.upsert).toHaveBeenCalledWith({
|
||||
where: {
|
||||
renterId_notificationType_channel: {
|
||||
renterId: 'renter_1',
|
||||
notificationType: 'RESERVATION_CONFIRMED',
|
||||
channel: 'PUSH',
|
||||
notificationType: 'BOOKING_CONFIRMED',
|
||||
channel: 'EMAIL',
|
||||
},
|
||||
},
|
||||
create: {
|
||||
renterId: 'renter_1',
|
||||
notificationType: 'RESERVATION_CONFIRMED',
|
||||
channel: 'PUSH',
|
||||
notificationType: 'BOOKING_CONFIRMED',
|
||||
channel: 'EMAIL',
|
||||
enabled: true,
|
||||
},
|
||||
update: { enabled: true },
|
||||
|
||||
@@ -3,22 +3,109 @@ import { NotificationType, NotificationChannel } from '@rentaldrivego/database'
|
||||
|
||||
// ─── Company notifications ────────────────────────────────────
|
||||
|
||||
export function findCompany(companyId: string, unread?: string) {
|
||||
const where: any = { companyId, channel: 'IN_APP' }
|
||||
function presentRecipientNotification(recipient: any) {
|
||||
const event = recipient.notificationEvent
|
||||
const inAppDelivery = recipient.deliveries?.find((delivery: any) => delivery.channel === 'IN_APP')
|
||||
const firstDelivery = inAppDelivery ?? recipient.deliveries?.[0] ?? null
|
||||
|
||||
return {
|
||||
id: recipient.id,
|
||||
notificationEventId: event.id,
|
||||
type: event.type,
|
||||
title: event.title,
|
||||
body: event.body,
|
||||
data: event.data,
|
||||
channel: firstDelivery?.channel ?? 'IN_APP',
|
||||
status: recipient.readAt ? 'READ' : (firstDelivery?.status ?? 'DELIVERED'),
|
||||
sentAt: firstDelivery?.sentAt ?? firstDelivery?.deliveredAt ?? null,
|
||||
readAt: recipient.readAt,
|
||||
createdAt: recipient.createdAt,
|
||||
providerMessageId: firstDelivery?.providerMessageId ?? null,
|
||||
locale: event.locale,
|
||||
}
|
||||
}
|
||||
|
||||
function presentDeliveryHistory(delivery: any) {
|
||||
const recipient = delivery.notificationRecipient
|
||||
const event = recipient.notificationEvent
|
||||
|
||||
return {
|
||||
id: delivery.id,
|
||||
recipientNotificationId: recipient.id,
|
||||
notificationEventId: event.id,
|
||||
type: event.type,
|
||||
title: event.title,
|
||||
body: event.body,
|
||||
data: event.data,
|
||||
channel: delivery.channel,
|
||||
status: delivery.status,
|
||||
attemptCount: delivery.attemptCount,
|
||||
provider: delivery.provider,
|
||||
lastAttemptAt: delivery.lastAttemptAt,
|
||||
nextAttemptAt: delivery.nextAttemptAt,
|
||||
sentAt: delivery.sentAt,
|
||||
deliveredAt: delivery.deliveredAt,
|
||||
failureCode: delivery.failureCode,
|
||||
failureReason: delivery.failureReason,
|
||||
preferenceDecision: delivery.preferenceDecision,
|
||||
readAt: recipient.readAt,
|
||||
createdAt: delivery.createdAt,
|
||||
providerMessageId: delivery.providerMessageId,
|
||||
locale: event.locale,
|
||||
sourceType: event.sourceType,
|
||||
sourceId: event.sourceId,
|
||||
recipient: {
|
||||
type: recipient.recipientType,
|
||||
employeeId: recipient.employeeId,
|
||||
renterId: recipient.renterId,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function findCompany(employeeId: string, unread?: string) {
|
||||
const where: any = {
|
||||
employeeId,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
}
|
||||
if (unread === 'true') where.readAt = null
|
||||
return prisma.notification.findMany({ where, orderBy: { createdAt: 'desc' }, take: 50 })
|
||||
const rows = await prisma.notificationRecipient.findMany({
|
||||
where,
|
||||
include: {
|
||||
notificationEvent: true,
|
||||
deliveries: { where: { channel: 'IN_APP' } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
})
|
||||
return rows.map(presentRecipientNotification)
|
||||
}
|
||||
|
||||
export function countUnread(companyId: string) {
|
||||
return prisma.notification.count({ where: { companyId, channel: 'IN_APP', readAt: null } })
|
||||
export function countUnread(employeeId: string) {
|
||||
return prisma.notificationRecipient.count({
|
||||
where: {
|
||||
employeeId,
|
||||
readAt: null,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function markRead(id: string, companyId: string) {
|
||||
return prisma.notification.updateMany({ where: { id, companyId }, data: { readAt: new Date(), status: 'READ' } })
|
||||
export function markRead(id: string, employeeId: string) {
|
||||
return prisma.notificationRecipient.updateMany({ where: { id, employeeId }, data: { readAt: new Date() } })
|
||||
}
|
||||
|
||||
export function markAllRead(companyId: string) {
|
||||
return prisma.notification.updateMany({ where: { companyId, readAt: null }, data: { readAt: new Date(), status: 'READ' } })
|
||||
export function markAllRead(employeeId: string) {
|
||||
return prisma.notificationRecipient.updateMany({
|
||||
where: {
|
||||
employeeId,
|
||||
readAt: null,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
},
|
||||
data: { readAt: new Date() },
|
||||
})
|
||||
}
|
||||
|
||||
export function findEmployeePreferences(employeeId: string) {
|
||||
@@ -39,28 +126,58 @@ export function findCompanyHistory(
|
||||
companyId: string,
|
||||
opts?: { channel?: string; status?: string; limit?: number },
|
||||
) {
|
||||
const where: any = { companyId }
|
||||
const where: any = {
|
||||
notificationRecipient: {
|
||||
notificationEvent: { companyId },
|
||||
},
|
||||
}
|
||||
if (opts?.channel) where.channel = opts.channel
|
||||
if (opts?.status) where.status = opts.status
|
||||
return prisma.notification.findMany({
|
||||
return prisma.notificationDelivery.findMany({
|
||||
where,
|
||||
include: {
|
||||
notificationRecipient: {
|
||||
include: { notificationEvent: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: opts?.limit ?? 200,
|
||||
})
|
||||
}).then((rows) => rows.map(presentDeliveryHistory))
|
||||
}
|
||||
|
||||
// ─── Renter notifications ─────────────────────────────────────
|
||||
|
||||
export function findRenter(renterId: string) {
|
||||
return prisma.notification.findMany({ where: { renterId, channel: 'IN_APP' }, orderBy: { createdAt: 'desc' }, take: 50 })
|
||||
export async function findRenter(renterId: string) {
|
||||
const rows = await prisma.notificationRecipient.findMany({
|
||||
where: {
|
||||
renterId,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
},
|
||||
include: {
|
||||
notificationEvent: true,
|
||||
deliveries: { where: { channel: 'IN_APP' } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
})
|
||||
return rows.map(presentRecipientNotification)
|
||||
}
|
||||
|
||||
export function markRenterRead(id: string, renterId: string) {
|
||||
return prisma.notification.updateMany({ where: { id, renterId }, data: { readAt: new Date(), status: 'READ' } })
|
||||
return prisma.notificationRecipient.updateMany({ where: { id, renterId }, data: { readAt: new Date() } })
|
||||
}
|
||||
|
||||
export function markAllRenterRead(renterId: string) {
|
||||
return prisma.notification.updateMany({ where: { renterId, channel: 'IN_APP', readAt: null }, data: { readAt: new Date(), status: 'READ' } })
|
||||
return prisma.notificationRecipient.updateMany({
|
||||
where: {
|
||||
renterId,
|
||||
readAt: null,
|
||||
archivedAt: null,
|
||||
deliveries: { some: { channel: 'IN_APP' } },
|
||||
},
|
||||
data: { readAt: new Date() },
|
||||
})
|
||||
}
|
||||
|
||||
export function findRenterPreferences(renterId: string) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { requireCompanyAuth } from '../../middleware/requireCompanyAuth'
|
||||
import { requireTenant } from '../../middleware/requireTenant'
|
||||
import { requireSubscriptionRead, requireSubscriptionWrite } from '../../middleware/requireSubscription'
|
||||
import { requireRenterAuth } from '../../middleware/requireRenterAuth'
|
||||
import { requireRole } from '../../middleware/requireRole'
|
||||
import { parseBody, parseQuery, parseParams } from '../../http/validate'
|
||||
import { ok } from '../../http/respond'
|
||||
import * as service from './notification.service'
|
||||
@@ -18,27 +19,27 @@ const companyWriteAuth = [requireCompanyAuth, requireTenant, requireSubscription
|
||||
router.get('/company', ...companyReadAuth, async (req, res, next) => {
|
||||
try {
|
||||
const { unread } = parseQuery(unreadQuerySchema, req)
|
||||
ok(res, await service.listCompany(req.companyId, unread))
|
||||
ok(res, await service.listCompany(req.employee.id, unread))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/unread-count', ...companyReadAuth, async (req, res, next) => {
|
||||
try {
|
||||
ok(res, { unread: await service.countUnread(req.companyId) })
|
||||
ok(res, { unread: await service.countUnread(req.employee.id) })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/company/:id/read', ...companyWriteAuth, async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
await service.markRead(id, req.companyId)
|
||||
await service.markRead(id, req.employee.id)
|
||||
ok(res, { success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/company/read-all', ...companyWriteAuth, async (req, res, next) => {
|
||||
try {
|
||||
await service.markAllRead(req.companyId)
|
||||
await service.markAllRead(req.employee.id)
|
||||
ok(res, { success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
@@ -57,7 +58,7 @@ router.patch('/company/preferences', ...companyWriteAuth, async (req, res, next)
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/history', ...companyReadAuth, async (req, res, next) => {
|
||||
router.get('/history', ...companyReadAuth, requireRole('MANAGER'), async (req, res, next) => {
|
||||
try {
|
||||
const { channel, status, limit } = parseQuery(historyQuerySchema, req)
|
||||
ok(res, await service.listCompanyHistory(req.companyId, { channel, status, limit }))
|
||||
|
||||
@@ -3,10 +3,12 @@ import { historyQuerySchema, idParamSchema, preferencesSchema, unreadQuerySchema
|
||||
|
||||
describe('notification schema contracts', () => {
|
||||
it('requires complete preference entries', () => {
|
||||
expect(preferencesSchema.parse([{ notificationType: 'BOOKING_CREATED', channel: 'EMAIL', enabled: true }])).toEqual([
|
||||
{ notificationType: 'BOOKING_CREATED', channel: 'EMAIL', enabled: true },
|
||||
expect(preferencesSchema.parse([{ notificationType: 'NEW_BOOKING', channel: 'EMAIL', enabled: true }])).toEqual([
|
||||
{ notificationType: 'NEW_BOOKING', channel: 'EMAIL', enabled: true },
|
||||
])
|
||||
expect(() => preferencesSchema.parse([{ notificationType: 'BOOKING_CREATED', channel: 'EMAIL' }])).toThrow()
|
||||
expect(() => preferencesSchema.parse([{ notificationType: 'NEW_BOOKING', channel: 'EMAIL' }])).toThrow()
|
||||
expect(() => preferencesSchema.parse([{ notificationType: 'RESERVATION_CREATED', channel: 'EMAIL', enabled: true }])).toThrow()
|
||||
expect(() => preferencesSchema.parse([{ notificationType: 'NEW_BOOKING', channel: 'PUSH', enabled: true }])).toThrow()
|
||||
})
|
||||
|
||||
it('coerces history limits and caps bulk history requests', () => {
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const notificationTypeSchema = z.enum([
|
||||
'ACCOUNT_CREATED',
|
||||
'NEW_BOOKING',
|
||||
'BOOKING_CANCELLED',
|
||||
'PAYMENT_RECEIVED',
|
||||
'PAYMENT_FAILED',
|
||||
'DOCUMENTS_REQUIRED',
|
||||
'PAYMENT_REQUIRED',
|
||||
'SUBSCRIPTION_TRIAL_ENDING',
|
||||
'SUBSCRIPTION_SUSPENDED',
|
||||
'VEHICLE_MAINTENANCE_DUE',
|
||||
'OFFER_EXPIRING',
|
||||
'NEW_REVIEW_RECEIVED',
|
||||
'BOOKING_CONFIRMED',
|
||||
'PICKUP_REMINDER_24H',
|
||||
'PICKUP_REMINDER_2H',
|
||||
'VEHICLE_READY',
|
||||
'RETURN_REMINDER',
|
||||
'BOOKING_CANCELLED_BY_COMPANY',
|
||||
'REFUND_PROCESSED',
|
||||
'NEW_OFFER_FROM_SAVED_COMPANY',
|
||||
'REVIEW_REQUEST',
|
||||
])
|
||||
|
||||
export const notificationChannelSchema = z.enum(['EMAIL', 'SMS', 'WHATSAPP', 'IN_APP', 'PUSH'])
|
||||
export const implementedNotificationChannelSchema = z.enum(['EMAIL', 'IN_APP'])
|
||||
export const notificationDeliveryStatusSchema = z.enum([
|
||||
'PENDING',
|
||||
'QUEUED',
|
||||
'SENT',
|
||||
'DELIVERED',
|
||||
'FAILED',
|
||||
'SKIPPED',
|
||||
'DEAD_LETTER',
|
||||
])
|
||||
|
||||
export const preferenceItemSchema = z.object({
|
||||
notificationType: z.string(),
|
||||
channel: z.string(),
|
||||
notificationType: notificationTypeSchema,
|
||||
channel: implementedNotificationChannelSchema,
|
||||
enabled: z.boolean(),
|
||||
})
|
||||
|
||||
@@ -17,7 +53,7 @@ export const unreadQuerySchema = z.object({
|
||||
})
|
||||
|
||||
export const historyQuerySchema = z.object({
|
||||
channel: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
channel: notificationChannelSchema.optional(),
|
||||
status: notificationDeliveryStatusSchema.optional(),
|
||||
limit: z.coerce.number().int().min(1).max(500).optional(),
|
||||
})
|
||||
|
||||
@@ -26,21 +26,21 @@ describe('notification.service', () => {
|
||||
vi.mocked(repo.findCompanyHistory).mockResolvedValue([{ id: 'h1' }] as never)
|
||||
vi.mocked(repo.countUnread).mockResolvedValue(4 as never)
|
||||
|
||||
await expect(service.listCompany('company_1', 'true')).resolves.toEqual([{ id: 'n1' }])
|
||||
await expect(service.listCompany('employee_1', 'true')).resolves.toEqual([{ id: 'n1' }])
|
||||
await expect(service.listCompanyHistory('company_1', { channel: 'EMAIL', status: 'FAILED', limit: 25 })).resolves.toEqual([{ id: 'h1' }])
|
||||
await expect(service.countUnread('company_1')).resolves.toBe(4)
|
||||
await expect(service.countUnread('employee_1')).resolves.toBe(4)
|
||||
|
||||
expect(repo.findCompany).toHaveBeenCalledWith('company_1', 'true')
|
||||
expect(repo.findCompany).toHaveBeenCalledWith('employee_1', 'true')
|
||||
expect(repo.findCompanyHistory).toHaveBeenCalledWith('company_1', { channel: 'EMAIL', status: 'FAILED', limit: 25 })
|
||||
expect(repo.countUnread).toHaveBeenCalledWith('company_1')
|
||||
expect(repo.countUnread).toHaveBeenCalledWith('employee_1')
|
||||
})
|
||||
|
||||
it('marks company notifications read only within the company tenant', async () => {
|
||||
await service.markRead('notification_1', 'company_1')
|
||||
await service.markAllRead('company_1')
|
||||
it('marks company notifications read only for the authenticated employee recipient', async () => {
|
||||
await service.markRead('notification_1', 'employee_1')
|
||||
await service.markAllRead('employee_1')
|
||||
|
||||
expect(repo.markRead).toHaveBeenCalledWith('notification_1', 'company_1')
|
||||
expect(repo.markAllRead).toHaveBeenCalledWith('company_1')
|
||||
expect(repo.markRead).toHaveBeenCalledWith('notification_1', 'employee_1')
|
||||
expect(repo.markAllRead).toHaveBeenCalledWith('employee_1')
|
||||
})
|
||||
|
||||
it('reads and writes employee preferences through the employee identity, not the company id', async () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as repo from './notification.repo'
|
||||
|
||||
export const listCompany = (companyId: string, unread?: string) => repo.findCompany(companyId, unread)
|
||||
export const listCompany = (employeeId: string, unread?: string) => repo.findCompany(employeeId, unread)
|
||||
export const listCompanyHistory = (companyId: string, opts?: { channel?: string; status?: string; limit?: number }) => repo.findCompanyHistory(companyId, opts)
|
||||
export const countUnread = (companyId: string) => repo.countUnread(companyId)
|
||||
export const markRead = (id: string, companyId: string) => repo.markRead(id, companyId)
|
||||
export const markAllRead = (companyId: string) => repo.markAllRead(companyId)
|
||||
export const countUnread = (employeeId: string) => repo.countUnread(employeeId)
|
||||
export const markRead = (id: string, employeeId: string) => repo.markRead(id, employeeId)
|
||||
export const markAllRead = (employeeId: string) => repo.markAllRead(employeeId)
|
||||
export const getPreferences = (employeeId: string) => repo.findEmployeePreferences(employeeId)
|
||||
export const setPreferences = (employeeId: string, prefs: any[]) => repo.upsertEmployeePreferences(employeeId, prefs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user