Skip to content

Commit

Permalink
HAI-1882 Add API for own permissions
Browse files Browse the repository at this point in the history
Add an API where the user (or in practice, the frontend) can ask what
permission level they have for a hanke. Returns 404 if the hanke is not
found or the user doesn't have any permissions for it.

Also rewrite the hanke not found log message to make it searchable.
  • Loading branch information
corvidian committed Sep 6, 2023
1 parent ca37573 commit 6548a14
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package fi.hel.haitaton.hanke

import assertk.all
import assertk.assertFailure
import assertk.assertions.each
import assertk.assertions.hasClass
import assertk.assertions.hasSize
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isNull
import assertk.assertions.messageContains
import com.ninjasquad.springmockk.MockkBean
import fi.hel.haitaton.hanke.allu.ApplicationStatus
import fi.hel.haitaton.hanke.allu.CableReportService
Expand Down Expand Up @@ -66,6 +70,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.skyscreamer.jsonassert.JSONAssert
Expand Down Expand Up @@ -101,6 +106,7 @@ class HankeServiceITests : DatabaseTest() {
@Autowired private lateinit var hankeKayttajaRepository: HankeKayttajaRepository
@Autowired private lateinit var kayttajaTunnisteRepository: KayttajaTunnisteRepository
@Autowired private lateinit var jdbcTemplate: JdbcTemplate
@Autowired private lateinit var hankeFactory: HankeFactory

@BeforeEach
fun clearMocks() {
Expand All @@ -113,6 +119,50 @@ class HankeServiceITests : DatabaseTest() {
confirmVerified(cableReportService)
}

@Nested
inner class GetHankeId {
private val hankeTunnus = "HAI23-41"

@Test
fun `Returns null if hanke not found`() {
val response = hankeService.getHankeId(hankeTunnus)

assertThat(response).isNull()
}

@Test
fun `Returns hanke id if hanke found`() {
val hanke = hankeFactory.save()

val response = hankeService.getHankeId(hanke.hankeTunnus!!)

assertThat(response).isEqualTo(hanke.id!!)
}
}

@Nested
inner class GetHankeIdOrThrow {
private val hankeTunnus = "HAI23-41"

@Test
fun `Throws exception if hanke not found`() {
assertFailure { hankeService.getHankeIdOrThrow(hankeTunnus) }
.all {
hasClass(HankeNotFoundException::class)
messageContains(hankeTunnus)
}
}

@Test
fun `Returns hanke id if hanke found`() {
val hanke = hankeFactory.save()

val response = hankeService.getHankeIdOrThrow(hanke.hankeTunnus!!)

assertThat(response).isEqualTo(hanke.id!!)
}
}

@Test
fun `create Hanke with full data set succeeds and returns a new domain object with the correct values`() {
val hanke: Hanke = getATestHanke().withYhteystiedot { it.id = null }.withHankealue()
Expand Down Expand Up @@ -302,12 +352,13 @@ class HankeServiceITests : DatabaseTest() {

@Test
fun `getHankeHakemuksetPair hanke does not exist throws not found`() {
val exception =
assertThrows<HankeNotFoundException> {
hankeService.getHankeWithApplications("HAI-1234")
}
val hankeTunnus = "HAI-1234"

assertThat(exception).hasMessage("Hanke HAI-1234 not found")
assertFailure { hankeService.getHankeWithApplications(hankeTunnus) }
.all {
hasClass(HankeNotFoundException::class)
messageContains(hankeTunnus)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,58 @@ class HankeKayttajaControllerITest(@Autowired override val mockMvc: MockMvc) : C
confirmVerified(hankeKayttajaService, hankeService, permissionService)
}

@Nested
inner class Whoami {
private val url = "/hankkeet/$HANKE_TUNNUS/whoami"
private val hankeId = 14
private val kayttooikeustaso = Kayttooikeustaso.KAIKKIEN_MUOKKAUS

private val permissionEntity =
PermissionEntity(
hankeId = hankeId,
userId = USERNAME,
kayttooikeustaso = KayttooikeustasoEntity(0, kayttooikeustaso, 0)
)

@Test
fun `Returns 404 if hanke not found`() {
every { hankeService.getHankeIdOrThrow(HANKE_TUNNUS) } throws
HankeNotFoundException(HANKE_TUNNUS)

get(url).andExpect(status().isNotFound).andExpect(hankeError(HankeError.HAI1001))

verify { hankeService.getHankeIdOrThrow(HANKE_TUNNUS) }
}

@Test
fun `Returns 404 if permission not found`() {
every { hankeService.getHankeIdOrThrow(HANKE_TUNNUS) } returns hankeId
every { permissionService.findPermission(hankeId, USERNAME) } returns null

get(url).andExpect(status().isNotFound).andExpect(hankeError(HankeError.HAI1001))

verifySequence {
hankeService.getHankeIdOrThrow(HANKE_TUNNUS)
permissionService.findPermission(hankeId, USERNAME)
}
}

@Test
fun `Returns kayttooikeustaso`() {
every { hankeService.getHankeIdOrThrow(HANKE_TUNNUS) } returns hankeId
every { permissionService.findPermission(hankeId, USERNAME) } returns permissionEntity

val response: WhoamiResponse = get(url).andExpect(status().isOk).andReturnBody()

val expectedResponse = WhoamiResponse(USERNAME, kayttooikeustaso)
assertThat(response).isEqualTo(expectedResponse)
verifySequence {
hankeService.getHankeIdOrThrow(HANKE_TUNNUS)
permissionService.findPermission(hankeId, USERNAME)
}
}
}

@Nested
inner class GetHankeKayttajat {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ data class HankeErrorDetail(
)

class HankeNotFoundException(val hankeTunnus: String?) :
RuntimeException("Hanke $hankeTunnus not found")
RuntimeException("Hanke not found with hankeTunnus $hankeTunnus")

class HankeArgumentException(message: String) : RuntimeException(message)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface HankeService {

fun getHankeId(hankeTunnus: String): Int?

fun getHankeIdOrThrow(hankeTunnus: String): Int

fun getHankeWithApplications(hankeTunnus: String): HankeWithApplications

@Transactional fun createHanke(hanke: Hanke): Hanke
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ open class HankeServiceImpl(
override fun getHankeId(hankeTunnus: String): Int? =
hankeRepository.findByHankeTunnus(hankeTunnus)?.id

override fun getHankeIdOrThrow(hankeTunnus: String): Int =
getHankeId(hankeTunnus) ?: throw HankeNotFoundException(hankeTunnus)

/**
* Hanke does not contain hakemukset. This function wraps Hanke and its hakemukset to a pair.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fi.hel.haitaton.hanke.permissions

import fi.hel.haitaton.hanke.HankeError
import fi.hel.haitaton.hanke.HankeNotFoundException
import fi.hel.haitaton.hanke.HankeService
import fi.hel.haitaton.hanke.configuration.Feature
import fi.hel.haitaton.hanke.configuration.FeatureFlags
Expand Down Expand Up @@ -36,6 +37,30 @@ class HankeKayttajaController(
private val disclosureLogService: DisclosureLogService,
private val featureFlags: FeatureFlags,
) {
@GetMapping("/hankkeet/{hankeTunnus}/whoami")
@Operation(summary = "Get your own permission for a hanke")
@ApiResponses(
value =
[
ApiResponse(
description = "Your permissions",
responseCode = "200",
content = [Content(schema = Schema(implementation = WhoamiResponse::class))]
),
ApiResponse(
description = "Hanke not found",
responseCode = "404",
content = [Content(schema = Schema(implementation = HankeError::class))]
),
]
)
fun whoami(@PathVariable hankeTunnus: String): WhoamiResponse {
val userId = currentUserId()
val hankeId = hankeService.getHankeIdOrThrow(hankeTunnus)

return permissionService.findPermission(hankeId, userId)?.let { WhoamiResponse(it) }
?: throw HankeNotFoundException(hankeTunnus)
}

@GetMapping("/hankkeet/{hankeTunnus}/kayttajat")
@Operation(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fi.hel.haitaton.hanke.permissions

data class WhoamiResponse(val userId: String, val kayttooikeustaso: Kayttooikeustaso) {
constructor(
permissionEntity: PermissionEntity
) : this(permissionEntity.userId, permissionEntity.kayttooikeustaso.kayttooikeustaso)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package fi.hel.haitaton.hanke

import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.hasClass
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import assertk.assertions.messageContains
import fi.hel.haitaton.hanke.application.ApplicationService
import fi.hel.haitaton.hanke.geometria.GeometriatService
import fi.hel.haitaton.hanke.logging.AuditLogService
import fi.hel.haitaton.hanke.logging.HankeLoggingService
import fi.hel.haitaton.hanke.permissions.HankeKayttajaService
import fi.hel.haitaton.hanke.permissions.PermissionService
import fi.hel.haitaton.hanke.tormaystarkastelu.TormaystarkasteluLaskentaService
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class HankeServiceTest {

private val hankeRepository: HankeRepository = mockk()
private val tormaystarkasteluService: TormaystarkasteluLaskentaService = mockk()
private val hanketunnusService: HanketunnusService = mockk()
private val geometriatService: GeometriatService = mockk()
private val auditLogService: AuditLogService = mockk()
private val hankeLoggingService: HankeLoggingService = mockk()
private val applicationService: ApplicationService = mockk()
private val permissionService: PermissionService = mockk()
private val hankeKayttajaService: HankeKayttajaService = mockk()

private val hankeService =
HankeServiceImpl(
hankeRepository,
tormaystarkasteluService,
hanketunnusService,
geometriatService,
auditLogService,
hankeLoggingService,
applicationService,
permissionService,
hankeKayttajaService,
)

@Nested
inner class GetHankeId {
val hankeTunnus = "HAI23-1"
val hankeId = 9984

@Test
fun `Returns hanke id if hanke found`() {
every { hankeRepository.findByHankeTunnus(hankeTunnus) } returns
HankeEntity(id = hankeId)

val response = hankeService.getHankeId(hankeTunnus)

assertThat(response).isNotNull().isEqualTo(hankeId)
}

@Test
fun `Returns null if hanke not found`() {
every { hankeRepository.findByHankeTunnus(hankeTunnus) } returns null

val response = hankeService.getHankeId(hankeTunnus)

assertThat(response).isNull()
}
}

@Nested
inner class GetHankeIdOrThrow {
val hankeTunnus = "HAI23-1"
val hankeId = 9984

@Test
fun `Returns hanke id if hanke found`() {
every { hankeRepository.findByHankeTunnus(hankeTunnus) } returns
HankeEntity(id = hankeId)

val response = hankeService.getHankeIdOrThrow(hankeTunnus)

assertThat(response).isNotNull().isEqualTo(hankeId)
}

@Test
fun `Returns null if hanke not found`() {
every { hankeRepository.findByHankeTunnus(hankeTunnus) } returns null

assertFailure { hankeService.getHankeIdOrThrow(hankeTunnus) }
.all {
hasClass(HankeNotFoundException::class)
messageContains(hankeTunnus)
}
}
}
}

0 comments on commit 6548a14

Please sign in to comment.