|
| 1 | +package org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings |
| 2 | + |
| 3 | +import kotlinx.coroutines.test.runTest |
| 4 | +import org.assertj.core.api.Assertions.assertThat |
| 5 | +import org.junit.Test |
| 6 | +import org.mockito.kotlin.any |
| 7 | +import org.mockito.kotlin.given |
| 8 | +import org.mockito.kotlin.mock |
| 9 | +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId |
| 10 | +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId |
| 11 | +import org.wordpress.android.fluxc.model.WCProductModel |
| 12 | +import org.wordpress.android.fluxc.persistence.dao.ProductsDao |
| 13 | +import org.wordpress.android.fluxc.persistence.entity.BookingEntity |
| 14 | +import org.wordpress.android.fluxc.persistence.entity.OrderEntity |
| 15 | +import java.math.BigDecimal |
| 16 | +import java.time.Instant |
| 17 | + |
| 18 | +class BookingDtoMapperTest { |
| 19 | + private val productsDao: ProductsDao = mock() |
| 20 | + private val localSiteId = LocalId(123) |
| 21 | + private val mapper = BookingDtoMapper(productsDao) |
| 22 | + |
| 23 | + @Test |
| 24 | + fun `given valid booking dto, when mapping to entity, then all fields are correctly mapped`() = runTest { |
| 25 | + // Given |
| 26 | + val bookingDto = createSampleBookingDto() |
| 27 | + |
| 28 | + // When |
| 29 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 30 | + |
| 31 | + // Then |
| 32 | + assertThat(result.id).isEqualTo(RemoteId(1L)) |
| 33 | + assertThat(result.localSiteId).isEqualTo(localSiteId) |
| 34 | + assertThat(result.start).isEqualTo(Instant.ofEpochSecond(1609459200L)) // 2021-01-01 00:00:00 |
| 35 | + assertThat(result.end).isEqualTo(Instant.ofEpochSecond(1609462800L)) // 2021-01-01 01:00:00 |
| 36 | + assertThat(result.allDay).isEqualTo(false) |
| 37 | + assertThat(result.status).isEqualTo(BookingEntity.Status.Confirmed) |
| 38 | + assertThat(result.cost).isEqualTo("50.00") |
| 39 | + assertThat(result.currency).isEqualTo("USD") |
| 40 | + assertThat(result.customerId).isEqualTo(456L) |
| 41 | + assertThat(result.productId).isEqualTo(789L) |
| 42 | + assertThat(result.resourceId).isEqualTo(101L) |
| 43 | + assertThat(result.dateCreated).isEqualTo(Instant.ofEpochSecond(1609455600L)) |
| 44 | + assertThat(result.dateModified).isEqualTo(Instant.ofEpochSecond(1609459200L)) |
| 45 | + assertThat(result.googleCalendarEventId).isEqualTo("cal-123") |
| 46 | + assertThat(result.orderId).isEqualTo(999L) |
| 47 | + assertThat(result.orderItemId).isEqualTo(111L) |
| 48 | + assertThat(result.parentId).isEqualTo(0L) |
| 49 | + assertThat(result.personCounts).containsExactly(2L, 1L) |
| 50 | + assertThat(result.localTimezone).isEqualTo("America/New_York") |
| 51 | + assertThat(result.order.productInfo).isNull() |
| 52 | + } |
| 53 | + |
| 54 | + @Test |
| 55 | + fun `given booking dto with null person counts, when mapping, then person counts is null`() = runTest { |
| 56 | + // Given |
| 57 | + val bookingDto = createSampleBookingDto(personCounts = null) |
| 58 | + |
| 59 | + // When |
| 60 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 61 | + |
| 62 | + // Then |
| 63 | + assertThat(result.personCounts).isNull() |
| 64 | + } |
| 65 | + |
| 66 | + @Test |
| 67 | + fun `given booking dto with empty person counts, when mapping, then person counts is empty`() = runTest { |
| 68 | + // Given |
| 69 | + val bookingDto = createSampleBookingDto(personCounts = emptyList()) |
| 70 | + |
| 71 | + // When |
| 72 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 73 | + |
| 74 | + // Then |
| 75 | + assertThat(result.personCounts).isEmpty() |
| 76 | + } |
| 77 | + |
| 78 | + @Test |
| 79 | + fun `given booking dto with unknown status, when mapping, then status is Unknown`() = runTest { |
| 80 | + // Given |
| 81 | + val bookingDto = createSampleBookingDto(status = "unknown-status") |
| 82 | + |
| 83 | + // When |
| 84 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 85 | + |
| 86 | + // Then |
| 87 | + assertThat(result.status).isInstanceOf(BookingEntity.Status.Unknown::class.java) |
| 88 | + assertThat(result.status.key).isEqualTo("unknown-status") |
| 89 | + } |
| 90 | + |
| 91 | + @Test |
| 92 | + fun `given booking dto with all valid statuses, when mapping, then correct status enum is returned`() = runTest { |
| 93 | + val statusMappings = mapOf( |
| 94 | + "unpaid" to BookingEntity.Status.Unpaid, |
| 95 | + "pending-confirmation" to BookingEntity.Status.PendingConfirmation, |
| 96 | + "confirmed" to BookingEntity.Status.Confirmed, |
| 97 | + "paid" to BookingEntity.Status.Paid, |
| 98 | + "cancelled" to BookingEntity.Status.Cancelled, |
| 99 | + "complete" to BookingEntity.Status.Complete |
| 100 | + ) |
| 101 | + |
| 102 | + statusMappings.forEach { (statusKey, expectedStatus) -> |
| 103 | + // Given |
| 104 | + val bookingDto = createSampleBookingDto(status = statusKey) |
| 105 | + |
| 106 | + // When |
| 107 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 108 | + |
| 109 | + // Then |
| 110 | + assertThat(result.status).isEqualTo(expectedStatus) |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + @Test |
| 115 | + fun `given booking dto with order entity, when mapping, then order info is populated from order`() = runTest { |
| 116 | + // Given |
| 117 | + val bookingDto = createSampleBookingDto() |
| 118 | + val orderEntity = createSampleOrderEntity() |
| 119 | + |
| 120 | + // When |
| 121 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, orderEntity) } |
| 122 | + |
| 123 | + // Then |
| 124 | + with(result.order) { |
| 125 | + assertThat(status).isEqualTo("processing") |
| 126 | + assertThat(productInfo?.name).isEqualTo("Test Product") |
| 127 | + assertThat(customerInfo?.billingFirstName).isEqualTo("John") |
| 128 | + assertThat(customerInfo?.billingLastName).isEqualTo("Doe") |
| 129 | + assertThat(customerInfo?.billingEmail).isEqualTo( "[email protected]") |
| 130 | + assertThat(customerInfo?.billingCompany).isNull() // empty string becomes null |
| 131 | + assertThat(customerInfo?.billingAddress1).isEqualTo("123 Main St") |
| 132 | + assertThat(paymentInfo?.subtotal).isEqualTo(BigDecimal("40.00")) |
| 133 | + assertThat(paymentInfo?.total).isEqualTo(BigDecimal("50.00")) |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + @Test |
| 138 | + fun `given booking dto without order entity and product exists, when mapping, then order info has product name`() = |
| 139 | + runTest { |
| 140 | + // Given |
| 141 | + val bookingDto = createSampleBookingDto() |
| 142 | + val productModel = WCProductModel(name = "Test Product from DB") |
| 143 | + given(productsDao.getProduct(localSiteId.value, bookingDto.productId)).willReturn(productModel) |
| 144 | + |
| 145 | + // When |
| 146 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 147 | + |
| 148 | + // Then |
| 149 | + assertThat(result.order.productInfo?.name).isEqualTo("Test Product from DB") |
| 150 | + assertThat(result.order.status).isNull() |
| 151 | + assertThat(result.order.customerInfo).isNull() |
| 152 | + assertThat(result.order.paymentInfo).isNull() |
| 153 | + } |
| 154 | + |
| 155 | + @Test |
| 156 | + fun `given booking dto without order entity and no product, when mapping, then order info has null product info`() = |
| 157 | + runTest { |
| 158 | + // Given |
| 159 | + val bookingDto = createSampleBookingDto() |
| 160 | + given(productsDao.getProduct(any<Int>(), any<Long>())).willReturn(null) |
| 161 | + |
| 162 | + // When |
| 163 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, null) } |
| 164 | + |
| 165 | + // Then |
| 166 | + assertThat(result.order.productInfo).isNull() |
| 167 | + assertThat(result.order.status).isNull() |
| 168 | + assertThat(result.order.customerInfo).isNull() |
| 169 | + assertThat(result.order.paymentInfo).isNull() |
| 170 | + } |
| 171 | + |
| 172 | + @Test |
| 173 | + fun `given order entity with empty billing fields, when mapping, then empty strings become null`() = runTest { |
| 174 | + // Given |
| 175 | + val bookingDto = createSampleBookingDto() |
| 176 | + val orderEntity = createSampleOrderEntity().copy( |
| 177 | + billingCompany = "", |
| 178 | + billingAddress2 = "", |
| 179 | + billingCity = "", |
| 180 | + billingState = "", |
| 181 | + billingPostcode = "", |
| 182 | + billingCountry = "", |
| 183 | + billingPhone = "" |
| 184 | + ) |
| 185 | + |
| 186 | + // When |
| 187 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, orderEntity) } |
| 188 | + |
| 189 | + // Then |
| 190 | + with(result.order.customerInfo!!) { |
| 191 | + assertThat(billingCompany).isNull() |
| 192 | + assertThat(billingAddress2).isNull() |
| 193 | + assertThat(billingCity).isNull() |
| 194 | + assertThat(billingState).isNull() |
| 195 | + assertThat(billingPostcode).isNull() |
| 196 | + assertThat(billingCountry).isNull() |
| 197 | + assertThat(billingPhone).isNull() |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + @Test |
| 202 | + fun `given order entity with no matching line item, when mapping, then payment info is null`() = runTest { |
| 203 | + // Given |
| 204 | + val bookingDto = createSampleBookingDto(orderItemId = 999L) // non-matching ID |
| 205 | + val orderEntity = createSampleOrderEntity() |
| 206 | + |
| 207 | + // When |
| 208 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, orderEntity) } |
| 209 | + |
| 210 | + // Then |
| 211 | + assertThat(result.order.paymentInfo).isNull() |
| 212 | + } |
| 213 | + |
| 214 | + @Test |
| 215 | + fun `given order entity with line item having invalid amounts, when mapping, then defaults to zero`() = runTest { |
| 216 | + // Given |
| 217 | + val bookingDto = createSampleBookingDto() |
| 218 | + val orderEntity = createSampleOrderEntity().copy( |
| 219 | + lineItems = "[{\"id\":111,\"name\":\"Test Product\",\"subtotal\":\"invalid\",\"total\":\"also-invalid\",\"subtotal_tax\":null,\"total_tax\":\"\"}]" |
| 220 | + ) |
| 221 | + |
| 222 | + // When |
| 223 | + val result = with(mapper) { bookingDto.toEntity(localSiteId, orderEntity) } |
| 224 | + |
| 225 | + // Then |
| 226 | + with(result.order.paymentInfo!!) { |
| 227 | + assertThat(subtotal).isEqualTo(BigDecimal.ZERO) |
| 228 | + assertThat(total).isEqualTo(BigDecimal.ZERO) |
| 229 | + assertThat(subtotalTax).isEqualTo(BigDecimal.ZERO) |
| 230 | + assertThat(totalTax).isEqualTo(BigDecimal.ZERO) |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + private fun createSampleBookingDto( |
| 235 | + id: Long = 1L, |
| 236 | + start: Long = 1609459200L, // 2021-01-01 00:00:00 |
| 237 | + end: Long = 1609462800L, // 2021-01-01 01:00:00 |
| 238 | + allDay: Boolean = false, |
| 239 | + status: String = "confirmed", |
| 240 | + cost: String = "50.00", |
| 241 | + currency: String = "USD", |
| 242 | + customerId: Long = 456L, |
| 243 | + productId: Long = 789L, |
| 244 | + resourceId: Long = 101L, |
| 245 | + dateCreated: Long = 1609455600L, |
| 246 | + dateModified: Long = 1609459200L, |
| 247 | + googleCalendarEventId: String = "cal-123", |
| 248 | + orderId: Long = 999L, |
| 249 | + orderItemId: Long = 111L, |
| 250 | + parentId: Long = 0L, |
| 251 | + personCounts: List<Int>? = listOf(2, 1), |
| 252 | + localTimezone: String = "America/New_York" |
| 253 | + ) = BookingDto( |
| 254 | + id = id, |
| 255 | + start = start, |
| 256 | + end = end, |
| 257 | + allDay = allDay, |
| 258 | + status = status, |
| 259 | + cost = cost, |
| 260 | + currency = currency, |
| 261 | + customerId = customerId, |
| 262 | + productId = productId, |
| 263 | + resourceId = resourceId, |
| 264 | + dateCreated = dateCreated, |
| 265 | + dateModified = dateModified, |
| 266 | + googleCalendarEventId = googleCalendarEventId, |
| 267 | + orderId = orderId, |
| 268 | + orderItemId = orderItemId, |
| 269 | + parentId = parentId, |
| 270 | + personCounts = personCounts, |
| 271 | + localTimezone = localTimezone |
| 272 | + ) |
| 273 | + |
| 274 | + private fun createSampleOrderEntity() = OrderEntity( |
| 275 | + localSiteId = localSiteId, |
| 276 | + orderId = 999L, |
| 277 | + number = "1001", |
| 278 | + status = "processing", |
| 279 | + currency = "USD", |
| 280 | + orderKey = "", |
| 281 | + dateCreated = "", |
| 282 | + dateModified = "", |
| 283 | + total = "50.00", |
| 284 | + totalTax = "", |
| 285 | + shippingTotal = "", |
| 286 | + paymentMethod = "", |
| 287 | + paymentMethodTitle = "", |
| 288 | + datePaid = "", |
| 289 | + pricesIncludeTax = false, |
| 290 | + customerNote = "", |
| 291 | + discountTotal = "", |
| 292 | + discountCodes = "", |
| 293 | + customerId = 456L, |
| 294 | + billingFirstName = "John", |
| 295 | + billingLastName = "Doe", |
| 296 | + billingCompany = "", |
| 297 | + billingAddress1 = "123 Main St", |
| 298 | + billingAddress2 = "Apt 1", |
| 299 | + billingCity = "New York", |
| 300 | + billingState = "NY", |
| 301 | + billingPostcode = "10001", |
| 302 | + billingCountry = "US", |
| 303 | + billingEmail = "[email protected]", |
| 304 | + billingPhone = "555-1234", |
| 305 | + shippingFirstName = "", |
| 306 | + shippingLastName = "", |
| 307 | + shippingCompany = "", |
| 308 | + shippingAddress1 = "", |
| 309 | + shippingAddress2 = "", |
| 310 | + shippingCity = "", |
| 311 | + shippingState = "", |
| 312 | + shippingPostcode = "", |
| 313 | + shippingCountry = "", |
| 314 | + shippingPhone = "", |
| 315 | + lineItems = "[{\"id\":111,\"name\":\"Test Product\",\"subtotal\":\"40.00\",\"subtotal_tax\":\"5.00\",\"total\":\"50.00\",\"total_tax\":\"7.50\"}]", |
| 316 | + shippingLines = "[]", |
| 317 | + feeLines = "[]", |
| 318 | + taxLines = "[]", |
| 319 | + couponLines = "[]", |
| 320 | + metaData = emptyList(), |
| 321 | + paymentUrl = "", |
| 322 | + isEditable = true, |
| 323 | + needsPayment = null, |
| 324 | + needsProcessing = null, |
| 325 | + giftCardCode = "", |
| 326 | + giftCardAmount = "", |
| 327 | + shippingTax = "", |
| 328 | + createdVia = "" |
| 329 | + ) |
| 330 | +} |
0 commit comments