add get and update for Type

- add UpdateType end-to-end
- add updateType to TypeController
- add updateType to TypeService
- add sql 004-create_type_description_data.sql
- update TypesEndpoint to use Types.Response
- update type end-to-end test
  - update TypeEndpoint with CreateType
  - add reset test table
  - add test data
- add getType to TypeController
- add getType to TypeService
- add findTypeWithDescription to TypeRepository
- update type end-to-end test
- update TypeController for changes for adding type
- update type mapping for latest changes in Mapping.kt
- update addType to use TypeDescriptionRepository and return TypeWithDescription in TypeService
- add TypeWithDescription
- add TypeDescriptionRepository
- add TypeDescriptionEntity
- add missing device mapping test
- add type_descriptions sql script for database changes
- update TypesEndpoint
  - update TypesController to use Types.Response
  - add TypeEntity.toTypesResponse to Mapping.kt
This commit is contained in:
2025-03-11 07:51:40 +01:00
parent 53db4408e2
commit d90a716df7
21 changed files with 947 additions and 84 deletions

View File

@@ -8,7 +8,7 @@ import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.UUID
import kotlinx.coroutines.test.runTest
import ltd.hlaeja.entity.TypeEntity
import ltd.hlaeja.dto.TypeWithDescription
import ltd.hlaeja.library.deviceRegistry.Type
import ltd.hlaeja.service.TypeService
import ltd.hlaeja.test.isEqualToUuid
@@ -34,16 +34,59 @@ class TypeControllerTest {
@Test
fun `add type`() = runTest {
// given
val request = Type.Request("name")
coEvery { service.addType(any()) } returns TypeEntity(id, timestamp, "name")
val request = Type.Request("name", "description")
coEvery { service.addType(any(), any()) } returns TypeWithDescription(id, timestamp, "name", "description")
// when
val response = controller.addType(request)
// then
coVerify(exactly = 1) { service.addType(any()) }
coVerify(exactly = 1) { service.addType(any(), any()) }
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(response.name).isEqualTo("name")
assertThat(response.description).isEqualTo("description")
}
@Test
fun `get type`() = runTest {
// given
coEvery { service.getType(any()) } returns TypeWithDescription(id, timestamp, "name", "description")
// when
val response = controller.getType(id)
// then
coVerify(exactly = 1) { service.getType(any()) }
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(response.name).isEqualTo("name")
assertThat(response.description).isEqualTo("description")
}
@Test
fun `update type`() = runTest {
// given
val request = Type.Request("name", "description")
coEvery { service.updateType(any(), any(), any()) } answers { call ->
TypeWithDescription(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = call.invocation.args[1] as String,
description = call.invocation.args[2] as String,
)
}
// when
val response = controller.updateType(id, request)
// then
coVerify(exactly = 1) { service.updateType(any(), any(), any()) }
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(response.timestamp).isEqualTo(timestamp)
assertThat(response.name).isEqualTo("name")
assertThat(response.description).isEqualTo("description")
}
}

View File

