more package declarations
This commit is contained in:
@@ -404,5 +404,26 @@ describe('data-collector', () => {
|
||||
|
||||
expect(result.homeTitleStats).toBeUndefined();
|
||||
});
|
||||
|
||||
it('includes homeTitleStats for WEEKLY_DIGEST', async () => {
|
||||
mockPrisma.exposure.findMany.mockResolvedValue([]);
|
||||
mockAlertCount.mockResolvedValue(0);
|
||||
mockPrisma.spamFeedback.findMany.mockResolvedValue([]);
|
||||
mockPrisma.voiceAnalysis.findMany.mockResolvedValue([]);
|
||||
mockPrisma.voiceEnrollment.count.mockResolvedValue(0);
|
||||
mockPrisma.watchlistItem.findMany.mockResolvedValue([
|
||||
{ subscriptionId: 'sub-1', type: 'address', isActive: true },
|
||||
]);
|
||||
|
||||
const result = await collectAllReportData(
|
||||
'user-1', 'sub-1', 'WEEKLY_DIGEST', periodStart, periodEnd
|
||||
);
|
||||
|
||||
expect(result.homeTitleStats).toBeDefined();
|
||||
expect(result.homeTitleStats?.propertiesMonitored).toBe(1);
|
||||
expect(result.exposureSummary).toBeDefined();
|
||||
expect(result.spamStats).toBeDefined();
|
||||
expect(result.voiceStats).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -294,7 +294,7 @@ export async function collectAllReportData(
|
||||
protectionScore,
|
||||
};
|
||||
|
||||
if (reportType === 'ANNUAL_PREMIUM') {
|
||||
if (reportType === 'ANNUAL_PREMIUM' || reportType === 'WEEKLY_DIGEST') {
|
||||
payload.homeTitleStats = await collectHomeTitleStats(
|
||||
subscriptionId,
|
||||
periodStart,
|
||||
|
||||
@@ -316,4 +316,57 @@ describe('ReportService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scheduleWeeklyDigest', () => {
|
||||
it('creates weekly digest reports for Premium subscriptions', async () => {
|
||||
mockSubscriptionFindMany.mockResolvedValue([
|
||||
{ id: 'sub-1', userId: 'user-1', user: { email: 'u1@test.com' } },
|
||||
{ id: 'sub-2', userId: 'user-2', user: { email: 'u2@test.com' } },
|
||||
]);
|
||||
mockFindFirst.mockResolvedValue(null);
|
||||
mockCreate.mockResolvedValue({ id: 'weekly-digest-1' });
|
||||
|
||||
const result = await service.scheduleWeeklyDigest();
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
reportType: 'WEEKLY_DIGEST',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('skips subscriptions that already have a digest for the period', async () => {
|
||||
mockSubscriptionFindMany.mockResolvedValue([
|
||||
{ id: 'sub-1', userId: 'user-1', user: { email: 'u1@test.com' } },
|
||||
]);
|
||||
mockFindFirst.mockResolvedValue({ id: 'existing-digest' });
|
||||
|
||||
const result = await service.scheduleWeeklyDigest();
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('only schedules for premium tier subscriptions', async () => {
|
||||
mockSubscriptionFindMany.mockResolvedValue([
|
||||
{ id: 'sub-1', userId: 'user-1', user: { email: 'u1@test.com' } },
|
||||
]);
|
||||
mockFindFirst.mockResolvedValue(null);
|
||||
mockCreate.mockResolvedValue({ id: 'weekly-digest-2' });
|
||||
|
||||
await service.scheduleWeeklyDigest();
|
||||
|
||||
expect(mockSubscriptionFindMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
tier: 'premium',
|
||||
status: 'active',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,6 +167,55 @@ export class ReportService {
|
||||
return createdIds;
|
||||
}
|
||||
|
||||
async scheduleWeeklyDigest(): Promise<string[]> {
|
||||
const premiumSubscriptions = await prisma.subscription.findMany({
|
||||
where: {
|
||||
tier: 'premium',
|
||||
status: 'active',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
user: { select: { email: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const createdIds: string[] = [];
|
||||
const now = new Date();
|
||||
const periodStart = new Date(now);
|
||||
periodStart.setDate(periodStart.getDate() - 7);
|
||||
const periodEnd = new Date(now);
|
||||
|
||||
for (const sub of premiumSubscriptions) {
|
||||
const existing = await prisma.securityReport.findFirst({
|
||||
where: {
|
||||
subscriptionId: sub.id,
|
||||
reportType: 'WEEKLY_DIGEST',
|
||||
periodStart: periodStart,
|
||||
periodEnd: periodEnd,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
const report = await prisma.securityReport.create({
|
||||
data: {
|
||||
userId: sub.userId,
|
||||
subscriptionId: sub.id,
|
||||
reportType: 'WEEKLY_DIGEST',
|
||||
status: 'PENDING',
|
||||
periodStart,
|
||||
periodEnd,
|
||||
title: `Weekly Digest — ${periodStart.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })} to ${periodEnd.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })}`,
|
||||
scheduledFor: new Date(now.getTime() + 3600000),
|
||||
},
|
||||
});
|
||||
createdIds.push(report.id);
|
||||
}
|
||||
}
|
||||
|
||||
return createdIds;
|
||||
}
|
||||
|
||||
async scheduleAnnualReports(): Promise<string[]> {
|
||||
const premiumSubscriptions = await prisma.subscription.findMany({
|
||||
where: {
|
||||
@@ -225,6 +274,11 @@ export class ReportService {
|
||||
if (reportType === 'MONTHLY_PLUS') {
|
||||
return new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
}
|
||||
if (reportType === 'WEEKLY_DIGEST') {
|
||||
const start = new Date(now);
|
||||
start.setDate(start.getDate() - 7);
|
||||
return start;
|
||||
}
|
||||
return new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
|
||||
}
|
||||
|
||||
@@ -239,6 +293,9 @@ export class ReportService {
|
||||
year: 'numeric',
|
||||
})}`;
|
||||
}
|
||||
if (reportType === 'WEEKLY_DIGEST') {
|
||||
return `Weekly Digest — ${periodStart.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })} to ${periodEnd.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })}`;
|
||||
}
|
||||
return `Annual Protection Audit — ${periodStart.getFullYear()}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user