diff --git a/build.gradle.kts b/build.gradle.kts index c652d1c669..f6065bc83e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,14 @@ plugins { id("com.google.cloud.tools.jib") apply false id("base") id("org.sonarqube") + id("idea") +} + +idea { + module { + isDownloadJavadoc = true + isDownloadSources = true + } } val agentDeps: Configuration by configurations.creating diff --git a/libs/dev-tools/src/main/kotlin/uk/gov/justice/digital/hmpps/resourceloader/ResourceLoader.kt b/libs/dev-tools/src/main/kotlin/uk/gov/justice/digital/hmpps/resourceloader/ResourceLoader.kt index 8fa782f4c2..18e7619197 100644 --- a/libs/dev-tools/src/main/kotlin/uk/gov/justice/digital/hmpps/resourceloader/ResourceLoader.kt +++ b/libs/dev-tools/src/main/kotlin/uk/gov/justice/digital/hmpps/resourceloader/ResourceLoader.kt @@ -21,19 +21,15 @@ object ResourceLoader { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .registerModule(SimpleModule().addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())) - fun event(filename: String): HmppsDomainEvent = - MAPPER.readValue(ResourceUtils.getFile("classpath:messages/$filename.json")) + fun event(filename: String): HmppsDomainEvent = get(filename) - inline fun message(filename: String): T = - MAPPER.readValue( - MAPPER.readValue>( - ResourceUtils.getFile("classpath:messages/$filename.json") - ).message - ) + inline fun get(filename: String): T = + MAPPER.readValue(ResourceUtils.getFile("classpath:messages/$filename.json")) + + inline fun message(filename: String): T = MAPPER.readValue(get>(filename).message) inline fun notification(filename: String): Notification { - val file = ResourceUtils.getFile("classpath:messages/$filename.json") - val stringMessage = MAPPER.readValue>(file) + val stringMessage = get>(filename) return Notification( message = MAPPER.readValue(stringMessage.message, T::class.java), attributes = stringMessage.attributes diff --git a/projects/approved-premises-and-delius/src/main/resources/application.yml b/projects/approved-premises-and-delius/src/main/resources/application.yml index eb288997ba..9547021ad2 100644 --- a/projects/approved-premises-and-delius/src/main/resources/application.yml +++ b/projects/approved-premises-and-delius/src/main/resources/application.yml @@ -85,7 +85,9 @@ spring.datasource.url: jdbc:h2:mem:./test;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH --- spring.config.activate.on-profile: oracle -spring.datasource.url: 'jdbc:tc:oracle:slim-faststart:///XEPDB1' +spring: + datasource.url: 'jdbc:tc:oracle:slim-faststart:///XEPDB1' + jpa.hibernate.ddl-auto: create --- spring.config.activate.on-profile: delius-db diff --git a/projects/justice-email-and-delius/build.gradle.kts b/projects/justice-email-and-delius/build.gradle.kts index a089a2f210..41c53d0c7a 100644 --- a/projects/justice-email-and-delius/build.gradle.kts +++ b/projects/justice-email-and-delius/build.gradle.kts @@ -5,19 +5,22 @@ apply(plugin = "com.google.cloud.tools.jib") dependencies { implementation(project(":libs:audit")) implementation(project(":libs:commons")) - implementation(project(":libs:oauth-server")) + implementation(project(":libs:messaging")) implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-data-ldap") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation(libs.springdoc) + implementation(libs.azure.identity) + implementation(libs.microsoft.graph) dev(project(":libs:dev-tools")) + dev("com.unboundid:unboundid-ldapsdk") dev("com.h2database:h2") dev("org.testcontainers:oracle-xe") diff --git a/projects/justice-email-and-delius/deploy/database/access.yml b/projects/justice-email-and-delius/deploy/database/access.yml index f76a1e10c5..54275fb984 100644 --- a/projects/justice-email-and-delius/deploy/database/access.yml +++ b/projects/justice-email-and-delius/deploy/database/access.yml @@ -2,6 +2,9 @@ database: access: username_key: /justice-email-and-delius/db-username password_key: /justice-email-and-delius/db-password + tables: + - audited_interaction + - contact audit: username: JusticeEmailAndDelius diff --git a/projects/justice-email-and-delius/deploy/values-dev.yml b/projects/justice-email-and-delius/deploy/values-dev.yml index a17a002cb4..67f597d11a 100644 --- a/projects/justice-email-and-delius/deploy/values-dev.yml +++ b/projects/justice-email-and-delius/deploy/values-dev.yml @@ -1,5 +1,3 @@ -enabled: false # TODO set this to true when you're ready to deploy your service - generic-service: ingress: host: justice-email-and-delius-dev.hmpps.service.justice.gov.uk @@ -10,8 +8,6 @@ generic-service: env: SENTRY_ENVIRONMENT: dev LOGGING_LEVEL_UK_GOV_DIGITAL_JUSTICE_HMPPS: DEBUG - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer generic-prometheus-alerts: businessHoursOnly: true diff --git a/projects/justice-email-and-delius/deploy/values-preprod.yml b/projects/justice-email-and-delius/deploy/values-preprod.yml index d490b99daf..9b4bb73dba 100644 --- a/projects/justice-email-and-delius/deploy/values-preprod.yml +++ b/projects/justice-email-and-delius/deploy/values-preprod.yml @@ -9,8 +9,6 @@ generic-service: env: SENTRY_ENVIRONMENT: preprod - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer generic-prometheus-alerts: businessHoursOnly: true \ No newline at end of file diff --git a/projects/justice-email-and-delius/deploy/values-prod.yml b/projects/justice-email-and-delius/deploy/values-prod.yml index c4734ababd..e184d4edf1 100644 --- a/projects/justice-email-and-delius/deploy/values-prod.yml +++ b/projects/justice-email-and-delius/deploy/values-prod.yml @@ -6,5 +6,3 @@ generic-service: env: SENTRY_ENVIRONMENT: prod - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/issuer diff --git a/projects/justice-email-and-delius/deploy/values.yaml b/projects/justice-email-and-delius/deploy/values.yaml index e6f6c676cb..fa5060563b 100644 --- a/projects/justice-email-and-delius/deploy/values.yaml +++ b/projects/justice-email-and-delius/deploy/values.yaml @@ -2,6 +2,7 @@ generic-service: productId: HMPPS518 nameOverride: justice-email-and-delius + serviceAccountName: justice-email-and-delius image: repository: ghcr.io/ministryofjustice/hmpps-probation-integration-services/justice-email-and-delius @@ -12,11 +13,21 @@ generic-service: namespace_secrets: common: SPRING_DATASOURCE_URL: DB_URL + SPRING_LDAP_URLS: LDAP_URL + SPRING_LDAP_PASSWORD: LDAP_PASSWORD justice-email-and-delius-database: SPRING_DATASOURCE_USERNAME: DB_USERNAME SPRING_DATASOURCE_PASSWORD: DB_PASSWORD justice-email-and-delius-sentry: SENTRY_DSN: SENTRY_DSN + justice-email-and-delius-queue: + MESSAGING_PRODUCER_QUEUE: QUEUE_NAME + MESSAGING_CONSUMER_QUEUE: QUEUE_NAME + justice-email-and-delius-microsoft-graph: + MICROSOFT-GRAPH_TENANT-ID: TENANT_ID + MICROSOFT-GRAPH_CLIENT-ID: CLIENT_ID + MICROSOFT-GRAPH_CLIENT-SECRET: CLIENT_SECRET + MICROSOFT-GRAPH_EMAIL-ADDRESS: EMAIL_ADDRESS generic-prometheus-alerts: targetApplication: justice-email-and-delius diff --git a/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index f6ea94c9a7..67884fc125 100644 --- a/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -1,25 +1,50 @@ package uk.gov.justice.digital.hmpps.data import jakarta.annotation.PostConstruct +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component +import uk.gov.justice.digital.hmpps.data.generator.Data.BUSINESS_INTERACTIONS +import uk.gov.justice.digital.hmpps.data.generator.Data.CONTACT_TYPES +import uk.gov.justice.digital.hmpps.data.generator.Data.DUPLICATE_STAFF_1 +import uk.gov.justice.digital.hmpps.data.generator.Data.DUPLICATE_STAFF_2 +import uk.gov.justice.digital.hmpps.data.generator.Data.MANAGER +import uk.gov.justice.digital.hmpps.data.generator.Data.MANAGER_STAFF +import uk.gov.justice.digital.hmpps.data.generator.Data.PERSON +import uk.gov.justice.digital.hmpps.data.generator.Data.STAFF import uk.gov.justice.digital.hmpps.data.generator.UserGenerator import uk.gov.justice.digital.hmpps.user.AuditUserRepository @Component @ConditionalOnProperty("seed.database") class DataLoader( - private val auditUserRepository: AuditUserRepository + private val auditUserRepository: AuditUserRepository, + private val entityManager: EntityManager, ) : ApplicationListener { - @PostConstruct fun saveAuditUser() { auditUserRepository.save(UserGenerator.AUDIT_USER) } + @Transactional override fun onApplicationEvent(are: ApplicationReadyEvent) { - // Perform dev/test database setup here, using JPA repositories and generator classes... + listOf( + PERSON, + STAFF, + STAFF.user, + DUPLICATE_STAFF_1, + DUPLICATE_STAFF_1.user, + DUPLICATE_STAFF_2, + DUPLICATE_STAFF_2.user, + MANAGER_STAFF, + MANAGER, + *CONTACT_TYPES, + *BUSINESS_INTERACTIONS, + ).forEach { + entityManager.persist(it) + } } } diff --git a/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/Data.kt b/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/Data.kt new file mode 100644 index 0000000000..637e67e376 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/Data.kt @@ -0,0 +1,23 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.audit.BusinessInteraction +import uk.gov.justice.digital.hmpps.audit.BusinessInteractionCode +import uk.gov.justice.digital.hmpps.entity.* +import uk.gov.justice.digital.hmpps.set +import java.time.ZonedDateTime + +object Data { + val PERSON = Person(id(), crn = "A000001") + val STAFF = staffWithUser(StaffUser(id(), username = "test-user")) + val DUPLICATE_STAFF_1 = staffWithUser(StaffUser(id(), username = "duplicate1")) + val DUPLICATE_STAFF_2 = staffWithUser(StaffUser(id(), username = "duplicate2")) + val MANAGER_STAFF = Staff(id()) + val MANAGER = PersonManager(id(), PERSON, MANAGER_STAFF.id, 102, 103) + val CONTACT_TYPES = ContactType.Code.entries.map { ContactType(id(), it.code) }.toTypedArray() + val BUSINESS_INTERACTIONS = BusinessInteractionCode.entries + .map { BusinessInteraction(id(), it.code, ZonedDateTime.now()) }.toTypedArray() + + private fun id() = IdGenerator.getAndIncrement() + + private fun staffWithUser(user: StaffUser) = Staff(id(), user = user).also { user.set("staff", it) } +} diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/multiple-crns.json b/projects/justice-email-and-delius/src/dev/resources/messages/multiple-crns.json new file mode 100644 index 0000000000..2c35ca944a --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/multiple-crns.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "RE: A000001 and B000002", + "bodyContent": "Example message", + "fromEmailAddress": "example@justice.gov.uk", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/multiple-staff.json b/projects/justice-email-and-delius/src/dev/resources/messages/multiple-staff.json new file mode 100644 index 0000000000..d3c8264384 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/multiple-staff.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "RE: A000001", + "bodyContent": "Example message", + "fromEmailAddress": "duplicate@justice.gov.uk", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/no-crn.json b/projects/justice-email-and-delius/src/dev/resources/messages/no-crn.json new file mode 100644 index 0000000000..6bb94bfc69 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/no-crn.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "No CRN here!", + "bodyContent": "Example message", + "fromEmailAddress": "example@justice.gov.uk", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/no-staff.json b/projects/justice-email-and-delius/src/dev/resources/messages/no-staff.json new file mode 100644 index 0000000000..b131a46d15 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/no-staff.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "RE: A000001", + "bodyContent": "Example message", + "fromEmailAddress": "some-other-staff@justice.gov.uk", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/non-justice-email.json b/projects/justice-email-and-delius/src/dev/resources/messages/non-justice-email.json new file mode 100644 index 0000000000..5bb4908726 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/non-justice-email.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "RE: A000001", + "bodyContent": "Example message", + "fromEmailAddress": "example@example.com", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/messages/successful-message.json b/projects/justice-email-and-delius/src/dev/resources/messages/successful-message.json new file mode 100644 index 0000000000..dc96ce335d --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/messages/successful-message.json @@ -0,0 +1,7 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "subject": "RE: A000001", + "bodyContent": "Example message", + "fromEmailAddress": "example@justice.gov.uk", + "receivedDateTime": "2020-01-01T12:34:56Z[Europe/London]" +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/dev/resources/schema.ldif b/projects/justice-email-and-delius/src/dev/resources/schema.ldif new file mode 100644 index 0000000000..ac69d56543 --- /dev/null +++ b/projects/justice-email-and-delius/src/dev/resources/schema.ldif @@ -0,0 +1,26 @@ +dn: ou=Users,dc=moj,dc=com +objectclass: top +objectclass: organizationalUnit +ou: Users + +dn: cn=test-user,ou=Users,dc=moj,dc=com +objectclass: top +objectclass: inetOrgPerson +cn: test-user +sn: test-user +mail: example@justice.gov.uk + +dn: cn=duplicate1,ou=Users,dc=moj,dc=com +objectclass: top +objectclass: inetOrgPerson +cn: duplicate1 +sn: duplicate1 +mail: duplicate@justice.gov.uk + +dn: cn=duplicate2,ou=Users,dc=moj,dc=com +objectclass: top +objectclass: inetOrgPerson +cn: duplicate2 +sn: duplicate2 +mail: duplicate@justice.gov.uk + diff --git a/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt index d9006c74c5..cb84c7816e 100644 --- a/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ b/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt @@ -1,30 +1,102 @@ package uk.gov.justice.digital.hmpps +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.Mockito.atLeastOnce +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken +import uk.gov.justice.digital.hmpps.data.generator.Data +import uk.gov.justice.digital.hmpps.entity.ContactRepository +import uk.gov.justice.digital.hmpps.entity.ContactType.Code.EMAIL_TEXT_FROM_OTHER +import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.messaging.EmailMessage +import uk.gov.justice.digital.hmpps.messaging.Handler +import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader.get +import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived import uk.gov.justice.digital.hmpps.telemetry.TelemetryService -@AutoConfigureMockMvc -@SpringBootTest(webEnvironment = RANDOM_PORT) +@SpringBootTest internal class IntegrationTest { @Autowired - lateinit var mockMvc: MockMvc + lateinit var handler: Handler + + @Autowired + lateinit var contactRepository: ContactRepository @MockBean lateinit var telemetryService: TelemetryService @Test - fun `API call retuns a success response`() { - mockMvc - .perform(get("/example/123").withToken()) - .andExpect(status().is2xxSuccessful) + fun `contact is created`() { + val notification = Notification(get("successful-message")) + handler.handle(notification) + verify(telemetryService).notificationReceived(notification) + + val contactId = with(argumentCaptor>()) { + verify(telemetryService).trackEvent(eq("CreatedContact"), capture(), any()) + firstValue["contactId"]!!.toLong() + } + val contact = contactRepository.findById(contactId).orElseThrow() + assertThat(contact.type.code, equalTo(EMAIL_TEXT_FROM_OTHER.code)) + assertThat(contact.notes, equalTo("Example message")) + assertThat( + contact.externalReference, + equalTo("urn:uk:gov:hmpps:justice-email:00000000-0000-0000-0000-000000000000") + ) + assertThat(contact.staffId, equalTo(Data.STAFF.id)) + assertThat(contact.teamId, equalTo(Data.MANAGER.teamId)) + assertThat(contact.providerId, equalTo(Data.MANAGER.providerId)) + } + + @Test + fun `allocates contact to manager if sender has no staff record`() { + val notification = Notification(get("no-staff")) + handler.handle(notification) + verify(telemetryService, atLeastOnce()).notificationReceived(notification) + + val contactId = with(argumentCaptor>()) { + verify(telemetryService).trackEvent(eq("CreatedContact"), capture(), any()) + firstValue["contactId"]!!.toLong() + } + val contact = contactRepository.findById(contactId).orElseThrow() + assertThat(contact.staffId, equalTo(Data.MANAGER.staffId)) + } + + @Test + fun `error when multiple crns`() { + val notification = Notification(get("multiple-crns")) + val exception = assertThrows { handler.handle(notification) } + assertThat(exception.message, equalTo("Multiple CRNs in message subject")) + } + + @Test + fun `error when missing crn`() { + val notification = Notification(get("no-crn")) + val exception = assertThrows { handler.handle(notification) } + assertThat(exception.message, equalTo("No CRN in message subject")) + } + + @Test + fun `error when multiple staff records have the same email address`() { + val notification = Notification(get("multiple-staff")) + val exception = assertThrows { handler.handle(notification) } + assertThat(exception.message, equalTo("Multiple staff records found for duplicate@justice.gov.uk")) + } + + @Test + fun `error for unexpected source email address`() { + val notification = Notification(get("non-justice-email")) + val exception = assertThrows { handler.handle(notification) } + assertThat( + exception.message, + equalTo("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk") + ) } } diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt index c7faac5b26..fe1c2e7f71 100644 --- a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt @@ -1,8 +1,10 @@ package uk.gov.justice.digital.hmpps +import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +@EnableAsyncApi @SpringBootApplication class App diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/audit/BusinessInteractionCode.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/audit/BusinessInteractionCode.kt new file mode 100644 index 0000000000..adcf5aa6de --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/audit/BusinessInteractionCode.kt @@ -0,0 +1,5 @@ +package uk.gov.justice.digital.hmpps.audit + +enum class BusinessInteractionCode(override val code: String) : InteractionCode { + ADD_CONTACT("CLBI003"), +} diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/AsyncApiConfig.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/AsyncApiConfig.kt new file mode 100644 index 0000000000..302cbcf321 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/AsyncApiConfig.kt @@ -0,0 +1,30 @@ +package uk.gov.justice.digital.hmpps.config + +import org.openfolder.kotlinasyncapi.springweb.service.AsyncApiExtension +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AsyncApiConfig { + @Bean + fun asyncApiExtension() = AsyncApiExtension.builder(order = 1) { + info.title("justice-email-and-delius") + servers { + server("dev") { + url("https://sqs.eu-west-2.amazonaws.com/754256621582/probation-integration-dev-justice-email-and-delius-queue") + protocol("sqs") + } + server("preprod") { + url("https://sqs.eu-west-2.amazonaws.com/754256621582/probation-integration-preprod-justice-email-and-delius-queue") + protocol("sqs") + } + server("prod") { + url("https://sqs.eu-west-2.amazonaws.com/754256621582/probation-integration-prod-justice-email-and-delius-queue") + protocol("sqs") + } + } + externalDocs { + url("https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/justice-email-and-delius/") + } + } +} diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/GraphServiceClientConfig.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/GraphServiceClientConfig.kt new file mode 100644 index 0000000000..afc180cdbf --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/GraphServiceClientConfig.kt @@ -0,0 +1,24 @@ +package uk.gov.justice.digital.hmpps.config + +import com.azure.identity.ClientSecretCredentialBuilder +import com.microsoft.graph.serviceclient.GraphServiceClient +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class GraphServiceClientConfig { + @Bean + fun graphServiceClient( + @Value("\${microsoft-graph.tenant-id}") tenantId: String, + @Value("\${microsoft-graph.client-id}") clientId: String, + @Value("\${microsoft-graph.client-secret}") clientSecret: String, + ): GraphServiceClient { + val credential = ClientSecretCredentialBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .build() + return GraphServiceClient(credential, "https://graph.microsoft.com/.default") + } +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt deleted file mode 100644 index 6e4f24c46f..0000000000 --- a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/ApiController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package uk.gov.justice.digital.hmpps.controller - -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -class ApiController { - @PreAuthorize("hasRole('EXAMPLE')") - @GetMapping(value = ["/example/{inputId}"]) - fun handle( - @PathVariable("inputId") inputId: String - ) { - // TODO Not yet implemented - } -} diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Contact.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Contact.kt new file mode 100644 index 0000000000..41085d1686 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Contact.kt @@ -0,0 +1,96 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.springframework.data.annotation.CreatedBy +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.exception.NotFoundException +import java.time.ZonedDateTime + +@Entity +@EntityListeners(AuditingEntityListener::class) +@SequenceGenerator(name = "contact_id_seq", sequenceName = "contact_id_seq", allocationSize = 1) +class Contact( + @Id + @Column(name = "contact_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "contact_id_seq") + val id: Long = 0, + + @Column + val externalReference: String, + + @Column(name = "offender_id") + val personId: Long, + + @ManyToOne + @JoinColumn(name = "contact_type_id") + val type: ContactType, + + @Lob + val notes: String, + + @Column(name = "probation_area_id") + val providerId: Long, + + @Column + val teamId: Long, + + @Column + val staffId: Long, + + @Column(name = "contact_date") + val date: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "contact_start_time") + val startTime: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "soft_deleted", columnDefinition = "number") + val softDeleted: Boolean = false, + + @Version + @Column(name = "row_version") + val version: Long = 0, + + @Column + val partitionAreaId: Long = 0, + + @CreatedDate + var createdDatetime: ZonedDateTime = ZonedDateTime.now(), + + @CreatedBy + var createdByUserId: Long = 0, + + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @LastModifiedDate + var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now() +) + +@Entity +@Immutable +@Table(name = "r_contact_type") +class ContactType( + @Id + @Column(name = "contact_type_id") + val id: Long, + + val code: String +) { + enum class Code(val code: String) { + EMAIL_TEXT_FROM_OTHER("CM3A") + } +} + +interface ContactRepository : JpaRepository + +interface ContactTypeRepository : JpaRepository { + fun findByCode(code: String): ContactType? +} + +fun ContactTypeRepository.getByCode(type: ContactType.Code) = findByCode(type.code) + ?: throw NotFoundException("Contact Type", "code", type.code) diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt new file mode 100644 index 0000000000..264186b850 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt @@ -0,0 +1,32 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.exception.NotFoundException + +@Entity +@Immutable +@SQLRestriction("soft_deleted = 0") +@Table(name = "offender") +class Person( + @Id + @Column(name = "offender_id") + val id: Long, + + @Column(columnDefinition = "char(7)") + val crn: String, + + @Column + val softDeleted: Boolean = false, +) + +interface PersonRepository : JpaRepository { + fun findByCrn(crn: String): Person? +} + +fun PersonRepository.getByCrn(crn: String) = findByCrn(crn) ?: throw NotFoundException("Person", "crn", crn) \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt new file mode 100644 index 0000000000..99626d8301 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt @@ -0,0 +1,43 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.exception.NotFoundException + +@Entity +@Immutable +@Table(name = "offender_manager") +@SQLRestriction("soft_deleted = 0 and active_flag = 1") +class PersonManager( + @Id + @Column(name = "offender_manager_id") + val id: Long, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @Column(name = "allocation_staff_id") + val staffId: Long, + + @Column(name = "team_id") + val teamId: Long, + + @Column(name = "probation_area_id") + val providerId: Long, + + @Column(name = "active_flag", columnDefinition = "number") + val active: Boolean = true, + + @Column(name = "soft_deleted", columnDefinition = "number") + val softDeleted: Boolean = false, +) + +interface PersonManagerRepository : JpaRepository { + fun findByPersonId(id: Long): PersonManager? +} + +fun PersonManagerRepository.getManager(personId: Long) = findByPersonId(personId) + ?: throw NotFoundException("Manager", "personId", personId) \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt new file mode 100644 index 0000000000..4cf9566253 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt @@ -0,0 +1,38 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +@Entity +@Immutable +class Staff( + @Id + @Column(name = "staff_id") + val id: Long, + + @OneToOne(mappedBy = "staff") + val user: StaffUser? = null, +) + +@Entity +@Immutable +@Table(name = "user_") +class StaffUser( + @Id + @Column(name = "user_id") + val id: Long, + + @Column(name = "distinguished_name") + val username: String, + + @OneToOne + @JoinColumn(name = "staff_id") + val staff: Staff? = null, +) + +interface StaffRepository : JpaRepository { + @Query("select s from Staff s where upper(s.user.username) = upper(:username)") + fun findByUserUsername(username: String): Staff? +} diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt new file mode 100644 index 0000000000..d93612c207 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt @@ -0,0 +1,12 @@ +package uk.gov.justice.digital.hmpps.messaging + +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.context.annotation.Primary +import org.springframework.stereotype.Component +import uk.gov.justice.digital.hmpps.converter.NotificationConverter + +@Primary +@Component +class Converter(objectMapper: ObjectMapper) : NotificationConverter(objectMapper) { + override fun getMessageType() = EmailMessage::class +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/EmailMessage.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/EmailMessage.kt new file mode 100644 index 0000000000..8030a4a9e2 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/EmailMessage.kt @@ -0,0 +1,13 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import java.time.ZonedDateTime + +@Message +data class EmailMessage( + val id: String, + val subject: String, + val bodyContent: String, + val fromEmailAddress: String, + val receivedDateTime: ZonedDateTime, +) diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt new file mode 100644 index 0000000000..c976173be1 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt @@ -0,0 +1,104 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.channel.Channel +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.annotation.channel.Publish +import org.springframework.ldap.NameNotFoundException +import org.springframework.ldap.core.AttributesMapper +import org.springframework.ldap.core.LdapTemplate +import org.springframework.ldap.query.LdapQueryBuilder.query +import org.springframework.ldap.query.SearchScope +import org.springframework.stereotype.Component +import uk.gov.justice.digital.hmpps.audit.BusinessInteractionCode.ADD_CONTACT +import uk.gov.justice.digital.hmpps.audit.service.AuditableService +import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService +import uk.gov.justice.digital.hmpps.converter.NotificationConverter +import uk.gov.justice.digital.hmpps.entity.* +import uk.gov.justice.digital.hmpps.entity.ContactType.Code.EMAIL_TEXT_FROM_OTHER +import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService + +@Component +@Channel("justice-email-and-delius-queue") +class Handler( + auditedInteractionService: AuditedInteractionService, + override val converter: NotificationConverter, + private val telemetryService: TelemetryService, + private val contactRepository: ContactRepository, + private val contactTypeRepository: ContactTypeRepository, + private val personRepository: PersonRepository, + private val personManagerRepository: PersonManagerRepository, + private val staffRepository: StaffRepository, + private val ldapTemplate: LdapTemplate, +) : NotificationHandler, AuditableService(auditedInteractionService) { + @Publish(messages = [Message(title = "email-message", payload = Schema(EmailMessage::class))]) + override fun handle(notification: Notification) = audit(ADD_CONTACT) { audit -> + telemetryService.notificationReceived(notification) + val message = notification.message + + val crn = message.extractCrn() + val emailAddress = + message.fromEmailAddress.takeIf { it.endsWith("@justice.gov.uk") || it.endsWith("@digital.justice.gov.uk") } + ?: throw IllegalArgumentException("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk") + val person = personRepository.getByCrn(crn) + val manager = personManagerRepository.getManager(person.id) + val staffId = findStaffIdForEmailAddress(emailAddress) ?: manager.staffId + val contact = contactRepository.save( + Contact( + personId = person.id, + externalReference = "urn:uk:gov:hmpps:justice-email:${message.id}", + type = contactTypeRepository.getByCode(EMAIL_TEXT_FROM_OTHER), + date = message.receivedDateTime, + startTime = message.receivedDateTime, + notes = message.bodyContent, + staffId = staffId, + teamId = manager.teamId, + providerId = manager.providerId, + ) + ) + audit["contactId"] = contact.id + + telemetryService.trackEvent( + "CreatedContact", mapOf( + "crn" to crn, + "staffId" to staffId.toString(), + "contactId" to contact.id.toString(), + "messageId" to message.id, + ) + ) + } + + private fun EmailMessage.extractCrn(): String { + val crns = Regex("[A-Za-z][0-9]{6}").findAll(subject) + return when (crns.count()) { + 1 -> crns.single().value.uppercase() + 0 -> throw IllegalArgumentException("No CRN in message subject") + else -> throw IllegalArgumentException("Multiple CRNs in message subject") + } + } + + private fun findStaffIdForEmailAddress(emailAddress: String): Long? { + val matchingStaffIds = try { + ldapTemplate + .search(query() + .attributes("cn") + .searchScope(SearchScope.ONELEVEL) + .where("objectclass").`is`("inetOrgPerson") + .and("objectclass").`is`("top") + .and("mail").`is`(emailAddress), + AttributesMapper { it["cn"]?.get()?.toString() }) + .filterNotNull() + .mapNotNull { staffRepository.findByUserUsername(it)?.id } + } catch (_: NameNotFoundException) { + return null + } + + return when (matchingStaffIds.size) { + 0 -> null + 1 -> matchingStaffIds.single() + else -> error("Multiple staff records found for $emailAddress") + } + } +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/scheduling/Poller.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/scheduling/Poller.kt new file mode 100644 index 0000000000..71fdb4762f --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/scheduling/Poller.kt @@ -0,0 +1,13 @@ +package uk.gov.justice.digital.hmpps.scheduling + +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.service.MailboxService + +@Service +class Poller(private val mailboxService: MailboxService) { + @Scheduled(fixedDelayString = "\${poller.fixed-delay:60000}") + fun poll() { + mailboxService.publishUnreadMessagesToQueue() + } +} diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt new file mode 100644 index 0000000000..8405af7a26 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt @@ -0,0 +1,59 @@ +package uk.gov.justice.digital.hmpps.service + +import com.microsoft.graph.models.Message +import com.microsoft.graph.serviceclient.GraphServiceClient +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.instrumentation.annotations.WithSpan +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.messaging.EmailMessage +import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher + +@Service +class MailboxService( + @Value("\${microsoft-graph.email-address}") + private val emailAddress: String, + private val graphServiceClient: GraphServiceClient, + private val notificationPublisher: NotificationPublisher +) { + @WithSpan("POLL mailbox", kind = SpanKind.SERVER) + fun publishUnreadMessagesToQueue() { + getUnreadMessages().forEach { message -> + notificationPublisher.publish(message.asNotification()) + message.markAsRead() + } + } + + private fun getUnreadMessages() = graphServiceClient + .users() + .byUserId(emailAddress) + .mailFolders() + .byMailFolderId("inbox") + .messages() + .get { request -> + request.queryParameters.top = 10 + request.queryParameters.filter = "isRead ne true" + request.queryParameters.select = arrayOf("subject", "from", "id", "receivedDateTime", "body", "isRead") + request.queryParameters.orderby = arrayOf("receivedDateTime DESC") + }.value + + private fun Message.markAsRead() { + graphServiceClient + .users() + .byUserId(emailAddress) + .messages() + .byMessageId(id) + .patch(Message().apply { isRead = true }) + } + + private fun Message.asNotification() = Notification( + message = EmailMessage( + id = id, + subject = subject, + bodyContent = body.content, + fromEmailAddress = from.emailAddress.address, + receivedDateTime = receivedDateTime.toZonedDateTime(), + ) + ) +} diff --git a/projects/justice-email-and-delius/src/main/resources/application.yml b/projects/justice-email-and-delius/src/main/resources/application.yml index f99b020d1b..1d59bfc682 100644 --- a/projects/justice-email-and-delius/src/main/resources/application.yml +++ b/projects/justice-email-and-delius/src/main/resources/application.yml @@ -16,13 +16,12 @@ spring: query.mutation_strategy.global_temporary: create_tables: false drop_tables: false + ldap: + base: ou=Users,dc=moj,dc=com + base-environment: + java.naming.ldap.derefAliases: never threads.virtual.enabled: true -oauth2.roles: - - EXAMPLE - -springdoc.default-produces-media-type: application/json - delius.db.username: JusticeEmailAndDelius # Should match value in [deploy/database/access.yml]. management: @@ -33,6 +32,7 @@ management: info.productId: HMPPS518 # https://developer-portal.hmpps.service.justice.gov.uk/products/185 + --- # Shared dev/test config spring.config.activate.on-profile: [ "dev", "integration-test" ] @@ -41,10 +41,18 @@ server.shutdown: immediate spring: datasource.url: jdbc:h2:file:./dev;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH;AUTO_SERVER=true;AUTO_SERVER_PORT=9092 jpa.hibernate.ddl-auto: create-drop + ldap.embedded.base-dn: ${spring.ldap.base} seed.database: true -wiremock.enabled: true -context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser + +messaging.consumer.queue: message-queue +messaging.producer.queue: message-queue + +microsoft-graph: + tenant-id: 00000000-0000-0000-0000-000000000000 + client-id: 00000000-0000-0000-0000-000000000000 + client-secret: 00000000-0000-0000-0000-000000000000 + email-address: example@example.com logging.level: uk.gov.justice.digital.hmpps: DEBUG @@ -71,3 +79,12 @@ spring: jpa.hibernate.ddl-auto: validate seed.database: false delius.db.username: NationalUser + +--- +spring.config.activate.on-profile: localstack +spring.cloud.aws: + sqs.endpoint: http://localhost:4566 + sns.endpoint: http://localhost:4566 + credentials: + access-key: localstack + secret-key: localstack diff --git a/projects/justice-email-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/scheduling/PollerTest.kt b/projects/justice-email-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/scheduling/PollerTest.kt new file mode 100644 index 0000000000..0ce4f78361 --- /dev/null +++ b/projects/justice-email-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/scheduling/PollerTest.kt @@ -0,0 +1,24 @@ +package uk.gov.justice.digital.hmpps.scheduling + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.verify +import uk.gov.justice.digital.hmpps.service.MailboxService + +@ExtendWith(MockitoExtension::class) +internal class PollerTest { + @Mock + lateinit var mailboxService: MailboxService + + @InjectMocks + lateinit var poller: Poller + + @Test + fun `poller calls service`() { + poller.poll() + verify(mailboxService).publishUnreadMessagesToQueue() + } +} \ No newline at end of file diff --git a/projects/justice-email-and-delius/tech-docs/Gemfile.lock b/projects/justice-email-and-delius/tech-docs/Gemfile.lock index 9fdb19f88e..9617050847 100644 --- a/projects/justice-email-and-delius/tech-docs/Gemfile.lock +++ b/projects/justice-email-and-delius/tech-docs/Gemfile.lock @@ -123,7 +123,7 @@ GEM middleman-syntax (3.2.0) middleman-core (>= 3.2) rouge (~> 3.2) - minitest (5.18.0) + minitest (5.19.0) multi_json (1.15.0) nokogiri (1.16.5-x86_64-linux) racc (~> 1.4) diff --git a/projects/justice-email-and-delius/tech-docs/config/tech-docs.yml b/projects/justice-email-and-delius/tech-docs/config/tech-docs.yml index 8c7b5c08b3..0b3e6cda15 100644 --- a/projects/justice-email-and-delius/tech-docs/config/tech-docs.yml +++ b/projects/justice-email-and-delius/tech-docs/config/tech-docs.yml @@ -39,6 +39,3 @@ github_branch: main # Slack owner_slack_workspace: mojdt default_owner_slack: '#probation-integration-tech' - -# OpenAPI -api_path: https://justice-email-and-delius-dev.hmpps.service.justice.gov.uk/v3/api-docs.yaml diff --git a/projects/justice-email-and-delius/tech-docs/source/api-reference.html.md.erb b/projects/justice-email-and-delius/tech-docs/source/api-reference.html.md.erb deleted file mode 100644 index e8a4f0baac..0000000000 --- a/projects/justice-email-and-delius/tech-docs/source/api-reference.html.md.erb +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: API Reference -source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/justice-email-and-delius/tech-docs/source/api-reference.html.md.erb' -weight: 20 ---- - -
- - API Reference -
- - -The following documentation is also available in these formats: - -* [OpenAPI JSON](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/justice-email-and-delius/api-docs.json) -* [OpenAPI YAML](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/justice-email-and-delius/api-docs.yaml) -* [Swagger UI](https://justice-email-and-delius-dev.hmpps.service.justice.gov.uk/swagger-ui/index.html) - -api> diff --git a/projects/justice-email-and-delius/tech-docs/source/asyncapi-reference.html.md.erb b/projects/justice-email-and-delius/tech-docs/source/asyncapi-reference.html.md.erb new file mode 100644 index 0000000000..2a50eaea6c --- /dev/null +++ b/projects/justice-email-and-delius/tech-docs/source/asyncapi-reference.html.md.erb @@ -0,0 +1,12 @@ +--- +title: AsyncAPI Reference +source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/justice-email-and-delius/tech-docs/source/asyncapi-reference.html.md.erb' +weight: 30 +--- + +# AsyncAPI Reference + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 2d4239365a..f8eb916ee8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -78,8 +78,10 @@ dependencyResolutionManagement { library("aws-sqs", "io.awspring.cloud:spring-cloud-aws-starter-sqs:3.2.1") library("aws-starter", "io.awspring.cloud:spring-cloud-aws-starter:3.2.1") library("aws-sts", "software.amazon.awssdk:sts:2.29.6") + library("azure-app-insights", "com.microsoft.azure:applicationinsights-web:3.6.2") + library("azure-identity", "com.azure:azure-identity:1.13.3") library("flipt", "io.flipt:flipt-java:1.1.1") - library("insights", "com.microsoft.azure:applicationinsights-web:3.6.2") + library("microsoft-graph", "com.microsoft.graph:microsoft-graph:6.16.0") library("mockito-inline", "org.mockito:mockito-inline:5.2.0") library("mockito-kotlin", "org.mockito.kotlin:mockito-kotlin:5.4.0") library("notify", "uk.gov.service.notify:notifications-java-client:5.2.1-RELEASE") @@ -96,7 +98,7 @@ dependencyResolutionManagement { listOf("aws-autoconfigure", "aws-starter", "aws-sns", "aws-sqs", "aws-sts", "aws-query-protocol") ) bundle("mockito", listOf("mockito-kotlin", "mockito-inline")) - bundle("telemetry", listOf("insights", "opentelemetry-annotations", "sentry")) + bundle("telemetry", listOf("azure-app-insights", "opentelemetry-annotations", "sentry")) } } }