findByLastNameStartingWith(String lastName, Pageable pageable);
/**
- * Retrieve an {@link Owner} from the data store by id.
- *
- * This method returns an {@link Optional} containing the {@link Owner} if found. If
- * no {@link Owner} is found with the provided id, it will return an empty
- * {@link Optional}.
- *
- * @param id the id to search for
- * @return an {@link Optional} containing the {@link Owner} if found, or an empty
- * {@link Optional} if not found.
- * @throws IllegalArgumentException if the id is null (assuming null is not a valid
- * input for id)
+ * Retrieve {@link Owner}s whose last name matches exactly the given value. This is
+ * used by tests (e.g., OwnerRepositoryIT).
+ */
+ List findByLastName(String lastName);
+
+ /**
+ * Retrieve an {@link Owner} from the data store by id. Returns an empty Optional if
+ * not found.
+ * @throws IllegalArgumentException if the id is null
*/
Optional findById(@Nonnull Integer id);
diff --git a/src/main/resources/application-postgres.properties b/src/main/resources/application-postgres.properties
deleted file mode 100644
index b265d7e5b41..00000000000
--- a/src/main/resources/application-postgres.properties
+++ /dev/null
@@ -1,7 +0,0 @@
-# database init, supports postgres too
-database=postgres
-spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic}
-spring.datasource.username=${POSTGRES_USER:petclinic}
-spring.datasource.password=${POSTGRES_PASS:petclinic}
-# SQL is written to be idempotent so this is safe
-spring.sql.init.mode=always
diff --git a/src/main/resources/application-postgres.yml b/src/main/resources/application-postgres.yml
new file mode 100644
index 00000000000..5180f8d8010
--- /dev/null
+++ b/src/main/resources/application-postgres.yml
@@ -0,0 +1,27 @@
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/petclinic
+ username: pet
+ password: pet
+ driver-class-name: org.postgresql.Driver
+ jpa:
+ hibernate:
+ ddl-auto: update
+ properties:
+ hibernate:
+ dialect: org.hibernate.dialect.PostgreSQLDialect
+ sql:
+ init:
+ mode: never
+
+server:
+ port: 8080
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health,info,prometheus
+ endpoint:
+ health:
+ show-details: always
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 00000000000..029384b493b
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health,info,prometheus
+
+springdoc:
+ api-docs:
+ enabled: true
+ swagger-ui:
+ path: /swagger-ui.html
diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerRepositoryIT.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerRepositoryIT.java
new file mode 100644
index 00000000000..ee6262521cc
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerRepositoryIT.java
@@ -0,0 +1,92 @@
+package org.springframework.samples.petclinic.owner;
+
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+/**
+ * Integration test that uses a real PostgreSQL instance via Testcontainers.
+ */
+@Testcontainers
+@DataJpaTest
+@AutoConfigureTestDatabase(replace = Replace.NONE)
+class OwnerRepositoryIT {
+
+ // PostgreSQL container used as the backing database for tests
+ @Container
+ static final PostgreSQLContainer> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
+ .withDatabaseName("petclinic")
+ .withUsername("pet")
+ .withPassword("pet");
+
+ // Configure Spring DataSource properties to point to the container
+ @DynamicPropertySource
+ static void dbProps(DynamicPropertyRegistry r) {
+ r.add("spring.datasource.url", postgres::getJdbcUrl);
+ r.add("spring.datasource.username", postgres::getUsername);
+ r.add("spring.datasource.password", postgres::getPassword);
+ r.add("spring.jpa.hibernate.ddl-auto", () -> "update"); // automatically create
+ // schema for tests
+ r.add("spring.sql.init.mode", () -> "never"); // skip default SQL initialization
+ }
+
+ @Autowired
+ OwnerRepository owners;
+
+ private Owner seeded;
+
+ // Insert a sample Owner before each test
+ @BeforeEach
+ void seed() {
+ Owner o = new Owner();
+ o.setFirstName("Grace");
+ o.setLastName("Hopper");
+ o.setAddress("123 Main St");
+ o.setCity("NYC");
+ o.setTelephone("1234567890");
+ seeded = owners.save(o);
+ }
+
+ // Remove all Owners after each test to ensure isolation
+ @AfterEach
+ void cleanup() {
+ owners.deleteAll();
+ }
+
+ // Verify that searching by last name returns the expected Owner with all fields
+ @Test
+ void findsOwnerByLastName_andChecksAllFields() {
+ List result = owners.findByLastName("Hopper");
+
+ Assertions.assertThat(result).hasSize(1);
+
+ Owner found = result.get(0);
+ Assertions.assertThat(found.getId()).isNotNull();
+ Assertions.assertThat(found.getFirstName()).isEqualTo("Grace");
+ Assertions.assertThat(found.getLastName()).isEqualTo("Hopper");
+ Assertions.assertThat(found.getAddress()).isEqualTo("123 Main St");
+ Assertions.assertThat(found.getCity()).isEqualTo("NYC");
+ Assertions.assertThat(found.getTelephone()).isEqualTo("1234567890");
+ Assertions.assertThat(found.getPets()).isEmpty();
+ }
+
+ // Verify that searching for an unknown last name returns no results
+ @Test
+ void returnsEmptyListForUnknownLastName() {
+ List result = owners.findByLastName("DoesNotExist");
+ Assertions.assertThat(result).isEmpty();
+ }
+
+}