mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-15 00:17:42 +00:00
feat: Add the implementation of Onion-Architecture (#3532)
* Add the implementation of Onion-Architecture * Execute maven command: mvn spotless:apply
This commit is contained in:
1 parent
b55fc2bcb5
commit
688095a4c7
32 files changed
+3182
-1
No files matched your search
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2022 Ilkka Seppälä
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>onion-architecture</artifactId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>infrastructure</artifactId>
|
||||
<name>Infrastructure</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>application</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- REST Controller -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JPA + Spring Data JPA-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- H2 Database -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.config;
|
||||
|
||||
import com.iluwatar.onion.application.usecase.GetPersonUseCase;
|
||||
import com.iluwatar.onion.application.usecase.SavePersonUseCase;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean
|
||||
public SavePersonUseCase savePersonUseCase(PersonRepository repository) {
|
||||
return new SavePersonUseCase(repository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GetPersonUseCase getPersonUseCase(PersonRepository repository) {
|
||||
return new GetPersonUseCase(repository);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.persistence;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "category")
|
||||
public class JpaCategoryEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String type;
|
||||
|
||||
public JpaCategoryEntity() {}
|
||||
|
||||
public JpaCategoryEntity(Long id, String type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.persistence;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "person")
|
||||
public class JpaPersonEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private int age;
|
||||
private String phoneNumber;
|
||||
private String email;
|
||||
|
||||
@ManyToOne private JpaCategoryEntity category;
|
||||
|
||||
public JpaPersonEntity() {}
|
||||
|
||||
public JpaPersonEntity(
|
||||
Long id,
|
||||
String firstName,
|
||||
String lastName,
|
||||
int age,
|
||||
String phoneNumber,
|
||||
String email,
|
||||
JpaCategoryEntity category) {
|
||||
this.id = id;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.email = email;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public JpaCategoryEntity getCategory() {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.persistence;
|
||||
|
||||
import com.iluwatar.onion.domain.model.Category;
|
||||
import com.iluwatar.onion.domain.model.Person;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class PersonRepositoryAdapter implements PersonRepository {
|
||||
|
||||
private final SpringDataPersonRepository repository;
|
||||
|
||||
public PersonRepositoryAdapter(SpringDataPersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Person> findById(Long id) {
|
||||
return repository.findById(id).map(this::mapToDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Person> findByFirstName(String firstName) {
|
||||
return repository.findByFirstName(firstName).map(this::mapToDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Person> findByLastName(String lastName) {
|
||||
return repository.findByLastName(lastName).map(this::mapToDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<Person>> findAll() {
|
||||
return repository.findAll().stream()
|
||||
.map(this::mapToDomain)
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Person save(Person person) {
|
||||
JpaPersonEntity entity = mapToEntity(person);
|
||||
JpaPersonEntity savedEntity = repository.save(entity);
|
||||
return mapToDomain(savedEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteById(Long id) {
|
||||
repository.deleteById(id);
|
||||
return repository.findById(id).isEmpty();
|
||||
}
|
||||
|
||||
private JpaPersonEntity mapToEntity(Person person) {
|
||||
return new JpaPersonEntity(
|
||||
person.getId(),
|
||||
person.getFirstName(),
|
||||
person.getLastName(),
|
||||
person.getAge(),
|
||||
person.getPhoneNumber(),
|
||||
person.getEmail(),
|
||||
new JpaCategoryEntity(person.getCategory().getId(), person.getCategory().getType()));
|
||||
}
|
||||
|
||||
private Person mapToDomain(JpaPersonEntity entity) {
|
||||
return new Person(
|
||||
entity.getId(),
|
||||
entity.getFirstName(),
|
||||
entity.getLastName(),
|
||||
entity.getAge(),
|
||||
entity.getPhoneNumber(),
|
||||
entity.getEmail(),
|
||||
new Category(entity.getCategory().getId(), entity.getCategory().getType()));
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.persistence;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface SpringDataPersonRepository extends JpaRepository<JpaPersonEntity, Long> {
|
||||
|
||||
Optional<JpaPersonEntity> findByFirstName(String firstName);
|
||||
|
||||
Optional<JpaPersonEntity> findByLastName(String lastName);
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.web;
|
||||
|
||||
import com.iluwatar.onion.application.dto.PersonResponse;
|
||||
import com.iluwatar.onion.application.dto.SavePersonCommand;
|
||||
import com.iluwatar.onion.application.usecase.GetPersonUseCase;
|
||||
import com.iluwatar.onion.application.usecase.SavePersonUseCase;
|
||||
import com.iluwatar.onion.domain.exception.DomainException;
|
||||
import java.util.List;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PersonController {
|
||||
|
||||
private final SavePersonUseCase savePersonUseCase;
|
||||
private final GetPersonUseCase getPersonUseCase;
|
||||
|
||||
public PersonController(SavePersonUseCase savePersonUseCase, GetPersonUseCase getPersonUseCase) {
|
||||
this.savePersonUseCase = savePersonUseCase;
|
||||
this.getPersonUseCase = getPersonUseCase;
|
||||
}
|
||||
|
||||
@GetMapping("/persons/{id}")
|
||||
public ResponseEntity<PersonResponse> getPerson(@PathVariable("id") Long id) {
|
||||
var person = getPersonUseCase.execute(id);
|
||||
return ResponseEntity.ok(person);
|
||||
}
|
||||
|
||||
@GetMapping("/persons")
|
||||
public ResponseEntity<List<PersonResponse>> getAllPersons() {
|
||||
var persons = getPersonUseCase.executeAll();
|
||||
return ResponseEntity.ok(persons);
|
||||
}
|
||||
|
||||
@PostMapping("/persons")
|
||||
public ResponseEntity<PersonResponse> savePerson(@RequestBody SavePersonCommand command) {
|
||||
try {
|
||||
var savedPerson = savePersonUseCase.execute(command);
|
||||
return ResponseEntity.status(HttpStatus.OK).body(savedPerson);
|
||||
} catch (DomainException e) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# You can also specify the server port in the application.properties file.
|
||||
# Uncomment the following line and set the desired port number to change
|
||||
# the default port (8080) to a different value.
|
||||
#server.port=8080
|
||||
|
||||
spring.datasource.url=jdbc:h2:mem:testdb
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=password
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
|
||||
# Force Spring Boot to always run SQL initialization scripts on startup
|
||||
spring.sql.init.mode=always
|
||||
|
||||
# If using Spring Data JPA / Hibernate, defer data insertion until AFTER entities are generated
|
||||
spring.jpa.defer-datasource-initialization=true
|
||||
|
||||
# Keep Hibernate's DDL generation active (or set to 'update')
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO category (id, type) VALUES (1, 'Teenage');
|
||||
INSERT INTO category (id, type) VALUES (2, 'Young Adult');
|
||||
INSERT INTO category (id, type) VALUES (3, 'Adult');
|
||||
INSERT INTO category (id, type) VALUES (4, 'Senior');
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.persistence;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.iluwatar.onion.domain.model.Category;
|
||||
import com.iluwatar.onion.domain.model.Person;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PersonRepositoryAdapterTest {
|
||||
|
||||
@Mock private SpringDataPersonRepository springDataRepository;
|
||||
|
||||
@Captor private ArgumentCaptor<JpaPersonEntity> entityCaptor;
|
||||
|
||||
private PersonRepositoryAdapter personRepositoryAdapter;
|
||||
private JpaPersonEntity jpaPersonEntity;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
personRepositoryAdapter = new PersonRepositoryAdapter(springDataRepository);
|
||||
|
||||
// Setup test data
|
||||
var jpaCategory = new JpaCategoryEntity(1L, "Professional");
|
||||
jpaPersonEntity =
|
||||
new JpaPersonEntity(
|
||||
1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", jpaCategory);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Find By ID")
|
||||
class FindById {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return Person when entity exists")
|
||||
void shouldReturnPersonWhenEntityExists() {
|
||||
// Arrange
|
||||
when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity));
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findById(1L);
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
Person person = result.get();
|
||||
assertEquals(1L, person.getId());
|
||||
assertEquals("John", person.getFirstName());
|
||||
assertEquals("Doe", person.getLastName());
|
||||
assertEquals(25, person.getAge());
|
||||
assertEquals("+1234567890", person.getPhoneNumber());
|
||||
assertEquals("john.doe@example.com", person.getEmail());
|
||||
assertEquals(1L, person.getCategory().getId());
|
||||
assertEquals("Professional", person.getCategory().getType());
|
||||
|
||||
verify(springDataRepository, times(1)).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty Optional when entity not found")
|
||||
void shouldReturnEmptyWhenEntityNotFound() {
|
||||
// Arrange
|
||||
when(springDataRepository.findById(999L)).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findById(999L);
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isEmpty());
|
||||
verify(springDataRepository, times(1)).findById(999L);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Find By First Name")
|
||||
class FindByFirstName {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return Person when entity with first name exists")
|
||||
void shouldReturnPersonWhenEntityExists() {
|
||||
// Arrange
|
||||
when(springDataRepository.findByFirstName("John")).thenReturn(Optional.of(jpaPersonEntity));
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findByFirstName("John");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("John", result.get().getFirstName());
|
||||
verify(springDataRepository, times(1)).findByFirstName("John");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty Optional when no entity found")
|
||||
void shouldReturnEmptyWhenNotFound() {
|
||||
// Arrange
|
||||
when(springDataRepository.findByFirstName("Unknown")).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findByFirstName("Unknown");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isEmpty());
|
||||
verify(springDataRepository, times(1)).findByFirstName("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Find By Last Name")
|
||||
class FindByLastName {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return Person when entity with last name exists")
|
||||
void shouldReturnPersonWhenEntityExists() {
|
||||
// Arrange
|
||||
when(springDataRepository.findByLastName("Doe")).thenReturn(Optional.of(jpaPersonEntity));
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findByLastName("Doe");
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Doe", result.get().getLastName());
|
||||
verify(springDataRepository, times(1)).findByLastName("Doe");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Find All")
|
||||
class FindAll {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return list of Persons when entities exist")
|
||||
void shouldReturnListOfPersons() {
|
||||
// Arrange
|
||||
var category2 = new JpaCategoryEntity(2L, "Personal");
|
||||
var person2 =
|
||||
new JpaPersonEntity(
|
||||
2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", category2);
|
||||
|
||||
when(springDataRepository.findAll()).thenReturn(List.of(jpaPersonEntity, person2));
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findAll();
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
var persons = result.get();
|
||||
assertEquals(2, persons.size());
|
||||
assertEquals("John", persons.get(0).getFirstName());
|
||||
assertEquals("Jane", persons.get(1).getFirstName());
|
||||
|
||||
verify(springDataRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when no entities exist")
|
||||
void shouldReturnEmptyListWhenNoEntities() {
|
||||
// Arrange
|
||||
when(springDataRepository.findAll()).thenReturn(List.of());
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.findAll();
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertTrue(result.get().isEmpty());
|
||||
verify(springDataRepository, times(1)).findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Save")
|
||||
class Save {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should save person and return domain model")
|
||||
void shouldSavePersonAndReturnDomainModel() {
|
||||
// Arrange
|
||||
var personToSave =
|
||||
new Person(
|
||||
null, // New person without ID
|
||||
"Jane",
|
||||
"Smith",
|
||||
28,
|
||||
"+1111111111",
|
||||
"jane@example.com",
|
||||
new Category(2L, "Personal"));
|
||||
|
||||
var savedCategory = new JpaCategoryEntity(2L, "Personal");
|
||||
var savedEntity =
|
||||
new JpaPersonEntity(
|
||||
100L, // ID assigned by database
|
||||
"Jane",
|
||||
"Smith",
|
||||
28,
|
||||
"+1111111111",
|
||||
"jane@example.com",
|
||||
savedCategory);
|
||||
|
||||
when(springDataRepository.save(any(JpaPersonEntity.class))).thenReturn(savedEntity);
|
||||
|
||||
// Act
|
||||
var result = personRepositoryAdapter.save(personToSave);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(100L, result.getId());
|
||||
assertEquals("Jane", result.getFirstName());
|
||||
assertEquals("Smith", result.getLastName());
|
||||
assertEquals(28, result.getAge());
|
||||
|
||||
verify(springDataRepository, times(1)).save(any(JpaPersonEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should correctly map domain model to JPA entity")
|
||||
void shouldCorrectlyMapDomainToEntity() {
|
||||
// Arrange
|
||||
var personToSave =
|
||||
new Person(
|
||||
null,
|
||||
"Bob",
|
||||
"Johnson",
|
||||
35,
|
||||
"+2222222222",
|
||||
"bob@example.com",
|
||||
new Category(3L, "Business"));
|
||||
|
||||
var savedEntity =
|
||||
new JpaPersonEntity(
|
||||
200L,
|
||||
"Bob",
|
||||
"Johnson",
|
||||
35,
|
||||
"+2222222222",
|
||||
"bob@example.com",
|
||||
new JpaCategoryEntity(3L, "Business"));
|
||||
|
||||
when(springDataRepository.save(any(JpaPersonEntity.class))).thenReturn(savedEntity);
|
||||
|
||||
// Act
|
||||
personRepositoryAdapter.save(personToSave);
|
||||
|
||||
// Assert - Verify what was passed to Spring Data repository
|
||||
verify(springDataRepository).save(entityCaptor.capture());
|
||||
JpaPersonEntity capturedEntity = entityCaptor.getValue();
|
||||
|
||||
assertEquals("Bob", capturedEntity.getFirstName());
|
||||
assertEquals("Johnson", capturedEntity.getLastName());
|
||||
assertEquals(35, capturedEntity.getAge());
|
||||
assertEquals("+2222222222", capturedEntity.getPhoneNumber());
|
||||
assertEquals("bob@example.com", capturedEntity.getEmail());
|
||||
assertEquals(3L, capturedEntity.getCategory().getId());
|
||||
assertEquals("Business", capturedEntity.getCategory().getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Delete By ID")
|
||||
class DeleteById {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return true when person is successfully deleted")
|
||||
void shouldReturnTrueWhenDeleted() {
|
||||
// Arrange
|
||||
doNothing().when(springDataRepository).deleteById(1L);
|
||||
when(springDataRepository.findById(1L)).thenReturn(Optional.empty());
|
||||
|
||||
// Act
|
||||
boolean result = personRepositoryAdapter.deleteById(1L);
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
verify(springDataRepository, times(1)).deleteById(1L);
|
||||
verify(springDataRepository, times(1)).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return false when person still exists after deletion")
|
||||
void shouldReturnFalseWhenStillExists() {
|
||||
// Arrange
|
||||
doNothing().when(springDataRepository).deleteById(1L);
|
||||
when(springDataRepository.findById(1L)).thenReturn(Optional.of(jpaPersonEntity));
|
||||
|
||||
// Act
|
||||
boolean result = personRepositoryAdapter.deleteById(1L);
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
verify(springDataRepository, times(1)).deleteById(1L);
|
||||
verify(springDataRepository, times(1)).findById(1L);
|
||||
}
|
||||
}
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.infrastructure.web;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.iluwatar.onion.application.dto.PersonResponse;
|
||||
import com.iluwatar.onion.application.dto.SavePersonCommand;
|
||||
import com.iluwatar.onion.application.usecase.GetPersonUseCase;
|
||||
import com.iluwatar.onion.application.usecase.SavePersonUseCase;
|
||||
import com.iluwatar.onion.domain.exception.DomainException;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@WebMvcTest(PersonController.class)
|
||||
class PersonControllerTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
@MockitoBean private SavePersonUseCase savePersonUseCase;
|
||||
|
||||
@MockitoBean private GetPersonUseCase getPersonUseCase;
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/persons/{id}")
|
||||
class GetPersonById {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return person when person exists")
|
||||
void shouldReturnPersonWhenExists() throws Exception {
|
||||
// Arrange
|
||||
var response =
|
||||
new PersonResponse(
|
||||
1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", 1L, "Professional");
|
||||
|
||||
when(getPersonUseCase.execute(1L)).thenReturn(response);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(get("/api/persons/1").contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$.id").value(1))
|
||||
.andExpect(jsonPath("$.firstName").value("John"))
|
||||
.andExpect(jsonPath("$.lastName").value("Doe"))
|
||||
.andExpect(jsonPath("$.age").value(25))
|
||||
.andExpect(jsonPath("$.phoneNumber").value("+1234567890"))
|
||||
.andExpect(jsonPath("$.email").value("john.doe@example.com"))
|
||||
.andExpect(jsonPath("$.categoryId").value(1))
|
||||
.andExpect(jsonPath("$.categoryType").value("Professional"));
|
||||
|
||||
verify(getPersonUseCase, times(1)).execute(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle different person IDs")
|
||||
void shouldHandleDifferentPersonIds() throws Exception {
|
||||
// Arrange
|
||||
var response =
|
||||
new PersonResponse(
|
||||
42L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal");
|
||||
|
||||
when(getPersonUseCase.execute(42L)).thenReturn(response);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(get("/api/persons/42").contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(42))
|
||||
.andExpect(jsonPath("$.firstName").value("Jane"));
|
||||
|
||||
verify(getPersonUseCase, times(1)).execute(42L);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/persons")
|
||||
class GetAllPersons {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return list of persons")
|
||||
void shouldReturnListOfPersons() throws Exception {
|
||||
// Arrange
|
||||
var person1 =
|
||||
new PersonResponse(
|
||||
1L, "John", "Doe", 25, "+1234567890", "john@example.com", 1L, "Professional");
|
||||
var person2 =
|
||||
new PersonResponse(
|
||||
2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal");
|
||||
|
||||
var persons = List.of(person1, person2);
|
||||
when(getPersonUseCase.executeAll()).thenReturn(persons);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$", hasSize(2)))
|
||||
.andExpect(jsonPath("$[0].id").value(1))
|
||||
.andExpect(jsonPath("$[0].firstName").value("John"))
|
||||
.andExpect(jsonPath("$[1].id").value(2))
|
||||
.andExpect(jsonPath("$[1].firstName").value("Jane"));
|
||||
|
||||
verify(getPersonUseCase, times(1)).executeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when no persons exist")
|
||||
void shouldReturnEmptyListWhenNoPersons() throws Exception {
|
||||
// Arrange
|
||||
when(getPersonUseCase.executeAll()).thenReturn(List.of());
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$", hasSize(0)));
|
||||
|
||||
verify(getPersonUseCase, times(1)).executeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle large list of persons")
|
||||
void shouldHandleLargeListOfPersons() throws Exception {
|
||||
// Arrange
|
||||
var persons =
|
||||
List.of(
|
||||
new PersonResponse(1L, "Person1", "Last1", 25, "+1", "p1@example.com", 1L, "Cat1"),
|
||||
new PersonResponse(2L, "Person2", "Last2", 26, "+2", "p2@example.com", 1L, "Cat1"),
|
||||
new PersonResponse(3L, "Person3", "Last3", 27, "+3", "p3@example.com", 1L, "Cat1"));
|
||||
|
||||
when(getPersonUseCase.executeAll()).thenReturn(persons);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(get("/api/persons").contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(3)));
|
||||
|
||||
verify(getPersonUseCase, times(1)).executeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("POST /api/persons")
|
||||
class SavePerson {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create person with valid data")
|
||||
void shouldCreatePersonWithValidData() throws Exception {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john.doe@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Professional");
|
||||
|
||||
var response =
|
||||
new PersonResponse(
|
||||
1L, "John", "Doe", 25, "+1234567890", "john.doe@example.com", 1L, "Professional");
|
||||
|
||||
when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/api/persons")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(command)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$.id").value(1))
|
||||
.andExpect(jsonPath("$.firstName").value("John"))
|
||||
.andExpect(jsonPath("$.lastName").value("Doe"))
|
||||
.andExpect(jsonPath("$.age").value(25))
|
||||
.andExpect(jsonPath("$.phoneNumber").value("+1234567890"))
|
||||
.andExpect(jsonPath("$.email").value("john.doe@example.com"))
|
||||
.andExpect(jsonPath("$.categoryId").value(1))
|
||||
.andExpect(jsonPath("$.categoryType").value("Professional"));
|
||||
|
||||
verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle validation errors from use case")
|
||||
void shouldPropagateValidationErrors() throws Exception {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"Young",
|
||||
"Person",
|
||||
17, // Invalid age
|
||||
"+1234567890",
|
||||
"young@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Student");
|
||||
|
||||
when(savePersonUseCase.execute(any(SavePersonCommand.class)))
|
||||
.thenThrow(new DomainException("Age cannot be less than 18"));
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/api/persons")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(command)))
|
||||
.andExpect(status().is4xxClientError());
|
||||
|
||||
verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle malformed JSON")
|
||||
void shouldHandleMalformedJson() throws Exception {
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/api/persons")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{invalid json}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
|
||||
verify(savePersonUseCase, never()).execute(any(SavePersonCommand.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should accept all required fields in command")
|
||||
void shouldAcceptAllRequiredFields() throws Exception {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"Complete",
|
||||
"Person",
|
||||
30,
|
||||
"+1111111111",
|
||||
"complete@example.com",
|
||||
"456 Oak Ave",
|
||||
5L,
|
||||
"Business");
|
||||
|
||||
var response =
|
||||
new PersonResponse(
|
||||
10L, "Complete", "Person", 30, "+1111111111", "complete@example.com", 5L, "Business");
|
||||
|
||||
when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(response);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/api/persons")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(command)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.categoryType").value("Business"));
|
||||
|
||||
verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Controller Integration")
|
||||
class ControllerIntegration {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple requests sequentially")
|
||||
void shouldHandleMultipleRequestsSequentially() throws Exception {
|
||||
// Arrange
|
||||
var getResponse =
|
||||
new PersonResponse(
|
||||
1L, "John", "Doe", 25, "+1234567890", "john@example.com", 1L, "Professional");
|
||||
|
||||
var savePersonCommand =
|
||||
new SavePersonCommand(
|
||||
"Jane", "Smith", 30, "+9876543210", "jane@example.com", "456 Oak", 2L, "Personal");
|
||||
|
||||
var savePersonResponse =
|
||||
new PersonResponse(
|
||||
2L, "Jane", "Smith", 30, "+9876543210", "jane@example.com", 2L, "Personal");
|
||||
|
||||
when(getPersonUseCase.execute(1L)).thenReturn(getResponse);
|
||||
when(savePersonUseCase.execute(any(SavePersonCommand.class))).thenReturn(savePersonResponse);
|
||||
|
||||
// Act & Assert - GET request
|
||||
mockMvc
|
||||
.perform(get("/api/persons/1"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.firstName").value("John"));
|
||||
|
||||
// Act & Assert - POST request
|
||||
mockMvc
|
||||
.perform(
|
||||
post("/api/persons")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(savePersonCommand)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.firstName").value("Jane"));
|
||||
|
||||
verify(getPersonUseCase, times(1)).execute(1L);
|
||||
verify(savePersonUseCase, times(1)).execute(any(SavePersonCommand.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user