diff --git a/pom.xml b/pom.xml index 9a8d9a8..66c7954 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,12 @@ kotlin-test-junit5 test + + org.mockito.kotlin + mockito-kotlin + 5.4.0 + test + org.postgresql postgresql diff --git a/src/main/kotlin/org/meliapp/backend/controller/MeliSearchController.kt b/src/main/kotlin/org/meliapp/backend/controller/MeliSearchController.kt index 70831cf..25aba8b 100644 --- a/src/main/kotlin/org/meliapp/backend/controller/MeliSearchController.kt +++ b/src/main/kotlin/org/meliapp/backend/controller/MeliSearchController.kt @@ -2,7 +2,7 @@ package org.meliapp.backend.controller import org.meliapp.backend.dto.ApiResponse import org.meliapp.backend.dto.meli.MeliSearchResponse -import org.meliapp.backend.dto.product.ProductResponse +import org.meliapp.backend.dto.product.ProductDetailsResponse import org.meliapp.backend.service.MeliSearchService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -17,7 +17,7 @@ class MeliSearchController(private val meliSearchService: MeliSearchService) { } @GetMapping("/{id}") - fun findById(@PathVariable id: String): ResponseEntity> { + fun findById(@PathVariable id: String): ResponseEntity> { return ResponseEntity.ok(ApiResponse(meliSearchService.findById(id))) } diff --git a/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt b/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt new file mode 100644 index 0000000..2e8270e --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/controller/PurchaseController.kt @@ -0,0 +1,26 @@ +package org.meliapp.backend.controller + +import org.meliapp.backend.dto.ApiResponse +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.dto.purchase.PurchaseResponse +import org.meliapp.backend.service.PurchaseService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/purchases") +class PurchaseController( + private val purchaseService: PurchaseService +) { + + @PostMapping + fun buy(@RequestBody purchaseRequest: PurchaseRequest): ResponseEntity> = + ResponseEntity.ok(ApiResponse(purchaseService.buy(purchaseRequest))) + + + @GetMapping + fun purchases(): ResponseEntity>> = + ResponseEntity.ok(ApiResponse(purchaseService.purchases())) + + +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/dto/meli/MeliSearchResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/meli/MeliSearchResponse.kt index c9a4a67..0f636ea 100644 --- a/src/main/kotlin/org/meliapp/backend/dto/meli/MeliSearchResponse.kt +++ b/src/main/kotlin/org/meliapp/backend/dto/meli/MeliSearchResponse.kt @@ -2,10 +2,10 @@ package org.meliapp.backend.dto.meli import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import org.meliapp.backend.dto.product.ProductResponse +import org.meliapp.backend.dto.product.ProductListResponse data class MeliSearchResponse @JsonCreator constructor( - val results: List = emptyList(), + val results: List = emptyList(), val filters: List = emptyList(), @JsonProperty(value = "available_filters") val availableFilters: List = emptyList(), diff --git a/src/main/kotlin/org/meliapp/backend/dto/product/ProductDetailsResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/product/ProductDetailsResponse.kt new file mode 100644 index 0000000..beddeaf --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/product/ProductDetailsResponse.kt @@ -0,0 +1,25 @@ +package org.meliapp.backend.dto.product + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import java.math.BigDecimal + +data class ProductDetailsResponse( + @JsonProperty("id") + val meliId: String, + val title: String, + val price: BigDecimal, + val thumbnail: String, + val pictures: List, + var description: String?, + + ) + +data class DescriptionResponse( + @JsonProperty("plain_text") + val description: String +) + +data class PicturesListResponse @JsonCreator constructor( + val url: String, +) \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/dto/product/ProductResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/product/ProductListResponse.kt similarity index 89% rename from src/main/kotlin/org/meliapp/backend/dto/product/ProductResponse.kt rename to src/main/kotlin/org/meliapp/backend/dto/product/ProductListResponse.kt index 8a1acd0..8771e03 100644 --- a/src/main/kotlin/org/meliapp/backend/dto/product/ProductResponse.kt +++ b/src/main/kotlin/org/meliapp/backend/dto/product/ProductListResponse.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty import java.math.BigDecimal -data class ProductResponse @JsonCreator constructor( +data class ProductListResponse @JsonCreator constructor( @JsonProperty("id") val id: String = "", @JsonProperty("title") diff --git a/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt new file mode 100644 index 0000000..cad96e4 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/product/ProductPurchaseResponse.kt @@ -0,0 +1,12 @@ +package org.meliapp.backend.dto.product + +import com.fasterxml.jackson.annotation.JsonProperty +import java.math.BigDecimal + +data class ProductPurchaseResponse( + @JsonProperty("meli_id") + val meliId: String, + val title: String, + val thumbnail: String, + val price: BigDecimal, +) diff --git a/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt new file mode 100644 index 0000000..bb55ea1 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseRequest.kt @@ -0,0 +1,9 @@ +package org.meliapp.backend.dto.purchase + +import com.fasterxml.jackson.annotation.JsonProperty + +data class PurchaseRequest( + @JsonProperty(value = "meli_id") + val meliId: String, + val quantity: Int +) diff --git a/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt new file mode 100644 index 0000000..5875764 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/dto/purchase/PurchaseResponse.kt @@ -0,0 +1,15 @@ +package org.meliapp.backend.dto.purchase + +import com.fasterxml.jackson.annotation.JsonProperty +import org.meliapp.backend.dto.product.ProductPurchaseResponse +import java.math.BigDecimal +import java.time.LocalDateTime + +data class PurchaseResponse( + val id: Long, + val quantity: Int, + val product: ProductPurchaseResponse, + @JsonProperty("created_at") + val createdAt: LocalDateTime, + val total: BigDecimal, +) diff --git a/src/main/kotlin/org/meliapp/backend/model/Purchase.kt b/src/main/kotlin/org/meliapp/backend/model/Purchase.kt new file mode 100644 index 0000000..f121ba6 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/model/Purchase.kt @@ -0,0 +1,21 @@ +package org.meliapp.backend.model + +import jakarta.persistence.* +import org.hibernate.annotations.CreationTimestamp +import java.math.BigDecimal +import java.time.LocalDateTime + +@Entity +class Purchase { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long = 0 + @ManyToOne + lateinit var user: User + @ManyToOne + lateinit var product: Product + var quantity: Int = 0 + @CreationTimestamp + var purchaseDate: LocalDateTime = LocalDateTime.now() + var totalPrice: BigDecimal = BigDecimal.ZERO +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt b/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt new file mode 100644 index 0000000..1fe2399 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/repository/PurchaseRepository.kt @@ -0,0 +1,11 @@ +package org.meliapp.backend.repository + +import org.meliapp.backend.model.Purchase +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param + +interface PurchaseRepository : JpaRepository { + @Query("SELECT p FROM Purchase p WHERE p.user.id = :id") + fun findByUserId(@Param("id") id: Long): List +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/service/BookmarkService.kt b/src/main/kotlin/org/meliapp/backend/service/BookmarkService.kt index 149a15d..c0b5f81 100644 --- a/src/main/kotlin/org/meliapp/backend/service/BookmarkService.kt +++ b/src/main/kotlin/org/meliapp/backend/service/BookmarkService.kt @@ -6,17 +6,14 @@ import org.meliapp.backend.dto.bookmark.BookmarkRequestBody import org.meliapp.backend.dto.bookmark.BookmarkSummary import org.meliapp.backend.exception.apc.BookmarkNotFoundException import org.meliapp.backend.model.Bookmark -import org.meliapp.backend.model.Product import org.meliapp.backend.repository.BookmarkRepository -import org.meliapp.backend.repository.ProductRepository import org.springframework.stereotype.Service @Service class BookmarkService( - private val meliSearchService: MeliSearchService, private val authService: AuthService, private val bookmarkRepository: BookmarkRepository, - private val productRepository: ProductRepository, + private val productService: ProductService, ) { fun getUserBookmarks(): List { @@ -47,18 +44,8 @@ class BookmarkService( @Transactional fun bookmarkProduct(request: BookmarkRequestBody): BookmarkDetails { val currentUser = authService.getUserAuthenticated() - val productResponse = meliSearchService.findById(request.meliId) - - val savedProduct = productRepository - .findByMeliId(productResponse.id) - .orElseGet { - productRepository.save(Product().apply { - meliId = productResponse.id - title = productResponse.title - thumbnail = productResponse.thumbnail - price = productResponse.price - }) - } + + val savedProduct = productService.findByMeliId(request.meliId) val bookmark = Bookmark().apply { product = savedProduct diff --git a/src/main/kotlin/org/meliapp/backend/service/MeliSearchService.kt b/src/main/kotlin/org/meliapp/backend/service/MeliSearchService.kt index 27ed1eb..e84966f 100644 --- a/src/main/kotlin/org/meliapp/backend/service/MeliSearchService.kt +++ b/src/main/kotlin/org/meliapp/backend/service/MeliSearchService.kt @@ -1,7 +1,8 @@ package org.meliapp.backend.service import org.meliapp.backend.dto.meli.MeliSearchResponse -import org.meliapp.backend.dto.product.ProductResponse +import org.meliapp.backend.dto.product.DescriptionResponse +import org.meliapp.backend.dto.product.ProductDetailsResponse import org.meliapp.backend.exception.apc.ProductNotFoundException import org.springframework.beans.factory.annotation.Value import org.springframework.core.ParameterizedTypeReference @@ -36,7 +37,7 @@ class MeliSearchService( } - fun findById(id: String): ProductResponse { + fun findById(id: String): ProductDetailsResponse { val queryString = "/items/${id}" return restClient @@ -45,7 +46,16 @@ class MeliSearchService( .accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus({ it.is4xxClientError }) { _, _ -> throw ProductNotFoundException(id) } - .body(object : ParameterizedTypeReference() {})!! + .body(object : ParameterizedTypeReference() {})!! + .also { + it.description = restClient + .get() + .uri("$queryString/description") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .body(object : ParameterizedTypeReference() {}) + ?.description + } } diff --git a/src/main/kotlin/org/meliapp/backend/service/ProductService.kt b/src/main/kotlin/org/meliapp/backend/service/ProductService.kt new file mode 100644 index 0000000..e91c7f2 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/service/ProductService.kt @@ -0,0 +1,32 @@ +package org.meliapp.backend.service + +import org.meliapp.backend.model.Product +import org.meliapp.backend.repository.ProductRepository +import org.springframework.stereotype.Service +import java.util.* + +@Service +class ProductService( + private val productRepository: ProductRepository, + private val meliSearchService: MeliSearchService +) { + + fun findByMeliId(id: String): Product { + return productRepository.findByMeliId(id) + .orElseGet { + meliSearchService.findById(id).let { + productRepository.save(Product().apply { + title = it.title + price = it.price + thumbnail = it.thumbnail + meliId = id + }) + } + } + } + + fun findByMeliIdOld(meliId: String): Optional { + return productRepository.findByMeliId(meliId) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt b/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt new file mode 100644 index 0000000..4f31a21 --- /dev/null +++ b/src/main/kotlin/org/meliapp/backend/service/PurchaseService.kt @@ -0,0 +1,54 @@ +package org.meliapp.backend.service + +import org.meliapp.backend.dto.product.ProductPurchaseResponse +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.dto.purchase.PurchaseResponse +import org.meliapp.backend.model.Product +import org.meliapp.backend.model.Purchase +import org.meliapp.backend.repository.PurchaseRepository +import org.springframework.stereotype.Service +import java.math.BigDecimal + +@Service +class PurchaseService( + private val purchaseRepository: PurchaseRepository, + private val authService: AuthService, + private val productService: ProductService +) { + + fun buy(purchaseRequest: PurchaseRequest): PurchaseResponse = + toPurchaseResponse( + purchaseRepository.save( + Purchase().apply { + this.user = authService.getUserAuthenticated() + this.product = productService.findByMeliId(purchaseRequest.meliId) + quantity = purchaseRequest.quantity + totalPrice = product.price * BigDecimal(quantity) + } + ) + ) + + fun purchases(): List = + purchaseRepository + .findByUserId(authService.getUserAuthenticated().id) + .map { toPurchaseResponse(it) } + + private fun toPurchaseResponse(purchase: Purchase): PurchaseResponse = + PurchaseResponse( + purchase.id, + purchase.quantity, + toProductPurchaseResponse(purchase.product), + purchase.purchaseDate, + purchase.totalPrice + ) + + private fun toProductPurchaseResponse(product: Product): ProductPurchaseResponse = + ProductPurchaseResponse( + product.meliId, + product.title, + product.thumbnail, + product.price + ) + + +} \ No newline at end of file diff --git a/src/test/kotlin/org/meliapp/backend/service/BookmarkServiceUnitTest.kt b/src/test/kotlin/org/meliapp/backend/service/BookmarkServiceUnitTest.kt index 3d4a8c2..19cc35a 100644 --- a/src/test/kotlin/org/meliapp/backend/service/BookmarkServiceUnitTest.kt +++ b/src/test/kotlin/org/meliapp/backend/service/BookmarkServiceUnitTest.kt @@ -6,30 +6,24 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import org.meliapp.backend.dto.bookmark.BookmarkRequestBody -import org.meliapp.backend.dto.product.ProductResponse import org.meliapp.backend.exception.apc.BookmarkNotFoundException import org.meliapp.backend.exception.apc.ProductNotFoundException import org.meliapp.backend.model.Bookmark import org.meliapp.backend.model.Product import org.meliapp.backend.model.User import org.meliapp.backend.repository.BookmarkRepository -import org.meliapp.backend.repository.ProductRepository -import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyString import org.mockito.InjectMocks import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.kotlin.* import org.springframework.test.context.junit.jupiter.SpringExtension -import java.math.BigDecimal import java.util.* -import org.mockito.Mockito.`when` as whenever @ExtendWith(SpringExtension::class) class BookmarkServiceUnitTest { - @Mock - private lateinit var meliSearchService: MeliSearchService - @Mock private lateinit var authService: AuthService @@ -37,7 +31,7 @@ class BookmarkServiceUnitTest { private lateinit var bookmarkRepository: BookmarkRepository @Mock - private lateinit var productRepository: ProductRepository + private lateinit var productService: ProductService @InjectMocks private lateinit var bookmarkService: BookmarkService @@ -47,24 +41,15 @@ class BookmarkServiceUnitTest { // Arrange val bookmarkRequest = BookmarkRequestBody("meli_id", 5, "comment") - val productResponse = ProductResponse( - id = bookmarkRequest.meliId, - price = BigDecimal(1), - title = "product_title", - availableQuantity = 1, - thumbnail = "product_thumbnail", - ) - - val mockUser = mock(User::class.java) - val mockProduct = mock(Product::class.java) + val mockUser: User = mock() + val mockProduct: Product = mock() whenever(mockUser.id).thenReturn(1L) whenever(mockProduct.meliId).thenReturn("meli_id") whenever(mockProduct.title).thenReturn("product_title") whenever(mockProduct.thumbnail).thenReturn("product_thumbnail") - whenever(meliSearchService.findById(bookmarkRequest.meliId)).thenReturn(productResponse) - whenever(productRepository.findByMeliId(bookmarkRequest.meliId)).thenReturn(Optional.of(mockProduct)) + whenever(productService.findByMeliId(bookmarkRequest.meliId)).thenReturn(mockProduct) whenever(authService.getUserAuthenticated()).thenReturn(mockUser) // Act @@ -77,52 +62,18 @@ class BookmarkServiceUnitTest { // Verify verify(bookmarkRepository, times(1)).save(any()) - verify(productRepository, times(0)).save(any()) - - } - - @Test - fun `should not save an already persisted product`() { - // Arrange - val bookmarkRequest = BookmarkRequestBody("meli_id", 5, "comment") - - val productResponse = ProductResponse( - id = bookmarkRequest.meliId, - price = BigDecimal(1), - title = "product_title", - availableQuantity = 1, - thumbnail = "product_thumbnail", - ) - - val mockUser = mock(User::class.java) - val mockProduct = mock(Product::class.java) - whenever(mockUser.id).thenReturn(1L) - whenever(mockProduct.meliId).thenReturn("meli_id") - whenever(mockProduct.title).thenReturn("product_title") - whenever(mockProduct.thumbnail).thenReturn("product_thumbnail") - - whenever(meliSearchService.findById(bookmarkRequest.meliId)).thenReturn(productResponse) - whenever(productRepository.findByMeliId(bookmarkRequest.meliId)).thenReturn(Optional.empty()) - whenever(productRepository.save(any())).thenReturn(mockProduct) - whenever(authService.getUserAuthenticated()).thenReturn(mockUser) - - // Act - bookmarkService.bookmarkProduct(bookmarkRequest) - - // Verify - verify(productRepository, times(1)).save(any()) } @Test fun `should throw exception when bookmarking with wrong meli id`() { // Arrange val bookmarkRequest = BookmarkRequestBody("meli_id", 5, "comment") - val mockUser = mock(User::class.java) + val mockUser: User = mock() whenever(mockUser.id).thenReturn(1L) - whenever(meliSearchService.findById(bookmarkRequest.meliId)).thenThrow(ProductNotFoundException::class.java) whenever(authService.getUserAuthenticated()).thenReturn(mockUser) + whenever(productService.findByMeliId(anyString())).thenThrow(ProductNotFoundException::class.java) // Act and Assert assertThrows { bookmarkService.bookmarkProduct(bookmarkRequest) } @@ -131,7 +82,7 @@ class BookmarkServiceUnitTest { @Test fun `should return a list of bookmarks from user`() { // Arrange - val mockUser = mock(User::class.java) + val mockUser: User = mock() whenever(mockUser.id).thenReturn(1L) whenever(authService.getUserAuthenticated()).thenReturn(mockUser) @@ -147,8 +98,8 @@ class BookmarkServiceUnitTest { // Arrange val bookmarkId = 1L val userId = 1L - val bookmark = mock(Bookmark::class.java) - val user = mock(User::class.java) + val bookmark: Bookmark = mock() + val user: User = mock() whenever(user.id).thenReturn(userId) whenever(authService.getUserAuthenticated()).thenReturn(user) @@ -167,7 +118,7 @@ class BookmarkServiceUnitTest { // Arrange val bookmarkId = 1L val userId = 1L - val user = mock(User::class.java) + val user: User = mock() whenever(bookmarkRepository.findByIdAndUserId(userId, bookmarkId)).thenReturn(Optional.empty()) whenever(user.id).thenReturn(userId) @@ -181,8 +132,8 @@ class BookmarkServiceUnitTest { fun `should edit bookmark`() { // Arrange val request = BookmarkRequestBody("meli_id", 4, "updated comment") - val mockUser = mock(User::class.java) - val mockProduct = mock(Product::class.java) + val mockUser: User = mock() + val mockProduct: Product = mock() val bookmark = Bookmark().apply { id = 1L diff --git a/src/test/kotlin/org/meliapp/backend/service/MeliSearchServiceUnitTest.kt b/src/test/kotlin/org/meliapp/backend/service/MeliSearchServiceUnitTest.kt index 1426029..2f131be 100644 --- a/src/test/kotlin/org/meliapp/backend/service/MeliSearchServiceUnitTest.kt +++ b/src/test/kotlin/org/meliapp/backend/service/MeliSearchServiceUnitTest.kt @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import org.meliapp.backend.dto.meli.MeliSearchResponse -import org.meliapp.backend.dto.product.ProductResponse import org.meliapp.backend.exception.apc.ProductNotFoundException import org.mockito.MockitoAnnotations import org.springframework.beans.factory.annotation.Autowired @@ -21,7 +20,6 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers.request import org.springframework.test.web.client.response.MockRestResponseCreators.withResourceNotFound import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess import org.springframework.web.client.RestClient -import java.math.BigDecimal import kotlin.test.assertEquals @@ -75,21 +73,29 @@ class MeliSearchServiceUnitTest { fun `find by id should return a product response`() { val id = "MLA123" val expectedUri = "/items/$id" - - val mockResponse = ProductResponse(id, "", BigDecimal.valueOf(0), "", 0) + val expectedDescriptionUri = "$expectedUri/description" server.expect(requestTo(expectedUri)) .andExpect(method(HttpMethod.GET)) .andRespond( withSuccess( - "{ \"id\": \"$id\", \"title\": \"\", \"price\": 0, \"thumbnail\": \"\", \"available_quantity\": 0 }", + "{ \"id\": \"$id\", \"title\": \"\", \"price\": 0, \"thumbnail\": \"\", \"available_quantity\": 0, \"pictures\": [] }", + MediaType.APPLICATION_JSON + ) + ) + + server.expect(requestTo(expectedDescriptionUri)) + .andExpect(method(HttpMethod.GET)) + .andRespond( + withSuccess( + "{ \"plain_text\": \"description\"}", MediaType.APPLICATION_JSON ) ) val response = meliSearchService.findById(id) - assertEquals(mockResponse, response) + assertEquals(id, response.meliId) } @Test diff --git a/src/test/kotlin/org/meliapp/backend/service/ProductServiceUnitTest.kt b/src/test/kotlin/org/meliapp/backend/service/ProductServiceUnitTest.kt new file mode 100644 index 0000000..c0c504a --- /dev/null +++ b/src/test/kotlin/org/meliapp/backend/service/ProductServiceUnitTest.kt @@ -0,0 +1,91 @@ +package org.meliapp.backend.service + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.meliapp.backend.dto.product.ProductDetailsResponse +import org.meliapp.backend.exception.apc.ProductNotFoundException +import org.meliapp.backend.model.Product +import org.meliapp.backend.repository.ProductRepository +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.kotlin.* +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.math.BigDecimal +import java.util.* + +@ExtendWith(SpringExtension::class) +class ProductServiceUnitTest { + + @Mock + private lateinit var meliSearchService: MeliSearchService + + @Mock + private lateinit var productRepository: ProductRepository + + @InjectMocks + private lateinit var productService: ProductService + + @Test + fun `can find a product using it's meli id locally`() { + // Arrange + val product: Product = mock() + val meliId = "MELI_ID" + + whenever(product.meliId).thenReturn(meliId) + whenever(productRepository.findByMeliId(meliId)).thenReturn(Optional.of(product)) + + // Act + val result = productService.findByMeliIdOld(meliId) + + // Assert + assertTrue(result.isPresent) + assertEquals(meliId, result.get().meliId) + + } + + @Test + fun `should use search service when cannot find product locally`() { + val meliId = "MELI_ID" + + val product = Product().apply { + title = "title" + thumbnail = "thumbnail" + price = BigDecimal(999) + this.meliId = meliId + } + + val productResponse = ProductDetailsResponse( + meliId = meliId, + thumbnail = product.thumbnail, + price = product.price, + title = product.title, + pictures = listOf(), + description = "" + + ) + + whenever(productRepository.findByMeliId(any())).thenReturn(Optional.empty()) + whenever(meliSearchService.findById(any())).thenReturn(productResponse) + whenever(productRepository.save(any())).thenReturn(product) + + val result = productService.findByMeliId(meliId) + + assertEquals(product.id, result.id) + verify(productRepository, times(1)).save(any()) + } + + @Test + fun `should throw an exception when the cannot find the product with the given id`() { + val meliId = "MELI_ID" + + whenever(productRepository.findByMeliId(meliId)).thenReturn(Optional.empty()) + whenever(meliSearchService.findById(meliId)).thenThrow(ProductNotFoundException::class.java) + + assertThrows { productService.findByMeliId(meliId) } + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt b/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt new file mode 100644 index 0000000..41e7a21 --- /dev/null +++ b/src/test/kotlin/org/meliapp/backend/service/PurchaseServiceUnitTest.kt @@ -0,0 +1,82 @@ +package org.meliapp.backend.service + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.meliapp.backend.dto.purchase.PurchaseRequest +import org.meliapp.backend.model.Product +import org.meliapp.backend.model.Purchase +import org.meliapp.backend.model.User +import org.meliapp.backend.repository.PurchaseRepository +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.kotlin.* +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.math.BigDecimal +import kotlin.test.assertEquals + +@ExtendWith(SpringExtension::class) +class PurchaseServiceUnitTest { + + @Mock + lateinit var purchaseRepository: PurchaseRepository + + @Mock + lateinit var productService: ProductService + + @Mock + lateinit var authService: AuthService + + @InjectMocks + lateinit var purchaseService: PurchaseService + + @BeforeEach + fun setup() { + val user: User = mock() + whenever(user.id).thenReturn(1) + whenever(authService.getUserAuthenticated()).thenReturn(user) + } + + @AfterEach + fun cleanup() { + reset(purchaseRepository, productService, authService) + } + + @Test + fun `can buy an existing product`() { + val meliId = "MELI_ID" + val productTitle = "TITLE" + val thumbnail = "THUMBNAIL" + val product: Product = mock() + + whenever(product.meliId).thenReturn(meliId) + whenever(product.price).thenReturn(BigDecimal.ZERO) + whenever(product.title).thenReturn(productTitle) + whenever(product.thumbnail).thenReturn(thumbnail) + whenever(productService.findByMeliId(meliId)).thenReturn(product) + whenever(purchaseRepository.save(any())).thenAnswer { it.getArgument(0) } + + val result = purchaseService.buy(PurchaseRequest(meliId, 1)) + + assertEquals(meliId, result.product.meliId) + assertEquals(productTitle, result.product.title) + assertEquals(thumbnail, result.product.thumbnail) + assertEquals(BigDecimal.ZERO, result.product.price) + assertEquals(1, result.quantity) + assertEquals(BigDecimal.ZERO, result.total) + + verify(purchaseRepository, times(1)).save(any()) + + } + + @Test + fun `can get all the purchases from current user`() { + whenever(purchaseRepository.findByUserId(any())).thenReturn(listOf()) + + val result = purchaseService.purchases() + + assertEquals(0, result.size) + } + +} \ No newline at end of file