Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package pl.milosnicyit.codewarehousebackend.controllers.v1.location;

import java.util.List;

import org.jspecify.annotations.NonNull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import pl.milosnicyit.codewarehousebackend.location.Location;
import pl.milosnicyit.codewarehousebackend.location.LocationService;

@RestController
@RequestMapping("/api/locations")
class LocationController {

private final LocationService locationService;

LocationController(LocationService locationService) {
this.locationService = locationService;
}

@GetMapping
ResponseEntity<List<Location>> getLocations() {
return ResponseEntity.ok(locationService.getAllLocations());
}
@PostMapping
ResponseEntity<Location>
createLocation(@RequestBody Location location) {
return ResponseEntity.ok(locationService.createLocation(location));
}
@PatchMapping("/{id}")
ResponseEntity<Location>
updateLocation(@PathVariable Long id, @RequestBody @NonNull Location locationUpdate) {
return ResponseEntity.ok(
locationService.updateLocationName(id, locationUpdate.getName()));
}
@DeleteMapping("/{id}")
ResponseEntity<Void> deleteLocation(@PathVariable Long id) {
locationService.deleteLocation(id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package pl.milosnicyit.codewarehousebackend.location;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Location {
Comment thread
dawiddykacz marked this conversation as resolved.
Comment thread
dawiddykacz marked this conversation as resolved.
private Long id;
private String name;
private boolean active = true;
private boolean empty = true;

public Location(Long id, String name) {
this.id = id;
this.name = name;
this.active = true;
this.empty = true;
}

void deactivate() {
if (!this.empty) {
throw new IllegalStateException("Cannot delete location: it is not empty");
}
this.active = false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pl.milosnicyit.codewarehousebackend.location.adapter;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pl.milosnicyit.codewarehousebackend.location.LocationRepository;
import pl.milosnicyit.codewarehousebackend.location.LocationService;
import pl.milosnicyit.codewarehousebackend.location.LocationServiceImpl;

@Configuration
class LocationConfiguration {

@Bean
public LocationService locationService(LocationRepository locationRepository) {
return new LocationServiceImpl(locationRepository);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pl.milosnicyit.codewarehousebackend.location;

import java.util.List;
import java.util.Optional;

public interface LocationRepository {
List<Location> findAll();
Optional<Location> findById(Long id);
Optional<Location> findByName(String name);
Location save(Location location);
void deleteById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.milosnicyit.codewarehousebackend.location;

import java.util.List;

public interface LocationService {
List<Location> getAllLocations();
Location createLocation(Location location);
Location updateLocationName(Long id, String newName);
void deleteLocation(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package pl.milosnicyit.codewarehousebackend.location;

import java.util.List;
import java.util.stream.Collectors;

public class LocationServiceImpl implements LocationService {

private final LocationRepository locationRepository;

public LocationServiceImpl(LocationRepository locationRepository) {
this.locationRepository = locationRepository;
}

@Override
public List<Location> getAllLocations() {
return locationRepository.findAll().stream()
.filter(Location::isActive)
.collect(Collectors.toList());
}

@Override
public Location createLocation(Location location) {
if (locationRepository.findByName(location.getName()).isPresent()) {
throw new IllegalArgumentException("Location with this name already exists");
}
return locationRepository.save(location);
}

@Override
public Location updateLocationName(Long id, String newName) {
Location existingLocation = locationRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Location not found"));
existingLocation.setName(newName);
return locationRepository.save(existingLocation);
}

@Override
public void deleteLocation(Long id) {
Location location = locationRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Location not found"));

location.deactivate();
locationRepository.save(location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package pl.milosnicyit.codewarehousebackend.location.database;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import pl.milosnicyit.codewarehousebackend.location.Location;

@Entity
@Table(name = "locations")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LocationEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String name;

@Column(nullable = false)
private boolean active = true;

@Column(nullable = false)
private boolean empty = true;

public static LocationEntity fromDomain(Location location) {
return new LocationEntity(
location.getId(),
location.getName(),
location.isActive(),
location.isEmpty()
);
}

public Location toDomain() {
return new Location(id, name, active, empty);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pl.milosnicyit.codewarehousebackend.location.database.wrapper;

import org.springframework.data.jpa.repository.JpaRepository;

import pl.milosnicyit.codewarehousebackend.location.database.LocationEntity;

import java.util.Optional;

interface LocationJpaRepository extends JpaRepository<LocationEntity, Long> {
Optional<LocationEntity> findByName(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package pl.milosnicyit.codewarehousebackend.location.database.wrapper;

import org.springframework.stereotype.Repository;
import pl.milosnicyit.codewarehousebackend.location.Location;
import pl.milosnicyit.codewarehousebackend.location.LocationRepository;
import pl.milosnicyit.codewarehousebackend.location.database.LocationEntity;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Repository
class LocationRepositoryWrapper implements LocationRepository {

private final LocationJpaRepository jpaRepository;

LocationRepositoryWrapper(LocationJpaRepository jpaRepository) {
this.jpaRepository = jpaRepository;
}

@Override
public List<Location> findAll() {
return jpaRepository.findAll().stream()
.map(LocationEntity::toDomain)
.collect(Collectors.toList());
}

@Override
public Optional<Location> findById(Long id) {
return jpaRepository.findById(id).map(LocationEntity::toDomain);
}

@Override
public Optional<Location> findByName(String name) {
return jpaRepository.findByName(name).map(LocationEntity::toDomain);
}

@Override
public Location save(Location location) {
LocationEntity entity = LocationEntity.fromDomain(location);
LocationEntity savedEntity = jpaRepository.save(entity);
return savedEntity.toDomain();
}

@Override
public void deleteById(Long id) {
jpaRepository.deleteById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package pl.milosnicyit.codewarehousebackend.controllers.v1.location;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import pl.milosnicyit.codewarehousebackend.location.Location;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static pl.milosnicyit.codewarehousebackend.helpers.JsonHelper.toJson;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class LocationControllerE2ETest {

private static final String LOCATION_ENDPOINT = "/api/locations";

@Autowired
private MockMvc mockMvc;

@Test
@WithMockUser
void shouldCreateLocationEndToEnd() throws Exception {
Location location = new Location();
location.setName("Magazyn E2E");

mockMvc.perform(post(LOCATION_ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.content(toJson(location)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").isNotEmpty())
.andExpect(jsonPath("$.name").value("Magazyn E2E"));
}

@Test
@WithMockUser
void shouldGetAllLocationsEndToEnd() throws Exception {
mockMvc.perform(get(LOCATION_ENDPOINT))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package pl.milosnicyit.codewarehousebackend.location;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class LocationServiceSpecificationTest {

@Mock
private LocationRepository locationRepository;

private LocationService locationService;

@BeforeEach
void setUp() {
locationService = new LocationServiceImpl(locationRepository);
}

@Test
void shouldDeactivateLocationWhenEmpty() {
// given (empty = true)
Location location = new Location(1L, "Magazyn", true, true);
when(locationRepository.findById(1L)).thenReturn(Optional.of(location));

// when
locationService.deleteLocation(1L);

// then
assertFalse(location.isActive());
verify(locationRepository).save(location);
}

@Test
void shouldThrowExceptionWhenDeletingNonEmptyLocation() {
// given (empty = false)
Location location = new Location(1L, "Magazyn", true, false);
when(locationRepository.findById(1L)).thenReturn(Optional.of(location));

// when / then
assertThrows(IllegalStateException.class, () -> locationService.deleteLocation(1L));
}
}
Loading