package at.procon.dip.testsupport; import at.procon.dip.FixedPortPostgreSQLContainer; import at.procon.dip.domain.document.repository.DocumentRepository; import at.procon.dip.domain.document.repository.DocumentTextRepresentationRepository; import at.procon.dip.domain.tenant.repository.DocumentTenantRepository; import javax.sql.DataSource; import at.procon.dip.testsupport.config.SearchTestConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.TestPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @SpringBootTest(classes = SearchTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @Testcontainers @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestPropertySource(properties = { "spring.jpa.hibernate.ddl-auto=create-drop", "spring.jpa.show-sql=false", "spring.jpa.open-in-view=false", "spring.jpa.properties.hibernate.default_schema=DOC", "spring.main.lazy-initialization=true", "ted.vectorization.enabled=false", "ted.search.default-page-size=20", "ted.search.max-page-size=100", "ted.search.fulltext-weight=0.60", "ted.search.trigram-weight=0.40", "ted.search.semantic-weight=0.45", "ted.search.recency-boost-weight=0.05", "ted.search.trigram-threshold=0.10", "server.servlet.context-path=/api" }) public abstract class AbstractSearchIntegrationTest { private static final int HOST_PORT = 15433; private static final String DB_NAME = "dip_search_test"; private static final String DB_USER = "test"; private static final String DB_PASSWORD = "test"; private static final String JDBC_URL = "jdbc:postgresql://localhost:" + HOST_PORT + "/" + DB_NAME; @Container static PostgreSQLContainer postgres = new FixedPortPostgreSQLContainer<>("postgres:16-alpine", HOST_PORT) .withDatabaseName(DB_NAME) .withUsername(DB_USER) .withPassword(DB_PASSWORD) .withInitScript("sql/create-doc-search-test-schemas.sql"); @DynamicPropertySource static void registerProperties(DynamicPropertyRegistry registry) { if (!postgres.isRunning()) { postgres.start(); } registry.add("spring.datasource.url", () -> JDBC_URL); registry.add("spring.datasource.username", () -> DB_USER); registry.add("spring.datasource.password", () -> DB_PASSWORD); registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver"); } @Autowired protected JdbcTemplate jdbcTemplate; @Autowired protected DataSource dataSource; @Autowired protected DocumentRepository documentRepository; @Autowired protected DocumentTextRepresentationRepository representationRepository; @Autowired protected DocumentTenantRepository tenantRepository; @BeforeEach void resetSearchTestDatabase() { ensureSearchColumnsAndIndexes(); cleanupDatabase(); } protected void ensureSearchColumnsAndIndexes() { jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm"); jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS doc"); jdbcTemplate.execute("ALTER TABLE doc.doc_text_representation ADD COLUMN IF NOT EXISTS search_config VARCHAR(64)"); jdbcTemplate.execute("ALTER TABLE doc.doc_text_representation ADD COLUMN IF NOT EXISTS search_vector tsvector"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_doc_text_repr_search_vector_test ON doc.doc_text_representation USING GIN (search_vector)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_doc_document_title_trgm_test ON doc.doc_document USING GIN (title gin_trgm_ops)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_doc_document_summary_trgm_test ON doc.doc_document USING GIN (summary gin_trgm_ops)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_doc_text_repr_text_trgm_test ON doc.doc_text_representation USING GIN (text_body gin_trgm_ops)"); } protected void cleanupDatabase() { jdbcTemplate.execute("TRUNCATE TABLE doc.doc_text_representation, doc.doc_document, doc.doc_tenant RESTART IDENTITY CASCADE"); } protected void setDocumentCreatedAt(java.util.UUID documentId, java.time.OffsetDateTime createdAt) { jdbcTemplate.update("UPDATE doc.doc_document SET created_at = ?, updated_at = ? WHERE id = ?", createdAt, createdAt, documentId); } protected boolean columnExists(String schema, String table, String column) { return Boolean.TRUE.equals(jdbcTemplate.queryForObject( "SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?", Boolean.class, schema.toLowerCase(), table.toLowerCase(), column.toLowerCase())); } }