diff --git a/api/src/controllers/property.controller.ts b/api/src/controllers/property.controller.ts index 0d31e2b613..30bfddaee7 100644 --- a/api/src/controllers/property.controller.ts +++ b/api/src/controllers/property.controller.ts @@ -11,6 +11,7 @@ import { ValidationPipe, Delete, UseGuards, + Request, } from '@nestjs/common'; import { ApiExtraModels, @@ -18,6 +19,7 @@ import { ApiOperation, ApiTags, } from '@nestjs/swagger'; +import { Request as ExpressRequest } from 'express'; import { PermissionTypeDecorator } from '../decorators/permission-type.decorator'; import { PaginatedPropertyDto } from '../dtos/properties/paginated-property.dto'; import { PropertyQueryParams } from '../dtos/properties/property-query-params.dto'; @@ -30,11 +32,13 @@ import { SuccessDTO } from '../dtos/shared/success.dto'; import { defaultValidationPipeOptions } from '../utilities/default-validation-pipe-options'; import { PaginationMeta } from '../dtos/shared/pagination.dto'; import { JwtAuthGuard } from '../guards/jwt.guard'; -import { PermissionGuard } from '../guards/permission.guard'; import { PermissionAction } from '../decorators/permission-action.decorator'; import { permissionActions } from '../enums/permissions/permission-actions-enum'; import { ApiKeyGuard } from '../guards/api-key.guard'; import { PropertyFilterParams } from '../dtos/properties/property-filter-params.dto'; +import { mapTo } from '../utilities/mapTo'; +import { User } from '../../src/dtos/users/user.dto'; +import { PermissionGuard } from '../../src/guards/permission.guard'; @Controller('properties') @ApiTags('properties') @@ -48,7 +52,7 @@ import { PropertyFilterParams } from '../dtos/properties/property-filter-params. IdDTO, ) @PermissionTypeDecorator('properties') -@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard) +@UseGuards(ApiKeyGuard, JwtAuthGuard) export class PropertyController { constructor(private readonly propertyService: PropertyService) {} @@ -59,6 +63,7 @@ export class PropertyController { }) @UsePipes(new ValidationPipe(defaultValidationPipeOptions)) @ApiOkResponse({ type: PaginatedPropertyDto }) + @UseGuards(PermissionGuard) public async getPaginatedSet( @Query() queryParams: PropertyQueryParams, ): Promise { @@ -71,6 +76,7 @@ export class PropertyController { operationId: 'getById', }) @ApiOkResponse({ type: Property }) + @UseGuards(PermissionGuard) public async getPropertyById( @Param('id', new ParseUUIDPipe({ version: '4' })) propertyId: string, ): Promise { @@ -85,6 +91,7 @@ export class PropertyController { @PermissionAction(permissionActions.read) @UsePipes(new ValidationPipe(defaultValidationPipeOptions)) @ApiOkResponse({ type: PaginatedPropertyDto }) + @UseGuards(PermissionGuard) public async getFiltrablePaginatedSet( @Body() queryParams: PropertyQueryParams, ): Promise { @@ -100,8 +107,12 @@ export class PropertyController { @ApiOkResponse({ type: Property }) public async addProperty( @Body() propertyDto: PropertyCreate, + @Request() req: ExpressRequest, ): Promise { - return await this.propertyService.create(propertyDto); + return await this.propertyService.create( + propertyDto, + mapTo(User, req['user']), + ); } @Put() @@ -113,8 +124,12 @@ export class PropertyController { @ApiOkResponse({ type: Property }) public async updateProperty( @Body() propertyDto: PropertyUpdate, + @Request() req: ExpressRequest, ): Promise { - return await this.propertyService.update(propertyDto); + return await this.propertyService.update( + propertyDto, + mapTo(User, req['user']), + ); } @Delete() @@ -124,7 +139,13 @@ export class PropertyController { }) @UsePipes(new ValidationPipe(defaultValidationPipeOptions)) @ApiOkResponse({ type: SuccessDTO }) - public async deleteById(@Body() idDto: IdDTO): Promise { - return await this.propertyService.deleteOne(idDto.id); + public async deleteById( + @Body() idDto: IdDTO, + @Request() req: ExpressRequest, + ): Promise { + return await this.propertyService.deleteOne( + idDto.id, + mapTo(User, req['user']), + ); } } diff --git a/api/src/permission-configs/permission_policy.csv b/api/src/permission-configs/permission_policy.csv index 5b79a02c74..f8d0d4014d 100644 --- a/api/src/permission-configs/permission_policy.csv +++ b/api/src/permission-configs/permission_policy.csv @@ -28,7 +28,6 @@ p, supportAdmin, properties, true, read p, jurisdictionAdmin, properties, true, read p, limitedJurisdictionAdmin, properties, true, read p, partner, properties, true, read -p, anonymous, properties, true, read p, admin, agency, true, .* p, supportAdmin, agency, true, read @@ -54,18 +53,6 @@ p, jurisdictionAdmin, listingEvent, true, .* p, limitedJurisdictionAdmin, listingEvent, true, .* p, partner, listingEvent, true, read -p, admin, property, true, .* -p, supportAdmin, property, true, .* -p, jurisdictionAdmin, property, true, .* -p, limitedJurisdictionAdmin, property, true, .* -p, partner, property, true, read - -p, admin, propertyGroup, true, .* -p, supportAdmin, propertyGroup, true, .* -p, jurisdictionAdmin, propertyGroup, true, .* -p, limitedJurisdictionAdmin, propertyGroup, true, .* -p, partner, propertyGroup, true, read - p, admin, amiChart, true, .* p, supportAdmin, amiChart, true, .* p, jurisdictionAdmin, amiChart, true, .* diff --git a/api/src/services/property.service.ts b/api/src/services/property.service.ts index c463d3dad2..d7664081e3 100644 --- a/api/src/services/property.service.ts +++ b/api/src/services/property.service.ts @@ -15,13 +15,19 @@ import { Property } from '../dtos/properties/property.dto'; import { PaginatedPropertyDto } from '../dtos/properties/paginated-property.dto'; import PropertyCreate from '../dtos/properties/property-create.dto'; import { PropertyUpdate } from '../dtos/properties/property-update.dto'; +import { User } from '../dtos/users/user.dto'; +import { permissionActions } from '../enums/permissions/permission-actions-enum'; import { SuccessDTO } from '../dtos/shared/success.dto'; import { Prisma } from '@prisma/client'; import { buildFilter } from '../utilities/build-filter'; +import { PermissionService } from './permission.service'; @Injectable() export class PropertyService { - constructor(private prisma: PrismaService) {} + constructor( + private prisma: PrismaService, + private permissionService: PermissionService, + ) {} /** * Returns a paginated list of properties matching the provided query parameters. @@ -99,7 +105,7 @@ export class PropertyService { * @throws {BadRequestException} If a jurisdiction is not provided. * @throws {NotFoundException} If the linked jurisdiction cannot be found. */ - async create(propertyDto: PropertyCreate) { + async create(propertyDto: PropertyCreate, requestingUser: User) { if (!propertyDto.jurisdictions) { throw new BadRequestException('A jurisdiction must be provided'); } @@ -120,6 +126,15 @@ export class PropertyService { ); } + await this.permissionService.canOrThrow( + requestingUser, + 'properties', + permissionActions.create, + { + jurisdictionId: rawJurisdiction.id, + }, + ); + const rawProperty = await this.prisma.properties.create({ data: { ...propertyDto, @@ -147,7 +162,7 @@ export class PropertyService { * @throws {BadRequestException} If a jurisdiction is not provided. * @throws {NotFoundException} If the linked jurisdiction cannot be found. */ - async update(propertyDto: PropertyUpdate) { + async update(propertyDto: PropertyUpdate, requestingUser: User) { if (!propertyDto.jurisdictions) { throw new BadRequestException('A jurisdiction must be provided'); } @@ -169,6 +184,17 @@ export class PropertyService { await this.findOrThrow(propertyDto.id); + await this.permissionService.canOrThrow( + requestingUser, + 'properties', + permissionActions.update, + { + jurisdictionId: rawJurisdiction.id, + }, + ); + + await this.findOrThrow(propertyDto.id); + const rawProperty = await this.prisma.properties.update({ data: { ...propertyDto, @@ -199,7 +225,7 @@ export class PropertyService { * @throws {BadRequestException} If no property ID is provided. * @throws {NotFoundException} If the property or its linked jurisdiction is not found. */ - async deleteOne(propertyId: string) { + async deleteOne(propertyId: string, requestingUser: User) { if (!propertyId) { throw new BadRequestException('a property ID must be provided'); } @@ -227,6 +253,15 @@ export class PropertyService { ); } + await this.permissionService.canOrThrow( + requestingUser, + 'properties', + permissionActions.delete, + { + jurisdictionId: rawJurisdiction.id, + }, + ); + await this.prisma.properties.delete({ where: { id: propertyId, @@ -243,7 +278,7 @@ export class PropertyService { * * @param propertyId - The ID of the property to look up. * @returns The raw property entity including its jurisdictions. - * @throws {BadRequestException} If no property is found for the given ID. + * @throws {NotFoundException} If no property is found for the given ID. */ async findOrThrow(propertyId: string): Promise { const property = await this.prisma.properties.findFirst({ @@ -256,7 +291,7 @@ export class PropertyService { }); if (!property) { - throw new BadRequestException( + throw new NotFoundException( `Property with id ${propertyId} was not found`, ); } diff --git a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts index e5aa457ead..e25489ad48 100644 --- a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts @@ -1479,7 +1479,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .expect(200); }); - it('should error as forbidden for create endpoint', async () => { + it('should succeed for create endpoint', async () => { const propertyData = { name: 'New Test Property', jurisdictions: { @@ -1492,7 +1492,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .send(propertyData) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(403); + .expect(201); }); it('should succeed for filterable list endpoint', async () => { @@ -1504,7 +1504,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .expect(201); }); - it('should error as forbidden for update endpoint', async () => { + it('should succeed for update endpoint', async () => { if (!propertyId) { throw new Error('Property ID not set up for test'); } @@ -1522,10 +1522,10 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .send(propertyUpdateData) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(403); + .expect(200); }); - it('should error as forbidden for delete endpoint', async () => { + it('should succeed for delete endpoint', async () => { const propertyData = { name: 'Property to Delete', jurisdictions: { @@ -1553,7 +1553,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr } as IdDTO) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(403); + .expect(200); }); }); diff --git a/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts index e519646d93..54b97c3113 100644 --- a/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts @@ -1380,15 +1380,15 @@ describe('Testing Permissioning of endpoints as public user', () => { } }); - it('should succeed for list endpoint', async () => { + it('should error as forbidden for list endpoint', async () => { await request(app.getHttpServer()) .get(`/properties?`) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(200); + .expect(403); }); - it('should succeed for retrieve endpoint', async () => { + it('should error as forbidden for retrieve endpoint', async () => { if (!propertyId) { throw new Error('Property ID not set up for test'); } @@ -1397,7 +1397,7 @@ describe('Testing Permissioning of endpoints as public user', () => { .get(`/properties/${propertyId}`) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(200); + .expect(403); }); it('should error as forbidden for create endpoint', async () => { @@ -1416,13 +1416,13 @@ describe('Testing Permissioning of endpoints as public user', () => { .expect(403); }); - it('should succeed for filterable list endpoint', async () => { + it('should error as forbidden for filterable list endpoint', async () => { await request(app.getHttpServer()) .post(`/properties/list`) .send({}) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(201); + .expect(403); }); it('should error as forbidden for update endpoint', async () => { diff --git a/api/test/integration/property.e2e-spec.ts b/api/test/integration/property.e2e-spec.ts index cd3c80c591..358ed738c4 100644 --- a/api/test/integration/property.e2e-spec.ts +++ b/api/test/integration/property.e2e-spec.ts @@ -385,7 +385,7 @@ describe('Properties Controller Tests', () => { expect(res.body.message[0]).toEqual('id should not be null or undefined'); }); - it('should throw error when an given ID does not exist', async () => { + it('should throw error when a given ID does not exist', async () => { const randId = randomUUID(); const res = await request(app.getHttpServer()) .put('/properties') @@ -396,7 +396,7 @@ describe('Properties Controller Tests', () => { }) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(400); + .expect(404); expect(res.body.message).toEqual( `Property with id ${randId} was not found`, @@ -453,7 +453,7 @@ describe('Properties Controller Tests', () => { expect(res.body.message[0]).toBe('id should not be null or undefined'); }); - it('should throw error when an given ID does not exist', async () => { + it('should throw error when a given ID does not exist', async () => { const randId = randomUUID(); const res = await request(app.getHttpServer()) .delete('/properties') @@ -462,7 +462,7 @@ describe('Properties Controller Tests', () => { }) .set({ passkey: process.env.API_PASS_KEY || '' }) .set('Cookie', cookies) - .expect(400); + .expect(404); expect(res.body.message).toBe(`Property with id ${randId} was not found`); }); diff --git a/api/test/unit/services/geocoding.service.spec.ts b/api/test/unit/services/geocoding.service.spec.ts index af65ea73b6..642cf8a75f 100644 --- a/api/test/unit/services/geocoding.service.spec.ts +++ b/api/test/unit/services/geocoding.service.spec.ts @@ -20,8 +20,6 @@ describe('GeocodingService', () => { const date = new Date(); const address: Address = { id: 'id', - createdAt: date, - updatedAt: date, city: 'Washington', county: null, state: 'DC', diff --git a/api/test/unit/services/jurisdiction.service.spec.ts b/api/test/unit/services/jurisdiction.service.spec.ts index 8bba3b2393..ec10fe8f49 100644 --- a/api/test/unit/services/jurisdiction.service.spec.ts +++ b/api/test/unit/services/jurisdiction.service.spec.ts @@ -147,6 +147,10 @@ describe('Testing jurisdiction service', () => { allowSingleUseCodeLogin: false, listingApprovalPermissions: [], duplicateListingPermissions: [], + regions: [], + requiredListingFields: [], + visibleAccessibilityPriorityTypes: [], + visibleNeighborhoodAmenities: [], }; expect(await service.create(params)).toEqual(mockedValue); @@ -167,6 +171,11 @@ describe('Testing jurisdiction service', () => { allowSingleUseCodeLogin: false, listingApprovalPermissions: [], duplicateListingPermissions: [], + listingFeaturesConfiguration: undefined, + regions: [], + requiredListingFields: [], + visibleAccessibilityPriorityTypes: [], + visibleNeighborhoodAmenities: [], }, include: { featureFlags: true, @@ -204,6 +213,10 @@ describe('Testing jurisdiction service', () => { allowSingleUseCodeLogin: false, listingApprovalPermissions: [], duplicateListingPermissions: [], + regions: [], + requiredListingFields: [], + visibleAccessibilityPriorityTypes: [], + visibleNeighborhoodAmenities: [], }; expect(await service.update(params)).toEqual({ @@ -245,6 +258,11 @@ describe('Testing jurisdiction service', () => { allowSingleUseCodeLogin: false, listingApprovalPermissions: [], duplicateListingPermissions: [], + listingFeaturesConfiguration: undefined, + regions: [], + requiredListingFields: [], + visibleAccessibilityPriorityTypes: [], + visibleNeighborhoodAmenities: [], }, where: { id: mockedJurisdiction.id, @@ -276,6 +294,10 @@ describe('Testing jurisdiction service', () => { allowSingleUseCodeLogin: false, listingApprovalPermissions: [], duplicateListingPermissions: [], + regions: [], + requiredListingFields: [], + visibleAccessibilityPriorityTypes: [], + visibleNeighborhoodAmenities: [], }; await expect(async () => await service.update(params)).rejects.toThrowError( diff --git a/api/test/unit/services/listing-csv-export.service.spec.ts b/api/test/unit/services/listing-csv-export.service.spec.ts index 07f5f4d1f3..8195f575f5 100644 --- a/api/test/unit/services/listing-csv-export.service.spec.ts +++ b/api/test/unit/services/listing-csv-export.service.spec.ts @@ -38,9 +38,9 @@ describe('Testing listing csv export service', () => { afterEach(() => { writeStream.end(); - fs.unlink('sampleFile.csv', () => { - // do nothing - }); + if (fs.existsSync('sampleFile.csv')) { + fs.unlinkSync('sampleFile.csv'); + } jest.restoreAllMocks(); }); const timestamp = new Date(1759430299657); @@ -64,6 +64,8 @@ describe('Testing listing csv export service', () => { duplicateListingPermissions: [], requiredListingFields: [], visibleNeighborhoodAmenities: [], + regions: [], + visibleAccessibilityPriorityTypes: [], }; const mockBaseUnit: Unit = { @@ -115,8 +117,6 @@ describe('Testing listing csv export service', () => { latitude: 100.5, longitude: 200.5, id: 'listingbuildingaddress1-ID', - createdAt: timestamp, - updatedAt: timestamp, }, neighborhood: 'neighborhood', yearBuilt: 2025, @@ -154,8 +154,6 @@ describe('Testing listing csv export service', () => { latitude: 100.5, longitude: 200.5, id: 'listingleasingagentaddress1-ID', - createdAt: timestamp, - updatedAt: timestamp, }, listingsApplicationMailingAddress: { street: '456 main st', @@ -165,8 +163,6 @@ describe('Testing listing csv export service', () => { latitude: 100.5, longitude: 200.5, id: 'listingmailingaddress1-ID', - createdAt: timestamp, - updatedAt: timestamp, }, listingsApplicationPickUpAddress: { street: '789 main st', @@ -176,8 +172,6 @@ describe('Testing listing csv export service', () => { latitude: 100.5, longitude: 200.5, id: 'listingpickupaddress1-ID', - createdAt: timestamp, - updatedAt: timestamp, }, applicationDueDate: timestamp, listingMultiselectQuestions: [], diff --git a/api/test/unit/services/listing.service.spec.ts b/api/test/unit/services/listing.service.spec.ts index e40ff5ebd1..1e5a80edd0 100644 --- a/api/test/unit/services/listing.service.spec.ts +++ b/api/test/unit/services/listing.service.spec.ts @@ -219,16 +219,16 @@ const lotteryPublishedApplicantMock = jest.fn(); const canOrThrowMock = jest.fn(); -const user = new User(); -user.firstName = 'Test'; -user.lastName = 'User'; -user.email = 'test@example.com'; - describe('Testing listing service', () => { let service: ListingService; let prisma: PrismaService; let config: ConfigService; + const user = new User(); + user.firstName = 'Test'; + user.lastName = 'User'; + user.email = 'test@example.com'; + const googleTranslateServiceMock = { isConfigured: () => true, fetch: jest.fn(), diff --git a/api/test/unit/services/lottery.service.spec.ts b/api/test/unit/services/lottery.service.spec.ts index 4f6f0ac267..9b3f2568ea 100644 --- a/api/test/unit/services/lottery.service.spec.ts +++ b/api/test/unit/services/lottery.service.spec.ts @@ -37,17 +37,17 @@ const lotteryReleasedMock = jest.fn(); const lotteryPublishedAdminMock = jest.fn(); const lotteryPublishedApplicantMock = jest.fn(); -const user = new User(); -user.firstName = 'Test'; -user.lastName = 'User'; -user.email = 'test@example.com'; - describe('Testing lottery service', () => { let service: LotteryService; let prisma: PrismaService; let listingService: ListingService; let config: ConfigService; + const user = new User(); + user.firstName = 'Test'; + user.lastName = 'User'; + user.email = 'test@example.com'; + beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ diff --git a/api/test/unit/services/multiselect-question.service.spec.ts b/api/test/unit/services/multiselect-question.service.spec.ts index f80de42ef0..bcb5cb3968 100644 --- a/api/test/unit/services/multiselect-question.service.spec.ts +++ b/api/test/unit/services/multiselect-question.service.spec.ts @@ -23,9 +23,6 @@ import { PermissionService } from '../../../src/services/permission.service'; import { PrismaService } from '../../../src/services/prisma.service'; import { CronJobService } from '../../../src/services/cron-job.service'; -const user = new User(); -const canOrThrowMock = jest.fn(); - export const mockMultiselectQuestion = ( position: number, date: Date, @@ -63,6 +60,8 @@ export const mockMultiselectQuestion = ( describe('Testing multiselect question service', () => { let service: MultiselectQuestionService; let prisma: PrismaService; + const user = new User(); + const canOrThrowMock = jest.fn(); const mockMultiselectQuestionSet = (numberToCreate: number, date: Date) => { const toReturn = []; diff --git a/api/test/unit/services/property.service.spec.ts b/api/test/unit/services/property.service.spec.ts index b2e678009a..1034fce93d 100644 --- a/api/test/unit/services/property.service.spec.ts +++ b/api/test/unit/services/property.service.spec.ts @@ -9,11 +9,14 @@ import PropertyCreate from '../../../src/dtos/properties/property-create.dto'; import { PropertyUpdate } from '../../../src/dtos/properties/property-update.dto'; import { Prisma } from '@prisma/client'; import { Compare } from '../../../src/dtos/shared/base-filter.dto'; +import { User } from '../../../src/dtos/users/user.dto'; describe('Testing property service', () => { let service: PropertyService; let prisma: PrismaService; + const user = new User(); + const mockProperty = ( position: number, date: Date, @@ -371,7 +374,7 @@ describe('Testing property service', () => { .fn() .mockResolvedValue(mockCreatedProperty); - const result = await service.create(propertyDto); + const result = await service.create(propertyDto, user); expect(result).toEqual(mockCreatedProperty); expect(prisma.jurisdictions.findFirst).toHaveBeenCalledWith({ @@ -407,10 +410,10 @@ describe('Testing property service', () => { }; await expect( - async () => await service.create(propertyDto), + async () => await service.create(propertyDto, user), ).rejects.toThrow(BadRequestException); await expect( - async () => await service.create(propertyDto), + async () => await service.create(propertyDto, user), ).rejects.toThrowError('A jurisdiction must be provided'); expect(prisma.jurisdictions.findFirst).not.toHaveBeenCalled(); @@ -430,10 +433,10 @@ describe('Testing property service', () => { prisma.jurisdictions.findFirst = jest.fn().mockResolvedValue(null); await expect( - async () => await service.create(propertyDto), + async () => await service.create(propertyDto, user), ).rejects.toThrow(NotFoundException); await expect( - async () => await service.create(propertyDto), + async () => await service.create(propertyDto, user), ).rejects.toThrowError( `Entry for the linked jurisdiction with id: ${jurisdictionId} was not found`, ); @@ -485,7 +488,7 @@ describe('Testing property service', () => { .fn() .mockResolvedValue(mockCreatedProperty); - const result = await service.create(propertyDto); + const result = await service.create(propertyDto, user); expect(result).toEqual(mockCreatedProperty); }); @@ -540,7 +543,7 @@ describe('Testing property service', () => { .fn() .mockResolvedValue(mockUpdatedProperty); - const result = await service.update(propertyDto); + const result = await service.update(propertyDto, user); expect(result).toEqual(mockUpdatedProperty); expect(prisma.jurisdictions.findFirst).toHaveBeenCalledWith({ @@ -585,10 +588,10 @@ describe('Testing property service', () => { }; await expect( - async () => await service.update(propertyDto), + async () => await service.update(propertyDto, user), ).rejects.toThrow(BadRequestException); await expect( - async () => await service.update(propertyDto), + async () => await service.update(propertyDto, user), ).rejects.toThrowError('A jurisdiction must be provided'); expect(prisma.jurisdictions.findFirst).not.toHaveBeenCalled(); @@ -607,10 +610,10 @@ describe('Testing property service', () => { prisma.jurisdictions.findFirst = jest.fn().mockResolvedValue(null); await expect( - async () => await service.update(propertyDto), + async () => await service.update(propertyDto, user), ).rejects.toThrow(NotFoundException); await expect( - async () => await service.update(propertyDto), + async () => await service.update(propertyDto, user), ).rejects.toThrowError( `Entry for the linked jurisdiction with id: ${jurisdictionId} was not found`, ); @@ -645,10 +648,10 @@ describe('Testing property service', () => { prisma.properties.findFirst = jest.fn().mockResolvedValue(null); await expect( - async () => await service.update(propertyDto), - ).rejects.toThrow(BadRequestException); + async () => await service.update(propertyDto, user), + ).rejects.toThrow(NotFoundException); await expect( - async () => await service.update(propertyDto), + async () => await service.update(propertyDto, user), ).rejects.toThrowError(`Property with id ${propertyId} was not found`); expect(prisma.properties.update).not.toHaveBeenCalled(); @@ -681,7 +684,7 @@ describe('Testing property service', () => { .mockResolvedValue(mockJurisdiction); prisma.properties.delete = jest.fn().mockResolvedValue(mockProperty); - const result = await service.deleteOne(propertyId); + const result = await service.deleteOne(propertyId, user); expect(result).toEqual({ success: true }); expect(prisma.properties.findFirst).toHaveBeenCalledWith({ @@ -708,11 +711,11 @@ describe('Testing property service', () => { }); it('should error when property id is not provided', async () => { - await expect(async () => await service.deleteOne('')).rejects.toThrow( - BadRequestException, - ); await expect( - async () => await service.deleteOne(''), + async () => await service.deleteOne('', user), + ).rejects.toThrow(BadRequestException); + await expect( + async () => await service.deleteOne('', user), ).rejects.toThrowError('a property ID must be provided'); expect(prisma.properties.findFirst).not.toHaveBeenCalled(); @@ -725,10 +728,10 @@ describe('Testing property service', () => { prisma.properties.findFirst = jest.fn().mockResolvedValue(null); await expect( - async () => await service.deleteOne(propertyId), - ).rejects.toThrow(BadRequestException); + async () => await service.deleteOne(propertyId, user), + ).rejects.toThrow(NotFoundException); await expect( - async () => await service.deleteOne(propertyId), + async () => await service.deleteOne(propertyId, user), ).rejects.toThrowError(`Property with id ${propertyId} was not found`); expect(prisma.properties.delete).not.toHaveBeenCalled(); @@ -749,10 +752,10 @@ describe('Testing property service', () => { prisma.properties.findFirst = jest.fn().mockResolvedValue(mockProperty); await expect( - async () => await service.deleteOne(propertyId), + async () => await service.deleteOne(propertyId, user), ).rejects.toThrow(NotFoundException); await expect( - async () => await service.deleteOne(propertyId), + async () => await service.deleteOne(propertyId, user), ).rejects.toThrowError( 'The property is not connected to any jurisdiction', ); @@ -779,10 +782,10 @@ describe('Testing property service', () => { prisma.jurisdictions.findFirst = jest.fn().mockResolvedValue(null); await expect( - async () => await service.deleteOne(propertyId), + async () => await service.deleteOne(propertyId, user), ).rejects.toThrow(NotFoundException); await expect( - async () => await service.deleteOne(propertyId), + async () => await service.deleteOne(propertyId, user), ).rejects.toThrowError( `Entry for the linked jurisdiction with id: ${jurisdictionId} was not found`, ); @@ -829,7 +832,7 @@ describe('Testing property service', () => { await expect( async () => await service.findOrThrow(propertyId), - ).rejects.toThrow(BadRequestException); + ).rejects.toThrow(NotFoundException); await expect( async () => await service.findOrThrow(propertyId), ).rejects.toThrowError(`Property with id ${propertyId} was not found`); diff --git a/sites/partners/src/components/settings/PropertyDrawer.tsx b/sites/partners/src/components/settings/PropertyDrawer.tsx index 5bd4396051..2f6867462d 100644 --- a/sites/partners/src/components/settings/PropertyDrawer.tsx +++ b/sites/partners/src/components/settings/PropertyDrawer.tsx @@ -41,17 +41,10 @@ export const PropertyDrawer = ({ const { profile } = useContext(AuthContext) // eslint-disable-next-line @typescript-eslint/unbound-method const { register, errors, clearErrors, trigger, getValues } = useForm() - const propretyHasListing = listings?.find( + const propertyHasListing = listings?.find( (listing) => listing.property?.id === editedProperty?.id ) - const handleSave = useCallback(async () => { - const validated = await trigger() - if (!validated) return - - saveQuestion(getValues()) - }, [trigger, getValues, saveQuestion]) - const jurisdictionOptions: SelectOption[] = profile.jurisdictions.length !== 0 ? [ @@ -66,11 +59,22 @@ export const PropertyDrawer = ({ const defaultJurisdiction = editedProperty?.jurisdictions ? editedProperty.jurisdictions.id : jurisdictionOptions.length !== 0 - ? jurisdictionOptions[0].value + ? jurisdictionOptions[1].value : null + const handleSave = useCallback(async () => { + const validated = await trigger() + if (!validated) return + const values = getValues() + if (!values.jurisdictions?.id && defaultJurisdiction) { + values.jurisdictions = { id: defaultJurisdiction } + } + + saveQuestion(values) + }, [trigger, getValues, defaultJurisdiction, saveQuestion]) + return ( - + {t(editedProperty ? "properties.drawer.editTitle" : "properties.drawer.addTitle")} diff --git a/sites/partners/src/pages/settings/properties.tsx b/sites/partners/src/pages/settings/properties.tsx index afb11f723b..e901d45f19 100644 --- a/sites/partners/src/pages/settings/properties.tsx +++ b/sites/partners/src/pages/settings/properties.tsx @@ -40,7 +40,12 @@ const SettingsProperties = () => { ) const v2Preferences = doJurisdictionsHaveFeatureFlagOn(FeatureFlagEnum.enableV2MSQ) - if (profile?.userRoles?.isPartner || profile?.userRoles?.isSupportAdmin || !enableProperties) { + if ( + !enableProperties || + profile?.userRoles?.isPartner || + profile?.userRoles?.isSupportAdmin || + profile?.userRoles?.isLimitedJurisdictionalAdmin + ) { void router.push("/unauthorized") }