From e6f7dfd3eee5bcdf0beece113d4df10f999d7f61 Mon Sep 17 00:00:00 2001 From: shadrackmanfred Date: Tue, 1 Sep 2026 04:41:55 +0000 Subject: [PATCH] fix: replace as any casts in admin, backup, search, and transactions Remove every `as any` cast in the four modules (58 sites including specs; repository-wide count drops 171 -> 113) so contract drift surfaces at compile time instead of at runtime. - admin: declare the cursor query param on the pagination DTOs and use it directly; remove the last-item index casts - backup: use the real BackupStatus/BackupTrigger/RestoreStatus enum constants instead of string literals cast to any - search: drop the prisma delegate casts (models exist on the client) and type the $queryRaw location query with its row shape - transactions: type milestone status with MilestoneStatus, add cursor to TransactionListQueryDto, use UserRole.ADMIN for @Roles, and narrow the audit log status to TransactionStatus (the previous 'STATUS_TRANSITION' literal was rejected by the DB enum at runtime) - specs: type prisma/service mocks with interfaces or Partial mocks instead of casting literals to any --- src/admin/admin.service.ts | 30 +++++++++---------- src/admin/dto/admin.dto.ts | 12 ++++++++ src/backup/backup.service.spec.ts | 27 +++++++++++++---- src/backup/backup.service.ts | 24 +++++++-------- src/search/search-autocomplete.service.ts | 20 +++++++------ src/search/search.service.spec.ts | 28 +++++++++-------- src/search/search.service.ts | 2 +- src/transactions/disputes.controller.ts | 6 ++-- src/transactions/dto/transaction.dto.ts | 5 ++++ src/transactions/transaction-audit.service.ts | 5 ++-- .../transaction-reminders.service.ts | 7 +++-- src/transactions/transactions.service.spec.ts | 20 ++++++++----- src/transactions/transactions.service.ts | 4 +-- 13 files changed, 116 insertions(+), 74 deletions(-) diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 1ef62d1b..4db0ca08 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -109,7 +109,7 @@ export class AdminService { async listUsers(query: AdminUsersQueryDto) { const page = query.page ?? 1; const limit = query.limit ?? 20; - const skip = (query as any).cursor ? undefined : (page - 1) * limit; + const skip = query.cursor ? undefined : (page - 1) * limit; const where: any = { role: query.role, @@ -122,8 +122,8 @@ export class AdminService { : undefined, }; - if ((query as any).cursor) { - where.createdAt = { lt: new Date(Buffer.from((query as any).cursor, 'base64').toString()) }; + if (query.cursor) { + where.createdAt = { lt: new Date(Buffer.from(query.cursor, 'base64').toString()) }; } const [items, total] = await Promise.all([ @@ -148,10 +148,10 @@ export class AdminService { const nextCursor = items.length === limit - ? Buffer.from((items[items.length - 1] as any).createdAt.toISOString()).toString('base64') + ? Buffer.from(items[items.length - 1].createdAt.toISOString()).toString('base64') : null; - return { total, page, limit, items, nextCursor, previousCursor: (query as any).cursor || null }; + return { total, page, limit, items, nextCursor, previousCursor: query.cursor || null }; } async updateUser(userId: string, payload: AdminUpdateUserDto, actorId?: string) { @@ -240,14 +240,14 @@ export class AdminService { async getModerationQueue(query: ModerationQueueQueryDto) { const page = query.page ?? 1; const limit = query.limit ?? 20; - const skip = (query as any).cursor ? undefined : (page - 1) * limit; + const skip = query.cursor ? undefined : (page - 1) * limit; const where: any = { status: query.status ?? PropertyStatus.PENDING, }; - if ((query as any).cursor) { - where.createdAt = { lt: new Date(Buffer.from((query as any).cursor, 'base64').toString()) }; + if (query.cursor) { + where.createdAt = { lt: new Date(Buffer.from(query.cursor, 'base64').toString()) }; } const [items, total] = await Promise.all([ @@ -272,10 +272,10 @@ export class AdminService { const nextCursor = items.length === limit - ? Buffer.from((items[items.length - 1] as any).createdAt.toISOString()).toString('base64') + ? Buffer.from(items[items.length - 1].createdAt.toISOString()).toString('base64') : null; - return { total, page, limit, items, nextCursor, previousCursor: (query as any).cursor || null }; + return { total, page, limit, items, nextCursor, previousCursor: query.cursor || null }; } async approveProperty(propertyId: string) { @@ -348,7 +348,7 @@ export class AdminService { async monitorTransactions(query: TransactionMonitoringQueryDto) { const page = query.page ?? 1; const limit = query.limit ?? 20; - const skip = (query as any).cursor ? undefined : (page - 1) * limit; + const skip = query.cursor ? undefined : (page - 1) * limit; const where: any = { status: query.status, @@ -356,8 +356,8 @@ export class AdminService { propertyId: query.propertyId, }; - if ((query as any).cursor) { - where.createdAt = { lt: new Date(Buffer.from((query as any).cursor, 'base64').toString()) }; + if (query.cursor) { + where.createdAt = { lt: new Date(Buffer.from(query.cursor, 'base64').toString()) }; } const [items, total] = await Promise.all([ @@ -383,10 +383,10 @@ export class AdminService { const nextCursor = items.length === limit - ? Buffer.from((items[items.length - 1] as any).createdAt.toISOString()).toString('base64') + ? Buffer.from(items[items.length - 1].createdAt.toISOString()).toString('base64') : null; - return { total, page, limit, items, nextCursor, previousCursor: (query as any).cursor || null }; + return { total, page, limit, items, nextCursor, previousCursor: query.cursor || null }; } async transactionMonitoringSummary() { diff --git a/src/admin/dto/admin.dto.ts b/src/admin/dto/admin.dto.ts index ea8d7914..9ab7296c 100644 --- a/src/admin/dto/admin.dto.ts +++ b/src/admin/dto/admin.dto.ts @@ -45,6 +45,10 @@ export class AdminUsersQueryDto { @Min(1) @Max(100) limit: number = 20; + + @IsOptional() + @IsString() + cursor?: string; } export class AdminUpdateUserDto { @@ -88,6 +92,10 @@ export class ModerationQueueQueryDto { @Min(1) @Max(100) limit: number = 20; + + @IsOptional() + @IsString() + cursor?: string; } export class FlagPropertyDto { @@ -141,6 +149,10 @@ export class TransactionMonitoringQueryDto { @Min(1) @Max(100) limit: number = 20; + + @IsOptional() + @IsString() + cursor?: string; } export class UpdateTransactionStatusDto { diff --git a/src/backup/backup.service.spec.ts b/src/backup/backup.service.spec.ts index ba159d90..d99484de 100644 --- a/src/backup/backup.service.spec.ts +++ b/src/backup/backup.service.spec.ts @@ -3,9 +3,21 @@ import { PrismaService } from '../database/prisma.service'; import { ConfigService } from '@nestjs/config'; import { NotificationsService } from '../notifications/notifications.service'; +interface MockPrisma { + databaseBackup: { + findMany: jest.Mock; + count: jest.Mock; + findFirst: jest.Mock; + }; + backupScheduleConfig: { + findUnique: jest.Mock; + upsert: jest.Mock; + }; +} + describe('BackupService', () => { let service: BackupService; - let prisma: jest.Mocked>; + let prisma: MockPrisma; beforeEach(() => { prisma = { @@ -13,14 +25,17 @@ describe('BackupService', () => { findMany: jest.fn().mockResolvedValue([]), count: jest.fn().mockResolvedValue(0), findFirst: jest.fn().mockResolvedValue(null), - } as any, + }, backupScheduleConfig: { findUnique: jest.fn().mockResolvedValue({ - id: 'default', enabled: false, cronExpression: '0 2 * * *', - retentionCount: 10, lastRunAt: null, + id: 'default', + enabled: false, + cronExpression: '0 2 * * *', + retentionCount: 10, + lastRunAt: null, }), upsert: jest.fn().mockResolvedValue({}), - } as any, + }, }; service = new BackupService( prisma as unknown as PrismaService, @@ -45,4 +60,4 @@ describe('BackupService', () => { expect(result.enabled).toBe(false); expect(result.cronExpression).toBe('0 2 * * *'); }); -}); \ No newline at end of file +}); diff --git a/src/backup/backup.service.ts b/src/backup/backup.service.ts index 52af9212..098ed370 100644 --- a/src/backup/backup.service.ts +++ b/src/backup/backup.service.ts @@ -64,7 +64,7 @@ export class BackupService implements OnModuleInit { }), this.prisma.databaseBackup.count({ where: { - OR: [{ status: 'RUNNING' as any }, { restoreStatus: 'RUNNING' as any }], + OR: [{ status: BackupStatus.RUNNING }, { restoreStatus: RestoreStatus.RUNNING }], }, }), this.prisma.databaseBackup.count(), @@ -107,7 +107,7 @@ export class BackupService implements OnModuleInit { } async createManualBackup(initiatedById?: string) { - return this.createBackup('MANUAL' as any, initiatedById); + return this.createBackup(BackupTrigger.MANUAL, initiatedById); } async restoreBackup(backupId: string, restoredById?: string) { @@ -128,7 +128,7 @@ export class BackupService implements OnModuleInit { await this.prisma.databaseBackup.update({ where: { id: backupId }, data: { - restoreStatus: 'RUNNING' as any, + restoreStatus: RestoreStatus.RUNNING, restoreError: null, restoredById: restoredById ?? null, }, @@ -140,7 +140,7 @@ export class BackupService implements OnModuleInit { const restored = await this.prisma.databaseBackup.update({ where: { id: backupId }, data: { - restoreStatus: 'COMPLETED' as any, + restoreStatus: RestoreStatus.COMPLETED, restoredAt: new Date(), restoreError: null, restoredById: restoredById ?? null, @@ -154,7 +154,7 @@ export class BackupService implements OnModuleInit { await this.prisma.databaseBackup.update({ where: { id: backupId }, data: { - restoreStatus: 'FAILED' as any, + restoreStatus: RestoreStatus.FAILED, restoreError: message, restoredById: restoredById ?? null, }, @@ -200,7 +200,7 @@ export class BackupService implements OnModuleInit { data: { filename, filePath, - status: 'RUNNING' as any, + status: BackupStatus.RUNNING, trigger, initiatedById: initiatedById ?? null, }, @@ -215,7 +215,7 @@ export class BackupService implements OnModuleInit { const completed = await this.prisma.databaseBackup.update({ where: { id: backup.id }, data: { - status: 'COMPLETED' as any, + status: BackupStatus.COMPLETED, completedAt: new Date(), sizeBytes: BigInt(stats.size), checksum, @@ -223,7 +223,7 @@ export class BackupService implements OnModuleInit { }, }); - if (trigger === ('SCHEDULED' as any)) { + if (trigger === BackupTrigger.SCHEDULED) { await this.prisma.backupScheduleConfig.update({ where: { id: DEFAULT_SCHEDULE_ID }, data: { lastRunAt: new Date() }, @@ -238,7 +238,7 @@ export class BackupService implements OnModuleInit { await this.prisma.databaseBackup.update({ where: { id: backup.id }, data: { - status: 'FAILED' as any, + status: BackupStatus.FAILED, completedAt: new Date(), errorMessage: message, }, @@ -279,7 +279,7 @@ export class BackupService implements OnModuleInit { while (attempt < maxRetries && !success) { attempt++; try { - await this.createBackup('SCHEDULED' as any); + await this.createBackup(BackupTrigger.SCHEDULED); success = true; } catch (error) { const errorMessage = this.toErrorMessage(error); @@ -335,7 +335,7 @@ export class BackupService implements OnModuleInit { private async enforceRetentionPolicy() { const schedule = await this.getScheduleConfig(); const backups = await this.prisma.databaseBackup.findMany({ - where: { status: 'COMPLETED' as any }, + where: { status: BackupStatus.COMPLETED }, orderBy: { createdAt: 'desc' }, skip: schedule.retentionCount, }); @@ -355,7 +355,7 @@ export class BackupService implements OnModuleInit { const activeJobs = await this.prisma.databaseBackup.count({ where: { id: excludedBackupId ? { not: excludedBackupId } : undefined, - OR: [{ status: 'RUNNING' as any }, { restoreStatus: 'RUNNING' as any }], + OR: [{ status: BackupStatus.RUNNING }, { restoreStatus: RestoreStatus.RUNNING }], }, }); diff --git a/src/search/search-autocomplete.service.ts b/src/search/search-autocomplete.service.ts index 56bc79fb..253cff97 100644 --- a/src/search/search-autocomplete.service.ts +++ b/src/search/search-autocomplete.service.ts @@ -80,7 +80,7 @@ export class SearchAutocompleteService { */ private async getPropertySuggestions(query: string, limit: number): Promise { try { - const properties = await (this.prisma as any).property.findMany({ + const properties = await this.prisma.property.findMany({ where: { deleted: false, OR: [ @@ -108,7 +108,9 @@ export class SearchAutocompleteService { */ private async getLocationSuggestions(query: string, limit: number): Promise { try { - const rows = await (this.prisma as any).$queryRaw` + const rows = await this.prisma.$queryRaw< + Array<{ city: string; state: string; zip_code: string }> + >` SELECT DISTINCT city, state, zip_code FROM properties WHERE deleted = false @@ -120,7 +122,7 @@ export class SearchAutocompleteService { LIMIT ${limit} `; - return rows.map((r: any) => ({ + return rows.map((r) => ({ text: `${r.city}, ${r.state} ${r.zip_code}`.trim(), type: 'location' as const, })); @@ -176,7 +178,7 @@ export class SearchAutocompleteService { if (!userId) return []; try { - const history = await (this.prisma as any).searchHistory.findMany({ + const history = await this.prisma.searchHistory.findMany({ where: { userId, query: { contains: query, mode: 'insensitive' }, @@ -206,7 +208,7 @@ export class SearchAutocompleteService { */ private async getPopularSearchSuggestions(query: string, limit: number): Promise { try { - const popular = await (this.prisma as any).popularSearch.findMany({ + const popular = await this.prisma.popularSearch.findMany({ where: { query: { contains: query, mode: 'insensitive' }, }, @@ -329,13 +331,13 @@ export class SearchAutocompleteService { async recordSuggestionClick(suggestion: string, userId: string): Promise { try { - await (this.prisma as any).searchHistory.upsert({ + await this.prisma.searchHistory.upsert({ where: { userId_query: { userId, query: suggestion } }, update: { frequency: { increment: 1 }, lastSearched: new Date() }, create: { userId, query: suggestion, frequency: 1 }, }); - await (this.prisma as any).popularSearch.upsert({ + await this.prisma.popularSearch.upsert({ where: { query: suggestion }, update: { frequency: { increment: 1 }, lastUpdated: new Date() }, create: { query: suggestion, frequency: 1, trend: 'stable' }, @@ -347,7 +349,7 @@ export class SearchAutocompleteService { async getPopularSearches(limit: number = 10): Promise { try { - const rows = await (this.prisma as any).popularSearch.findMany({ + const rows = await this.prisma.popularSearch.findMany({ orderBy: { frequency: 'desc' }, take: limit, select: { query: true }, @@ -371,7 +373,7 @@ export class SearchAutocompleteService { async getRecentSearches(userId: string, limit: number = 5): Promise { try { - const rows = await (this.prisma as any).searchHistory.findMany({ + const rows = await this.prisma.searchHistory.findMany({ where: { userId }, orderBy: { lastSearched: 'desc' }, take: limit, diff --git a/src/search/search.service.spec.ts b/src/search/search.service.spec.ts index 4fc74545..2098a743 100644 --- a/src/search/search.service.spec.ts +++ b/src/search/search.service.spec.ts @@ -11,38 +11,38 @@ import { SearchFacetsService } from './search-facets.service'; describe('SearchService', () => { let service: SearchService; let prisma: { property: { findMany: jest.Mock } }; - let geographicService: jest.Mocked; - let filtersService: jest.Mocked; - let autocompleteService: jest.Mocked; - let analyticsService: jest.Mocked; - let historyService: jest.Mocked; - let facetsService: jest.Mocked; + let geographicService: jest.Mocked>; + let filtersService: jest.Mocked>; + let autocompleteService: jest.Mocked>; + let analyticsService: jest.Mocked>; + let historyService: jest.Mocked>; + let facetsService: jest.Mocked>; beforeEach(async () => { prisma = { property: { findMany: jest.fn().mockResolvedValue([]) } }; geographicService = { applyGeographicFilter: jest.fn().mockResolvedValue({}), - } as any; + }; filtersService = { applyFilters: jest.fn().mockResolvedValue({}), getSavedFilters: jest.fn().mockResolvedValue([]), saveFilter: jest.fn().mockResolvedValue({ id: '1' }), - } as any; + }; autocompleteService = { getSuggestions: jest.fn().mockResolvedValue(['suggestion1']), - } as any; + }; analyticsService = { recordSearch: jest.fn().mockResolvedValue('query-id-1'), recordSearchError: jest.fn().mockResolvedValue(undefined), getAnalytics: jest.fn().mockResolvedValue({ totalSearches: 0 }), getPopularSearches: jest.fn().mockResolvedValue(['popular']), - } as any; + }; historyService = { record: jest.fn().mockResolvedValue(undefined), - } as any; + }; facetsService = { buildFacets: jest.fn().mockResolvedValue({}), - } as any; + }; const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -106,7 +106,9 @@ describe('SearchService', () => { }); it('should record analytics error on failure', async () => { - analyticsService.recordSearch.mockRejectedValueOnce(new Error('analytics fail')); + jest + .spyOn(analyticsService, 'recordSearch') + .mockRejectedValueOnce(new Error('analytics fail')); await expect(service.searchProperties('user-1', { query: 'test' })).rejects.toThrow( 'analytics fail', diff --git a/src/search/search.service.ts b/src/search/search.service.ts index 4da29855..2bbf244a 100644 --- a/src/search/search.service.ts +++ b/src/search/search.service.ts @@ -101,7 +101,7 @@ export class SearchService { const nextCursor = items.length === limit && items.length > 0 - ? Buffer.from((items[items.length - 1] as any).createdAt.toISOString()).toString('base64') + ? Buffer.from(items[items.length - 1].createdAt.toISOString()).toString('base64') : null; // Generate facets diff --git a/src/transactions/disputes.controller.ts b/src/transactions/disputes.controller.ts index 988cb716..7b75e796 100644 --- a/src/transactions/disputes.controller.ts +++ b/src/transactions/disputes.controller.ts @@ -4,7 +4,7 @@ import { CreateDisputeDto, ResolveDisputeDto } from './dto/dispute.dto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../auth/guards/roles.guard'; import { Roles } from '../auth/decorators/roles.decorator'; -import { DisputeStatus } from '../types/prisma.types'; +import { DisputeStatus, UserRole } from '../types/prisma.types'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; @@ -35,7 +35,7 @@ export class DisputesController { } @Patch(':id/resolve') - @Roles('ADMIN' as any) + @Roles(UserRole.ADMIN) @ApiOperation({ summary: 'Resolve a dispute (Admin only)' }) @ApiResponse({ status: 200, description: 'Dispute resolved successfully' }) resolve(@Req() req: any, @Param('id') id: string, @Body() dto: ResolveDisputeDto) { @@ -43,7 +43,7 @@ export class DisputesController { } @Patch(':id/status') - @Roles('ADMIN' as any) + @Roles(UserRole.ADMIN) @ApiOperation({ summary: 'Update dispute status (Admin only) (#564)' }) @ApiResponse({ status: 200, description: 'Dispute status updated' }) updateStatus(@Req() req: any, @Param('id') id: string, @Body() dto: UpdateDisputeStatusDto) { diff --git a/src/transactions/dto/transaction.dto.ts b/src/transactions/dto/transaction.dto.ts index 7529f8d2..eceac0cd 100644 --- a/src/transactions/dto/transaction.dto.ts +++ b/src/transactions/dto/transaction.dto.ts @@ -138,6 +138,11 @@ export class TransactionResponseDto { } export class TransactionListQueryDto { + @ApiPropertyOptional() + @IsOptional() + @IsString() + cursor?: string; + @ApiPropertyOptional() @IsOptional() @IsUUID() diff --git a/src/transactions/transaction-audit.service.ts b/src/transactions/transaction-audit.service.ts index 6c070be0..a999f397 100644 --- a/src/transactions/transaction-audit.service.ts +++ b/src/transactions/transaction-audit.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { TransactionStatus } from '../types/prisma.types'; import { PrismaService } from '../database/prisma.service'; export interface AuditContext { @@ -21,7 +22,7 @@ export class TransactionAuditService { async log( transactionId: string, - action: string, + status: TransactionStatus, previousData: object | null, newData: object | null, ctx: AuditContext = {}, @@ -29,7 +30,7 @@ export class TransactionAuditService { return this.prisma.transactionHistory.create({ data: { transactionId, - status: action as any, + status, actorId: ctx.actorId, notes: JSON.stringify({ previousData, newData }), metadata: { diff --git a/src/transactions/transaction-reminders.service.ts b/src/transactions/transaction-reminders.service.ts index fb01acb9..ee93c1c3 100644 --- a/src/transactions/transaction-reminders.service.ts +++ b/src/transactions/transaction-reminders.service.ts @@ -1,4 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; +import { MilestoneStatus } from '@prisma/client'; import { PrismaService } from '../database/prisma.service'; import { NotificationsService } from '../notifications/notifications.service'; import { CacheService } from '../cache/cache.service'; @@ -36,10 +37,10 @@ export class TransactionRemindersService { const milestones = await this.prisma.transactionMilestone.findMany({ where: { - status: 'PENDING' as any, + status: MilestoneStatus.PENDING, expectedDate: { gte: now, lte: cutoff }, reminderSentAt: null, - } as any, + }, include: { transaction: { select: { buyerId: true, sellerId: true }, @@ -89,6 +90,6 @@ export class TransactionRemindersService { private async getUserOptOut(userId: string): Promise { const prefs = await this.prisma.userPreferences.findUnique({ where: { userId } }); - return (prefs as any)?.optOutReminders ?? false; + return prefs?.optOutReminders ?? false; } } diff --git a/src/transactions/transactions.service.spec.ts b/src/transactions/transactions.service.spec.ts index 40fc8ba4..afd106f2 100644 --- a/src/transactions/transactions.service.spec.ts +++ b/src/transactions/transactions.service.spec.ts @@ -5,7 +5,11 @@ import { TransactionsService } from './transactions.service'; import { PrismaService } from '../database/prisma.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { NotificationsService } from '../notifications/notifications.service'; -import { TransactionAnalyticsGranularity, TransactionTypeDto } from './dto/transaction.dto'; +import { + TransactionAnalyticsGranularity, + TransactionStatusDto, + TransactionTypeDto, +} from './dto/transaction.dto'; import { CommissionsService } from '../commissions/commissions.service'; import { TransactionFeesService } from './transaction-fees.service'; import { TimelineService } from './timeline.service'; @@ -138,8 +142,8 @@ describe('TransactionsService', () => { propertyId: 'prop-1', buyerId: 'user-1', sellerId: 'user-2', - status: 'PENDING' as any, - type: 'SALE' as any, + status: TransactionStatusDto.PENDING, + type: TransactionTypeDto.SALE, }); expect(prisma.transaction.findMany).toHaveBeenCalledWith( @@ -160,7 +164,7 @@ describe('TransactionsService', () => { mockPrismaService.transaction.findMany.mockResolvedValue([]); mockPrismaService.transaction.count.mockResolvedValue(0); - await service.findAll({ page: 1, limit: 10, cursor } as any); + await service.findAll({ page: 1, limit: 10, cursor }); expect(prisma.transaction.findMany).toHaveBeenCalledWith( expect.objectContaining({ @@ -283,7 +287,7 @@ describe('TransactionsService', () => { status: 'COMPLETED', }); - const result = await service.update('t-1', { status: 'COMPLETED' as any }); + const result = await service.update('t-1', { status: TransactionStatusDto.COMPLETED }); expect(result.status).toBe('COMPLETED'); expect(mockCommissionsService.updateCommissionsStatus).toHaveBeenCalledWith( @@ -295,9 +299,9 @@ describe('TransactionsService', () => { it('should throw NotFoundException if transaction not found', async () => { mockPrismaService.transaction.findUnique.mockResolvedValue(null); - await expect(service.update('nonexistent', { status: 'COMPLETED' as any })).rejects.toThrow( - NotFoundException, - ); + await expect( + service.update('nonexistent', { status: TransactionStatusDto.COMPLETED }), + ).rejects.toThrow(NotFoundException); }); }); diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 88eb1c67..1b71d487 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -104,7 +104,7 @@ export class TransactionsService { try { const page = query.page ?? 1; const limit = query.limit ?? 20; - const cursor = (query as any).cursor as string | undefined; + const cursor = query.cursor; const skip = cursor ? undefined : (page - 1) * limit; const where: Prisma.TransactionWhereInput = {}; @@ -452,7 +452,7 @@ export class TransactionsService { // Audit log the transition (#557) await this.transactionAuditService.log( transactionId, - 'STATUS_TRANSITION', + nextStatus, { status: currentStatus }, { status: nextStatus }, { actorId },