├── .env.example ├── .github ├── dependabot.yml └── workflows │ ├── codecov.yaml │ └── docker-upload.yml ├── src ├── main │ ├── resources │ │ ├── application.yaml │ │ └── kafka │ │ │ └── producer.xml │ └── java │ │ └── com │ │ └── example │ │ └── datageneratormicroservice │ │ ├── service │ │ ├── KafkaDataService.java │ │ ├── TestDataService.java │ │ ├── KafkaDataServiceImpl.java │ │ └── TestDataServiceImpl.java │ │ ├── web │ │ ├── mapper │ │ │ ├── Mappable.java │ │ │ ├── DataMapper.java │ │ │ └── DataTestOptionsMapper.java │ │ ├── dto │ │ │ ├── DataTestOptionsDto.java │ │ │ └── DataDto.java │ │ └── controller │ │ │ └── DataController.java │ │ ├── model │ │ ├── test │ │ │ └── DataTestOptions.java │ │ └── Data.java │ │ ├── DataGeneratorMicroserviceApplication.java │ │ └── config │ │ ├── TextXpath.java │ │ ├── BeanConfig.java │ │ └── KafkaConfig.java └── test │ └── java │ ├── KafkaDataServiceTest.java │ └── TestDataServiceTest.java ├── Dockerfile ├── .gitignore ├── README.md └── pom.xml /.env.example: -------------------------------------------------------------------------------- 1 | KAFKA_BOOTSTRAP_SERVERS=localhost:9092 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:file:.env[.properties] 4 | kafka: 5 | bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} 6 | server: 7 | port: 8081 8 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/service/KafkaDataService.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.service; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | 5 | public interface KafkaDataService { 6 | 7 | void send(Data data); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.8.5-openjdk-17 AS build 2 | COPY pom.xml . 3 | RUN mvn dependency:go-offline 4 | COPY /src /src 5 | RUN mvn clean package -DskipTests 6 | 7 | FROM openjdk:17-jdk-slim 8 | COPY --from=build /target/*.jar application.jar 9 | EXPOSE 8081 10 | ENTRYPOINT ["java", "-jar", "application.jar"] -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/service/TestDataService.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.service; 2 | 3 | import com.example.datageneratormicroservice.model.test.DataTestOptions; 4 | 5 | public interface TestDataService { 6 | 7 | void sendMessages(DataTestOptions testOptions); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/kafka/producer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.apache.kafka.common.serialization.StringSerializer 5 | 6 | 7 | org.springframework.kafka.support.serializer.JsonSerializer 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/mapper/Mappable.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.mapper; 2 | 3 | import java.util.List; 4 | 5 | public interface Mappable { 6 | 7 | E toEntity(D d); 8 | 9 | List toEntity(List d); 10 | 11 | D toDto(E e); 12 | 13 | List toDto(List e); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/mapper/DataMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.mapper; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import com.example.datageneratormicroservice.web.dto.DataDto; 5 | import org.mapstruct.Mapper; 6 | 7 | @Mapper(componentModel = "spring") 8 | public interface DataMapper extends Mappable { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/model/test/DataTestOptions.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.model.test; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @Setter 11 | public class DataTestOptions { 12 | 13 | private int delayInSeconds; 14 | private Data.MeasurementType[] measurementTypes; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/dto/DataTestOptionsDto.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.dto; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @Setter 11 | public class DataTestOptionsDto { 12 | 13 | private int delayInSeconds; 14 | private Data.MeasurementType[] measurementTypes; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/mapper/DataTestOptionsMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.mapper; 2 | 3 | import com.example.datageneratormicroservice.model.test.DataTestOptions; 4 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto; 5 | import org.mapstruct.Mapper; 6 | 7 | @Mapper(componentModel = "spring") 8 | public interface DataTestOptionsMapper 9 | extends Mappable { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/DataGeneratorMicroserviceApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DataGeneratorMicroserviceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DataGeneratorMicroserviceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | .env -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/config/TextXpath.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.config; 2 | 3 | import com.jcabi.xml.XML; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | public final class TextXpath { 8 | 9 | private final XML xml; 10 | private final String node; 11 | 12 | @Override 13 | public String toString() { 14 | return this.xml.nodes(this.node) 15 | .get(0) 16 | .xpath("text()") 17 | .get(0); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/model/Data.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | @Setter 12 | public class Data { 13 | 14 | private Long sensorId; 15 | private LocalDateTime timestamp; 16 | private double measurement; 17 | private MeasurementType measurementType; 18 | 19 | public enum MeasurementType { 20 | 21 | TEMPERATURE, 22 | VOLTAGE, 23 | POWER 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/config/BeanConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.config; 2 | 3 | import com.jcabi.xml.XML; 4 | import com.jcabi.xml.XMLDocument; 5 | import lombok.SneakyThrows; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class BeanConfig { 11 | 12 | @SneakyThrows 13 | @Bean 14 | public XML producerXml() { 15 | return new XMLDocument( 16 | getClass().getResourceAsStream("/kafka/producer.xml").readAllBytes() 17 | ); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/dto/DataDto.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.dto; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | @NoArgsConstructor 12 | @Getter 13 | @Setter 14 | public class DataDto { 15 | 16 | private Long sensorId; 17 | 18 | @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") 19 | private LocalDateTime timestamp; 20 | 21 | private double measurement; 22 | private Data.MeasurementType measurementType; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yaml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | codecov: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-java@v3 14 | with: 15 | distribution: 'temurin' 16 | java-version: 17 17 | - uses: actions/cache@v3 18 | with: 19 | path: ~/.m2/repository 20 | key: maven-${{ hashFiles('**/pom.xml') }} 21 | restore-keys: | 22 | maven- 23 | - run: | 24 | mvn clean install 25 | - name: Upload coverage reports to Codecov 26 | uses: codecov/codecov-action@v3 27 | env: 28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 29 | with: 30 | files: ./target/site/jacoco/jacoco.xml 31 | fail_ci_if_error: false -------------------------------------------------------------------------------- /.github/workflows/docker-upload.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Login to Docker Hub 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | - name: Build and Push to Docker Hub 21 | uses: mr-smithers-excellent/docker-build-push@v5 22 | with: 23 | image: ${{ secrets.DOCKERHUB_USERNAME }}/data-generator-microservice 24 | tags: 0.0.$GITHUB_RUN_NUMBER, latest 25 | dockerfile: Dockerfile 26 | registry: docker.io 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data generator microservice 2 | 3 | [![codecov](https://codecov.io/gh/IlyaLisov/data-generator-microservice/graph/badge.svg?token=SI8VHQM1I3)](https://codecov.io/gh/SpringBootCourses/data-generator-microservice) 4 | 5 | This is data generator microservice for [YouTube course](https://www.youtube.com/playlist?list=PL3Ur78l82EFBhKojbSO26BVqQ7n4AthHC). 6 | 7 | This application produces data and sends it to [Data consumer service](https://github.com/IlyaLisov/data-analyser-microservice) with Apache Kafka. 8 | 9 | ### Usage 10 | 11 | To start an application you need to pass variables to `.env` file. 12 | 13 | You can use example `.env.example` file with some predefined environments. 14 | 15 | Application is running on port `8081`. 16 | 17 | All insignificant features (checkstyle, build check, dto validation) are not presented. 18 | 19 | Application has two endpoints: 20 | * POST `/api/v1/data/send` 21 | #### Example JSON 22 | ```json 23 | { 24 | "sensorId": 1, 25 | "timestamp": "2023-09-12T12:10:05", 26 | "measurement": 15.5, 27 | "measurementType": "TEMPERATURE" 28 | } 29 | ``` 30 | 31 | * POST `/api/v1/data/test/send` 32 | #### Example JSON 33 | ```json 34 | { 35 | "delayInSeconds": 3, 36 | "measurementTypes": [ 37 | "POWER", 38 | "VOLTAGE", 39 | "TEMPERATURE" 40 | ] 41 | } 42 | ``` -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/service/KafkaDataServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.service; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | import reactor.core.publisher.Mono; 7 | import reactor.kafka.sender.KafkaSender; 8 | import reactor.kafka.sender.SenderRecord; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class KafkaDataServiceImpl implements KafkaDataService { 13 | 14 | private final KafkaSender sender; 15 | 16 | @Override 17 | public void send(Data data) { 18 | String topic = switch (data.getMeasurementType()) { 19 | case TEMPERATURE -> "data-temperature"; 20 | case VOLTAGE -> "data-voltage"; 21 | case POWER -> "data-power"; 22 | }; 23 | sender.send( 24 | Mono.just( 25 | SenderRecord.create( 26 | topic, 27 | 0, 28 | System.currentTimeMillis(), 29 | String.valueOf(data.hashCode()), 30 | data, 31 | null 32 | ) 33 | ) 34 | ) 35 | .subscribe(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/web/controller/DataController.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.web.controller; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import com.example.datageneratormicroservice.model.test.DataTestOptions; 5 | import com.example.datageneratormicroservice.service.KafkaDataService; 6 | import com.example.datageneratormicroservice.service.TestDataService; 7 | import com.example.datageneratormicroservice.web.dto.DataDto; 8 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto; 9 | import com.example.datageneratormicroservice.web.mapper.DataMapper; 10 | import com.example.datageneratormicroservice.web.mapper.DataTestOptionsMapper; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping("/api/v1/data") 19 | @RequiredArgsConstructor 20 | public class DataController { 21 | 22 | private final KafkaDataService kafkaDataService; 23 | private final TestDataService testDataService; 24 | 25 | private final DataMapper dataMapper; 26 | private final DataTestOptionsMapper dataTestOptionsMapper; 27 | 28 | @PostMapping("/send") 29 | public void send(@RequestBody DataDto dataDto) { 30 | Data data = dataMapper.toEntity(dataDto); 31 | kafkaDataService.send(data); 32 | } 33 | 34 | @PostMapping("/test/send") 35 | public void testSend(@RequestBody DataTestOptionsDto testOptionsDto) { 36 | DataTestOptions testOptions = dataTestOptionsMapper.toEntity(testOptionsDto); 37 | testDataService.sendMessages(testOptions); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/service/TestDataServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.service; 2 | 3 | import com.example.datageneratormicroservice.model.Data; 4 | import com.example.datageneratormicroservice.model.test.DataTestOptions; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.ScheduledExecutorService; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class TestDataServiceImpl implements TestDataService { 16 | 17 | private final ScheduledExecutorService executorService 18 | = Executors.newSingleThreadScheduledExecutor(); 19 | private final KafkaDataService kafkaDataService; 20 | 21 | @Override 22 | public void sendMessages(DataTestOptions testOptions) { 23 | if (testOptions.getMeasurementTypes().length > 0) { 24 | executorService.scheduleAtFixedRate( 25 | () -> { 26 | Data data = new Data(); 27 | data.setSensorId( 28 | (long) getRandomNumber(1, 10) 29 | ); 30 | data.setMeasurement( 31 | getRandomNumber(15, 20) 32 | ); 33 | data.setMeasurementType( 34 | getRandomMeasurement( 35 | testOptions.getMeasurementTypes() 36 | ) 37 | ); 38 | data.setTimestamp( 39 | LocalDateTime.now() 40 | ); 41 | kafkaDataService.send(data); 42 | }, 43 | 0, 44 | testOptions.getDelayInSeconds(), 45 | TimeUnit.SECONDS 46 | ); 47 | } 48 | } 49 | 50 | private double getRandomNumber(double min, double max) { 51 | return (Math.random() * (max - min)) + min; 52 | } 53 | 54 | private Data.MeasurementType getRandomMeasurement( 55 | Data.MeasurementType[] measurementTypes 56 | ) { 57 | int randomTypeId = (int) (Math.random() * measurementTypes.length); 58 | return measurementTypes[randomTypeId]; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/example/datageneratormicroservice/config/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.datageneratormicroservice.config; 2 | 3 | import com.jcabi.xml.XML; 4 | import lombok.RequiredArgsConstructor; 5 | import org.apache.kafka.clients.admin.NewTopic; 6 | import org.apache.kafka.clients.producer.ProducerConfig; 7 | import org.apache.kafka.common.config.TopicConfig; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.kafka.config.TopicBuilder; 12 | import reactor.kafka.sender.KafkaSender; 13 | import reactor.kafka.sender.SenderOptions; 14 | 15 | import java.time.Duration; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | @Configuration 20 | @RequiredArgsConstructor 21 | public class KafkaConfig { 22 | 23 | @Value("${spring.kafka.bootstrap-servers}") 24 | private String servers; 25 | 26 | private final XML settings; 27 | 28 | @Bean 29 | public NewTopic temperatureTopic() { 30 | return TopicBuilder.name("data-temperature") 31 | .partitions(5) 32 | .replicas(1) 33 | .config( 34 | TopicConfig.RETENTION_MS_CONFIG, 35 | String.valueOf(Duration.ofDays(7).toMillis()) 36 | ) 37 | .build(); 38 | } 39 | 40 | @Bean 41 | public NewTopic voltageTopic() { 42 | return TopicBuilder.name("data-voltage") 43 | .partitions(5) 44 | .replicas(1) 45 | .config( 46 | TopicConfig.RETENTION_MS_CONFIG, 47 | String.valueOf(Duration.ofDays(7).toMillis()) 48 | ) 49 | .build(); 50 | } 51 | 52 | @Bean 53 | public NewTopic powerTopic() { 54 | return TopicBuilder.name("data-power") 55 | .partitions(5) 56 | .replicas(1) 57 | .config( 58 | TopicConfig.RETENTION_MS_CONFIG, 59 | String.valueOf(Duration.ofDays(7).toMillis()) 60 | ) 61 | .build(); 62 | } 63 | 64 | @Bean 65 | public SenderOptions senderOptions() { 66 | Map props = new HashMap<>(3); 67 | props.put( 68 | ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, 69 | servers 70 | ); 71 | props.put( 72 | ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 73 | new TextXpath(this.settings, "//keySerializer") 74 | .toString() 75 | ); 76 | props.put( 77 | ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 78 | new TextXpath(this.settings, "//valueSerializer") 79 | .toString() 80 | ); 81 | return SenderOptions.create(props); 82 | } 83 | 84 | @Bean 85 | public KafkaSender sender( 86 | SenderOptions senderOptions 87 | ) { 88 | return KafkaSender.create(senderOptions); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/KafkaDataServiceTest.java: -------------------------------------------------------------------------------- 1 | import com.example.datageneratormicroservice.DataGeneratorMicroserviceApplication; 2 | import com.example.datageneratormicroservice.model.Data; 3 | import com.example.datageneratormicroservice.web.dto.DataDto; 4 | import io.restassured.RestAssured; 5 | import io.restassured.http.ContentType; 6 | import org.apache.kafka.clients.admin.AdminClient; 7 | import org.apache.kafka.clients.admin.AdminClientConfig; 8 | import org.apache.kafka.clients.admin.ListTopicsResult; 9 | import org.awaitility.Awaitility; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.MethodOrderer; 14 | import org.junit.jupiter.api.Order; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestMethodOrder; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.boot.test.context.TestConfiguration; 19 | import org.springframework.boot.test.web.server.LocalServerPort; 20 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 21 | import org.springframework.test.context.DynamicPropertyRegistry; 22 | import org.springframework.test.context.DynamicPropertySource; 23 | import org.testcontainers.containers.KafkaContainer; 24 | import org.testcontainers.junit.jupiter.Container; 25 | import org.testcontainers.junit.jupiter.Testcontainers; 26 | import org.testcontainers.utility.DockerImageName; 27 | 28 | import java.time.Duration; 29 | import java.time.LocalDateTime; 30 | import java.util.Map; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | @Testcontainers 34 | @TestConfiguration(proxyBeanMethods = false) 35 | @SpringBootTest(classes = DataGeneratorMicroserviceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 36 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 37 | public class KafkaDataServiceTest { 38 | 39 | @Container 40 | @ServiceConnection 41 | protected static final KafkaContainer KAFKA = new KafkaContainer( 42 | DockerImageName.parse("confluentinc/cp-kafka:latest") 43 | ); 44 | 45 | @DynamicPropertySource 46 | static void properties(DynamicPropertyRegistry registry) { 47 | registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers); 48 | } 49 | 50 | @BeforeAll 51 | static void setup() { 52 | KAFKA.addEnv("KAFKA_BROKER_ID", "1"); 53 | KAFKA.addEnv("KAFKA_ADVERTISED_LISTENERS", KAFKA.getBootstrapServers()); 54 | KAFKA.addEnv("KAFKA_LISTENERS", "LISTENER_PUBLIC://" + KAFKA.getContainerName() + ":29092,LISTENER_INTERNAL://" + KAFKA.getBootstrapServers()); 55 | KAFKA.addEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "LISTENER_PUBLIC:PLAINTEXT,LISTENER_INTERNAL:PLAINTEXT"); 56 | adminClient = AdminClient.create(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers())); 57 | } 58 | 59 | @LocalServerPort 60 | private int port; 61 | 62 | private static AdminClient adminClient; 63 | 64 | @BeforeEach 65 | void setupRest() { 66 | RestAssured.baseURI = "http://localhost:" + port; 67 | } 68 | 69 | @Test 70 | @Order(1) 71 | public void testSendPower() { 72 | DataDto data = new DataDto(); 73 | data.setSensorId(1L); 74 | data.setTimestamp(LocalDateTime.now()); 75 | data.setMeasurementType(Data.MeasurementType.POWER); 76 | data.setMeasurement(15); 77 | 78 | Awaitility.await() 79 | .pollInterval(Duration.ofSeconds(3)) 80 | .atMost(10, TimeUnit.SECONDS) 81 | .untilAsserted(() -> { 82 | RestAssured.given() 83 | .contentType(ContentType.JSON) 84 | .body(data) 85 | .post("/api/v1/data/send") 86 | .then() 87 | .statusCode(200); 88 | 89 | ListTopicsResult ltr = adminClient.listTopics(); 90 | Assertions.assertTrue(ltr.names().get().contains("data-power")); 91 | }); 92 | } 93 | 94 | // @Test 95 | // @Order(2) 96 | // void testConsumeMessagesFromKafka() { 97 | // Properties consumerProps = new Properties(); 98 | // consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()); 99 | // consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "1"); 100 | // consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); 101 | // consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); 102 | // ReceiverOptions options = ReceiverOptions.create(consumerProps); 103 | // ReceiverOptions subscribedOptions = options.subscription(List.of("data-power")); 104 | // 105 | // KafkaReceiver consumer = KafkaReceiver.create(subscribedOptions); 106 | // Awaitility.await() 107 | // .pollInterval(Duration.ofSeconds(3)) 108 | // .atMost(15, TimeUnit.SECONDS) 109 | // .untilAsserted(() -> consumer.receive().subscribe(r -> 110 | // Assertions.assertTrue(r.value().toString().contains("15")) 111 | // ) 112 | // ); 113 | // } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/TestDataServiceTest.java: -------------------------------------------------------------------------------- 1 | import com.example.datageneratormicroservice.DataGeneratorMicroserviceApplication; 2 | import com.example.datageneratormicroservice.model.Data; 3 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto; 4 | import io.restassured.RestAssured; 5 | import io.restassured.http.ContentType; 6 | import org.apache.kafka.clients.admin.AdminClient; 7 | import org.apache.kafka.clients.admin.AdminClientConfig; 8 | import org.apache.kafka.clients.admin.ListTopicsResult; 9 | import org.awaitility.Awaitility; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.MethodOrderer; 14 | import org.junit.jupiter.api.Order; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestMethodOrder; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.boot.test.context.TestConfiguration; 19 | import org.springframework.boot.test.web.server.LocalServerPort; 20 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 21 | import org.springframework.test.context.DynamicPropertyRegistry; 22 | import org.springframework.test.context.DynamicPropertySource; 23 | import org.testcontainers.containers.KafkaContainer; 24 | import org.testcontainers.junit.jupiter.Container; 25 | import org.testcontainers.junit.jupiter.Testcontainers; 26 | import org.testcontainers.utility.DockerImageName; 27 | 28 | import java.time.Duration; 29 | import java.util.Map; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | @Testcontainers 33 | @TestConfiguration(proxyBeanMethods = false) 34 | @SpringBootTest(classes = DataGeneratorMicroserviceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 35 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 36 | public class TestDataServiceTest { 37 | 38 | @Container 39 | @ServiceConnection 40 | protected static final KafkaContainer KAFKA = new KafkaContainer( 41 | DockerImageName.parse("confluentinc/cp-kafka:latest") 42 | ); 43 | 44 | @DynamicPropertySource 45 | static void properties(DynamicPropertyRegistry registry) { 46 | registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers); 47 | } 48 | 49 | @BeforeAll 50 | static void setup() { 51 | KAFKA.addEnv("KAFKA_BROKER_ID", "1"); 52 | KAFKA.addEnv("KAFKA_ADVERTISED_LISTENERS", KAFKA.getBootstrapServers()); 53 | KAFKA.addEnv("KAFKA_LISTENERS", "LISTENER_PUBLIC://" + KAFKA.getContainerName() + ":29092,LISTENER_INTERNAL://" + KAFKA.getBootstrapServers()); 54 | KAFKA.addEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "LISTENER_PUBLIC:PLAINTEXT,LISTENER_INTERNAL:PLAINTEXT"); 55 | adminClient = AdminClient.create(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers())); 56 | } 57 | 58 | @LocalServerPort 59 | private int port; 60 | 61 | private static AdminClient adminClient; 62 | 63 | @BeforeEach 64 | void setupRest() { 65 | RestAssured.baseURI = "http://localhost:" + port; 66 | } 67 | 68 | @Test 69 | @Order(1) 70 | public void testSendPower() { 71 | DataTestOptionsDto data = new DataTestOptionsDto(); 72 | data.setDelayInSeconds(3); 73 | data.setMeasurementTypes(new Data.MeasurementType[]{Data.MeasurementType.POWER, Data.MeasurementType.VOLTAGE}); 74 | 75 | Awaitility.await() 76 | .pollInterval(Duration.ofSeconds(3)) 77 | .atMost(10, TimeUnit.SECONDS) 78 | .untilAsserted(() -> { 79 | RestAssured.given() 80 | .contentType(ContentType.JSON) 81 | .body(data) 82 | .post("/api/v1/data/test/send") 83 | .then() 84 | .statusCode(200); 85 | 86 | ListTopicsResult ltr = adminClient.listTopics(); 87 | Assertions.assertTrue(ltr.names().get().contains("data-power")); 88 | Assertions.assertTrue(ltr.names().get().contains("data-voltage")); 89 | }); 90 | } 91 | 92 | // @Test 93 | // @Order(2) 94 | // void testConsumeMessagesFromKafka() { 95 | // Properties consumerProps = new Properties(); 96 | // consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()); 97 | // consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "1"); 98 | // consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); 99 | // consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); 100 | // ReceiverOptions options = ReceiverOptions.create(consumerProps); 101 | // ReceiverOptions subscribedOptions = options.subscription(List.of("data-power", "data-voltage")); 102 | // 103 | // KafkaReceiver consumer = KafkaReceiver.create(subscribedOptions); 104 | // Awaitility.await() 105 | // .pollInterval(Duration.ofSeconds(15)) 106 | // .atMost(45, TimeUnit.SECONDS) 107 | // .untilAsserted(() -> consumer.receive().subscribe(r -> 108 | // Assertions.assertNotNull(r.value()) 109 | // ) 110 | // ); 111 | // } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.example 9 | data-generator-microservice 10 | 0.0.1-SNAPSHOT 11 | data-generator-microservice 12 | Data generator microservice 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 3.2.5 18 | 19 | 20 | 21 | 22 | 17 23 | 1.5.5.Final 24 | 1.18.32 25 | 3.1.4 26 | 0.29.0 27 | 1.3.22 28 | 0.8.12 29 | 5.4.0 30 | 4.2.1 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-web 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-testcontainers 48 | test 49 | 50 | 51 | 52 | org.testcontainers 53 | kafka 54 | test 55 | 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | true 61 | ${lombok.version} 62 | 63 | 64 | 65 | org.mapstruct 66 | mapstruct 67 | ${mapstruct.version} 68 | 69 | 70 | 71 | org.mapstruct 72 | mapstruct-processor 73 | ${mapstruct.version} 74 | 75 | 76 | 77 | org.springframework.kafka 78 | spring-kafka 79 | ${spring-kafka.version} 80 | 81 | 82 | 83 | io.projectreactor.kafka 84 | reactor-kafka 85 | ${reactor-kafka.version} 86 | 87 | 88 | 89 | com.jcabi 90 | jcabi-xml 91 | ${xml.version} 92 | 93 | 94 | 95 | org.testcontainers 96 | junit-jupiter 97 | test 98 | 99 | 100 | 101 | io.rest-assured 102 | rest-assured 103 | test 104 | ${rest-assured.version} 105 | 106 | 107 | 108 | org.awaitility 109 | awaitility 110 | ${awaitility.version} 111 | test 112 | 113 | 114 | 115 | 116 | 117 | 118 | org.testcontainers 119 | testcontainers-bom 120 | 1.19.7 121 | pom 122 | import 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-maven-plugin 132 | 133 | 134 | 135 | org.projectlombok 136 | lombok 137 | 138 | 139 | 140 | 141 | 142 | org.jacoco 143 | jacoco-maven-plugin 144 | ${jacoco.version} 145 | 146 | 147 | default-prepare-agent 148 | 149 | prepare-agent 150 | 151 | 152 | 153 | report 154 | prepare-package 155 | 156 | report 157 | 158 | 159 | 160 | check 161 | 162 | check 163 | 164 | 165 | 166 | 167 | BUNDLE 168 | 169 | 170 | INSTRUCTION 171 | COVEREDRATIO 172 | 0.65 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | --------------------------------------------------------------------------------