Skip to content

Commit

Permalink
Merge pull request #614 from codecentric/add-events-to-habit-service
Browse files Browse the repository at this point in the history
Add events to habit service
  • Loading branch information
denniseffing authored Jan 3, 2024
2 parents d1384da + cf56aa9 commit 9c36212
Show file tree
Hide file tree
Showing 47 changed files with 693 additions and 432 deletions.
5 changes: 3 additions & 2 deletions infrastructure/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ services:
- habit-db
environment:
DB_HOST: habit-db
KAFKA_BOOTSTRAP_SERVERS: kafka:11001
networks:
- habitcentric-net
habit-db:
Expand Down Expand Up @@ -124,8 +125,8 @@ services:
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:11001,EXTERNAL://localhost:11003
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:11002
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CLIENT_USERS=track,kafka-ui
- KAFKA_CLIENT_PASSWORDS=track,kafka-ui
- KAFKA_CLIENT_USERS=habit,track,kafka-ui
- KAFKA_CLIENT_PASSWORDS=habit,track,kafka-ui
networks:
- habitcentric-net
kafka-ui:
Expand Down
1 change: 1 addition & 0 deletions infrastructure/istio/config/23-mtls-authz-policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ spec:
- from:
- source:
principals:
- cluster.local/ns/hc-habit/sa/habit
- cluster.local/ns/hc-track/sa/track
---
apiVersion: security.istio.io/v1beta1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ image:

## habitcentric habit service configuration

{{- if (eq .Environment.Name "traefik-mesh") }}
extraEnv:
- name: KAFKA_BOOTSTRAP_SERVERS
value: kafka.hc-kafka.svc.cluster.local:9092
{{- if (eq .Environment.Name "traefik-mesh") }}
- name: MANAGEMENT_ZIPKIN_TRACING_ENDPOINT
value: http://jaeger-collector.traefik-mesh.svc.cluster.local:9411
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ sasl:
password: "kafka"
client:
users:
- "habit"
- "track"
passwords:
- "habit"
- "track"

externalAccess:
Expand Down
12 changes: 12 additions & 0 deletions services/habit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,19 @@ ext {
ext['junit-jupiter.version'] = versions.junitJupiter
ext['jna.version'] = versions.jna // Required for Docker on ARM

dependencyManagement {
imports {
mavenBom 'org.springframework.modulith:spring-modulith-bom:1.1.0'
}
}

dependencies {
// Spring Modulith
implementation "org.springframework.modulith:spring-modulith-starter-core"
implementation "org.springframework.modulith:spring-modulith-starter-jpa"
implementation "org.springframework.modulith:spring-modulith-events-kafka"
intTestImplementation "org.springframework.modulith:spring-modulith-starter-test"

implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand Down
28 changes: 28 additions & 0 deletions services/habit/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,31 @@ services:
environment:
POSTGRESQL_PASSWORD: postgres
POSTGRESQL_PORT_NUMBER: 10001

kafka:
image: 'bitnami/kafka:latest'
ports:
- "11003:11003"
environment:
- KAFKA_CFG_NODE_ID=0
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_LISTENERS=PLAINTEXT://:11001,CONTROLLER://:11002,EXTERNAL://:11003
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:SASL_PLAINTEXT,EXTERNAL:SASL_PLAINTEXT
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:11001,EXTERNAL://localhost:11003
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:11002
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CLIENT_USERS=habit,kafka-ui
- KAFKA_CLIENT_PASSWORDS=habit,kafka-ui

kafka-ui:
container_name: kafka-ui
image: provectuslabs/kafka-ui:latest
ports:
- "11004:11004"
environment:
SERVER_PORT: 11004
DYNAMIC_CONFIG_ENABLED: 'true'
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:11001
KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL: SASL_PLAINTEXT
KAFKA_CLUSTERS_0_PROPERTIES_SASL_MECHANISM: PLAIN
KAFKA_CLUSTERS_0_PROPERTIES_SASL_JAAS_CONFIG: 'org.apache.kafka.common.security.plain.PlainLoginModule required username="kafka-ui" password="kafka-ui";'
5 changes: 5 additions & 0 deletions services/habit/habit-requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ Content-Type: application/json
"frequency": "MONTHLY"
}
}

