Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates update logic to require animation id in url. #8

Merged
merged 1 commit into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions matrix-animator-api.http
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ GET http://localhost:8080/rest/animations/{{id}}
GET http://localhost:8080/rest/animations

### Update existing animation
PUT http://localhost:8080/rest/animations
< {%
request.variables.set("id", "999")
%}
PUT http://localhost:8080/rest/animations/{{id}}
Content-Type: application/json

{
"title": "Updated animation",
"frames": [],
"id": 34,
"height": 1,
"width": 2,
"speed": 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.lightinspiration.matrixanimatorapi.controllers

import com.lightinspiration.matrixanimatorapi.domain.Animation
import com.lightinspiration.matrixanimatorapi.domain.AnimationMeta
import com.lightinspiration.matrixanimatorapi.repositories.NoRecordToUpdateException
import com.lightinspiration.matrixanimatorapi.services.AnimationService
import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY
Expand Down Expand Up @@ -29,12 +30,13 @@ class AnimationController(
return animationService.saveAnimation(animation)
}

@PutMapping
fun updateAnimation(@RequestBody animation: Animation) {
return if (animation.id != null)
animationService.updateAnimation(animation.id, animation)
else
@PutMapping("/{id}")
fun updateAnimation(@PathVariable("id") animationId: Int, @RequestBody animation: Animation): Int {
try {
return animationService.updateAnimation(animationId, animation)
} catch (exception: NoRecordToUpdateException) {
throw ResponseStatusException(UNPROCESSABLE_ENTITY, "We can't update an animation without it's ID.")
}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ class AnimationRepository(
return keyholder.keys?.get("id") as Int
}

fun updateAnimation(id: Int, animation: Animation) {
template.update(
fun updateAnimation(id: Int, animation: Animation): Int {
val keyHolder = GeneratedKeyHolder()
val affected = template.update(
"""
UPDATE matrix_animator.animations
SET
Expand All @@ -57,8 +58,13 @@ class AnimationRepository(
(:title, :userId, :height, :width, :speed, :frames::jsonb)
WHERE id = :id
""",
mapAnimationToParameters(animation).apply { addValue("id", id) }
mapAnimationToParameters(animation).apply { addValue("id", id) },
keyHolder
)
if (affected == 0) {
throw NoRecordToUpdateException("No records were affected by the update.")
}
return keyHolder.keys?.get("id") as Int
}

fun deleteAnimation(id: Int): Int {
Expand Down Expand Up @@ -102,3 +108,6 @@ class AnimationRepository(
"""
}
}

class NoRecordToUpdateException(message: String?) : Exception(message)

Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class AnimationService(
.map { it.metadata() }
}

fun updateAnimation(id: Int, animation: Animation) {
animationRepository.updateAnimation(id, animation)
fun updateAnimation(id: Int, animation: Animation): Int {
return animationRepository.updateAnimation(id, animation)
}

fun deleteAnimation(id: Int) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ spring:
schmitz-sandbox:
# ! Note that the host is `database` which is the name of the database service running
# ! postgres in docker-compose.yml
jdbc-url: jdbc:postgresql://localhost:5433/postgres
# jdbc-url: jdbc:postgresql://database:5432/postgres
# jdbc-url: jdbc:postgresql://localhost:5433/postgres
jdbc-url: jdbc:postgresql://database:5432/postgres
username: postgres
password: password # TODO: figure out how to pull this out to .env and inject it on launch. gradle task??
driver-class-name: org.postgresql.Driver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import com.lightinspiration.matrixanimatorapi.domain.AnimationMeta
import com.lightinspiration.matrixanimatorapi.domain.Frame
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.fail
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.jdbc.support.GeneratedKeyHolder
import org.springframework.test.context.ActiveProfiles
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException

@SpringBootTest
@ActiveProfiles("integration-test")
Expand Down Expand Up @@ -96,9 +99,30 @@ class AnimationsControllerIntegrationTest {
id
)

animationController.updateAnimation(updatedAnimation)
val actual = animationController.updateAnimation(id, updatedAnimation)

assertEquals(updatedAnimation, getAnimationsFromDatabase().first())
assertEquals(id, actual)
}

@Test
@Transactional
fun `updateAnimation - if the animation record doesn't exist, expect a 422 http exception`() {
val updatedAnimation = Animation(
"New title",
2,
3,
4,
5,
listOf(Frame(0, listOf(0xFFFFFF))),
999
)

val exception = assertThrows<ResponseStatusException> {
animationController.updateAnimation(999, updatedAnimation)
}

assertEquals(exception.statusCode, UNPROCESSABLE_ENTITY)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,19 @@ class AnimationRepositoryTest {
val id = insertAnimationRecord(animation)
val updatedAnimation = Animation("New title", 2, 3, 4, 5, listOf(Frame(0, listOf(0xFFFFFF))))

animationRepository.updateAnimation(id, updatedAnimation)
val actual = animationRepository.updateAnimation(id, updatedAnimation)

assertEquals(updatedAnimation.copy(id = id), getAnimationRecord(id))
assertEquals(id, actual)
}

@Test
@Transactional
fun `updateAnimation - if record can't be updated - expect exception`() {
val updatedAnimation = Animation("New title", 2, 3, 4, 5, listOf(Frame(0, listOf(0xFFFFFF))))

assertThrows<NoRecordToUpdateException> {
animationRepository.updateAnimation(1, updatedAnimation)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ class AnimationServiceTest {
fun `updateAnimation - can update an animation`() {
val id = 10
val animation = buildAnimation(id)
whenever(animationRepository.updateAnimation(id, animation)).thenReturn(id)

animationService.updateAnimation(id, animation)
val actual = animationService.updateAnimation(id, animation)

verify(animationRepository).updateAnimation(id, animation)
assertEquals(id, actual)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.lightinspiration.matrixanimatorapi.domain.Frame
import com.lightinspiration.matrixanimatorapi.services.AnimationService
import org.junit.jupiter.api.Test
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
Expand Down Expand Up @@ -93,31 +92,42 @@ class AnimationsWebLayerTest {

@Test
fun `updateAnimation - can update an animation`() {
val animation = buildAnimation("anAnimation", 10)
val animationId = 10
val animation = buildAnimation("anAnimation", animationId)
whenever(animationService.updateAnimation(animation.id!!, animation))
.thenReturn(animationId)
val request = MockMvcRequestBuilders
.put("/rest/animations")
.put("/rest/animations/${animation.id}")
.content(animation.toJson())
.contentType(APPLICATION_JSON)

mockMvc.perform(request)
.andExpect(status().isOk)

verify(animationService).updateAnimation(animation.id!!, animation)
.andExpect(content().string(animation.id.toString()))
}

@Test
fun `updateAnimation - if an animation does not have an id - expect a 422`() {
val animation = buildAnimation("anAnimation")
val request = MockMvcRequestBuilders
.put("/rest/animations")
.content(animation.toJson())
.contentType(APPLICATION_JSON)

mockMvc.perform(request)
.andExpect(status().isUnprocessableEntity)

verifyNoMoreInteractions(animationService)
}
//@Test
//fun `updateAnimation - if there isn't an animation record to update - expect a 422`() {
// val badId = 9999
// val animation = buildAnimation("anAnimation")
// TODO ask andrew about this
// * this is the exception that I want to throw, but it's really being thrown at the repository level, which I can't get to from here
// * because I'm mocking at the service level. I figured that I could still throw from the mock here to at least assert the conversion from
// * the internal exception to the http status exception, but when I do I get an error that the service isn't supposed to raise that checked
// * exception.
// * at the moment I'm going to put the check in the integration test, which maybe it belongs in anyway, but I figured this would be a good
// * place to have all of the http-y stuff. What's his opinion on approach??
// whenever(animationService.updateAnimation(badId, animation)).thenThrow(NoRecordToUpdateException("can't do it"))
// val request = MockMvcRequestBuilders
// .put("/rest/animations/$badId")
// .content(animation.toJson())
// .contentType(APPLICATION_JSON)
//
// mockMvc.perform(request)
// .andExpect(status().isUnprocessableEntity)
//
// verifyNoMoreInteractions(animationService)
//}


@Test
Expand Down