diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt new file mode 100644 index 0000000..ee0ad0a --- /dev/null +++ b/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt @@ -0,0 +1,26 @@ +package com.github.dannecron.demo.http.controllers + +import com.github.dannecron.demo.http.responses.neko.ImagesResponse +import com.github.dannecron.demo.services.neko.Client +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping(value = ["/api/neko"], produces = [MediaType.APPLICATION_JSON_VALUE]) +class NekoController( + private val nekoClient: Client, +) { + @GetMapping("/categories") + fun categories(): ResponseEntity = ResponseEntity(nekoClient.getCategories(), HttpStatus.OK) + + @GetMapping("/images/{category}") + fun images( + @PathVariable category: String, + @RequestParam imagesCount: Int = 1, + ): ResponseEntity = ResponseEntity( + ImagesResponse(baseImages = nekoClient.getImages(category = category, amount = imagesCount)), + HttpStatus.OK, + ) +} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt b/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt index 3a90dde..868d7ba 100644 --- a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt +++ b/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt @@ -1,6 +1,7 @@ package com.github.dannecron.demo.http.exceptions import com.github.dannecron.demo.http.responses.BadRequestResponse +import com.github.dannecron.demo.http.responses.BaseResponse import com.github.dannecron.demo.http.responses.NotFoundResponse import com.github.dannecron.demo.http.responses.UnprocessableResponse import org.springframework.http.HttpStatus @@ -42,4 +43,12 @@ class ExceptionHandler { UnprocessableResponse(exception.javaClass.name, exception.allErrors), HttpStatus.UNPROCESSABLE_ENTITY, ) + + // 500 + @ExceptionHandler(RuntimeException::class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + fun handleUnexpectedRuntimeException(exception: RuntimeException): ResponseEntity = ResponseEntity( + BaseResponse(com.github.dannecron.demo.http.responses.ResponseStatus.INTERNAL_ERROR), + HttpStatus.INTERNAL_SERVER_ERROR, + ) } diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/ResponseStatus.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/ResponseStatus.kt index 031ec28..46942f2 100644 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/ResponseStatus.kt +++ b/src/main/kotlin/com/github/dannecron/demo/http/responses/ResponseStatus.kt @@ -6,5 +6,6 @@ enum class ResponseStatus(@JsonValue val status: String) { OK("ok"), NOT_FOUND("not found"), BAD_REQUEST("bad request"), - UNPROCESSABLE("unprocessable"); + UNPROCESSABLE("unprocessable"), + INTERNAL_ERROR("internal"); } diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/Image.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/Image.kt new file mode 100644 index 0000000..2897ce7 --- /dev/null +++ b/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/Image.kt @@ -0,0 +1,14 @@ +package com.github.dannecron.demo.http.responses.neko + +import kotlinx.serialization.Serializable + +@Serializable +data class Image( + val url: String, + val animeName: String?, + val artistHref: String?, + val artistName: String?, + val sourceUrl: String?, +) { + fun isGif() = animeName != null +} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt new file mode 100644 index 0000000..c5e4705 --- /dev/null +++ b/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt @@ -0,0 +1,15 @@ +package com.github.dannecron.demo.http.responses.neko + +import kotlinx.serialization.Serializable +import com.github.dannecron.demo.services.neko.dto.ImagesResponse as BaseResponse + +@Serializable +data class ImagesResponse( + val images: List, +) { + constructor(baseImages: BaseResponse): this( + images = baseImages.results.map { + Image(it.url, it.animeName, it.artistHref, it.artistName, it.sourceUrl) + } + ) +} diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt b/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt new file mode 100644 index 0000000..e22eb82 --- /dev/null +++ b/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt @@ -0,0 +1,53 @@ +package com.github.dannecron.demo.http.controllers + +import com.github.dannecron.demo.BaseUnitTest +import com.github.dannecron.demo.services.neko.Client +import com.github.dannecron.demo.services.neko.dto.Image +import com.github.dannecron.demo.services.neko.dto.ImagesResponse +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import kotlin.test.Test + +@WebMvcTest(NekoController::class) +class NekoControllerTest( + @Autowired val mockMvc: MockMvc, +): BaseUnitTest() { + @MockBean + private lateinit var nekoClient: Client + + @Test + fun categories_success() { + whenever(nekoClient.getCategories()) doReturn setOf("cat1", "cat2") + + mockMvc.get("/api/neko/categories") + .andExpect { status { isOk() } } + .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } + .andExpect { content { string("""["cat1","cat2"]""") } } + } + + @Test + fun images_success() { + val category = "some" + val animeName = "boku no pico" + whenever(nekoClient.getImages(category = category, amount = 1)) doReturn ImagesResponse( + results = listOf( + Image( + "http://localhost", + animeName = animeName, + ) + ) + ) + + mockMvc.get("/api/neko/images/$category") + .andExpect { status { isOk() } } + .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } + .andExpect { jsonPath("\$.images[0].animeName") { value(animeName) } } + .andExpect { jsonPath("\$.images[0].artistName") { value(null) } } + } +}