FRE-5352 Apply P1/P2/P3 fixes from code review: severity type rename, dedup query fix, SMS phone field, test assertions
This commit is contained in:
@@ -2,39 +2,40 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { HomeTitleSchedulerService } from '../src/scheduler.service';
|
||||
import { PropertySnapshot } from '../src/types';
|
||||
|
||||
// Mock @shieldai/db
|
||||
const mockPrisma = {
|
||||
subscription: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
$queryRaw: vi.fn(),
|
||||
};
|
||||
// All mocks inside vi.hoisted() to avoid vitest hoisting issues
|
||||
const mocked = vi.hoisted(() => {
|
||||
const mockPrisma = {
|
||||
subscription: { findMany: vi.fn() },
|
||||
$queryRaw: vi.fn(),
|
||||
};
|
||||
const mockProcessChangeDetection = vi.fn();
|
||||
const mockDetectChanges = vi.fn();
|
||||
const mockShouldTriggerAlert = vi.fn();
|
||||
|
||||
return {
|
||||
mockPrisma,
|
||||
mockProcessChangeDetection,
|
||||
mockDetectChanges,
|
||||
mockShouldTriggerAlert,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@shieldai/db', () => ({
|
||||
prisma: mockPrisma,
|
||||
prisma: mocked.mockPrisma,
|
||||
}));
|
||||
|
||||
// Mock alert pipeline
|
||||
const mockProcessChangeDetection = vi.fn();
|
||||
const mockHomeTitleAlertPipeline = {
|
||||
processChangeDetection: mockProcessChangeDetection,
|
||||
};
|
||||
|
||||
vi.mock('../src/alert.pipeline', () => ({
|
||||
homeTitleAlertPipeline: mockHomeTitleAlertPipeline,
|
||||
homeTitleAlertPipeline: {
|
||||
processChangeDetection: mocked.mockProcessChangeDetection,
|
||||
},
|
||||
HomeTitleAlertPipeline: class {},
|
||||
}));
|
||||
|
||||
// Mock change-detector
|
||||
const mockDetectChanges = vi.fn();
|
||||
const mockShouldTriggerAlert = vi.fn();
|
||||
|
||||
vi.mock('../src/change-detector', () => ({
|
||||
detectChanges: mockDetectChanges,
|
||||
shouldTriggerAlert: mockShouldTriggerAlert,
|
||||
detectChanges: mocked.mockDetectChanges,
|
||||
shouldTriggerAlert: mocked.mockShouldTriggerAlert,
|
||||
}));
|
||||
|
||||
// Mock uuid
|
||||
vi.mock('uuid', () => ({
|
||||
v4: () => 'scan-uuid-' + Date.now(),
|
||||
}));
|
||||
@@ -46,7 +47,7 @@ const mockSubscription = {
|
||||
};
|
||||
|
||||
function mockLatestSnapshots(snapshots: PropertySnapshot[]) {
|
||||
mockPrisma.$queryRaw.mockResolvedValue(
|
||||
mocked.mockPrisma.$queryRaw.mockResolvedValue(
|
||||
snapshots.map(s => ({
|
||||
id: s.id,
|
||||
propertyId: s.propertyId,
|
||||
@@ -64,9 +65,9 @@ function mockLatestSnapshots(snapshots: PropertySnapshot[]) {
|
||||
|
||||
function mockPreviousSnapshot(snapshot: PropertySnapshot | null) {
|
||||
if (!snapshot) {
|
||||
mockPrisma.$queryRaw.mockResolvedValue([]);
|
||||
mocked.mockPrisma.$queryRaw.mockResolvedValue([]);
|
||||
} else {
|
||||
mockPrisma.$queryRaw.mockResolvedValue([
|
||||
mocked.mockPrisma.$queryRaw.mockResolvedValue([
|
||||
{
|
||||
id: snapshot.id,
|
||||
propertyId: snapshot.propertyId,
|
||||
@@ -88,12 +89,16 @@ describe('HomeTitleSchedulerService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.clearAllMocks();
|
||||
mocked.mockProcessChangeDetection.mockReset();
|
||||
mocked.mockDetectChanges.mockReset();
|
||||
mocked.mockShouldTriggerAlert.mockReset();
|
||||
|
||||
scheduler = new HomeTitleSchedulerService({
|
||||
scanIntervalMinutes: 60,
|
||||
maxPropertiesPerScan: 100,
|
||||
enabled: true,
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -145,13 +150,14 @@ describe('HomeTitleSchedulerService', () => {
|
||||
|
||||
describe('runScan', () => {
|
||||
it('returns empty results when no subscriptions', async () => {
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
|
||||
const result = await scheduler.runScan();
|
||||
|
||||
expect(result.propertiesScanned).toBe(0);
|
||||
expect(result.changesDetected).toBe(0);
|
||||
expect(result.alertsCreated).toBe(0);
|
||||
expect(result.notificationsSent).toBe(0);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -171,10 +177,10 @@ describe('HomeTitleSchedulerService', () => {
|
||||
ownerName: 'Jane Smith',
|
||||
};
|
||||
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mockLatestSnapshots([currentSnapshot]);
|
||||
mockPreviousSnapshot(previousSnapshot);
|
||||
mockDetectChanges.mockReturnValue({
|
||||
mocked.mockDetectChanges.mockReturnValue({
|
||||
propertyId: 'prop-001',
|
||||
changeType: 'ownership_transfer',
|
||||
severity: 'major',
|
||||
@@ -184,8 +190,8 @@ describe('HomeTitleSchedulerService', () => {
|
||||
currentSnapshot,
|
||||
detectedAt: new Date().toISOString(),
|
||||
});
|
||||
mockShouldTriggerAlert.mockReturnValue(true);
|
||||
mockProcessChangeDetection.mockResolvedValue({
|
||||
mocked.mockShouldTriggerAlert.mockReturnValue(true);
|
||||
mocked.mockProcessChangeDetection.mockResolvedValue({
|
||||
id: 'alert-001',
|
||||
propertyId: 'prop-001',
|
||||
subscriptionId: 'sub-001',
|
||||
@@ -202,14 +208,13 @@ describe('HomeTitleSchedulerService', () => {
|
||||
|
||||
const result = await scheduler.runScan();
|
||||
|
||||
expect(result.propertiesScanned).toBeGreaterThanOrEqual(0);
|
||||
expect(result.changesDetected).toBeGreaterThanOrEqual(1);
|
||||
expect(result.alertsCreated).toBeGreaterThanOrEqual(1);
|
||||
expect(result.notificationsSent).toBeGreaterThanOrEqual(1);
|
||||
expect(result.changesDetected).toBe(1);
|
||||
expect(result.alertsCreated).toBe(1);
|
||||
expect(result.notificationsSent).toBe(1);
|
||||
});
|
||||
|
||||
it('skips snapshots without previous', async () => {
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mockLatestSnapshots([{
|
||||
id: 'snap-1',
|
||||
propertyId: 'prop-001',
|
||||
@@ -226,10 +231,10 @@ describe('HomeTitleSchedulerService', () => {
|
||||
});
|
||||
|
||||
it('handles subscription scan errors gracefully', async () => {
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([mockSubscription]);
|
||||
mockLatestSnapshots([]);
|
||||
mockPreviousSnapshot(null);
|
||||
mockDetectChanges.mockReturnValue({
|
||||
mocked.mockDetectChanges.mockReturnValue({
|
||||
propertyId: 'prop-001',
|
||||
changeType: 'metadata_change',
|
||||
severity: 'minor',
|
||||
@@ -239,7 +244,7 @@ describe('HomeTitleSchedulerService', () => {
|
||||
currentSnapshot: {} as any,
|
||||
detectedAt: new Date().toISOString(),
|
||||
});
|
||||
mockShouldTriggerAlert.mockReturnValue(false);
|
||||
mocked.mockShouldTriggerAlert.mockReturnValue(false);
|
||||
|
||||
const result = await scheduler.runScan();
|
||||
|
||||
@@ -248,7 +253,7 @@ describe('HomeTitleSchedulerService', () => {
|
||||
});
|
||||
|
||||
it('tracks scan metadata', async () => {
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
|
||||
const result = await scheduler.runScan();
|
||||
|
||||
@@ -278,10 +283,10 @@ describe('HomeTitleSchedulerService', () => {
|
||||
};
|
||||
|
||||
const nonPremiumSub = { ...mockSubscription, tier: 'plus' as const };
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([nonPremiumSub]);
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([nonPremiumSub]);
|
||||
mockLatestSnapshots([currentSnapshot]);
|
||||
mockPreviousSnapshot(previousSnapshot);
|
||||
mockDetectChanges.mockReturnValue({
|
||||
mocked.mockDetectChanges.mockReturnValue({
|
||||
propertyId: 'prop-001',
|
||||
changeType: 'ownership_transfer',
|
||||
severity: 'major',
|
||||
@@ -291,8 +296,8 @@ describe('HomeTitleSchedulerService', () => {
|
||||
currentSnapshot,
|
||||
detectedAt: new Date().toISOString(),
|
||||
});
|
||||
mockShouldTriggerAlert.mockReturnValue(true);
|
||||
mockProcessChangeDetection.mockResolvedValue({
|
||||
mocked.mockShouldTriggerAlert.mockReturnValue(true);
|
||||
mocked.mockProcessChangeDetection.mockResolvedValue({
|
||||
id: 'alert-002',
|
||||
propertyId: 'prop-001',
|
||||
subscriptionId: 'sub-001',
|
||||
@@ -309,8 +314,8 @@ describe('HomeTitleSchedulerService', () => {
|
||||
|
||||
const result = await scheduler.runScan();
|
||||
|
||||
expect(result.changesDetected).toBeGreaterThanOrEqual(1);
|
||||
expect(result.alertsCreated).toBeGreaterThanOrEqual(1);
|
||||
expect(result.changesDetected).toBe(1);
|
||||
expect(result.alertsCreated).toBe(1);
|
||||
expect(result.notificationsSent).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -321,8 +326,9 @@ describe('HomeTitleSchedulerService', () => {
|
||||
});
|
||||
|
||||
it('returns last scan result after scan', async () => {
|
||||
mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
await scheduler.runScan();
|
||||
mocked.mockPrisma.subscription.findMany.mockResolvedValue([]);
|
||||
scheduler.start();
|
||||
await vi.advanceTimersByTimeAsync(60 * 60 * 1000);
|
||||
expect(scheduler.getLastScanResult()).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user