diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/ApiClient.kt b/src/main/kotlin/com/ecwid/apiclient/v3/ApiClient.kt index ab3964113..b5bff611b 100644 --- a/src/main/kotlin/com/ecwid/apiclient/v3/ApiClient.kt +++ b/src/main/kotlin/com/ecwid/apiclient/v3/ApiClient.kt @@ -26,8 +26,6 @@ import com.ecwid.apiclient.v3.dto.customergroup.request.* import com.ecwid.apiclient.v3.dto.customergroup.result.* import com.ecwid.apiclient.v3.dto.instantsite.redirects.request.* import com.ecwid.apiclient.v3.dto.instantsite.redirects.result.* -import com.ecwid.apiclient.v3.dto.order.request.* -import com.ecwid.apiclient.v3.dto.order.result.* import com.ecwid.apiclient.v3.dto.productreview.request.* import com.ecwid.apiclient.v3.dto.productreview.result.* import com.ecwid.apiclient.v3.dto.producttype.request.* @@ -55,6 +53,7 @@ import kotlin.reflect.KClass open class ApiClient private constructor( protected val apiClientHelper: ApiClientHelper, storeProfileApiClient: StoreProfileApiClient, + brandsApiClient: BrandsApiClient, productsApiClient: ProductsApiClient, categoriesApiClient: CategoriesApiClient, ordersApiClient: OrdersApiClient, @@ -76,6 +75,7 @@ open class ApiClient private constructor( storeExtrafieldsApiClient: StoreExtrafieldsApiClientImpl, ) : StoreProfileApiClient by storeProfileApiClient, + BrandsApiClient by brandsApiClient, ProductsApiClient by productsApiClient, CategoriesApiClient by categoriesApiClient, OrdersApiClient by ordersApiClient, @@ -99,6 +99,7 @@ open class ApiClient private constructor( constructor(apiClientHelper: ApiClientHelper) : this( apiClientHelper = apiClientHelper, storeProfileApiClient = StoreProfileApiClientImpl(apiClientHelper), + brandsApiClient = BrandsApiClientImpl(apiClientHelper), productsApiClient = ProductsApiClientImpl(apiClientHelper), categoriesApiClient = CategoriesApiClientImpl(apiClientHelper), ordersApiClient = OrdersApiClientImpl(apiClientHelper), diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/BrandsApiClient.kt b/src/main/kotlin/com/ecwid/apiclient/v3/BrandsApiClient.kt new file mode 100644 index 000000000..0e932ab75 --- /dev/null +++ b/src/main/kotlin/com/ecwid/apiclient/v3/BrandsApiClient.kt @@ -0,0 +1,21 @@ +package com.ecwid.apiclient.v3 + +import com.ecwid.apiclient.v3.dto.brand.request.BrandsSearchRequest +import com.ecwid.apiclient.v3.dto.brand.result.BrandsSearchResult +import com.ecwid.apiclient.v3.dto.common.PartialResult +import kotlin.reflect.KClass + +// Brands +// https://api-docs.ecwid.com/reference/product-brands +interface BrandsApiClient { + fun searchBrands(request: BrandsSearchRequest): BrandsSearchResult + fun searchBrands(request: BrandsSearchRequest, resultClass: KClass): Result + where Result : PartialResult +} + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +inline fun > BrandsApiClient.searchBrands( + request: BrandsSearchRequest +): Result { + return searchBrands(request, resultClass = Result::class) +} diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/request/BrandsSearchRequest.kt b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/request/BrandsSearchRequest.kt new file mode 100644 index 000000000..eb6876477 --- /dev/null +++ b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/request/BrandsSearchRequest.kt @@ -0,0 +1,49 @@ +package com.ecwid.apiclient.v3.dto.brand.request + +import com.ecwid.apiclient.v3.dto.ApiRequest +import com.ecwid.apiclient.v3.dto.common.PagingRequest +import com.ecwid.apiclient.v3.impl.RequestInfo +import com.ecwid.apiclient.v3.responsefields.ResponseFields + +sealed class BrandsSearchRequest : ApiRequest { + + data class ByFilters( + val limit: Int = 100, + override val offset: Int = 0, + val lang: String? = null, + val hiddenBrands: Boolean? = null, + val baseUrl: String? = null, + val cleanUrls: Boolean? = null, + val sortBy: SortOrder? = null, + val responseFields: ResponseFields = ResponseFields.All, + ) : BrandsSearchRequest(), PagingRequest { + override fun toRequestInfo() = RequestInfo.createGetRequest( + pathSegments = listOf( + "brands", + ), + params = toParams(), + responseFields = responseFields, + ) + + private fun toParams(): Map { + val request = this + return mutableMapOf().apply { + put("limit", request.limit.toString()) + put("offset", request.offset.toString()) + request.lang?.let { put("lang", it) } + request.hiddenBrands?.let { put("hiddenBrands", it.toString()) } + request.baseUrl?.let { put("baseUrl", it) } + request.cleanUrls?.let { put("cleanUrls", it.toString()) } + request.sortBy?.let { put("sortBy", it.name) } + }.toMap() + } + + override fun copyWithOffset(offset: Int) = copy(offset = offset) + } + + @Suppress("unused") + enum class SortOrder { + PRODUCT_COUNT_DESC, + NAME_ASC, + } +} diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/BrandsSearchResult.kt b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/BrandsSearchResult.kt new file mode 100644 index 000000000..ca65cc8df --- /dev/null +++ b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/BrandsSearchResult.kt @@ -0,0 +1,11 @@ +package com.ecwid.apiclient.v3.dto.brand.result + +import com.ecwid.apiclient.v3.dto.common.ApiResultDTO + +data class BrandsSearchResult( + val items: List = listOf(), + val count: Int = 0, + val total: Int = 0, + val limit: Int = 0, + val offset: Int = 0 +) : ApiResultDTO diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/FetchedBrand.kt b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/FetchedBrand.kt new file mode 100644 index 000000000..09ebf1da9 --- /dev/null +++ b/src/main/kotlin/com/ecwid/apiclient/v3/dto/brand/result/FetchedBrand.kt @@ -0,0 +1,12 @@ +package com.ecwid.apiclient.v3.dto.brand.result + +import com.ecwid.apiclient.v3.dto.common.ApiFetchedDTO +import com.ecwid.apiclient.v3.dto.common.ApiResultDTO + +data class FetchedBrand( + val name: String = "", + val nameTranslated: Map? = null, + val productsFilteredByBrandUrl: String? = null, +) : ApiFetchedDTO, ApiResultDTO { + override fun getModifyKind() = ApiFetchedDTO.ModifyKind.ReadOnly +} diff --git a/src/main/kotlin/com/ecwid/apiclient/v3/impl/BrandsApiClientImpl.kt b/src/main/kotlin/com/ecwid/apiclient/v3/impl/BrandsApiClientImpl.kt new file mode 100644 index 000000000..8d56c15ce --- /dev/null +++ b/src/main/kotlin/com/ecwid/apiclient/v3/impl/BrandsApiClientImpl.kt @@ -0,0 +1,26 @@ +package com.ecwid.apiclient.v3.impl + +import com.ecwid.apiclient.v3.ApiClientHelper +import com.ecwid.apiclient.v3.BrandsApiClient +import com.ecwid.apiclient.v3.dto.brand.request.BrandsSearchRequest +import com.ecwid.apiclient.v3.dto.brand.result.BrandsSearchResult +import com.ecwid.apiclient.v3.dto.common.PartialResult +import kotlin.reflect.KClass + +internal class BrandsApiClientImpl( + private val apiClientHelper: ApiClientHelper, +) : BrandsApiClient { + + override fun searchBrands(request: BrandsSearchRequest) = + apiClientHelper.makeObjectResultRequest(request) + + override fun > searchBrands( + request: BrandsSearchRequest, + resultClass: KClass + ): Result { + return apiClientHelper.makeObjectPartialResultRequest( + request = request, + resultClass = resultClass, + ) + } +} diff --git a/src/test/kotlin/com/ecwid/apiclient/v3/entity/BrandsTest.kt b/src/test/kotlin/com/ecwid/apiclient/v3/entity/BrandsTest.kt new file mode 100644 index 000000000..5b740896e --- /dev/null +++ b/src/test/kotlin/com/ecwid/apiclient/v3/entity/BrandsTest.kt @@ -0,0 +1,96 @@ +package com.ecwid.apiclient.v3.entity + +import com.ecwid.apiclient.v3.dto.brand.request.BrandsSearchRequest +import com.ecwid.apiclient.v3.dto.product.request.ProductCreateRequest +import com.ecwid.apiclient.v3.dto.product.request.ProductsSearchRequest.ByFilters +import com.ecwid.apiclient.v3.dto.product.request.UpdatedProduct +import com.ecwid.apiclient.v3.dto.product.request.UpdatedProduct.* +import com.ecwid.apiclient.v3.util.randomAlphanumeric +import com.ecwid.apiclient.v3.util.randomPrice +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import kotlin.test.Test +import kotlin.test.assertEquals + +class BrandsTest : BaseEntityTest() { + + @BeforeEach + override fun beforeEach() { + super.beforeEach() + + initStoreProfile() + removeAllProducts() + } + + @Test + fun getBrands() { + val brandedProductsCreateResult = createProductsWithBrands() + + // Waiting till product became available for searching + brandedProductsCreateResult.skus.forEach { sku -> + waitForIndexedProducts( + productsSearchRequest = ByFilters(sku = sku), + desiredProductCount = 1 + ) + } + + val result = apiClient.searchBrands(BrandsSearchRequest.ByFilters()) + assertEquals( + brandedProductsCreateResult.brandNames, + result.items.map { it.name } + ) + } + + private fun createProductsWithBrands(productsCount: Int = 5): BrandedProductsCreateResult { + val brands = mutableListOf() + val skus = mutableListOf() + + for (i in 1..productsCount) { + val randomBrand = randomAlphanumeric(8) + val randomSku = randomAlphanumeric(10) + val price = randomPrice() + + val productCreateRequest = ProductCreateRequest( + newProduct = UpdatedProduct( + name = "Product $i ${randomAlphanumeric(8)}", + sku = randomSku, + price = price, + compareToPrice = 2 * price, + enabled = true, + quantity = 10, + attributes = listOf( + AttributeValue.createBrandAttributeValue(randomBrand), + ), + options = listOf( + ProductOption.createSelectOption( + name = "Color", + choices = listOf( + ProductOptionChoice("Black"), + ProductOptionChoice("White"), + ProductOptionChoice("Yellow"), + ProductOptionChoice("Red") + ), + defaultChoice = 0, + required = true + ) + ) + ) + ) + val productCreateResult = apiClient.createProduct(productCreateRequest) + assertTrue(productCreateResult.id > 0) + + skus.add(randomSku) + brands.add(randomBrand) + } + + return BrandedProductsCreateResult( + brandNames = brands, + skus = skus, + ) + } + + data class BrandedProductsCreateResult( + val brandNames: List, + val skus: List, + ) +} diff --git a/src/test/kotlin/com/ecwid/apiclient/v3/rule/NullablePropertyRules.kt b/src/test/kotlin/com/ecwid/apiclient/v3/rule/NullablePropertyRules.kt index e0ed2c843..f139e9d67 100644 --- a/src/test/kotlin/com/ecwid/apiclient/v3/rule/NullablePropertyRules.kt +++ b/src/test/kotlin/com/ecwid/apiclient/v3/rule/NullablePropertyRules.kt @@ -200,6 +200,8 @@ val nullablePropertyRules: List> = listOf( productReviewMassUpdateRequestNullablePropertyRules, productReviewSearchRequestNullablePropertyRules, fetchedCustomersConfigNullablePropertyRules, + brandsSearchRequestNullablePropertyRules, + fetchedBrandNullablePropertyRules, ).flatten() sealed class NullablePropertyRule( diff --git a/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/BrandsSearchRequestRules.kt b/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/BrandsSearchRequestRules.kt new file mode 100644 index 000000000..7bac458af --- /dev/null +++ b/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/BrandsSearchRequestRules.kt @@ -0,0 +1,13 @@ +package com.ecwid.apiclient.v3.rule.nullablepropertyrules + +import com.ecwid.apiclient.v3.dto.brand.request.BrandsSearchRequest +import com.ecwid.apiclient.v3.rule.NullablePropertyRule +import com.ecwid.apiclient.v3.rule.NullablePropertyRule.AllowNullable + +val brandsSearchRequestNullablePropertyRules: List> = listOf( + AllowNullable(BrandsSearchRequest.ByFilters::lang), + AllowNullable(BrandsSearchRequest.ByFilters::hiddenBrands), + AllowNullable(BrandsSearchRequest.ByFilters::baseUrl), + AllowNullable(BrandsSearchRequest.ByFilters::cleanUrls), + AllowNullable(BrandsSearchRequest.ByFilters::sortBy), +) diff --git a/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/FetchedBrandRules.kt b/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/FetchedBrandRules.kt new file mode 100644 index 000000000..6b15f06f1 --- /dev/null +++ b/src/test/kotlin/com/ecwid/apiclient/v3/rule/nullablepropertyrules/FetchedBrandRules.kt @@ -0,0 +1,10 @@ +package com.ecwid.apiclient.v3.rule.nullablepropertyrules + +import com.ecwid.apiclient.v3.dto.brand.result.FetchedBrand +import com.ecwid.apiclient.v3.rule.NullablePropertyRule +import com.ecwid.apiclient.v3.rule.NullablePropertyRule.AllowNullable + +val fetchedBrandNullablePropertyRules: List> = listOf( + AllowNullable(FetchedBrand::nameTranslated), + AllowNullable(FetchedBrand::productsFilteredByBrandUrl), +) diff --git a/src/test/kotlin/com/ecwid/apiclient/v3/util/OrderUtils.kt b/src/test/kotlin/com/ecwid/apiclient/v3/util/OrderUtils.kt index 7942d0c62..a359db5db 100644 --- a/src/test/kotlin/com/ecwid/apiclient/v3/util/OrderUtils.kt +++ b/src/test/kotlin/com/ecwid/apiclient/v3/util/OrderUtils.kt @@ -105,9 +105,9 @@ fun generateTestOrder(): UpdatedOrder { shippingMethodName = "Method " + randomAlphanumeric(8), shippingRate = randomPrice(), estimatedTransitTime = "Estimates " + randomAlphanumeric(8), - isPickup = false, + isPickup = true, pickupInstruction = "Instruction " + randomAlphanumeric(64), - fulfillmentType = FulfillmentType.SHIPPING + fulfillmentType = FulfillmentType.PICKUP ), taxesOnShipping = listOf(), handlingFee = UpdatedOrder.HandlingFee(