diff --git a/sql/001-nodes.sql b/sql/001-nodes.sql new file mode 100644 index 0000000..dac8ae6 --- /dev/null +++ b/sql/001-nodes.sql @@ -0,0 +1,5 @@ +-- make device index unique + +DROP INDEX IF EXISTS public.i_nodes_type; + +CREATE UNIQUE INDEX IF NOT EXISTS i_nodes_device ON public.nodes (device); diff --git a/sql/initial/004-nodes.sql b/sql/initial/004-nodes.sql index 511926f..35724a1 100644 --- a/sql/initial/004-nodes.sql +++ b/sql/initial/004-nodes.sql @@ -19,7 +19,7 @@ ALTER TABLE IF EXISTS public.nodes -- Index: public.i_nodes_type -- DROP INDEX IF EXISTS public.i_nodes_type; -CREATE INDEX IF NOT EXISTS i_nodes_device ON public.nodes (device); +CREATE UNIQUE INDEX IF NOT EXISTS i_nodes_device ON public.nodes (device); -- Revoke all permissions from existing roles diff --git a/src/integration-test/kotlin/ltd/hlaeja/controller/NodeEndpoint.kt b/src/integration-test/kotlin/ltd/hlaeja/controller/NodeEndpoint.kt index 79da16b..e40dfff 100644 --- a/src/integration-test/kotlin/ltd/hlaeja/controller/NodeEndpoint.kt +++ b/src/integration-test/kotlin/ltd/hlaeja/controller/NodeEndpoint.kt @@ -9,6 +9,8 @@ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.boot.test.web.server.LocalServerPort @@ -46,7 +48,7 @@ class NodeEndpoint { // then result.expectStatus() - .isOk + .isCreated .expectBody() .consumeWith { softly.assertThat(it.responseBody?.id?.version()).isEqualTo(7) @@ -55,4 +57,25 @@ class NodeEndpoint { softly.assertThat(it.responseBody?.name).isEqualTo(name) } } + + @ParameterizedTest + @CsvSource( + // not a device + "'00000000-0000-0000-0002-000000000000'", + // already a node + "'00000000-0000-0000-0002-000000000002'", + ) + fun `added node - fail`(device: String) { + // given + val name = "Node 5" + val client = UUID.fromString("00000000-0000-0000-0000-000000000000") + val request = Node.Request(device = UUID.fromString(device), client = client, name = name) + + // when + val result = webClient.post().uri("/node").bodyValue(request).exchange() + + // then + result.expectStatus() + .isBadRequest + } } diff --git a/src/integration-test/resources/postgres/schema.sql b/src/integration-test/resources/postgres/schema.sql index 122ae3e..f83239a 100644 --- a/src/integration-test/resources/postgres/schema.sql +++ b/src/integration-test/resources/postgres/schema.sql @@ -61,4 +61,4 @@ CREATE TABLE IF NOT EXISTS public.nodes -- Index: public.i_nodes_type -CREATE INDEX IF NOT EXISTS i_nodes_device ON public.nodes (device); +CREATE UNIQUE INDEX IF NOT EXISTS i_nodes_device ON public.nodes (device); diff --git a/src/main/kotlin/ltd/hlaeja/controller/NodeController.kt b/src/main/kotlin/ltd/hlaeja/controller/NodeController.kt index 0f1a705..f637c61 100644 --- a/src/main/kotlin/ltd/hlaeja/controller/NodeController.kt +++ b/src/main/kotlin/ltd/hlaeja/controller/NodeController.kt @@ -4,8 +4,10 @@ import ltd.hlaeja.library.deviceRegistry.Node import ltd.hlaeja.service.NodeService import ltd.hlaeja.util.toEntity import ltd.hlaeja.util.toNodeResponse +import org.springframework.http.HttpStatus.CREATED import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController @RestController @@ -14,6 +16,7 @@ class NodeController( ) { @PostMapping("/node") + @ResponseStatus(CREATED) suspend fun addNode( @RequestBody request: Node.Request, ): Node.Response = nodeService.addNode(request.toEntity()).toNodeResponse() diff --git a/src/main/kotlin/ltd/hlaeja/service/NodeService.kt b/src/main/kotlin/ltd/hlaeja/service/NodeService.kt index a8f123f..d84da2b 100644 --- a/src/main/kotlin/ltd/hlaeja/service/NodeService.kt +++ b/src/main/kotlin/ltd/hlaeja/service/NodeService.kt @@ -4,6 +4,8 @@ import io.github.oshai.kotlinlogging.KotlinLogging import java.util.UUID import ltd.hlaeja.entity.NodeEntity import ltd.hlaeja.repository.NodeRepository +import org.springframework.dao.DataIntegrityViolationException +import org.springframework.http.HttpStatus.BAD_REQUEST import org.springframework.http.HttpStatus.NOT_FOUND import org.springframework.stereotype.Service import org.springframework.web.server.ResponseStatusException @@ -17,8 +19,11 @@ class NodeService( suspend fun addNode( node: NodeEntity, - ): NodeEntity = nodeRepository.save(node) - .also { log.debug { "Added node ${it.id}" } } + ): NodeEntity = try { + nodeRepository.save(node).also { log.debug { "Added node ${it.id}" } } + } catch (exception: DataIntegrityViolationException) { + throw ResponseStatusException(BAD_REQUEST, null, exception) + } suspend fun getNodeFromDevice( device: UUID,