@@ -19,8 +19,10 @@ import org.junit.jupiter.api.Test
class TypesControllerTest {
companion object {
val id = UUID.fromString("00000000-0000-0000-0000-000000000000")
val timestamp = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 1), ZoneId.of("UTC"))
const val NAME: String = "name"
const val NIL_UUID: String = "00000000-0000-0000-0000-000000000000"
val id: UUID = UUID.fromString(NIL_UUID)
val timestamp: ZonedDateTime = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 1), ZoneId.of("UTC"))
}
val service: TypeService = mockk()
@@ -35,7 +37,9 @@ class TypesControllerTest {
@Test
fun `get all types`() = runTest {
// given
every { service.getTypes(any(), any(), any()) } returns flowOf(TypeEntity(id, timestamp, "name"))
every {
service.getTypes(any(), any(), any())
} returns flowOf(TypeEntity(id, timestamp, NAME))
// when
val response = controller.getTypes().single()
@@ -43,7 +47,8 @@ class TypesControllerTest {
// then
verify(exactly = 1) { service.getTypes(0, 25, null) }
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(response.name).isEqualTo("name")
assertThat(response.id).isEqualToUuid(NIL_UUID)
assertThat(response.name).isEqualTo(NAME)
assertThat(response.timestamp).isEqualTo(timestamp)
}
}

View File

@@ -4,90 +4,321 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.UUID
import kotlin.test.assertFailsWith
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import ltd.hlaeja.dto.TypeWithDescription
import ltd.hlaeja.entity.TypeDescriptionEntity
import ltd.hlaeja.entity.TypeEntity
import ltd.hlaeja.repository.TypeDescriptionRepository
import ltd.hlaeja.repository.TypeRepository
import ltd.hlaeja.test.isEqualToUuid
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.dao.DuplicateKeyException
import org.springframework.http.HttpStatus.ACCEPTED
import org.springframework.http.HttpStatus.CONFLICT
import org.springframework.http.HttpStatus.EXPECTATION_FAILED
import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.web.server.ResponseStatusException
class TypeServiceTest {
companion object {
val timestamp = ZonedDateTime.ofInstant(Instant.parse("2000-01-01T00:00:00.001Z"), ZoneId.of("UTC"))
val timestamp: ZonedDateTime = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 1), ZoneId.of("UTC"))
val uuid = UUID.fromString("00000000-0000-0000-0000-000000000000")
}
val repository: TypeRepository = mockk()
val typeRepository: TypeRepository = mockk()
val typeDescriptionRepository: TypeDescriptionRepository = mockk()
lateinit var service: TypeService
@BeforeEach
fun setUp() {
service = TypeService(repository)
service = TypeService(typeRepository, typeDescriptionRepository)
mockkStatic(ZonedDateTime::class)
every { ZonedDateTime.now() } returns timestamp
}
@AfterEach
fun tearDown() {
unmockkStatic(ZonedDateTime::class)
}
@Test
fun `get all types`() {
// given
every { repository.findAll(any(), any()) } returns flowOf(mockk<TypeEntity>())
every { typeRepository.findAll(any(), any()) } returns flowOf(mockk<TypeEntity>())
// when
service.getTypes(1, 10, null)
// then
verify(exactly = 1) { repository.findAll(1, 10) }
verify(exactly = 0) { repository.findAllContaining(any(), any(), any()) }
verify(exactly = 1) { typeRepository.findAll(1, 10) }
verify(exactly = 0) { typeRepository.findAllContaining(any(), any(), any()) }
}
@Test
fun `get all types with filter`() {
// given
every { repository.findAllContaining(any(), any(), any()) } returns flowOf(mockk<TypeEntity>())
every { typeRepository.findAllContaining(any(), any(), any()) } returns flowOf(mockk<TypeEntity>())
// when
service.getTypes(1, 10, "abc")
// then
verify(exactly = 1) { repository.findAllContaining("%abc%", 1, 10) }
verify(exactly = 0) { repository.findAll(any(), any()) }
verify(exactly = 1) { typeRepository.findAllContaining("%abc%", 1, 10) }
verify(exactly = 0) { typeRepository.findAll(any(), any()) }
}
@Test
fun `add new type success`() = runTest {
// given
val entity = TypeEntity(
null,
timestamp,
"name",
)
coEvery { repository.save(any()) } answers { call -> (call.invocation.args[0] as TypeEntity).copy(id = uuid) }
coEvery { typeRepository.save(any()) } answers { call ->
(call.invocation.args[0] as TypeEntity).copy(id = uuid)
}
coEvery { typeDescriptionRepository.upsert(any(), any()) } answers { call ->
TypeDescriptionEntity(
typeId = call.invocation.args[0] as UUID,
description = call.invocation.args[1] as String,
)
}
// when
service.addType(entity)
val result = service.addType("name", "description")
// then
coVerify(exactly = 1) { repository.save(any()) }
coVerify(exactly = 1) { typeRepository.save(any()) }
coVerify(exactly = 1) { typeDescriptionRepository.upsert(any(), any()) }
assertThat(result.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(result.timestamp).isEqualTo(timestamp)
assertThat(result.name).isEqualTo("name")
assertThat(result.description).isEqualTo("description")
}
@Test
fun `add new type exception`() = runTest {
fun `add new type - fail this should never happen save not updating id`() = runTest {
// given
val entity: TypeEntity = mockk()
coEvery { typeRepository.save(any()) } answers { call -> call.invocation.args[0] as TypeEntity }
coEvery { repository.save(any()) } throws DuplicateKeyException("duplicate key")
// then exception
assertFailsWith<ResponseStatusException> {
service.addType(entity)
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.addType("name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.save(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.upsert(any(), any()) }
assertThat(response.statusCode).isEqualTo(EXPECTATION_FAILED)
}
@Test
fun `add new type - fail duplicate key`() = runTest {
// given
coEvery { typeRepository.save(any()) } throws DuplicateKeyException("duplicate key")
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.addType("name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.save(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.upsert(any(), any()) }
assertThat(response.statusCode).isEqualTo(CONFLICT)
}
@Test
fun `get type - success`() = runTest {
// given
coEvery { typeRepository.findTypeWithDescription(any()) } answers { call ->
TypeWithDescription(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = "name",
description = "description",
)
}
// when
val result = service.getType(uuid)
// then
coVerify(exactly = 1) { typeRepository.findTypeWithDescription(any()) }
assertThat(result.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(result.timestamp).isEqualTo(timestamp)
assertThat(result.name).isEqualTo("name")
assertThat(result.description).isEqualTo("description")
}
@Test
fun `get type - fail`() = runTest {
// given
coEvery { typeRepository.findTypeWithDescription(any()) } returns null
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.getType(uuid)
}
// then
coVerify(exactly = 1) { typeRepository.findTypeWithDescription(any()) }
assertThat(response.statusCode).isEqualTo(NOT_FOUND)
}
@Test
fun `update type - success`() = runTest {
// given
coEvery { typeRepository.findById(any()) } answers { call ->
TypeEntity(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = "name",
)
}
coEvery { typeRepository.save(any()) } answers { call ->
call.invocation.args[0] as TypeEntity
}
coEvery { typeDescriptionRepository.findById(any()) } answers { call ->
TypeDescriptionEntity(
typeId = call.invocation.args[0] as UUID,
description = "description",
)
}
coEvery { typeDescriptionRepository.save(any()) } answers { call ->
call.invocation.args[0] as TypeDescriptionEntity
}
// when
val result = service.updateType(uuid, "new-name", "new-description")
// then
coVerify(exactly = 1) { typeRepository.findById(any()) }
coVerify(exactly = 1) { typeRepository.save(any()) }
coVerify(exactly = 1) { typeDescriptionRepository.findById(any()) }
coVerify(exactly = 1) { typeDescriptionRepository.save(any()) }
assertThat(result.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(result.timestamp).isEqualTo(timestamp)
assertThat(result.name).isEqualTo("new-name")
assertThat(result.description).isEqualTo("new-description")
}
@Test
fun `update type - success no change`() = runTest {
// given
coEvery { typeRepository.findById(any()) } answers { call ->
TypeEntity(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = "name",
)
}
coEvery { typeDescriptionRepository.findById(any()) } answers { call ->
TypeDescriptionEntity(
typeId = call.invocation.args[0] as UUID,
description = "description",
)
}
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.updateType(uuid, "name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.findById(any()) }
coVerify(exactly = 0) { typeRepository.save(any()) }
coVerify(exactly = 1) { typeDescriptionRepository.findById(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.save(any()) }
assertThat(response.statusCode).isEqualTo(ACCEPTED)
}
@Test
fun `update type - fail type dont exist`() = runTest {
// given
coEvery { typeRepository.findById(any()) } returns null
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.updateType(uuid, "name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.findById(any()) }
coVerify(exactly = 0) { typeRepository.save(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.findById(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.save(any()) }
assertThat(response.statusCode).isEqualTo(NOT_FOUND)
}
@Test
fun `update type - fail type description dont exist`() = runTest {
// given
coEvery { typeRepository.findById(any()) } answers { call ->
TypeEntity(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = "name",
)
}
coEvery { typeDescriptionRepository.findById(any()) } returns null
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.updateType(uuid, "name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.findById(any()) }
coVerify(exactly = 0) { typeRepository.save(any()) }
coVerify(exactly = 1) { typeDescriptionRepository.findById(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.save(any()) }
assertThat(response.statusCode).isEqualTo(NOT_FOUND)
}
@Test
fun `update type - fail name already exists`() = runTest {
// given
coEvery { typeRepository.findById(any()) } answers { call ->
TypeEntity(
id = call.invocation.args[0] as UUID,
timestamp = timestamp,
name = "name",
)
}
coEvery { typeRepository.save(any()) } throws DuplicateKeyException("duplicate key")
// when exception
val response: ResponseStatusException = assertFailsWith<ResponseStatusException> {
service.updateType(uuid, "taken-name", "description")
}
// then
coVerify(exactly = 1) { typeRepository.findById(any()) }
coVerify(exactly = 1) { typeRepository.save(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.findById(any()) }
coVerify(exactly = 0) { typeDescriptionRepository.save(any()) }
assertThat(response.statusCode).isEqualTo(CONFLICT)
}
}

View File

@@ -1,6 +1,7 @@
package ltd.hlaeja.util
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import java.time.LocalDateTime
@@ -8,8 +9,11 @@ import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.UUID
import kotlin.test.Test
import ltd.hlaeja.dto.TypeWithDescription
import ltd.hlaeja.entity.DeviceEntity
import ltd.hlaeja.entity.NodeEntity
import ltd.hlaeja.entity.TypeEntity
import ltd.hlaeja.jwt.service.PrivateJwtService
import ltd.hlaeja.library.deviceRegistry.Node
import ltd.hlaeja.library.deviceRegistry.Type
import ltd.hlaeja.test.isEqualToUuid
@@ -40,23 +44,82 @@ class MappingKtTest {
inner class TypeMapping {
@Test
fun `request to entity successful`() {
fun `request to type entity successful`() {
// given
val id = UUID.fromString("00000000-0000-0000-0000-000000000001")
val request = Type.Request(
"test",
"name",
"description",
)
// when
val result = request.toTypeEntity()
val entity = request.toTypeEntity(id)
// then
assertThat(result.id).isNull()
assertThat(result.timestamp.toString()).isEqualTo("2000-01-01T00:00:00.000000001Z[UTC]")
assertThat(result.name).isEqualTo("test")
assertThat(entity.id).isEqualToUuid("00000000-0000-0000-0000-000000000001")
assertThat(entity.timestamp).isEqualTo(timestamp)
assertThat(entity.name).isEqualTo("name")
}
@Test
fun `entity to response successful`() {
fun `request to type description entity successful`() {
// given
val id = UUID.fromString("00000000-0000-0000-0000-000000000001")
val request = Type.Request(
"name",
"description",
)
// when
val entity = request.toTypeDescriptionEntity(id)
// then
assertThat(entity.typeId).isEqualToUuid("00000000-0000-0000-0000-000000000001")
assertThat(entity.description).isEqualTo("description")
}
@Test
fun `type with description to response successful`() {
// given
val typeWithDescription = TypeWithDescription(
UUID.fromString("00000000-0000-0000-0000-000000000001"),
timestamp,
"name",
"description",
)
// when
val response = typeWithDescription.toTypeResponse()
// then
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000001")
assertThat(response.timestamp).isEqualTo(timestamp)
assertThat(response.name).isEqualTo("name")
assertThat(response.description).isEqualTo("description")
}
@Test
fun `type with description to response, description null successful`() {
// given
val typeWithDescription = TypeWithDescription(
UUID.fromString("00000000-0000-0000-0000-000000000001"),
timestamp,
"name",
null,
)
// when
val response = typeWithDescription.toTypeResponse()
// then
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000001")
assertThat(response.timestamp).isEqualTo(timestamp)
assertThat(response.name).isEqualTo("name")
assertThat(response.description).isEmpty()
}
@Test
fun `type entity to response successful`() {
// given
val entity = TypeEntity(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
@@ -65,20 +128,13 @@ class MappingKtTest {
)
// when
val response = entity.toTypeResponse()
val response = entity.toTypesResponse()
// then
assertThat(response.id).isEqualToUuid("00000000-0000-0000-0000-000000000000")
assertThat(response.timestamp).isEqualTo(timestamp)
assertThat(response.name).isEqualTo("name")
}
@Test
fun `entity to response exception`() {
// then exception
assertThrows(ResponseStatusException::class.java) {
TypeEntity(null, timestamp, "name").toTypeResponse()
}
}
}
@Nested
@@ -98,7 +154,7 @@ class MappingKtTest {
// then
assertThat(result.id).isNull()
assertThat(result.timestamp.toString()).isEqualTo("2000-01-01T00:00:00.000000001Z[UTC]")
assertThat(result.timestamp).isEqualTo(timestamp)
assertThat(result.client.toString()).isEqualTo("00000000-0000-0000-0000-000000000001")
assertThat(result.device.toString()).isEqualTo("00000000-0000-0000-0000-000000000002")
assertThat(result.name).isEqualTo("test")
@@ -189,4 +245,48 @@ class MappingKtTest {
assertThat(exception.message).isEqualTo("417 EXPECTATION_FAILED")
}
}
@Nested
inner class DeviceMapping {
val jwtService: PrivateJwtService = mockk()
@Test
fun `entity to identity response successful`() {
// given
val entity = DeviceEntity(
UUID.fromString("00000000-0000-0000-0000-000000000001"),
timestamp,
UUID.fromString("00000000-0000-0000-0000-000000000002"),
)
every { jwtService.sign(any()) } returns "header.payload.signature"
// when
val result = entity.toDeviceResponse(jwtService)
// then
assertThat(result.id).isEqualToUuid("00000000-0000-0000-0000-000000000001")
assertThat(result.type).isEqualToUuid("00000000-0000-0000-0000-000000000002")
assertThat(result.identity).isEqualTo("header.payload.signature")
}
@Test
fun `entity to identity response exception`() {
// given
val entity = DeviceEntity(
null,
timestamp,
UUID.fromString("00000000-0000-0000-0000-000000000002"),
)
// then exception
val exception = assertThrows(ResponseStatusException::class.java) {
entity.toDeviceResponse(jwtService)
}
// then
assertThat(exception.message).isEqualTo("417 EXPECTATION_FAILED")
}
}
}