### Delete habit
# Retrieve the ID by fetching the habits first
DELETE http://localhost:9001/habits/8ffa23a1-e91b-45a3-9060-3b0f53c0c6f3
X-User-Id: default
15 changes: 6 additions & 9 deletions services/habit/pacts/hc-ui-hc-habit.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,20 @@
},
"body": [
{
"id": 1,
"name": "Jogging",
"schedule": {
"repetitions": 2,
"frequency": "WEEKLY"
}
},
{
"id": 101,
"name": "Meditate",
"schedule": {
"repetitions": 1,
"frequency": "DAILY"
}
},
{
"id": 51,
"name": "Play guitar",
"schedule": {
"repetitions": 5,
Expand All @@ -49,13 +46,13 @@
],
"matchingRules": {
"$.body[0].id": {
"match": "type"
"match": "uuid"
},
"$.body[1].id": {
"match": "type"
"match": "uuid"
},
"$.body[2].id": {
"match": "type"
"match": "uuid"
}
}
}
Expand Down Expand Up @@ -84,10 +81,10 @@
},
{
"description": "request to delete the habit with id '123'",
"providerState": "habit with id '123' exists",
"providerState": "habit with id 'd712645f-cd4f-40c4-b171-bb2ea72d180d' exists",
"request": {
"method": "DELETE",
"path": "/habits/123"
"path": "/habits/d712645f-cd4f-40c4-b171-bb2ea72d180d"
},
"response": {
"status": 200,
Expand All @@ -101,4 +98,4 @@
"version": "2.0.0"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import static de.codecentric.hc.habit.habits.Habit.Schedule.Frequency.DAILY;
import static de.codecentric.hc.habit.habits.Habit.Schedule.Frequency.WEEKLY;
import static de.codecentric.hc.habit.testing.CustomMatchers.isValidUuid;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
Expand All @@ -23,6 +23,7 @@
import de.codecentric.hc.habit.testing.RestAssuredTest;
import io.restassured.http.Header;
import java.sql.SQLException;
import java.util.UUID;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -61,7 +62,7 @@ public void getHabits() throws InterruptedException {
.then()
.statusCode(200)
.body("name", contains(expected))
.body("id", everyItem(greaterThan(0)));
.body("id", everyItem(isValidUuid()));
}

@Test
Expand All @@ -79,7 +80,7 @@ public void getHabitsOrderedByNameAscending() throws InterruptedException {
.then()
.statusCode(200)
.body("name", contains(expected))
.body("id", everyItem(greaterThan(0)));
.body("id", everyItem(isValidUuid()));
}

@Test
Expand Down Expand Up @@ -246,19 +247,20 @@ public void deleteHabit() {

@Test
public void deleteHabitNotFound() {
UUID habitId = UUID.randomUUID();
given()
.header(DEFAULT_USER_ID_HEADER)
.when()
.delete("/habits/{id}", 999)
.delete("/habits/{id}", habitId)
.then()
.statusCode(NOT_FOUND.value())
.body("message", equalTo("Habit '999' could not be found."));
.body("message", equalTo(String.format("Habit '%s' could not be found.", habitId)));
}

@Test
public void deleteHabitWithoutAuthShouldFail() {
when()
.delete("/habits/{id}", 123)
.delete("/habits/{id}", UUID.randomUUID())
.then()
.statusCode(
INTERNAL_SERVER_ERROR.value()) // TODO: HTTP 400 or 401 would be more appropriate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static de.codecentric.hc.habit.habits.Habit.Schedule.Frequency.DAILY;
import static de.codecentric.hc.habit.habits.Habit.Schedule.Frequency.WEEKLY;
import static de.codecentric.hc.habit.testing.CustomMatchers.isValidUuid;
import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -13,6 +14,7 @@
import de.codecentric.hc.habit.testing.RestAssuredTest;
import io.restassured.http.Header;
import java.sql.SQLException;
import java.util.UUID;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -57,7 +59,7 @@ public void getHabits() throws InterruptedException {
.then()
.statusCode(OK.value())
.body("name", contains(expected))
.body("id", everyItem(greaterThan(0)));
.body("id", everyItem(isValidUuid()));
}

@Test
Expand All @@ -75,7 +77,7 @@ public void getHabitsOrderedByNameAscending() throws InterruptedException {
.then()
.statusCode(OK.value())
.body("name", contains(expected))
.body("id", everyItem(greaterThan(0)));
.body("id", everyItem(isValidUuid()));
}

@Test
Expand Down Expand Up @@ -238,13 +240,14 @@ public void deleteHabit() {

@Test
public void deleteHabitNotFound() {
UUID habitId = UUID.randomUUID();
given()
.header(DEFAULT_AUTHORIZATION_HEADER)
.when()
.delete("/habits/{id}", 999)
.delete("/habits/{id}", habitId)
.then()
.statusCode(NOT_FOUND.value())
.body("message", equalTo("Habit '999' could not be found."));
.body("message", equalTo(String.format("Habit '%s' could not be found.", habitId)));
}

private String insertHabit(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package de.codecentric.hc.habit.habits;

import static de.codecentric.hc.habit.habits.Habit.Schedule.Frequency.WEEKLY;
import static org.assertj.core.api.Assertions.assertThat;

import de.codecentric.hc.habit.auth.UserIdArgumentResolver;
import de.codecentric.hc.habit.habits.Habit.ModificationRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.modulith.test.ApplicationModuleTest;
import org.springframework.modulith.test.Scenario;
import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("intTest")
@ApplicationModuleTest
@MockBean(UserIdArgumentResolver.class)
public class HabitModuleIntegrationTest {

@Autowired private HabitController habitController;
@Autowired private HabitRepository habitRepository;

@AfterEach
void tearDown() {
habitRepository.deleteAll();
}

@Test
void shouldPublishHabitCreatedEventWhenHabitIsCreated(Scenario scenario) {
ModificationRequest createJoggingHabitRequest =
ModificationRequest.builder()
.name("Jogging")
.schedule(Habit.Schedule.builder().frequency(WEEKLY).repetitions(3).build())
.build();

scenario
.stimulate(() -> habitController.createHabit(createJoggingHabitRequest, "userId"))
.andWaitForEventOfType(Habit.HabitCreated.class)
.toArriveAndVerify(
event -> {
assertThat(event.name()).isEqualTo("Jogging");
assertThat(event.frequency()).isEqualTo(WEEKLY);
assertThat(event.repetitions()).isEqualTo(3);
});
}

@Test
void shouldPublishHabitDeletedEventWhenHabitIsDeleted(Scenario scenario) {
Habit habit =
Habit.from(
ModificationRequest.builder()
.name("Jogging")
.schedule(Habit.Schedule.builder().frequency(WEEKLY).repetitions(3).build())
.build(),
"userId");
habitRepository.save(habit);

scenario
.stimulate(() -> habitController.deleteHabit(habit.getId().toString(), "userId"))
.andWaitForEventOfType(Habit.HabitDeleted.class)
.toArriveAndVerify(
event -> {
assertThat(event.habitId()).isEqualTo(habit.getId());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import au.com.dius.pact.provider.junitsupport.State;
import au.com.dius.pact.provider.junitsupport.loader.PactFolder;
import de.codecentric.hc.habit.habits.Habit.Schedule.Frequency;
import java.util.UUID;
import org.apache.hc.core5.http.HttpRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -56,13 +57,22 @@ public void cleanUp() {

@State("habits 'Jogging', 'Meditate' and 'Play guitar' exist")
public void createHabitsJoggingAndMeditateAndPlayGuitar() {
jdbcTemplate.update(INSERT_STATEMENT, 1, "Jogging", 2, Frequency.WEEKLY.name(), USER_ID);
jdbcTemplate.update(INSERT_STATEMENT, 51, "Play guitar", 5, Frequency.MONTHLY.name(), USER_ID);
jdbcTemplate.update(INSERT_STATEMENT, 101, "Meditate", 1, Frequency.DAILY.name(), USER_ID);
jdbcTemplate.update(
INSERT_STATEMENT, UUID.randomUUID(), "Jogging", 2, Frequency.WEEKLY.name(), USER_ID);
jdbcTemplate.update(
INSERT_STATEMENT, UUID.randomUUID(), "Play guitar", 5, Frequency.MONTHLY.name(), USER_ID);
jdbcTemplate.update(
INSERT_STATEMENT, UUID.randomUUID(), "Meditate", 1, Frequency.DAILY.name(), USER_ID);
}

@State("habit with id '123' exists")
@State("habit with id 'd712645f-cd4f-40c4-b171-bb2ea72d180d' exists")
public void createHabit123() {
jdbcTemplate.update(INSERT_STATEMENT, 123, "Habit name", 1, Frequency.DAILY.name(), USER_ID);
jdbcTemplate.update(
INSERT_STATEMENT,
UUID.fromString("d712645f-cd4f-40c4-b171-bb2ea72d180d"),
"Habit name",
1,
Frequency.DAILY.name(),
USER_ID);
}
}
Loading

0 comments on commit 9c36212

Please sign in to comment.