diff --git a/2020-08/spring-20/.gitignore b/2020-08/spring-20/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2020-08/spring-20/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2020-08/spring-20/pom.xml b/2020-08/spring-20/pom.xml
new file mode 100644
index 00000000..8997be90
--- /dev/null
+++ b/2020-08/spring-20/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-20
+ 1.0
+
+ pom
+
+
+ spring-20-exercise
+ spring-20-solution
+
+
diff --git a/2020-08/spring-20/spring-20-exercise/pom.xml b/2020-08/spring-20/spring-20-exercise/pom.xml
new file mode 100644
index 00000000..e0ce100a
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-20-exercise
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.4.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+
+
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/Main.java b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..dcecffd5
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,80 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repository.PersonRepository;
+
+import java.util.Arrays;
+
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main( String[] args ) {
+ ApplicationContext context = SpringApplication.run( Main.class );
+ PersonRepository repository = context.getBean( PersonRepository.class );
+
+ repository.saveAll( Arrays.asList(
+ new Person( "Pushkin", 22 ),
+ new Person( "Lermontov", 22 ),
+ new Person( "Tolstoy", 60 )
+ ) ).subscribe( p -> System.out.println( p.getLastName() ) );
+
+ }
+
+ @Bean
+ public RouterFunction composedRoutes( PersonRepository repository ) {
+
+ PersonHandler handler = new PersonHandler( repository );
+
+ RouterFunction route = route()
+ .GET( "/func/person", accept( APPLICATION_JSON ), handler::list )
+ .GET( "/func/person/{id}", accept( APPLICATION_JSON ),
+ request -> repository.findById( request.pathVariable( "id" ) )
+ .flatMap( person -> ok().contentType( APPLICATION_JSON ).body( fromObject( person ) ) )
+ )
+ .GET( "/func/person/age/{age}", accept( APPLICATION_JSON ),
+ serverRequest -> ok().contentType( APPLICATION_JSON )
+ .body( repository.findAllByAge( Integer.valueOf( serverRequest.pathVariable( "age" ) ) ), Person.class ) )
+ .GET( "/func/person/find", accept( APPLICATION_JSON ),
+ serverRequest -> ok().contentType( APPLICATION_JSON )
+ .body( repository.findAllByAge( Integer.valueOf( serverRequest.queryParam( "age" ).get() ) ), Person.class ) )
+ .build();
+
+ return route;
+ }
+
+
+ static class PersonHandler {
+
+ private PersonRepository repository;
+
+ PersonHandler( PersonRepository repository ) {
+ this.repository = repository;
+ }
+
+ Mono list( ServerRequest request ) {
+ return ok().contentType( APPLICATION_JSON ).body( repository.findAll(), Person.class );
+ }
+
+ Mono listAge( ServerRequest request ) {
+ System.out.println( "I'm here" );
+ return ok().contentType( APPLICATION_JSON )
+ .body( repository.findAllByAge( Integer.valueOf( request.queryParam( "age" ).get() ) ), Person.class );
+ }
+ }
+}
+
+
diff --git a/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/domain/Person.java b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..d40218ec
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,55 @@
+package ru.otus.spring.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
+
+@Document
+public class Person {
+
+ @Id
+ private String id;
+
+ @JsonProperty("name")
+ @Field("name")
+ private String lastName;
+
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public Person(String lastName, int age) {
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/repository/PersonRepository.java b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/repository/PersonRepository.java
new file mode 100644
index 00000000..0584f0cb
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/repository/PersonRepository.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.repository;
+
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+
+public interface PersonRepository
+ extends ReactiveMongoRepository {
+
+ Flux findAll();
+
+ Mono findById(String id);
+
+ Mono save(Mono person);
+
+ Flux findAllByLastName(String lastName);
+
+ Flux findAllByAge(int age);
+}
diff --git a/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/AnnotatedController.java b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/AnnotatedController.java
new file mode 100644
index 00000000..0f1a75b5
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/AnnotatedController.java
@@ -0,0 +1,33 @@
+package ru.otus.spring.rest;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+@RestController
+public class AnnotatedController {
+
+ @GetMapping("/flux/one")
+ public Mono one() {
+ return Mono.just( "one" );
+ }
+
+ @GetMapping("/flux/ten")
+ public Flux list() {
+ return Flux.range( 1, 10 ).delayElements( Duration.ofSeconds( 1 ) );
+ }
+
+ @GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public Flux stream() {
+ return Flux.generate( () -> 0, ( state, emitter ) -> {
+ emitter.next( state );
+ return state + 1;
+ } )
+ .delayElements( Duration.ofSeconds( 1L ) )
+ .map( i -> "" + i );
+ }
+}
diff --git a/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/PersonController.java b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..0ed2451c
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,37 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repository.PersonRepository;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @GetMapping("/person")
+ public Flux all() {
+ return repository.findAll();
+ }
+
+ @GetMapping("/person/{id}")
+ public Mono byId(@PathVariable("id") String id) {
+ return repository.findById(id);
+ }
+
+ @PostMapping("/person")
+ public Mono save(@RequestBody Mono dto) {
+ return repository.save(dto);
+ }
+
+ @GetMapping("/person/find")
+ public Flux byName(@RequestParam("name") String name){
+ return repository.findAllByLastName( name );
+ }
+}
diff --git a/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java b/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java
new file mode 100644
index 00000000..0ecf3ca8
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java
@@ -0,0 +1,31 @@
+package ru.otus.spring.repository;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+import ru.otus.spring.domain.Person;
+
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(SpringRunner.class)
+@DataMongoTest
+public class PersonRepositoryTest {
+
+ @Autowired
+ private PersonRepository repository;
+
+ @Test
+ public void shouldSetIdOnSave() {
+ Mono personMono = repository.save(new Person("Bill", 12));
+
+ StepVerifier
+ .create(personMono)
+ .assertNext(person -> assertNotNull(person.getId()))
+ .expectComplete()
+ .verify();
+ }
+}
diff --git a/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..b6eb9806
--- /dev/null
+++ b/2020-08/spring-20/spring-20-exercise/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,32 @@
+package ru.otus.spring.rest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.reactive.function.server.RouterFunction;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class PersonControllerTest {
+
+ @Autowired
+ private RouterFunction route;
+
+ @Test
+ public void testRoute() {
+ WebTestClient client = WebTestClient
+ .bindToRouterFunction(route)
+ .build();
+
+ client.get()
+ .uri("/func/person")
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+
+}
diff --git a/2020-08/spring-20/spring-20-solution/pom.xml b/2020-08/spring-20/spring-20-solution/pom.xml
new file mode 100644
index 00000000..e584c885
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-20-solution
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.4.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+
+
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/Main.java b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..934532de
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,58 @@
+package ru.otus.spring;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repository.PersonRepository;
+
+import java.util.Arrays;
+
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.web.reactive.function.BodyInserters.fromObject;
+import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
+import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
+import static org.springframework.web.reactive.function.server.ServerResponse.ok;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ ApplicationContext context = SpringApplication.run(Main.class);
+ PersonRepository repository = context.getBean(PersonRepository.class);
+
+ repository.saveAll(Arrays.asList(
+ new Person("Pushkin", 22),
+ new Person("Lermontov", 22),
+ new Person("Tolstoy", 60)
+ )).subscribe(p -> System.out.println(p.getLastName()));
+
+ }
+
+ @Bean
+ public RouterFunction composedRoutes(PersonRepository repository) {
+ return route()
+ .GET("/func/person", queryParam("name", StringUtils::isNotEmpty),
+ request -> request.queryParam("name")
+ .map(repository::findAllByLastName)
+ .map(persons -> ok().body(persons, Person.class))
+ .orElse(notFound().build())
+ )
+ .GET("/func/person", accept(APPLICATION_JSON),
+ request -> ok().contentType(APPLICATION_JSON).body(repository.findAll(), Person.class))
+ .GET("/func/person/{id}", accept(APPLICATION_JSON),
+ request -> repository.findById(request.pathVariable("id"))
+ .flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person)))
+ ).build();
+ }
+}
+
+
diff --git a/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/domain/Person.java b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..d40218ec
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,55 @@
+package ru.otus.spring.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
+
+@Document
+public class Person {
+
+ @Id
+ private String id;
+
+ @JsonProperty("name")
+ @Field("name")
+ private String lastName;
+
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public Person(String lastName, int age) {
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/repository/PersonRepository.java b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/repository/PersonRepository.java
new file mode 100644
index 00000000..0584f0cb
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/repository/PersonRepository.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.repository;
+
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+
+public interface PersonRepository
+ extends ReactiveMongoRepository {
+
+ Flux findAll();
+
+ Mono findById(String id);
+
+ Mono save(Mono person);
+
+ Flux findAllByLastName(String lastName);
+
+ Flux findAllByAge(int age);
+}
diff --git a/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/AnnotatedController.java b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/AnnotatedController.java
new file mode 100644
index 00000000..3d723f35
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/AnnotatedController.java
@@ -0,0 +1,33 @@
+package ru.otus.spring.rest;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+@RestController
+public class AnnotatedController {
+
+ @GetMapping("/flux/one")
+ public Mono one() {
+ return Mono.just("one");
+ }
+
+ @GetMapping("/flux/ten")
+ public Flux list() {
+ return Flux.range(1, 10);
+ }
+
+ @GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public Flux stream() {
+ return Flux.generate(() -> 0, (state, emitter) -> {
+ emitter.next(state);
+ return state + 1;
+ })
+ .delayElements(Duration.ofSeconds(1L))
+ .map(i -> "" + i);
+ }
+}
diff --git a/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/PersonController.java b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..2edcaea9
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,42 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.domain.Person;
+import ru.otus.spring.repository.PersonRepository;
+
+@RestController
+public class PersonController {
+
+ private PersonRepository repository;
+
+ public PersonController(PersonRepository repository) {
+ this.repository = repository;
+ }
+
+ @GetMapping("/person")
+ public Flux all() {
+ return repository.findAll();
+ }
+
+ @GetMapping("/person/{id}")
+ public Mono byId(@PathVariable("id") String id) {
+ return repository.findById(id);
+ }
+
+ @GetMapping("/person/byname")
+ public Flux byName(@RequestParam("name") String lastName) {
+ return repository.findAllByLastName(lastName);
+ }
+
+ @GetMapping("/person/byage")
+ public Flux byAge(@RequestParam int age) {
+ return repository.findAllByAge(age);
+ }
+
+ @PostMapping("/person")
+ public Mono save(@RequestBody Mono dto) {
+ return repository.save(dto);
+ }
+}
diff --git a/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java b/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java
new file mode 100644
index 00000000..69eef1f6
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/repository/PersonRepositoryTest.java
@@ -0,0 +1,43 @@
+package ru.otus.spring.repository;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+import ru.otus.spring.domain.Person;
+
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(SpringRunner.class)
+@DataMongoTest
+public class PersonRepositoryTest {
+
+ @Autowired
+ private PersonRepository repository;
+
+ @Test
+ public void shouldSetIdOnSave() {
+ Mono personMono = repository.save(new Person("Bill", 12));
+
+ StepVerifier
+ .create(personMono)
+ .assertNext(person -> assertNotNull(person.getId()))
+ .expectComplete()
+ .verify();
+ }
+
+ @Test
+ public void shouldFindByAge() {
+ repository.save(new Person("Pushkin", 18)).subscribe();
+
+ StepVerifier.create(
+ repository.findAllByAge(18)
+ )
+ .expectNextCount(1)
+ .expectComplete()
+ .verify();
+ }
+}
diff --git a/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
new file mode 100644
index 00000000..b6eb9806
--- /dev/null
+++ b/2020-08/spring-20/spring-20-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java
@@ -0,0 +1,32 @@
+package ru.otus.spring.rest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.reactive.function.server.RouterFunction;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class PersonControllerTest {
+
+ @Autowired
+ private RouterFunction route;
+
+ @Test
+ public void testRoute() {
+ WebTestClient client = WebTestClient
+ .bindToRouterFunction(route)
+ .build();
+
+ client.get()
+ .uri("/func/person")
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+
+}