diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b312ed4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Compile + run: ./mvnw compile -B + + - name: Run tests + run: ./mvnw test -B diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..7164225 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..7f3c032 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.0/maven-wrapper-3.3.0.jar diff --git a/pom.xml b/pom.xml index 8d104b3..4dcc2fd 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ Environment manager 21 + 1.5.5.Final @@ -49,6 +50,15 @@ springdoc-openapi-starter-webmvc-ui 2.3.0 + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-actuator @@ -92,6 +102,16 @@ lombok 1.18.30 + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + diff --git a/src/main/java/preponderous/viron/controllers/DebugController.java b/src/main/java/preponderous/viron/controllers/DebugController.java index def2681..1dd54ba 100644 --- a/src/main/java/preponderous/viron/controllers/DebugController.java +++ b/src/main/java/preponderous/viron/controllers/DebugController.java @@ -1,17 +1,18 @@ package preponderous.viron.controllers; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import jakarta.validation.constraints.NotBlank; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import preponderous.viron.dto.EntityDto; +import preponderous.viron.dto.EnvironmentDto; +import preponderous.viron.exceptions.InvalidRequestException; +import preponderous.viron.mappers.EntityMapper; +import preponderous.viron.mappers.EnvironmentMapper; import preponderous.viron.models.Entity; import preponderous.viron.models.Environment; import preponderous.viron.models.Grid; @@ -24,19 +25,26 @@ @RestController @RequestMapping("/api/v1/debug") @Slf4j +@Validated public class DebugController { private final EntityService entityService; private final EnvironmentService environmentService; private final GridService gridService; private final LocationService locationService; + private final EntityMapper entityMapper; + private final EnvironmentMapper environmentMapper; - List entityNamePool = new ArrayList<>(Arrays.asList("Tom", "Jerry", "Spike", "Tyke", "Nibbles", "Butch", "Tuffy", "Lightning", "Mammy", "Quacker", "Toodles", "Droopy", "Screwy", "Meathead", "George", "Dripple", "McWolf")); + private final List entityNamePool = List.of("Tom", "Jerry", "Spike", "Tyke", "Nibbles", "Butch", "Tuffy", "Lightning", "Mammy", "Quacker", "Toodles", "Droopy", "Screwy", "Meathead", "George", "Dripple", "McWolf"); - public DebugController(EntityService entityService, EnvironmentService environmentService, GridService gridService, LocationService locationService) { + public DebugController(EntityService entityService, EnvironmentService environmentService, + GridService gridService, LocationService locationService, + EntityMapper entityMapper, EnvironmentMapper environmentMapper) { this.entityService = entityService; this.environmentService = environmentService; this.gridService = gridService; this.locationService = locationService; + this.entityMapper = entityMapper; + this.environmentMapper = environmentMapper; } /** @@ -44,10 +52,14 @@ public DebugController(EntityService entityService, EnvironmentService environme * It ensures the entities are properly created and assigned to valid locations in the grid. */ @PostMapping("/create-sample-data") - public ResponseEntity createSampleData() { + @ResponseStatus(HttpStatus.CREATED) + public EnvironmentDto createSampleData() { // create an environment with one 10x10 grid Environment environment = environmentService.createEnvironment("Sample Environment", 1, 10); List grids = gridService.getGridsInEnvironment(environment.getEnvironmentId()); + if (grids.isEmpty()) { + throw new InvalidRequestException("No grids found in environment: " + environment.getEnvironmentId()); + } Grid grid = grids.getFirst(); List locations = locationService.getLocationsInGrid(grid.getGridId()); @@ -66,12 +78,12 @@ public ResponseEntity createSampleData() { } } if (location == null) { - return ResponseEntity.badRequest().body(null); // exit if no valid location found + throw new InvalidRequestException("No valid location found at coordinates (" + x + ", " + y + ")"); } locationService.addEntityToLocation(entity.getEntityId(), location.getLocationId()); } - return ResponseEntity.ok(environment); + return environmentMapper.toDto(environment); } /** @@ -81,7 +93,8 @@ public ResponseEntity createSampleData() { * @param environmentName the name of the environment to be created */ @PostMapping("/create-world-and-place-entity/{environmentName}") - public ResponseEntity createWorldAndPlaceEntity(@PathVariable String environmentName) { + @ResponseStatus(HttpStatus.CREATED) + public EntityDto createWorldAndPlaceEntity(@PathVariable @NotBlank String environmentName) { // create an environment int numGrids = 1; int gridSize = 5; @@ -90,7 +103,10 @@ public ResponseEntity createWorldAndPlaceEntity(@PathVariable String env // get grid info List grids = gridService.getGridsInEnvironment(environment.getEnvironmentId()); - Grid grid = grids.get(0); + if (grids.isEmpty()) { + throw new InvalidRequestException("No grids found in environment: " + environment.getEnvironmentId()); + } + Grid grid = grids.getFirst(); log.info("Grid created: {} with size {}x{}", grid.getGridId(), grid.getRows(), grid.getColumns()); // create an entity @@ -110,11 +126,10 @@ public ResponseEntity createWorldAndPlaceEntity(@PathVariable String env } } if (location == null) { - log.error("No valid location found for entity at row {} and column {}", entityRow, entityColumn); - return ResponseEntity.badRequest().body(null); + throw new InvalidRequestException("No valid location found for entity at row " + entityRow + " and column " + entityColumn); } locationService.addEntityToLocation(entity.getEntityId(), location.getLocationId()); log.info("Entity {} placed at location ({}, {})", entity.getName(), entityRow, entityColumn); - return ResponseEntity.ok(entity); + return entityMapper.toDto(entity); } } \ No newline at end of file diff --git a/src/main/java/preponderous/viron/controllers/EntityController.java b/src/main/java/preponderous/viron/controllers/EntityController.java index 14a3e1c..a1bf6bb 100644 --- a/src/main/java/preponderous/viron/controllers/EntityController.java +++ b/src/main/java/preponderous/viron/controllers/EntityController.java @@ -2,120 +2,93 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import preponderous.viron.exceptions.EntityCreationException; +import preponderous.viron.dto.EntityDto; +import preponderous.viron.exceptions.NotFoundException; +import preponderous.viron.exceptions.ServiceException; import preponderous.viron.factories.EntityFactory; +import preponderous.viron.mappers.EntityMapper; import preponderous.viron.models.Entity; import preponderous.viron.repositories.EntityRepository; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import java.util.List; @RestController @RequestMapping("/api/v1/entities") @Slf4j @RequiredArgsConstructor +@Validated public class EntityController { private final EntityRepository entityRepository; private final EntityFactory entityFactory; + private final EntityMapper entityMapper; @GetMapping - public ResponseEntity> getAllEntities() { - try { - return ResponseEntity.ok(entityRepository.findAll()); - } catch (Exception e) { - log.error("Error fetching all entities: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getAllEntities() { + List entities = entityRepository.findAll(); + return entityMapper.toDtoList(entities); } @GetMapping("/{id}") - public ResponseEntity getEntityById(@PathVariable int id) { - try { - return entityRepository.findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching entity by id {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public EntityDto getEntityById(@PathVariable @Min(1) int id) { + Entity entity = entityRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Entity not found with id: " + id)); + return entityMapper.toDto(entity); } @GetMapping("/environment/{environmentId}") - public ResponseEntity> getEntitiesInEnvironment(@PathVariable int environmentId) { - try { - return ResponseEntity.ok(entityRepository.findByEnvironmentId(environmentId)); - } catch (Exception e) { - log.error("Error fetching entities in environment {}: {}", environmentId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getEntitiesInEnvironment(@PathVariable @Min(1) int environmentId) { + List entities = entityRepository.findByEnvironmentId(environmentId); + return entityMapper.toDtoList(entities); } @GetMapping("/grid/{gridId}") - public ResponseEntity> getEntitiesInGrid(@PathVariable int gridId) { - try { - return ResponseEntity.ok(entityRepository.findByGridId(gridId)); - } catch (Exception e) { - log.error("Error fetching entities in grid {}: {}", gridId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getEntitiesInGrid(@PathVariable @Min(1) int gridId) { + List entities = entityRepository.findByGridId(gridId); + return entityMapper.toDtoList(entities); } @GetMapping("/location/{locationId}") - public ResponseEntity> getEntitiesInLocation(@PathVariable int locationId) { - try { - return ResponseEntity.ok(entityRepository.findByLocationId(locationId)); - } catch (Exception e) { - log.error("Error fetching entities in location {}: {}", locationId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getEntitiesInLocation(@PathVariable @Min(1) int locationId) { + List entities = entityRepository.findByLocationId(locationId); + return entityMapper.toDtoList(entities); } @GetMapping("/unassigned") - public ResponseEntity> getEntitiesNotInAnyLocation() { - try { - return ResponseEntity.ok(entityRepository.findEntitiesNotInAnyLocation()); - } catch (Exception e) { - log.error("Error fetching unassigned entities: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getEntitiesNotInAnyLocation() { + List entities = entityRepository.findEntitiesNotInAnyLocation(); + return entityMapper.toDtoList(entities); } @PostMapping("/{name}") - public ResponseEntity createEntity(@PathVariable String name) { - try { - Entity newEntity = entityFactory.createEntity(name); - return ResponseEntity.ok(newEntity); - } catch (EntityCreationException e) { - log.error("Error creating entity with name {}: {}", name, e.getMessage()); - return ResponseEntity.badRequest().build(); - } catch (Exception e) { - log.error("Unexpected error creating entity: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + @ResponseStatus(HttpStatus.CREATED) + public EntityDto createEntity(@PathVariable @NotBlank String name) { + Entity newEntity = entityFactory.createEntity(name); + return entityMapper.toDto(newEntity); } @DeleteMapping("/{id}") - public ResponseEntity deleteEntity(@PathVariable int id) { - try { - return entityRepository.deleteById(id) - ? ResponseEntity.ok().build() - : ResponseEntity.notFound().build(); - } catch (Exception e) { - log.error("Error deleting entity {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteEntity(@PathVariable @Min(1) int id) { + if (entityRepository.findById(id).isEmpty()) { + throw new NotFoundException("Entity not found with id: " + id); + } + if (!entityRepository.deleteById(id)) { + throw new ServiceException("Failed to delete entity with id: " + id); } } @PatchMapping("/{id}/name/{name}") - public ResponseEntity updateEntityName(@PathVariable int id, @PathVariable String name) { - try { - return entityRepository.updateName(id, name) - ? ResponseEntity.ok().build() - : ResponseEntity.notFound().build(); - } catch (Exception e) { - log.error("Error updating name for entity {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); + public void updateEntityName(@PathVariable @Min(1) int id, @PathVariable @NotBlank String name) { + if (entityRepository.findById(id).isEmpty()) { + throw new NotFoundException("Entity not found with id: " + id); + } + if (!entityRepository.updateName(id, name)) { + throw new ServiceException("Failed to update name for entity " + id); } } -} \ No newline at end of file +} diff --git a/src/main/java/preponderous/viron/controllers/EnvironmentController.java b/src/main/java/preponderous/viron/controllers/EnvironmentController.java index ff1933e..c7708cf 100644 --- a/src/main/java/preponderous/viron/controllers/EnvironmentController.java +++ b/src/main/java/preponderous/viron/controllers/EnvironmentController.java @@ -6,189 +6,148 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import preponderous.viron.exceptions.EnvironmentCreationException; +import preponderous.viron.dto.EnvironmentDto; +import preponderous.viron.exceptions.NotFoundException; +import preponderous.viron.exceptions.ServiceException; import preponderous.viron.factories.EnvironmentFactory; +import preponderous.viron.mappers.EnvironmentMapper; import preponderous.viron.models.Environment; import preponderous.viron.repositories.EnvironmentRepository; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import java.util.List; @RestController @RequestMapping("/api/v1/environments") @Slf4j @RequiredArgsConstructor +@Validated public class EnvironmentController { private final EnvironmentRepository environmentRepository; private final EnvironmentFactory environmentFactory; + private final EnvironmentMapper environmentMapper; @GetMapping - public ResponseEntity> getAllEnvironments() { - try { - return ResponseEntity.ok(environmentRepository.findAll()); - } catch (Exception e) { - log.error("Error fetching all environments: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getAllEnvironments() { + List environments = environmentRepository.findAll(); + return environmentMapper.toDtoList(environments); } @GetMapping("/{id}") - public ResponseEntity getEnvironmentById(@PathVariable int id) { - try { - return environmentRepository.findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching environment by id {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public EnvironmentDto getEnvironmentById(@PathVariable @Min(1) int id) { + Environment environment = environmentRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Environment not found with id: " + id)); + return environmentMapper.toDto(environment); } @GetMapping("/name/{name}") - public ResponseEntity getEnvironmentByName(@PathVariable String name) { - try { - return environmentRepository.findByName(name) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching environment by name {}: {}", name, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public EnvironmentDto getEnvironmentByName(@PathVariable @NotBlank String name) { + Environment environment = environmentRepository.findByName(name) + .orElseThrow(() -> new NotFoundException("Environment not found with name: " + name)); + return environmentMapper.toDto(environment); } @GetMapping("/entity/{entityId}") - public ResponseEntity getEnvironmentOfEntity(@PathVariable int entityId) { - try { - return environmentRepository.findByEntityId(entityId) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching environment for entity {}: {}", entityId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public EnvironmentDto getEnvironmentOfEntity(@PathVariable @Min(1) int entityId) { + Environment environment = environmentRepository.findByEntityId(entityId) + .orElseThrow(() -> new NotFoundException("Environment not found for entity: " + entityId)); + return environmentMapper.toDto(environment); } @PostMapping("/{name}/{numGrids}/{gridSize}") - public ResponseEntity createEnvironment( - @PathVariable String name, - @PathVariable int numGrids, - @PathVariable int gridSize) { - try { - Environment newEnvironment = environmentFactory.createEnvironment(name, numGrids, gridSize); - return ResponseEntity.ok(newEnvironment); - } catch (EnvironmentCreationException e) { - log.error("Error creating environment with name {}: {}", name, e.getMessage()); - return ResponseEntity.badRequest().build(); - } catch (Exception e) { - log.error("Unexpected error creating environment: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + @ResponseStatus(HttpStatus.CREATED) + public EnvironmentDto createEnvironment( + @PathVariable @NotBlank String name, + @PathVariable @Min(1) int numGrids, + @PathVariable @Min(1) int gridSize) { + Environment newEnvironment = environmentFactory.createEnvironment(name, numGrids, gridSize); + return environmentMapper.toDto(newEnvironment); } @DeleteMapping("/{id}") - public ResponseEntity deleteEnvironment(@PathVariable int id) { - try { - if (environmentRepository.findById(id).isEmpty()) { - log.info("Environment with id {} does not exist, cannot delete", id); - return ResponseEntity.notFound().build(); - } - - log.info("Attempting to delete environment with id {}", id); + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteEnvironment(@PathVariable @Min(1) int id) { + if (environmentRepository.findById(id).isEmpty()) { + throw new NotFoundException("Environment not found with id: " + id); + } - List entityIds = environmentRepository.findEntityIdsByEnvironmentId(id); - List locationIds = environmentRepository.findLocationIdsByEnvironmentId(id); - List gridIds = environmentRepository.findGridIdsByEnvironmentId(id); + log.info("Attempting to delete environment with id {}", id); - // Delete associations - for (int entityId : entityIds) { - if (!environmentRepository.deleteEntityLocation(entityId)) { - log.error("Error deleting entity_location with entity id {}", entityId); - return ResponseEntity.internalServerError().build(); - } - } + List entityIds = environmentRepository.findEntityIdsByEnvironmentId(id); + List locationIds = environmentRepository.findLocationIdsByEnvironmentId(id); + List gridIds = environmentRepository.findGridIdsByEnvironmentId(id); - for (int locationId : locationIds) { - if (!environmentRepository.deleteLocationGrid(locationId)) { - log.error("Error deleting location_grid with location id {}", locationId); - return ResponseEntity.internalServerError().build(); - } + // Delete associations + for (int entityId : entityIds) { + if (!environmentRepository.deleteEntityLocation(entityId)) { + throw new ServiceException("Error deleting entity_location with entity id " + entityId); } + } - for (int gridId : gridIds) { - if (!environmentRepository.deleteGridEnvironment(gridId)) { - log.error("Error deleting grid_environment with grid id {}", gridId); - return ResponseEntity.internalServerError().build(); - } + for (int locationId : locationIds) { + if (!environmentRepository.deleteLocationGrid(locationId)) { + throw new ServiceException("Error deleting location_grid with location id " + locationId); } + } - // Delete entities - for (int entityId : entityIds) { - if (!environmentRepository.deleteEntity(entityId)) { - log.error("Error deleting entity with id {}", entityId); - return ResponseEntity.internalServerError().build(); - } - } - if (!entityIds.isEmpty()) { - log.info("Entities deleted: {}", entityIds); + for (int gridId : gridIds) { + if (!environmentRepository.deleteGridEnvironment(gridId)) { + throw new ServiceException("Error deleting grid_environment with grid id " + gridId); } + } - // Delete locations - for (int locationId : locationIds) { - if (!environmentRepository.deleteLocation(locationId)) { - log.error("Error deleting location with id {}", locationId); - return ResponseEntity.internalServerError().build(); - } - } - if (!locationIds.isEmpty()) { - log.info("Locations deleted: {}", locationIds); + // Delete entities + for (int entityId : entityIds) { + if (!environmentRepository.deleteEntity(entityId)) { + throw new ServiceException("Error deleting entity with id " + entityId); } + } + if (!entityIds.isEmpty()) { + log.info("Entities deleted: {}", entityIds); + } - // Delete grids - for (int gridId : gridIds) { - if (!environmentRepository.deleteGrid(gridId)) { - log.error("Error deleting grid with id {}", gridId); - return ResponseEntity.internalServerError().build(); - } - } - if (!gridIds.isEmpty()) { - log.info("Grids deleted: {}", gridIds); + // Delete locations + for (int locationId : locationIds) { + if (!environmentRepository.deleteLocation(locationId)) { + throw new ServiceException("Error deleting location with id " + locationId); } + } + if (!locationIds.isEmpty()) { + log.info("Locations deleted: {}", locationIds); + } - // Delete environment - if (!environmentRepository.deleteById(id)) { - log.error("Error deleting environment with id {}", id); - return ResponseEntity.internalServerError().build(); + // Delete grids + for (int gridId : gridIds) { + if (!environmentRepository.deleteGrid(gridId)) { + throw new ServiceException("Error deleting grid with id " + gridId); } + } + if (!gridIds.isEmpty()) { + log.info("Grids deleted: {}", gridIds); + } - log.info("Environment with id {} deleted", id); - return ResponseEntity.ok().build(); - } catch (Exception e) { - log.error("Error deleting environment {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); + // Delete environment + if (!environmentRepository.deleteById(id)) { + throw new ServiceException("Error deleting environment with id " + id); } + + log.info("Environment with id {} deleted", id); } @PatchMapping("/{id}/name/{name}") - public ResponseEntity updateEnvironmentName(@PathVariable int id, @PathVariable String name) { - try { - // First check if the environment exists - if (environmentRepository.findById(id).isEmpty()) { - log.info("Environment with id {} not found", id); - return ResponseEntity.notFound().build(); - } + public void updateEnvironmentName(@PathVariable @Min(1) int id, @PathVariable @NotBlank String name) { + if (environmentRepository.findById(id).isEmpty()) { + throw new NotFoundException("Environment not found with id: " + id); + } - boolean updated = environmentRepository.updateName(id, name); - if (updated) { - log.info("Updated name for environment {} to '{}'", id, name); - return ResponseEntity.ok().build(); - } else { - log.error("Failed to update name for environment {}", id); - return ResponseEntity.internalServerError().build(); - } - } catch (Exception e) { - log.error("Error updating name for environment {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); + if (!environmentRepository.updateName(id, name)) { + throw new ServiceException("Failed to update name for environment " + id); } + + log.info("Updated name for environment {} to '{}'", id, name); } -} \ No newline at end of file +} diff --git a/src/main/java/preponderous/viron/controllers/GridController.java b/src/main/java/preponderous/viron/controllers/GridController.java index 9a78727..2436429 100644 --- a/src/main/java/preponderous/viron/controllers/GridController.java +++ b/src/main/java/preponderous/viron/controllers/GridController.java @@ -2,61 +2,49 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import preponderous.viron.dto.GridDto; +import preponderous.viron.exceptions.NotFoundException; +import preponderous.viron.mappers.GridMapper; import preponderous.viron.models.Grid; import preponderous.viron.repositories.GridRepository; +import jakarta.validation.constraints.Min; import java.util.List; @RestController @RequestMapping("/api/v1/grids") @Slf4j @RequiredArgsConstructor +@Validated public class GridController { private final GridRepository gridRepository; + private final GridMapper gridMapper; @GetMapping - public ResponseEntity> getAllGrids() { - try { - return ResponseEntity.ok(gridRepository.findAll()); - } catch (Exception e) { - log.error("Error fetching all grids: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getAllGrids() { + List grids = gridRepository.findAll(); + return gridMapper.toDtoList(grids); } @GetMapping("/{id}") - public ResponseEntity getGridById(@PathVariable int id) { - try { - return gridRepository.findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching grid by id {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public GridDto getGridById(@PathVariable @Min(1) int id) { + Grid grid = gridRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Grid not found with id: " + id)); + return gridMapper.toDto(grid); } @GetMapping("/environment/{environmentId}") - public ResponseEntity> getGridsInEnvironment(@PathVariable int environmentId) { - try { - return ResponseEntity.ok(gridRepository.findByEnvironmentId(environmentId)); - } catch (Exception e) { - log.error("Error fetching grids in environment {}: {}", environmentId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getGridsInEnvironment(@PathVariable @Min(1) int environmentId) { + List grids = gridRepository.findByEnvironmentId(environmentId); + return gridMapper.toDtoList(grids); } @GetMapping("/entity/{entityId}") - public ResponseEntity getGridOfEntity(@PathVariable int entityId) { - try { - return gridRepository.findByEntityId(entityId) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching grid for entity {}: {}", entityId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public GridDto getGridOfEntity(@PathVariable @Min(1) int entityId) { + Grid grid = gridRepository.findByEntityId(entityId) + .orElseThrow(() -> new NotFoundException("Grid not found for entity: " + entityId)); + return gridMapper.toDto(grid); } -} \ No newline at end of file +} diff --git a/src/main/java/preponderous/viron/controllers/LocationController.java b/src/main/java/preponderous/viron/controllers/LocationController.java index 152bde9..cbe90aa 100644 --- a/src/main/java/preponderous/viron/controllers/LocationController.java +++ b/src/main/java/preponderous/viron/controllers/LocationController.java @@ -2,107 +2,89 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import preponderous.viron.dto.LocationDto; +import preponderous.viron.exceptions.NotFoundException; +import preponderous.viron.exceptions.ServiceException; +import preponderous.viron.mappers.LocationMapper; import preponderous.viron.models.Location; import preponderous.viron.repositories.LocationRepository; +import jakarta.validation.constraints.Min; import java.util.List; @RestController @RequestMapping("/api/v1/locations") @Slf4j @RequiredArgsConstructor +@Validated public class LocationController { private final LocationRepository locationRepository; + private final LocationMapper locationMapper; @GetMapping - public ResponseEntity> getAllLocations() { - try { - return ResponseEntity.ok(locationRepository.findAll()); - } catch (Exception e) { - log.error("Error fetching all locations: {}", e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getAllLocations() { + List locations = locationRepository.findAll(); + return locationMapper.toDtoList(locations); } @GetMapping("/{id}") - public ResponseEntity getLocationById(@PathVariable int id) { - try { - return locationRepository.findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching location by id {}: {}", id, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public LocationDto getLocationById(@PathVariable @Min(1) int id) { + Location location = locationRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Location not found with id: " + id)); + return locationMapper.toDto(location); } @GetMapping("/environment/{environmentId}") - public ResponseEntity> getLocationsInEnvironment(@PathVariable int environmentId) { - try { - return ResponseEntity.ok(locationRepository.findByEnvironmentId(environmentId)); - } catch (Exception e) { - log.error("Error fetching locations in environment {}: {}", environmentId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getLocationsInEnvironment(@PathVariable @Min(1) int environmentId) { + List locations = locationRepository.findByEnvironmentId(environmentId); + return locationMapper.toDtoList(locations); } @GetMapping("/grid/{gridId}") - public ResponseEntity> getLocationsInGrid(@PathVariable int gridId) { - try { - return ResponseEntity.ok(locationRepository.findByGridId(gridId)); - } catch (Exception e) { - log.error("Error fetching locations in grid {}: {}", gridId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public List getLocationsInGrid(@PathVariable @Min(1) int gridId) { + List locations = locationRepository.findByGridId(gridId); + return locationMapper.toDtoList(locations); } @GetMapping("/entity/{entityId}") - public ResponseEntity getLocationOfEntity(@PathVariable int entityId) { - try { - return locationRepository.findByEntityId(entityId) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } catch (Exception e) { - log.error("Error fetching location of entity {}: {}", entityId, e.getMessage()); - return ResponseEntity.internalServerError().build(); - } + public LocationDto getLocationOfEntity(@PathVariable @Min(1) int entityId) { + Location location = locationRepository.findByEntityId(entityId) + .orElseThrow(() -> new NotFoundException("Location not found for entity: " + entityId)); + return locationMapper.toDto(location); } @PutMapping("/{locationId}/entity/{entityId}") - public ResponseEntity addEntityToLocation(@PathVariable int entityId, @PathVariable int locationId) { - try { - return locationRepository.addEntityToLocation(entityId, locationId) - ? ResponseEntity.ok().build() - : ResponseEntity.notFound().build(); - } catch (Exception e) { - log.error("Error adding entity {} to location {}: {}", entityId, locationId, e.getMessage()); - return ResponseEntity.internalServerError().build(); + public void addEntityToLocation(@PathVariable("entityId") @Min(1) int entityId, @PathVariable("locationId") @Min(1) int locationId) { + if (locationRepository.findById(locationId).isEmpty()) { + throw new NotFoundException("Location not found with id: " + locationId); + } + if (!locationRepository.addEntityToLocation(entityId, locationId)) { + throw new ServiceException("Failed to add entity " + entityId + " to location " + locationId); } } @DeleteMapping("/{locationId}/entity/{entityId}") - public ResponseEntity removeEntityFromLocation(@PathVariable int entityId, @PathVariable int locationId) { - try { - return locationRepository.removeEntityFromLocation(entityId, locationId) - ? ResponseEntity.ok().build() - : ResponseEntity.notFound().build(); - } catch (Exception e) { - log.error("Error removing entity {} from location {}: {}", entityId, locationId, e.getMessage()); - return ResponseEntity.internalServerError().build(); + @ResponseStatus(HttpStatus.NO_CONTENT) + public void removeEntityFromLocation(@PathVariable("entityId") @Min(1) int entityId, @PathVariable("locationId") @Min(1) int locationId) { + if (locationRepository.findById(locationId).isEmpty()) { + throw new NotFoundException("Location not found with id: " + locationId); + } + if (!locationRepository.removeEntityFromLocation(entityId, locationId)) { + throw new ServiceException("Failed to remove entity " + entityId + " from location " + locationId); } } @DeleteMapping("/entity/{entityId}") - public ResponseEntity removeEntityFromCurrentLocation(@PathVariable int entityId) { - try { - return locationRepository.removeEntityFromCurrentLocation(entityId) - ? ResponseEntity.ok().build() - : ResponseEntity.notFound().build(); - } catch (Exception e) { - log.error("Error removing entity {} from current location: {}", entityId, e.getMessage()); - return ResponseEntity.internalServerError().build(); + @ResponseStatus(HttpStatus.NO_CONTENT) + public void removeEntityFromCurrentLocation(@PathVariable @Min(1) int entityId) { + if (locationRepository.findByEntityId(entityId).isEmpty()) { + throw new NotFoundException("Location not found for entity: " + entityId); + } + if (!locationRepository.removeEntityFromCurrentLocation(entityId)) { + throw new ServiceException("Failed to remove entity " + entityId + " from current location"); } } -} \ No newline at end of file +} diff --git a/src/main/java/preponderous/viron/dto/EntityDto.java b/src/main/java/preponderous/viron/dto/EntityDto.java new file mode 100644 index 0000000..3cbd425 --- /dev/null +++ b/src/main/java/preponderous/viron/dto/EntityDto.java @@ -0,0 +1,21 @@ +package preponderous.viron.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Data transfer object representing an entity") +public class EntityDto { + @Schema(description = "Unique identifier of the entity") + private int entityId; + + @Schema(description = "Name of the entity") + private String name; + + @Schema(description = "Date when the entity was created") + private String creationDate; +} diff --git a/src/main/java/preponderous/viron/dto/EnvironmentDto.java b/src/main/java/preponderous/viron/dto/EnvironmentDto.java new file mode 100644 index 0000000..e5e9716 --- /dev/null +++ b/src/main/java/preponderous/viron/dto/EnvironmentDto.java @@ -0,0 +1,21 @@ +package preponderous.viron.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Data transfer object representing an environment") +public class EnvironmentDto { + @Schema(description = "Unique identifier of the environment") + private int environmentId; + + @Schema(description = "Name of the environment") + private String name; + + @Schema(description = "Date when the environment was created") + private String creationDate; +} diff --git a/src/main/java/preponderous/viron/dto/ErrorResponse.java b/src/main/java/preponderous/viron/dto/ErrorResponse.java new file mode 100644 index 0000000..b7e65e1 --- /dev/null +++ b/src/main/java/preponderous/viron/dto/ErrorResponse.java @@ -0,0 +1,18 @@ +package preponderous.viron.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Standard error response body") +public class ErrorResponse { + @Schema(description = "HTTP status code") + private int status; + + @Schema(description = "Error message describing what went wrong") + private String message; +} diff --git a/src/main/java/preponderous/viron/dto/GridDto.java b/src/main/java/preponderous/viron/dto/GridDto.java new file mode 100644 index 0000000..dc278eb --- /dev/null +++ b/src/main/java/preponderous/viron/dto/GridDto.java @@ -0,0 +1,21 @@ +package preponderous.viron.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Data transfer object representing a grid") +public class GridDto { + @Schema(description = "Unique identifier of the grid") + private int gridId; + + @Schema(description = "Number of rows in the grid") + private int rows; + + @Schema(description = "Number of columns in the grid") + private int columns; +} diff --git a/src/main/java/preponderous/viron/dto/LocationDto.java b/src/main/java/preponderous/viron/dto/LocationDto.java new file mode 100644 index 0000000..fc84495 --- /dev/null +++ b/src/main/java/preponderous/viron/dto/LocationDto.java @@ -0,0 +1,21 @@ +package preponderous.viron.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Data transfer object representing a location") +public class LocationDto { + @Schema(description = "Unique identifier of the location") + private int locationId; + + @Schema(description = "X coordinate of the location") + private int x; + + @Schema(description = "Y coordinate of the location") + private int y; +} diff --git a/src/main/java/preponderous/viron/exceptions/GlobalExceptionHandler.java b/src/main/java/preponderous/viron/exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000..1bd538f --- /dev/null +++ b/src/main/java/preponderous/viron/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,82 @@ +package preponderous.viron.exceptions; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import preponderous.viron.dto.ErrorResponse; + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFoundException(NotFoundException ex) { + log.warn("Resource not found: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + + @ExceptionHandler(EntityCreationException.class) + public ResponseEntity handleEntityCreationException(EntityCreationException ex) { + log.warn("Entity creation failed: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(EnvironmentCreationException.class) + public ResponseEntity handleEnvironmentCreationException(EnvironmentCreationException ex) { + log.warn("Environment creation failed: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(InvalidRequestException.class) + public ResponseEntity handleInvalidRequestException(InvalidRequestException ex) { + log.warn("Invalid request: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(EntityServiceException.class) + public ResponseEntity handleEntityServiceException(EntityServiceException ex) { + log.error("Entity service error: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + + @ExceptionHandler(ServiceException.class) + public ResponseEntity handleServiceException(ServiceException ex) { + log.error("Service error: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .reduce((a, b) -> a + "; " + b) + .orElse("Validation failed"); + log.warn("Validation error: {}", message); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), message); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(HandlerMethodValidationException.class) + public ResponseEntity handleHandlerMethodValidationException(HandlerMethodValidationException ex) { + log.warn("Validation error during handler method validation: {}", ex.getMessage()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Validation failed"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex) { + log.error("Unexpected error: {}", ex.getMessage(), ex); + ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } +} diff --git a/src/main/java/preponderous/viron/exceptions/InvalidRequestException.java b/src/main/java/preponderous/viron/exceptions/InvalidRequestException.java new file mode 100644 index 0000000..9995309 --- /dev/null +++ b/src/main/java/preponderous/viron/exceptions/InvalidRequestException.java @@ -0,0 +1,7 @@ +package preponderous.viron.exceptions; + +public class InvalidRequestException extends RuntimeException { + public InvalidRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/preponderous/viron/mappers/EntityMapper.java b/src/main/java/preponderous/viron/mappers/EntityMapper.java new file mode 100644 index 0000000..2ee4498 --- /dev/null +++ b/src/main/java/preponderous/viron/mappers/EntityMapper.java @@ -0,0 +1,14 @@ +package preponderous.viron.mappers; + +import org.mapstruct.Mapper; +import preponderous.viron.dto.EntityDto; +import preponderous.viron.models.Entity; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface EntityMapper { + EntityDto toDto(Entity entity); + Entity toEntity(EntityDto dto); + List toDtoList(List entities); +} diff --git a/src/main/java/preponderous/viron/mappers/EnvironmentMapper.java b/src/main/java/preponderous/viron/mappers/EnvironmentMapper.java new file mode 100644 index 0000000..4af9fde --- /dev/null +++ b/src/main/java/preponderous/viron/mappers/EnvironmentMapper.java @@ -0,0 +1,14 @@ +package preponderous.viron.mappers; + +import org.mapstruct.Mapper; +import preponderous.viron.dto.EnvironmentDto; +import preponderous.viron.models.Environment; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface EnvironmentMapper { + EnvironmentDto toDto(Environment environment); + Environment toEnvironment(EnvironmentDto dto); + List toDtoList(List environments); +} diff --git a/src/main/java/preponderous/viron/mappers/GridMapper.java b/src/main/java/preponderous/viron/mappers/GridMapper.java new file mode 100644 index 0000000..05a9e7f --- /dev/null +++ b/src/main/java/preponderous/viron/mappers/GridMapper.java @@ -0,0 +1,14 @@ +package preponderous.viron.mappers; + +import org.mapstruct.Mapper; +import preponderous.viron.dto.GridDto; +import preponderous.viron.models.Grid; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface GridMapper { + GridDto toDto(Grid grid); + Grid toGrid(GridDto dto); + List toDtoList(List grids); +} diff --git a/src/main/java/preponderous/viron/mappers/LocationMapper.java b/src/main/java/preponderous/viron/mappers/LocationMapper.java new file mode 100644 index 0000000..80b6d3c --- /dev/null +++ b/src/main/java/preponderous/viron/mappers/LocationMapper.java @@ -0,0 +1,14 @@ +package preponderous.viron.mappers; + +import org.mapstruct.Mapper; +import preponderous.viron.dto.LocationDto; +import preponderous.viron.models.Location; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface LocationMapper { + LocationDto toDto(Location location); + Location toLocation(LocationDto dto); + List toDtoList(List locations); +} diff --git a/src/test/java/preponderous/viron/controllers/DebugControllerTest.java b/src/test/java/preponderous/viron/controllers/DebugControllerTest.java index 9305dc4..8b9c09a 100644 --- a/src/test/java/preponderous/viron/controllers/DebugControllerTest.java +++ b/src/test/java/preponderous/viron/controllers/DebugControllerTest.java @@ -1,43 +1,207 @@ package preponderous.viron.controllers; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import preponderous.viron.config.DbConfig; +import preponderous.viron.database.DbInteractions; +import preponderous.viron.dto.EntityDto; +import preponderous.viron.dto.EnvironmentDto; +import preponderous.viron.exceptions.ServiceException; +import preponderous.viron.mappers.EntityMapper; +import preponderous.viron.mappers.EnvironmentMapper; +import preponderous.viron.models.Entity; +import preponderous.viron.models.Environment; +import preponderous.viron.models.Grid; +import preponderous.viron.models.Location; import preponderous.viron.services.EntityService; import preponderous.viron.services.EnvironmentService; import preponderous.viron.services.GridService; import preponderous.viron.services.LocationService; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + @SpringBootTest -public class DebugControllerTest { +@AutoConfigureMockMvc +@DirtiesContext +class DebugControllerTest { + + @Autowired + private MockMvc mockMvc; - @Mock + @MockBean private EntityService entityService; - @Mock + @MockBean private EnvironmentService environmentService; - @Mock + @MockBean private GridService gridService; - @Mock + @MockBean private LocationService locationService; - @InjectMocks - private DebugController debugController; + @MockBean + private EntityMapper entityMapper; + + @MockBean + private EnvironmentMapper environmentMapper; + + @MockBean + private DbInteractions dbInteractions; + + @MockBean + private DbConfig dbConfig; + + // --- POST /api/v1/debug/create-sample-data --- + + @Test + void createSampleData_Success() throws Exception { + Environment environment = new Environment(1, "Sample Environment", "2024-01-01"); + Grid grid = new Grid(1, 10, 10); + List locations = buildLocationGrid(10, 10); + + when(environmentService.createEnvironment("Sample Environment", 1, 10)).thenReturn(environment); + when(gridService.getGridsInEnvironment(1)).thenReturn(List.of(grid)); + when(locationService.getLocationsInGrid(1)).thenReturn(locations); + when(entityService.createEntity(anyString())).thenAnswer(invocation -> { + String name = invocation.getArgument(0); + return new Entity(name.hashCode(), name, "2024-01-01"); + }); + doNothing().when(locationService).addEntityToLocation(anyInt(), anyInt()); + + EnvironmentDto dto = new EnvironmentDto(1, "Sample Environment", "2024-01-01"); + when(environmentMapper.toDto(environment)).thenReturn(dto); - @BeforeEach - public void setUp() { - entityService = mock(EntityService.class); - environmentService = mock(EnvironmentService.class); - gridService = mock(GridService.class); - locationService = mock(LocationService.class); - debugController = new DebugController(entityService, environmentService, gridService, locationService); + mockMvc.perform(post("/api/v1/debug/create-sample-data")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.environmentId").value(1)) + .andExpect(jsonPath("$.name").value("Sample Environment")) + .andExpect(jsonPath("$.creationDate").value("2024-01-01")); + + verify(environmentService).createEnvironment("Sample Environment", 1, 10); + verify(gridService).getGridsInEnvironment(1); + verify(locationService).getLocationsInGrid(1); + verify(entityService, times(10)).createEntity(anyString()); + verify(locationService, times(10)).addEntityToLocation(anyInt(), anyInt()); + verify(environmentMapper).toDto(environment); + } + + @Test + void createSampleData_NoGrids_Returns400() throws Exception { + Environment environment = new Environment(5, "NoGridEnv", "2024-05-01"); + + when(environmentService.createEnvironment("Sample Environment", 1, 10)).thenReturn(environment); + when(gridService.getGridsInEnvironment(5)).thenReturn(List.of()); + + mockMvc.perform(post("/api/v1/debug/create-sample-data")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.message").value("No grids found in environment: 5")); } - // TODO: implement tests for DebugController methods + // --- POST /api/v1/debug/create-world-and-place-entity/{environmentName} --- + + @Test + void createWorldAndPlaceEntity_Success() throws Exception { + Environment environment = new Environment(2, "TestWorld", "2024-02-01"); + Grid grid = new Grid(5, 5, 5); + List locations = buildLocationGrid(5, 5); + + when(environmentService.createEnvironment("TestWorld", 1, 5)).thenReturn(environment); + when(gridService.getGridsInEnvironment(2)).thenReturn(List.of(grid)); + when(locationService.getLocationsInGrid(5)).thenReturn(locations); + + Entity entity = new Entity(10, "Tom", "2024-02-01"); + when(entityService.createEntity(anyString())).thenReturn(entity); + doNothing().when(locationService).addEntityToLocation(anyInt(), anyInt()); + + EntityDto dto = new EntityDto(10, "Tom", "2024-02-01"); + when(entityMapper.toDto(entity)).thenReturn(dto); + + mockMvc.perform(post("/api/v1/debug/create-world-and-place-entity/TestWorld")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.entityId").value(10)) + .andExpect(jsonPath("$.name").value("Tom")) + .andExpect(jsonPath("$.creationDate").value("2024-02-01")); + + verify(environmentService).createEnvironment("TestWorld", 1, 5); + verify(gridService).getGridsInEnvironment(2); + verify(entityService).createEntity(anyString()); + verify(locationService).getLocationsInGrid(5); + verify(locationService).addEntityToLocation(eq(10), anyInt()); + verify(entityMapper).toDto(entity); + } + + @Test + void createWorldAndPlaceEntity_ServiceException_Returns500() throws Exception { + Environment environment = new Environment(3, "FailWorld", "2024-03-01"); + Grid grid = new Grid(6, 5, 5); + + when(environmentService.createEnvironment("FailWorld", 1, 5)).thenReturn(environment); + when(gridService.getGridsInEnvironment(3)).thenReturn(List.of(grid)); + when(entityService.createEntity(anyString())).thenThrow(new ServiceException("Entity creation failed")); + + mockMvc.perform(post("/api/v1/debug/create-world-and-place-entity/FailWorld")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").value("Entity creation failed")); + } + + @Test + void createWorldAndPlaceEntity_NoGrids_Returns400() throws Exception { + Environment environment = new Environment(6, "NoGridWorld", "2024-06-01"); + + when(environmentService.createEnvironment("NoGridWorld", 1, 5)).thenReturn(environment); + when(gridService.getGridsInEnvironment(6)).thenReturn(List.of()); + + mockMvc.perform(post("/api/v1/debug/create-world-and-place-entity/NoGridWorld")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.message").value("No grids found in environment: 6")); + } + + @Test + void createWorldAndPlaceEntity_NoValidLocation_Returns400() throws Exception { + Environment environment = new Environment(4, "EmptyWorld", "2024-04-01"); + Grid grid = new Grid(7, 5, 5); + + when(environmentService.createEnvironment("EmptyWorld", 1, 5)).thenReturn(environment); + when(gridService.getGridsInEnvironment(4)).thenReturn(List.of(grid)); + + Entity entity = new Entity(20, "Jerry", "2024-04-01"); + when(entityService.createEntity(anyString())).thenReturn(entity); + // Return an empty location list so no valid location can be found + when(locationService.getLocationsInGrid(7)).thenReturn(List.of()); + + mockMvc.perform(post("/api/v1/debug/create-world-and-place-entity/EmptyWorld")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.message").isString()); + } + + /** + * Builds a list of locations covering all coordinates in a grid of the given size. + * Location IDs are assigned sequentially starting from 1. + */ + private List buildLocationGrid(int rows, int columns) { + List locations = new ArrayList<>(); + int id = 1; + for (int x = 0; x < rows; x++) { + for (int y = 0; y < columns; y++) { + locations.add(new Location(id++, x, y)); + } + } + return locations; + } } \ No newline at end of file diff --git a/src/test/java/preponderous/viron/controllers/EntityControllerTest.java b/src/test/java/preponderous/viron/controllers/EntityControllerTest.java index f675be9..fdeee4b 100644 --- a/src/test/java/preponderous/viron/controllers/EntityControllerTest.java +++ b/src/test/java/preponderous/viron/controllers/EntityControllerTest.java @@ -1,236 +1,321 @@ package preponderous.viron.controllers; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import preponderous.viron.config.DbConfig; +import preponderous.viron.database.DbInteractions; +import preponderous.viron.dto.EntityDto; import preponderous.viron.exceptions.EntityCreationException; import preponderous.viron.factories.EntityFactory; +import preponderous.viron.mappers.EntityMapper; import preponderous.viron.models.Entity; import preponderous.viron.repositories.EntityRepository; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.Matchers.*; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext class EntityControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean private EntityRepository entityRepository; + + @MockBean private EntityFactory entityFactory; - private EntityController entityController; - @BeforeEach - void setUp() { - entityRepository = Mockito.mock(EntityRepository.class); - entityFactory = Mockito.mock(EntityFactory.class); - entityController = new EntityController(entityRepository, entityFactory); - } + @MockBean + private EntityMapper entityMapper; + + @MockBean + private DbInteractions dbInteractions; + + @MockBean + private DbConfig dbConfig; + + // --- GET /api/v1/entities --- @Test - void getAllEntities_Success() { - // prepare + void getAllEntities_Success() throws Exception { List entities = List.of( new Entity(1, "Entity1", "2024-01-01"), new Entity(2, "Entity2", "2024-01-01") ); + List dtos = List.of( + new EntityDto(1, "Entity1", "2024-01-01"), + new EntityDto(2, "Entity2", "2024-01-01") + ); when(entityRepository.findAll()).thenReturn(entities); + when(entityMapper.toDtoList(entities)).thenReturn(dtos); - // execute - ResponseEntity> response = entityController.getAllEntities(); + mockMvc.perform(get("/api/v1/entities")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].entityId").value(1)) + .andExpect(jsonPath("$[0].name").value("Entity1")) + .andExpect(jsonPath("$[0].creationDate").value("2024-01-01")) + .andExpect(jsonPath("$[1].entityId").value(2)) + .andExpect(jsonPath("$[1].name").value("Entity2")); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entities, response.getBody()); verify(entityRepository).findAll(); + verify(entityMapper).toDtoList(entities); } @Test - void getAllEntities_Exception() { - // prepare - when(entityRepository.findAll()).thenThrow(new RuntimeException("Database error")); + void getAllEntities_EmptyList() throws Exception { + when(entityRepository.findAll()).thenReturn(Collections.emptyList()); + when(entityMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/v1/entities")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); + } - // execute - ResponseEntity> response = entityController.getAllEntities(); + @Test + void getAllEntities_RepositoryThrowsException() throws Exception { + when(entityRepository.findAll()).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/entities")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/entities/{id} --- + @Test - void getEntityById_Success() { - // prepare + void getEntityById_Success() throws Exception { Entity entity = new Entity(1, "Entity1", "2024-01-01"); + EntityDto dto = new EntityDto(1, "Entity1", "2024-01-01"); when(entityRepository.findById(1)).thenReturn(Optional.of(entity)); + when(entityMapper.toDto(entity)).thenReturn(dto); - // execute - ResponseEntity response = entityController.getEntityById(1); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entity, response.getBody()); + mockMvc.perform(get("/api/v1/entities/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.entityId").value(1)) + .andExpect(jsonPath("$.name").value("Entity1")) + .andExpect(jsonPath("$.creationDate").value("2024-01-01")); } @Test - void getEntityById_NotFound() { - // prepare - when(entityRepository.findById(anyInt())).thenReturn(Optional.empty()); + void getEntityById_NotFound() throws Exception { + when(entityRepository.findById(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = entityController.getEntityById(1); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + mockMvc.perform(get("/api/v1/entities/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Entity not found with id: 999")); } @Test - void getEntitiesInEnvironment_Success() { - // prepare - List entities = new ArrayList<>(); - when(entityRepository.findByEnvironmentId(anyInt())).thenReturn(entities); - - // execute - ResponseEntity> response = entityController.getEntitiesInEnvironment(1); + void getEntityById_RepositoryThrowsException() throws Exception { + when(entityRepository.findById(1)).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entities, response.getBody()); + mockMvc.perform(get("/api/v1/entities/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/entities/environment/{environmentId} --- + @Test - void getEntitiesInGrid_Success() { - // prepare - List entities = new ArrayList<>(); - when(entityRepository.findByGridId(anyInt())).thenReturn(entities); + void getEntitiesInEnvironment_Success() throws Exception { + List entities = List.of(new Entity(1, "Entity1", "2024-01-01")); + List dtos = List.of(new EntityDto(1, "Entity1", "2024-01-01")); + when(entityRepository.findByEnvironmentId(1)).thenReturn(entities); + when(entityMapper.toDtoList(entities)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/entities/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].entityId").value(1)); + } - // execute - ResponseEntity> response = entityController.getEntitiesInGrid(1); + @Test + void getEntitiesInEnvironment_EmptyList() throws Exception { + when(entityRepository.findByEnvironmentId(1)).thenReturn(Collections.emptyList()); + when(entityMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entities, response.getBody()); + mockMvc.perform(get("/api/v1/entities/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); } + // --- GET /api/v1/entities/grid/{gridId} --- + @Test - void getEntitiesInLocation_Success() { - // prepare - List entities = new ArrayList<>(); - when(entityRepository.findByLocationId(anyInt())).thenReturn(entities); + void getEntitiesInGrid_Success() throws Exception { + List entities = List.of(new Entity(2, "GridEntity", "2024-02-01")); + List dtos = List.of(new EntityDto(2, "GridEntity", "2024-02-01")); + when(entityRepository.findByGridId(5)).thenReturn(entities); + when(entityMapper.toDtoList(entities)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/entities/grid/5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].entityId").value(2)) + .andExpect(jsonPath("$[0].name").value("GridEntity")); + } - // execute - ResponseEntity> response = entityController.getEntitiesInLocation(1); + // --- GET /api/v1/entities/location/{locationId} --- - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entities, response.getBody()); + @Test + void getEntitiesInLocation_Success() throws Exception { + List entities = List.of(new Entity(3, "LocEntity", "2024-03-01")); + List dtos = List.of(new EntityDto(3, "LocEntity", "2024-03-01")); + when(entityRepository.findByLocationId(10)).thenReturn(entities); + when(entityMapper.toDtoList(entities)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/entities/location/10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].entityId").value(3)) + .andExpect(jsonPath("$[0].name").value("LocEntity")); } + // --- GET /api/v1/entities/unassigned --- + @Test - void getEntitiesNotInAnyLocation_Success() { - // prepare - List entities = new ArrayList<>(); + void getEntitiesNotInAnyLocation_Success() throws Exception { + List entities = List.of(new Entity(4, "Unassigned", "2024-04-01")); + List dtos = List.of(new EntityDto(4, "Unassigned", "2024-04-01")); when(entityRepository.findEntitiesNotInAnyLocation()).thenReturn(entities); + when(entityMapper.toDtoList(entities)).thenReturn(dtos); - // execute - ResponseEntity> response = entityController.getEntitiesNotInAnyLocation(); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entities, response.getBody()); + mockMvc.perform(get("/api/v1/entities/unassigned")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].entityId").value(4)) + .andExpect(jsonPath("$[0].name").value("Unassigned")); } @Test - void createEntity_Success() throws EntityCreationException { - // prepare - Entity entity = new Entity(1, "New Entity", "2024-01-01"); - when(entityFactory.createEntity(anyString())).thenReturn(entity); + void getEntitiesNotInAnyLocation_EmptyList() throws Exception { + when(entityRepository.findEntitiesNotInAnyLocation()).thenReturn(Collections.emptyList()); + when(entityMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // execute - ResponseEntity response = entityController.createEntity("New Entity"); + mockMvc.perform(get("/api/v1/entities/unassigned")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); + } + + // --- POST /api/v1/entities/{name} --- - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(entity, response.getBody()); + @Test + void createEntity_ReturnsCreated() throws Exception { + Entity entity = new Entity(1, "NewEntity", "2024-01-01"); + EntityDto dto = new EntityDto(1, "NewEntity", "2024-01-01"); + when(entityFactory.createEntity("NewEntity")).thenReturn(entity); + when(entityMapper.toDto(entity)).thenReturn(dto); + + mockMvc.perform(post("/api/v1/entities/NewEntity")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.entityId").value(1)) + .andExpect(jsonPath("$.name").value("NewEntity")) + .andExpect(jsonPath("$.creationDate").value("2024-01-01")); } @Test - void createEntity_EntityCreationException() throws EntityCreationException { - // prepare - when(entityFactory.createEntity(anyString())) + void createEntity_FactoryThrowsEntityCreationException() throws Exception { + when(entityFactory.createEntity("BadEntity")) .thenThrow(new EntityCreationException("Creation failed")); - // execute - ResponseEntity response = entityController.createEntity("New Entity"); - - // verify - assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + mockMvc.perform(post("/api/v1/entities/BadEntity")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.message").value("Creation failed")); } @Test - void deleteEntity_Success() { - // prepare - when(entityRepository.deleteById(anyInt())).thenReturn(true); - - // execute - ResponseEntity response = entityController.deleteEntity(1); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); + void createEntity_FactoryThrowsRuntimeException() throws Exception { + when(entityFactory.createEntity("ErrorEntity")) + .thenThrow(new RuntimeException("Unexpected error")); + + mockMvc.perform(post("/api/v1/entities/ErrorEntity")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } - @Test - void deleteEntity_NotFound() { - // prepare - when(entityRepository.deleteById(anyInt())).thenReturn(false); + // --- DELETE /api/v1/entities/{id} --- - // execute - ResponseEntity response = entityController.deleteEntity(1); + @Test + void deleteEntity_ReturnsNoContent() throws Exception { + when(entityRepository.findById(1)).thenReturn(Optional.of(new Entity(1, "Entity1", "2024-01-01"))); + when(entityRepository.deleteById(1)).thenReturn(true); - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + mockMvc.perform(delete("/api/v1/entities/1")) + .andExpect(status().isNoContent()); } @Test - void updateEntityName_Success() { - // prepare - when(entityRepository.updateName(anyInt(), anyString())).thenReturn(true); + void deleteEntity_NotFound() throws Exception { + when(entityRepository.findById(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = entityController.updateEntityName(1, "New Name"); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); + mockMvc.perform(delete("/api/v1/entities/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Entity not found with id: 999")); } @Test - void updateEntityName_NotFound() { - // prepare - when(entityRepository.updateName(anyInt(), anyString())).thenReturn(false); + void deleteEntity_RepositoryThrowsException() throws Exception { + when(entityRepository.findById(1)).thenReturn(Optional.of(new Entity(1, "Entity1", "2024-01-01"))); + when(entityRepository.deleteById(1)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(delete("/api/v1/entities/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); + } - // execute - ResponseEntity response = entityController.updateEntityName(1, "New Name"); + // --- PATCH /api/v1/entities/{id}/name/{name} --- - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + @Test + void updateEntityName_Success() throws Exception { + when(entityRepository.findById(1)).thenReturn(Optional.of(new Entity(1, "OldName", "2024-01-01"))); + when(entityRepository.updateName(1, "UpdatedName")).thenReturn(true); + + mockMvc.perform(patch("/api/v1/entities/1/name/UpdatedName")) + .andExpect(status().isOk()); } @Test - void testExceptionHandling_InternalServerError() { - // prepare - when(entityRepository.findAll()).thenThrow(new RuntimeException("Unexpected error")); + void updateEntityName_NotFound() throws Exception { + when(entityRepository.findById(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity> response = entityController.getAllEntities(); + mockMvc.perform(patch("/api/v1/entities/999/name/UpdatedName")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Entity not found with id: 999")); + } - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + @Test + void updateEntityName_RepositoryThrowsException() throws Exception { + when(entityRepository.findById(1)).thenReturn(Optional.of(new Entity(1, "OldName", "2024-01-01"))); + when(entityRepository.updateName(1, "Name")).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(patch("/api/v1/entities/1/name/Name")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } } \ No newline at end of file diff --git a/src/test/java/preponderous/viron/controllers/EnvironmentControllerTest.java b/src/test/java/preponderous/viron/controllers/EnvironmentControllerTest.java index 019d353..1e89233 100644 --- a/src/test/java/preponderous/viron/controllers/EnvironmentControllerTest.java +++ b/src/test/java/preponderous/viron/controllers/EnvironmentControllerTest.java @@ -1,211 +1,240 @@ package preponderous.viron.controllers; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import preponderous.viron.config.DbConfig; +import preponderous.viron.database.DbInteractions; +import preponderous.viron.dto.EnvironmentDto; import preponderous.viron.exceptions.EnvironmentCreationException; import preponderous.viron.factories.EnvironmentFactory; +import preponderous.viron.mappers.EnvironmentMapper; import preponderous.viron.models.Environment; import preponderous.viron.repositories.EnvironmentRepository; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext class EnvironmentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean private EnvironmentRepository environmentRepository; + + @MockBean private EnvironmentFactory environmentFactory; - private EnvironmentController environmentController; - @BeforeEach - void setUp() { - environmentRepository = Mockito.mock(EnvironmentRepository.class); - environmentFactory = Mockito.mock(EnvironmentFactory.class); - environmentController = new EnvironmentController(environmentRepository, environmentFactory); - } + @MockBean + private EnvironmentMapper environmentMapper; + + @MockBean + private DbInteractions dbInteractions; + + @MockBean + private DbConfig dbConfig; @Test - void getAllEnvironments_Success() { - // setup + void getAllEnvironments_Success() throws Exception { List environments = Arrays.asList( new Environment(1, "Env1", "2023-01-01"), new Environment(2, "Env2", "2023-01-02") ); + List dtos = Arrays.asList( + new EnvironmentDto(1, "Env1", "2023-01-01"), + new EnvironmentDto(2, "Env2", "2023-01-02") + ); when(environmentRepository.findAll()).thenReturn(environments); + when(environmentMapper.toDtoList(environments)).thenReturn(dtos); - // execute - ResponseEntity> response = environmentController.getAllEnvironments(); + mockMvc.perform(get("/api/v1/environments")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].environmentId", is(1))) + .andExpect(jsonPath("$[0].name", is("Env1"))) + .andExpect(jsonPath("$[0].creationDate", is("2023-01-01"))) + .andExpect(jsonPath("$[1].environmentId", is(2))) + .andExpect(jsonPath("$[1].name", is("Env2"))); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertEquals(2, response.getBody().size()); verify(environmentRepository).findAll(); + verify(environmentMapper).toDtoList(environments); } @Test - void getAllEnvironments_ThrowsException() { - // setup - when(environmentRepository.findAll()).thenThrow(new RuntimeException("Database error")); + void getAllEnvironments_EmptyList() throws Exception { + when(environmentRepository.findAll()).thenReturn(Collections.emptyList()); + when(environmentMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // execute - ResponseEntity> response = environmentController.getAllEnvironments(); + mockMvc.perform(get("/api/v1/environments")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); verify(environmentRepository).findAll(); } @Test - void getEnvironmentById_Success() { - // setup + void getAllEnvironments_ThrowsException() throws Exception { + when(environmentRepository.findAll()).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(get("/api/v1/environments")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status", is(500))) + .andExpect(jsonPath("$.message", is("An unexpected error occurred"))); + } + + @Test + void getEnvironmentById_Success() throws Exception { Environment environment = new Environment(1, "Env1", "2023-01-01"); + EnvironmentDto dto = new EnvironmentDto(1, "Env1", "2023-01-01"); when(environmentRepository.findById(1)).thenReturn(Optional.of(environment)); + when(environmentMapper.toDto(environment)).thenReturn(dto); - // execute - ResponseEntity response = environmentController.getEnvironmentById(1); + mockMvc.perform(get("/api/v1/environments/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.environmentId", is(1))) + .andExpect(jsonPath("$.name", is("Env1"))) + .andExpect(jsonPath("$.creationDate", is("2023-01-01"))); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertEquals("Env1", response.getBody().getName()); verify(environmentRepository).findById(1); + verify(environmentMapper).toDto(environment); } @Test - void getEnvironmentById_NotFound() { - // setup + void getEnvironmentById_NotFound() throws Exception { when(environmentRepository.findById(1)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = environmentController.getEnvironmentById(1); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); - verify(environmentRepository).findById(1); + mockMvc.perform(get("/api/v1/environments/1")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status", is(404))) + .andExpect(jsonPath("$.message", is("Environment not found with id: 1"))); } @Test - void getEnvironmentById_ThrowsException() { - // setup + void getEnvironmentById_ThrowsException() throws Exception { when(environmentRepository.findById(1)).thenThrow(new RuntimeException("Database error")); - // execute - ResponseEntity response = environmentController.getEnvironmentById(1); - - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); - verify(environmentRepository).findById(1); + mockMvc.perform(get("/api/v1/environments/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status", is(500))) + .andExpect(jsonPath("$.message", is("An unexpected error occurred"))); } @Test - void getEnvironmentByName_Success() { - // setup + void getEnvironmentByName_Success() throws Exception { Environment environment = new Environment(1, "Env1", "2023-01-01"); + EnvironmentDto dto = new EnvironmentDto(1, "Env1", "2023-01-01"); when(environmentRepository.findByName("Env1")).thenReturn(Optional.of(environment)); + when(environmentMapper.toDto(environment)).thenReturn(dto); - // execute - ResponseEntity response = environmentController.getEnvironmentByName("Env1"); + mockMvc.perform(get("/api/v1/environments/name/Env1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.environmentId", is(1))) + .andExpect(jsonPath("$.name", is("Env1"))) + .andExpect(jsonPath("$.creationDate", is("2023-01-01"))); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertEquals("Env1", response.getBody().getName()); verify(environmentRepository).findByName("Env1"); + verify(environmentMapper).toDto(environment); } @Test - void getEnvironmentByName_NotFound() { - // setup + void getEnvironmentByName_NotFound() throws Exception { when(environmentRepository.findByName("NonExistent")).thenReturn(Optional.empty()); - // execute - ResponseEntity response = environmentController.getEnvironmentByName("NonExistent"); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); - verify(environmentRepository).findByName("NonExistent"); + mockMvc.perform(get("/api/v1/environments/name/NonExistent")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status", is(404))) + .andExpect(jsonPath("$.message", is("Environment not found with name: NonExistent"))); } @Test - void getEnvironmentOfEntity_Success() { - // setup + void getEnvironmentOfEntity_Success() throws Exception { Environment environment = new Environment(1, "Env1", "2023-01-01"); + EnvironmentDto dto = new EnvironmentDto(1, "Env1", "2023-01-01"); when(environmentRepository.findByEntityId(1)).thenReturn(Optional.of(environment)); + when(environmentMapper.toDto(environment)).thenReturn(dto); - // execute - ResponseEntity response = environmentController.getEnvironmentOfEntity(1); + mockMvc.perform(get("/api/v1/environments/entity/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.environmentId", is(1))) + .andExpect(jsonPath("$.name", is("Env1"))) + .andExpect(jsonPath("$.creationDate", is("2023-01-01"))); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertEquals("Env1", response.getBody().getName()); verify(environmentRepository).findByEntityId(1); + verify(environmentMapper).toDto(environment); } @Test - void getEnvironmentOfEntity_NotFound() { - // setup + void getEnvironmentOfEntity_NotFound() throws Exception { when(environmentRepository.findByEntityId(1)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = environmentController.getEnvironmentOfEntity(1); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); - verify(environmentRepository).findByEntityId(1); + mockMvc.perform(get("/api/v1/environments/entity/1")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status", is(404))) + .andExpect(jsonPath("$.message", is("Environment not found for entity: 1"))); } @Test - void createEnvironment_Success() throws EnvironmentCreationException { - // setup + void createEnvironment_Success() throws Exception { Environment environment = new Environment(1, "NewEnv", "2023-01-01"); + EnvironmentDto dto = new EnvironmentDto(1, "NewEnv", "2023-01-01"); when(environmentFactory.createEnvironment("NewEnv", 5, 10)).thenReturn(environment); + when(environmentMapper.toDto(environment)).thenReturn(dto); - // execute - ResponseEntity response = environmentController.createEnvironment("NewEnv", 5, 10); + mockMvc.perform(post("/api/v1/environments/NewEnv/5/10")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.environmentId", is(1))) + .andExpect(jsonPath("$.name", is("NewEnv"))) + .andExpect(jsonPath("$.creationDate", is("2023-01-01"))); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertEquals("NewEnv", response.getBody().getName()); verify(environmentFactory).createEnvironment("NewEnv", 5, 10); + verify(environmentMapper).toDto(environment); } @Test - void createEnvironment_CreationException() throws EnvironmentCreationException { - // setup + void createEnvironment_CreationException() throws Exception { when(environmentFactory.createEnvironment("NewEnv", 5, 10)) .thenThrow(new EnvironmentCreationException("Creation failed")); - // execute - ResponseEntity response = environmentController.createEnvironment("NewEnv", 5, 10); + mockMvc.perform(post("/api/v1/environments/NewEnv/5/10")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.message", is("Creation failed"))); + } - // verify - assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); - assertNull(response.getBody()); - verify(environmentFactory).createEnvironment("NewEnv", 5, 10); + @Test + void createEnvironment_ThrowsException() throws Exception { + when(environmentFactory.createEnvironment("NewEnv", 5, 10)) + .thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(post("/api/v1/environments/NewEnv/5/10")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status", is(500))) + .andExpect(jsonPath("$.message", is("An unexpected error occurred"))); } @Test - void deleteEnvironment_Success() { - // setup - when(environmentRepository.findById(1)).thenReturn(Optional.of(new Environment(1, "Env1", "2023-01-01"))); - when(environmentRepository.findEntityIdsByEnvironmentId(1)).thenReturn(Arrays.asList(1, 2)); - when(environmentRepository.findLocationIdsByEnvironmentId(1)).thenReturn(Arrays.asList(3, 4)); - when(environmentRepository.findGridIdsByEnvironmentId(1)).thenReturn(Arrays.asList(5, 6)); + void deleteEnvironment_Success() throws Exception { + Environment env = new Environment(1, "Env1", "2023-01-01"); + when(environmentRepository.findById(1)).thenReturn(Optional.of(env)); + when(environmentRepository.findEntityIdsByEnvironmentId(1)).thenReturn(List.of(1, 2)); + when(environmentRepository.findLocationIdsByEnvironmentId(1)).thenReturn(List.of(3, 4)); + when(environmentRepository.findGridIdsByEnvironmentId(1)).thenReturn(List.of(5)); when(environmentRepository.deleteEntityLocation(anyInt())).thenReturn(true); when(environmentRepository.deleteLocationGrid(anyInt())).thenReturn(true); when(environmentRepository.deleteGridEnvironment(anyInt())).thenReturn(true); @@ -214,65 +243,55 @@ void deleteEnvironment_Success() { when(environmentRepository.deleteGrid(anyInt())).thenReturn(true); when(environmentRepository.deleteById(1)).thenReturn(true); - // execute - ResponseEntity response = environmentController.deleteEnvironment(1); + mockMvc.perform(delete("/api/v1/environments/1")) + .andExpect(status().isNoContent()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); verify(environmentRepository).deleteById(1); } @Test - void deleteEnvironment_NotFound() { - // setup + void deleteEnvironment_NotFound() throws Exception { when(environmentRepository.findById(1)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = environmentController.deleteEnvironment(1); + mockMvc.perform(delete("/api/v1/environments/1")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status", is(404))) + .andExpect(jsonPath("$.message", is("Environment not found with id: 1"))); - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); verify(environmentRepository, never()).deleteById(anyInt()); } @Test - void updateEnvironmentName_Success() { - // setup + void updateEnvironmentName_Success() throws Exception { when(environmentRepository.findById(1)).thenReturn(Optional.of(new Environment(1, "OldName", "2023-01-01"))); when(environmentRepository.updateName(1, "NewName")).thenReturn(true); - // execute - ResponseEntity response = environmentController.updateEnvironmentName(1, "NewName"); + mockMvc.perform(patch("/api/v1/environments/1/name/NewName")) + .andExpect(status().isOk()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); verify(environmentRepository).updateName(1, "NewName"); } @Test - void updateEnvironmentName_NotFound() { - // setup + void updateEnvironmentName_NotFound() throws Exception { when(environmentRepository.findById(1)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = environmentController.updateEnvironmentName(1, "NewName"); + mockMvc.perform(patch("/api/v1/environments/1/name/NewName")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status", is(404))) + .andExpect(jsonPath("$.message", is("Environment not found with id: 1"))); - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); verify(environmentRepository, never()).updateName(anyInt(), anyString()); } @Test - void updateEnvironmentName_ThrowsException() { - // setup + void updateEnvironmentName_ThrowsException() throws Exception { when(environmentRepository.findById(1)).thenReturn(Optional.of(new Environment(1, "OldName", "2023-01-01"))); when(environmentRepository.updateName(1, "NewName")).thenThrow(new RuntimeException("Database error")); - // execute - ResponseEntity response = environmentController.updateEnvironmentName(1, "NewName"); - - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - verify(environmentRepository).updateName(1, "NewName"); + mockMvc.perform(patch("/api/v1/environments/1/name/NewName")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status", is(500))) + .andExpect(jsonPath("$.message", is("An unexpected error occurred"))); } } \ No newline at end of file diff --git a/src/test/java/preponderous/viron/controllers/GridControllerTest.java b/src/test/java/preponderous/viron/controllers/GridControllerTest.java index 48eef5d..47ae6c9 100644 --- a/src/test/java/preponderous/viron/controllers/GridControllerTest.java +++ b/src/test/java/preponderous/viron/controllers/GridControllerTest.java @@ -1,190 +1,202 @@ package preponderous.viron.controllers; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import preponderous.viron.config.DbConfig; +import preponderous.viron.database.DbInteractions; +import preponderous.viron.dto.GridDto; +import preponderous.viron.mappers.GridMapper; import preponderous.viron.models.Grid; import preponderous.viron.repositories.GridRepository; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest -public class GridControllerTest { +@AutoConfigureMockMvc +@DirtiesContext +class GridControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean private GridRepository gridRepository; - private GridController gridController; - @BeforeEach - void setUp() { - gridRepository = Mockito.mock(GridRepository.class); - gridController = new GridController(gridRepository); - } + @MockBean + private GridMapper gridMapper; + + @MockBean + private DbInteractions dbInteractions; + + @MockBean + private DbConfig dbConfig; + + // --- GET /api/v1/grids --- @Test - void testGetAllGrids_Success() { - // setup - Grid grid1 = new Grid(1, 1, 1); - Grid grid2 = new Grid(2, 2, 2); - List expectedGrids = Arrays.asList(grid1, grid2); - when(gridRepository.findAll()).thenReturn(expectedGrids); - - // execute - ResponseEntity> response = gridController.getAllGrids(); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(expectedGrids, response.getBody()); + void getAllGrids_Success() throws Exception { + List grids = List.of( + new Grid(1, 10, 20), + new Grid(2, 30, 40) + ); + List dtos = List.of( + new GridDto(1, 10, 20), + new GridDto(2, 30, 40) + ); + when(gridRepository.findAll()).thenReturn(grids); + when(gridMapper.toDtoList(grids)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/grids")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].gridId").value(1)) + .andExpect(jsonPath("$[0].rows").value(10)) + .andExpect(jsonPath("$[0].columns").value(20)) + .andExpect(jsonPath("$[1].gridId").value(2)) + .andExpect(jsonPath("$[1].rows").value(30)) + .andExpect(jsonPath("$[1].columns").value(40)); + + verify(gridRepository).findAll(); + verify(gridMapper).toDtoList(grids); } @Test - void testGetAllGrids_Exception() { - // setup - when(gridRepository.findAll()).thenThrow(new RuntimeException("Test Exception")); + void getAllGrids_RepositoryThrowsException() throws Exception { + when(gridRepository.findAll()).thenThrow(new RuntimeException("Database error")); - // execute - ResponseEntity> response = gridController.getAllGrids(); - - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/grids/{id} --- + @Test - void testGetGridById_Success() { - // setup - int gridId = 1; - Grid expectedGrid = new Grid(gridId, 1, 1); - when(gridRepository.findById(gridId)).thenReturn(Optional.of(expectedGrid)); - - // execute - ResponseEntity response = gridController.getGridById(gridId); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(expectedGrid, response.getBody()); + void getGridById_Success() throws Exception { + Grid grid = new Grid(1, 10, 20); + GridDto dto = new GridDto(1, 10, 20); + when(gridRepository.findById(1)).thenReturn(Optional.of(grid)); + when(gridMapper.toDto(grid)).thenReturn(dto); + + mockMvc.perform(get("/api/v1/grids/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.gridId").value(1)) + .andExpect(jsonPath("$.rows").value(10)) + .andExpect(jsonPath("$.columns").value(20)); + + verify(gridRepository).findById(1); + verify(gridMapper).toDto(grid); } @Test - void testGetGridById_NotFound() { - // setup - int gridId = 1; - when(gridRepository.findById(gridId)).thenReturn(Optional.empty()); + void getGridById_NotFound() throws Exception { + when(gridRepository.findById(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = gridController.getGridById(gridId); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Grid not found with id: 999")); } @Test - void testGetGridById_Exception() { - // setup - int gridId = 1; - when(gridRepository.findById(gridId)).thenThrow(new RuntimeException("Test Exception")); - - // execute - ResponseEntity response = gridController.getGridById(gridId); + void getGridById_RepositoryThrowsException() throws Exception { + when(gridRepository.findById(1)).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/grids/environment/{environmentId} --- + @Test - void testGetGridsInEnvironment_Success() { - // setup - int environmentId = 1; - Grid grid1 = new Grid(1, 1, 1); - Grid grid2 = new Grid(2, 2, 2); - List expectedGrids = Arrays.asList(grid1, grid2); - when(gridRepository.findByEnvironmentId(environmentId)).thenReturn(expectedGrids); - - // execute - ResponseEntity> response = gridController.getGridsInEnvironment(environmentId); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(expectedGrids, response.getBody()); + void getGridsInEnvironment_Success() throws Exception { + List grids = List.of(new Grid(1, 10, 20)); + List dtos = List.of(new GridDto(1, 10, 20)); + when(gridRepository.findByEnvironmentId(1)).thenReturn(grids); + when(gridMapper.toDtoList(grids)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/grids/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].gridId").value(1)) + .andExpect(jsonPath("$[0].rows").value(10)) + .andExpect(jsonPath("$[0].columns").value(20)); + + verify(gridRepository).findByEnvironmentId(1); + verify(gridMapper).toDtoList(grids); } @Test - void testGetGridsInEnvironment_EmptyList() { - // setup - int environmentId = 1; - when(gridRepository.findByEnvironmentId(environmentId)).thenReturn(Collections.emptyList()); - - // execute - ResponseEntity> response = gridController.getGridsInEnvironment(environmentId); + void getGridsInEnvironment_EmptyList() throws Exception { + when(gridRepository.findByEnvironmentId(1)).thenReturn(Collections.emptyList()); + when(gridMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue(response.getBody().isEmpty()); + mockMvc.perform(get("/api/v1/grids/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); } @Test - void testGetGridsInEnvironment_Exception() { - // setup - int environmentId = 1; - when(gridRepository.findByEnvironmentId(environmentId)).thenThrow(new RuntimeException("Test Exception")); + void getGridsInEnvironment_RepositoryThrowsException() throws Exception { + when(gridRepository.findByEnvironmentId(1)).thenThrow(new RuntimeException("Database error")); - // execute - ResponseEntity> response = gridController.getGridsInEnvironment(environmentId); - - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids/environment/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/grids/entity/{entityId} --- + @Test - void testGetGridOfEntity_Success() { - // setup - int entityId = 1; - Grid expectedGrid = new Grid(1, 1, 1); - when(gridRepository.findByEntityId(entityId)).thenReturn(Optional.of(expectedGrid)); - - // execute - ResponseEntity response = gridController.getGridOfEntity(entityId); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(expectedGrid, response.getBody()); + void getGridOfEntity_Success() throws Exception { + Grid grid = new Grid(5, 15, 25); + GridDto dto = new GridDto(5, 15, 25); + when(gridRepository.findByEntityId(1)).thenReturn(Optional.of(grid)); + when(gridMapper.toDto(grid)).thenReturn(dto); + + mockMvc.perform(get("/api/v1/grids/entity/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.gridId").value(5)) + .andExpect(jsonPath("$.rows").value(15)) + .andExpect(jsonPath("$.columns").value(25)); + + verify(gridRepository).findByEntityId(1); + verify(gridMapper).toDto(grid); } @Test - void testGetGridOfEntity_NotFound() { - // setup - int entityId = 1; - when(gridRepository.findByEntityId(entityId)).thenReturn(Optional.empty()); + void getGridOfEntity_NotFound() throws Exception { + when(gridRepository.findByEntityId(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = gridController.getGridOfEntity(entityId); - - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids/entity/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Grid not found for entity: 999")); } @Test - void testGetGridOfEntity_Exception() { - // setup - int entityId = 1; - when(gridRepository.findByEntityId(entityId)).thenThrow(new RuntimeException("Test Exception")); - - // execute - ResponseEntity response = gridController.getGridOfEntity(entityId); + void getGridOfEntity_RepositoryThrowsException() throws Exception { + when(gridRepository.findByEntityId(1)).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); + mockMvc.perform(get("/api/v1/grids/entity/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } } \ No newline at end of file diff --git a/src/test/java/preponderous/viron/controllers/LocationControllerTest.java b/src/test/java/preponderous/viron/controllers/LocationControllerTest.java index 351adac..eaf5d0f 100644 --- a/src/test/java/preponderous/viron/controllers/LocationControllerTest.java +++ b/src/test/java/preponderous/viron/controllers/LocationControllerTest.java @@ -1,267 +1,343 @@ package preponderous.viron.controllers; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; + +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import preponderous.viron.config.DbConfig; +import preponderous.viron.database.DbInteractions; +import preponderous.viron.dto.LocationDto; +import preponderous.viron.mappers.LocationMapper; import preponderous.viron.models.Location; import preponderous.viron.repositories.LocationRepository; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest +@AutoConfigureMockMvc class LocationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean private LocationRepository locationRepository; - private LocationController locationController; - @BeforeEach - void setUp() { - locationRepository = Mockito.mock(LocationRepository.class); - locationController = new LocationController(locationRepository); - } + @MockBean + private LocationMapper locationMapper; + + @MockBean + private DbInteractions dbInteractions; + + @MockBean + private DbConfig dbConfig; + + // --- GET /api/v1/locations --- @Test - void getAllLocations_Success() { - // setup - List locations = Arrays.asList( - new Location(1, 10, 20) + void getAllLocations_Success() throws Exception { + List locations = List.of( + new Location(1, 10, 20), + new Location(2, 30, 40) + ); + List dtos = List.of( + new LocationDto(1, 10, 20), + new LocationDto(2, 30, 40) ); when(locationRepository.findAll()).thenReturn(locations); + when(locationMapper.toDtoList(locations)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/locations")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].locationId").value(1)) + .andExpect(jsonPath("$[0].x").value(10)) + .andExpect(jsonPath("$[0].y").value(20)) + .andExpect(jsonPath("$[1].locationId").value(2)) + .andExpect(jsonPath("$[1].x").value(30)) + .andExpect(jsonPath("$[1].y").value(40)); - // execute - ResponseEntity> response = locationController.getAllLocations(); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(locations, response.getBody()); verify(locationRepository).findAll(); + verify(locationMapper).toDtoList(locations); } @Test - void getAllLocations_Exception() { - // setup - when(locationRepository.findAll()).thenThrow(new RuntimeException("Database error")); + void getAllLocations_EmptyList() throws Exception { + when(locationRepository.findAll()).thenReturn(Collections.emptyList()); + when(locationMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // execute - ResponseEntity> response = locationController.getAllLocations(); + mockMvc.perform(get("/api/v1/locations")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); + } - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); - verify(locationRepository).findAll(); + @Test + void getAllLocations_RepositoryThrowsException() throws Exception { + when(locationRepository.findAll()).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(get("/api/v1/locations")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/locations/{id} --- + @Test - void getLocationById_Success() { - // setup + void getLocationById_Success() throws Exception { Location location = new Location(1, 10, 20); + LocationDto dto = new LocationDto(1, 10, 20); when(locationRepository.findById(1)).thenReturn(Optional.of(location)); + when(locationMapper.toDto(location)).thenReturn(dto); - // execute - ResponseEntity response = locationController.getLocationById(1); + mockMvc.perform(get("/api/v1/locations/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.locationId").value(1)) + .andExpect(jsonPath("$.x").value(10)) + .andExpect(jsonPath("$.y").value(20)); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(location, response.getBody()); verify(locationRepository).findById(1); + verify(locationMapper).toDto(location); } @Test - void getLocationById_NotFound() { - // setup - when(locationRepository.findById(1)).thenReturn(Optional.empty()); + void getLocationById_NotFound() throws Exception { + when(locationRepository.findById(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = locationController.getLocationById(1); + mockMvc.perform(get("/api/v1/locations/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Location not found with id: 999")); + } - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); - verify(locationRepository).findById(1); + @Test + void getLocationById_RepositoryThrowsException() throws Exception { + when(locationRepository.findById(1)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(get("/api/v1/locations/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/locations/environment/{environmentId} --- + @Test - void getLocationsInEnvironment_Success() { - // setup - List locations = Arrays.asList(new Location(1, 10, 20)); + void getLocationsInEnvironment_Success() throws Exception { + List locations = List.of(new Location(1, 10, 20)); + List dtos = List.of(new LocationDto(1, 10, 20)); when(locationRepository.findByEnvironmentId(1)).thenReturn(locations); + when(locationMapper.toDtoList(locations)).thenReturn(dtos); - // execute - ResponseEntity> response = locationController.getLocationsInEnvironment(1); + mockMvc.perform(get("/api/v1/locations/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].locationId").value(1)) + .andExpect(jsonPath("$[0].x").value(10)) + .andExpect(jsonPath("$[0].y").value(20)); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(locations, response.getBody()); verify(locationRepository).findByEnvironmentId(1); + verify(locationMapper).toDtoList(locations); } @Test - void getLocationsInEnvironment_Exception() { - // setup - when(locationRepository.findByEnvironmentId(1)).thenThrow(new RuntimeException()); + void getLocationsInEnvironment_EmptyList() throws Exception { + when(locationRepository.findByEnvironmentId(1)).thenReturn(Collections.emptyList()); + when(locationMapper.toDtoList(Collections.emptyList())).thenReturn(Collections.emptyList()); - // execute - ResponseEntity> response = locationController.getLocationsInEnvironment(1); - - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); - verify(locationRepository).findByEnvironmentId(1); + mockMvc.perform(get("/api/v1/locations/environment/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); } @Test - void getLocationsInGrid_Success() { - // setup - List locations = Arrays.asList(new Location(1, 10, 20)); - when(locationRepository.findByGridId(1)).thenReturn(locations); - - // execute - ResponseEntity> response = locationController.getLocationsInGrid(1); - - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(locations, response.getBody()); - verify(locationRepository).findByGridId(1); + void getLocationsInEnvironment_RepositoryThrowsException() throws Exception { + when(locationRepository.findByEnvironmentId(1)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(get("/api/v1/locations/environment/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/locations/grid/{gridId} --- + @Test - void getLocationsInGrid_Exception() { - // setup - when(locationRepository.findByGridId(1)).thenThrow(new RuntimeException()); + void getLocationsInGrid_Success() throws Exception { + List locations = List.of(new Location(5, 50, 60)); + List dtos = List.of(new LocationDto(5, 50, 60)); + when(locationRepository.findByGridId(3)).thenReturn(locations); + when(locationMapper.toDtoList(locations)).thenReturn(dtos); + + mockMvc.perform(get("/api/v1/locations/grid/3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].locationId").value(5)) + .andExpect(jsonPath("$[0].x").value(50)) + .andExpect(jsonPath("$[0].y").value(60)); + + verify(locationRepository).findByGridId(3); + verify(locationMapper).toDtoList(locations); + } - // execute - ResponseEntity> response = locationController.getLocationsInGrid(1); + @Test + void getLocationsInGrid_RepositoryThrowsException() throws Exception { + when(locationRepository.findByGridId(1)).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNull(response.getBody()); - verify(locationRepository).findByGridId(1); + mockMvc.perform(get("/api/v1/locations/grid/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- GET /api/v1/locations/entity/{entityId} --- + @Test - void getLocationOfEntity_Success() { - // setup + void getLocationOfEntity_Success() throws Exception { Location location = new Location(1, 10, 20); + LocationDto dto = new LocationDto(1, 10, 20); when(locationRepository.findByEntityId(1)).thenReturn(Optional.of(location)); + when(locationMapper.toDto(location)).thenReturn(dto); - // execute - ResponseEntity response = locationController.getLocationOfEntity(1); + mockMvc.perform(get("/api/v1/locations/entity/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.locationId").value(1)) + .andExpect(jsonPath("$.x").value(10)) + .andExpect(jsonPath("$.y").value(20)); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(location, response.getBody()); verify(locationRepository).findByEntityId(1); + verify(locationMapper).toDto(location); } @Test - void getLocationOfEntity_NotFound() { - // setup - when(locationRepository.findByEntityId(1)).thenReturn(Optional.empty()); - - // execute - ResponseEntity response = locationController.getLocationOfEntity(1); + void getLocationOfEntity_NotFound() throws Exception { + when(locationRepository.findByEntityId(999)).thenReturn(Optional.empty()); - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - assertNull(response.getBody()); - verify(locationRepository).findByEntityId(1); + mockMvc.perform(get("/api/v1/locations/entity/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Location not found for entity: 999")); } @Test - void addEntityToLocation_Success() { - // setup - when(locationRepository.addEntityToLocation(1, 1)).thenReturn(true); - - // execute - ResponseEntity response = locationController.addEntityToLocation(1, 1); + void getLocationOfEntity_RepositoryThrowsException() throws Exception { + when(locationRepository.findByEntityId(1)).thenThrow(new RuntimeException("Database error")); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - verify(locationRepository).addEntityToLocation(1, 1); + mockMvc.perform(get("/api/v1/locations/entity/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- PUT /api/v1/locations/{locationId}/entity/{entityId} --- + @Test - void addEntityToLocation_NotFound() { - // setup - when(locationRepository.addEntityToLocation(1, 1)).thenReturn(false); + void addEntityToLocation_Success() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.of(new Location(2, 10, 20))); + when(locationRepository.addEntityToLocation(1, 2)).thenReturn(true); - // execute - ResponseEntity response = locationController.addEntityToLocation(1, 1); + mockMvc.perform(put("/api/v1/locations/2/entity/1")) + .andExpect(status().isOk()); - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - verify(locationRepository).addEntityToLocation(1, 1); + verify(locationRepository).addEntityToLocation(1, 2); } @Test - void addEntityToLocation_Exception() { - // setup - when(locationRepository.addEntityToLocation(1, 1)).thenThrow(new RuntimeException()); + void addEntityToLocation_NotFound() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = locationController.addEntityToLocation(1, 1); + mockMvc.perform(put("/api/v1/locations/2/entity/1")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Location not found with id: 2")); + } - // verify - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - verify(locationRepository).addEntityToLocation(1, 1); + @Test + void addEntityToLocation_RepositoryThrowsException() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.of(new Location(2, 10, 20))); + when(locationRepository.addEntityToLocation(1, 2)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(put("/api/v1/locations/2/entity/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- DELETE /api/v1/locations/{locationId}/entity/{entityId} --- + @Test - void removeEntityFromLocation_Success() { - // setup - when(locationRepository.removeEntityFromLocation(1, 1)).thenReturn(true); + void removeEntityFromLocation_Success() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.of(new Location(2, 10, 20))); + when(locationRepository.removeEntityFromLocation(1, 2)).thenReturn(true); - // execute - ResponseEntity response = locationController.removeEntityFromLocation(1, 1); + mockMvc.perform(delete("/api/v1/locations/2/entity/1")) + .andExpect(status().isNoContent()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); - verify(locationRepository).removeEntityFromLocation(1, 1); + verify(locationRepository).removeEntityFromLocation(1, 2); } @Test - void removeEntityFromLocation_NotFound() { - // setup - when(locationRepository.removeEntityFromLocation(1, 1)).thenReturn(false); + void removeEntityFromLocation_NotFound() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = locationController.removeEntityFromLocation(1, 1); + mockMvc.perform(delete("/api/v1/locations/2/entity/1")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Location not found with id: 2")); + } - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - verify(locationRepository).removeEntityFromLocation(1, 1); + @Test + void removeEntityFromLocation_RepositoryThrowsException() throws Exception { + when(locationRepository.findById(2)).thenReturn(Optional.of(new Location(2, 10, 20))); + when(locationRepository.removeEntityFromLocation(1, 2)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(delete("/api/v1/locations/2/entity/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } + // --- DELETE /api/v1/locations/entity/{entityId} --- + @Test - void removeEntityFromCurrentLocation_Success() { - // setup + void removeEntityFromCurrentLocation_Success() throws Exception { + when(locationRepository.findByEntityId(1)).thenReturn(Optional.of(new Location(5, 10, 20))); when(locationRepository.removeEntityFromCurrentLocation(1)).thenReturn(true); - // execute - ResponseEntity response = locationController.removeEntityFromCurrentLocation(1); + mockMvc.perform(delete("/api/v1/locations/entity/1")) + .andExpect(status().isNoContent()); - // verify - assertEquals(HttpStatus.OK, response.getStatusCode()); verify(locationRepository).removeEntityFromCurrentLocation(1); } @Test - void removeEntityFromCurrentLocation_NotFound() { - // setup - when(locationRepository.removeEntityFromCurrentLocation(1)).thenReturn(false); + void removeEntityFromCurrentLocation_NotFound() throws Exception { + when(locationRepository.findByEntityId(999)).thenReturn(Optional.empty()); - // execute - ResponseEntity response = locationController.removeEntityFromCurrentLocation(1); + mockMvc.perform(delete("/api/v1/locations/entity/999")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status").value(404)) + .andExpect(jsonPath("$.message").value("Location not found for entity: 999")); + } - // verify - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); - verify(locationRepository).removeEntityFromCurrentLocation(1); + @Test + void removeEntityFromCurrentLocation_RepositoryThrowsException() throws Exception { + when(locationRepository.findByEntityId(1)).thenReturn(Optional.of(new Location(5, 10, 20))); + when(locationRepository.removeEntityFromCurrentLocation(1)).thenThrow(new RuntimeException("Database error")); + + mockMvc.perform(delete("/api/v1/locations/entity/1")) + .andExpect(status().isInternalServerError()) + .andExpect(jsonPath("$.status").value(500)) + .andExpect(jsonPath("$.message").isString()); } } \ No newline at end of file