From 5dac09b07ecddb5250bb390bf8046a7cb8e36ea3 Mon Sep 17 00:00:00 2001 From: Slava Rudkovskiy Date: Mon, 11 May 2026 14:05:03 +0300 Subject: [PATCH 1/2] add DiscountService --- .../labs64/netlicensing/domain/Constants.java | 3 + .../netlicensing/service/DiscountService.java | 99 ++++++++++ .../service/DiscountServiceTest.java | 180 ++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java create mode 100644 NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java diff --git a/NetLicensingClient/src/main/java/com/labs64/netlicensing/domain/Constants.java b/NetLicensingClient/src/main/java/com/labs64/netlicensing/domain/Constants.java index c3be1798..ee036ff8 100644 --- a/NetLicensingClient/src/main/java/com/labs64/netlicensing/domain/Constants.java +++ b/NetLicensingClient/src/main/java/com/labs64/netlicensing/domain/Constants.java @@ -82,6 +82,9 @@ public static final class Product { public static final String PROP_VAT_MODE = "vatMode"; public static final class Discount { + public static final String ENDPOINT_PATH = "discount"; + public static final String ENDPOINT_RESOLVE_PATH = "resolve"; + public static final String CART_TOTAL = "cartTotal"; public static final String TOTAL_PRICE = "totalPrice"; public static final String AMOUNT_FIX = "amountFix"; public static final String AMOUNT_PERCENT = "amountPercent"; diff --git a/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java b/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java new file mode 100644 index 00000000..de085fdf --- /dev/null +++ b/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java @@ -0,0 +1,99 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.labs64.netlicensing.service; + +import java.math.BigDecimal; + +import org.apache.commons.lang3.StringUtils; + +import com.labs64.netlicensing.domain.Constants; +import com.labs64.netlicensing.domain.entity.LicenseTemplate; +import com.labs64.netlicensing.domain.vo.Context; +import com.labs64.netlicensing.exception.NetLicensingException; +import com.labs64.netlicensing.exception.ServiceException; +import com.labs64.netlicensing.provider.Form; +import com.labs64.netlicensing.util.CheckUtils; + +/** + * Provides discount handling routines. + */ +public class DiscountService { + + private static final int HTTP_STATUS_NOT_FOUND = 404; + private static final String NOT_FOUND_EXCEPTION_PREFIX = "NotFoundException:"; + + /** + * Resolves a discount license template by product number and promo code. + * + * @param context + * determines the vendor on whose behalf the call is performed + * @param productNumber + * product number that defines promo code uniqueness scope + * @param promoCode + * promo code to resolve + * @return resolved discount license template or null if discount was not found + * @throws NetLicensingException + * any non-not-found service error + */ + public static LicenseTemplate resolve(final Context context, final String productNumber, final String promoCode) + throws NetLicensingException { + return resolve(context, productNumber, promoCode, null); + } + + /** + * Resolves a discount license template by product number and promo code. + * + * @param context + * determines the vendor on whose behalf the call is performed + * @param productNumber + * product number that defines promo code uniqueness scope + * @param promoCode + * promo code to resolve + * @param cartTotal + * optional cart total used by the API to validate minimum cart total + * @return resolved discount license template or null if discount was not found + * @throws NetLicensingException + * any non-not-found service error + */ + public static LicenseTemplate resolve(final Context context, final String productNumber, final String promoCode, + final BigDecimal cartTotal) throws NetLicensingException { + CheckUtils.paramNotEmpty(productNumber, "productNumber"); + CheckUtils.paramNotEmpty(promoCode, "promoCode"); + + final Form form = new Form(); + if (cartTotal != null) { + form.param(Constants.Product.Discount.CART_TOTAL, cartTotal.toString()); + } + + try { + return NetLicensingService.getInstance().post(context, resolveEndpoint(productNumber, promoCode), form, + LicenseTemplate.class); + } catch (final ServiceException e) { + if (isNotFound(e)) { + return null; + } + throw e; + } + } + + private static String resolveEndpoint(final String productNumber, final String promoCode) { + return Constants.Product.ENDPOINT_PATH + "/" + productNumber + "/" + + Constants.Product.Discount.ENDPOINT_PATH + "/" + promoCode + "/" + + Constants.Product.Discount.ENDPOINT_RESOLVE_PATH; + } + + private static boolean isNotFound(final ServiceException e) { + return (e.getStatusCode() == HTTP_STATUS_NOT_FOUND) + || StringUtils.startsWith(e.getMessage(), NOT_FOUND_EXCEPTION_PREFIX); + } +} diff --git a/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java b/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java new file mode 100644 index 00000000..e5fd1fdd --- /dev/null +++ b/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java @@ -0,0 +1,180 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.labs64.netlicensing.service; + +import java.math.BigDecimal; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.labs64.netlicensing.domain.Constants; +import com.labs64.netlicensing.domain.entity.LicenseTemplate; +import com.labs64.netlicensing.domain.vo.Context; +import com.labs64.netlicensing.domain.vo.LicenseType; +import com.labs64.netlicensing.exception.ServiceException; +import com.labs64.netlicensing.schema.SchemaFunction; +import com.labs64.netlicensing.schema.context.InfoEnum; +import com.labs64.netlicensing.schema.context.Item; +import com.labs64.netlicensing.schema.context.Netlicensing; +import com.labs64.netlicensing.schema.context.ObjectFactory; +import com.labs64.netlicensing.schema.context.Property; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Integration tests for {@link DiscountService}. + */ +public class DiscountServiceTest extends BaseServiceTest { + + private static final String PRODUCT_NUMBER = "P001-TEST"; + private static final String PROMO_CODE = "PROMO-001"; + private static final String LICENSE_TEMPLATE_NUMBER = "LT-DISCOUNT-001"; + private static final String MIN_CART_TOTAL = "minCartTotal"; + private static final String PROMO_CODE_PROPERTY = "promoCode"; + + private static Context context; + + @BeforeAll + public static void setup() { + context = createContext(); + } + + @BeforeEach + public void resetResourceState() { + DiscountServiceResource.lastProductNumber = null; + DiscountServiceResource.lastPromoCode = null; + DiscountServiceResource.lastCartTotal = null; + } + + @Test + public void testResolve() throws Exception { + final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, PROMO_CODE); + + assertNotNull(discount); + assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); + assertEquals("Promo discount", discount.getName()); + assertEquals(LicenseType.FEATURE, discount.getLicenseType()); + assertEquals("25", discount.getProperties().get("discountAmount")); + assertEquals(PROMO_CODE, discount.getProperties().get(PROMO_CODE_PROPERTY)); + assertEquals(PRODUCT_NUMBER, DiscountServiceResource.lastProductNumber); + assertEquals(PROMO_CODE, DiscountServiceResource.lastPromoCode); + assertNull(DiscountServiceResource.lastCartTotal); + } + + @Test + public void testResolveWithCartTotal() throws Exception { + final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, PROMO_CODE, + new BigDecimal("100.50")); + + assertNotNull(discount); + assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); + assertEquals("100.50", DiscountServiceResource.lastCartTotal); + } + + @Test + public void testResolveReturnsNullWhenDiscountDoesNotExist() throws Exception { + final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, "UNKNOWN"); + + assertNull(discount); + } + + @Test + public void testResolveRethrowsNonNotFoundServiceException() { + final ServiceException ex = assertThrows(ServiceException.class, + () -> DiscountService.resolve(context, PRODUCT_NUMBER, "BROKEN")); + + assertEquals("IllegalOperationException: Discount resolve failed", ex.getMessage()); + } + + @Override + protected Class getResourceClass() { + return DiscountServiceResource.class; + } + + @Path(REST_API_PATH + "/product/{productNumber}/discount/{promoCode}/resolve") + public static class DiscountServiceResource { + + private static String lastProductNumber; + private static String lastPromoCode; + private static String lastCartTotal; + + private final ObjectFactory objectFactory = new ObjectFactory(); + + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response resolve(@PathParam("productNumber") final String productNumber, + @PathParam("promoCode") final String promoCode, final MultivaluedMap formParams) { + lastProductNumber = productNumber; + lastPromoCode = promoCode; + lastCartTotal = formParams.getFirst(Constants.Product.Discount.CART_TOTAL); + + if ("UNKNOWN".equals(promoCode)) { + return notFoundResponse(); + } + if ("BROKEN".equals(promoCode)) { + return errorResponse("IllegalOperationException", "Discount resolve failed"); + } + + final Netlicensing netlicensing = objectFactory.createNetlicensing(); + netlicensing.setItems(objectFactory.createNetlicensingItems()); + + final Item item = objectFactory.createItem(); + item.setType("LicenseTemplate"); + netlicensing.getItems().getItem().add(item); + + addProperty(item, Constants.NUMBER, LICENSE_TEMPLATE_NUMBER); + addProperty(item, Constants.NAME, "Promo discount"); + addProperty(item, Constants.ACTIVE, Boolean.TRUE.toString()); + addProperty(item, Constants.LicenseTemplate.LICENSE_TYPE, LicenseType.FEATURE.value()); + addProperty(item, Constants.LicenseTemplate.AUTOMATIC, Boolean.FALSE.toString()); + addProperty(item, Constants.LicenseTemplate.HIDDEN, Boolean.TRUE.toString()); + addProperty(item, Constants.LicenseTemplate.HIDE_LICENSES, Boolean.TRUE.toString()); + addProperty(item, Constants.ProductModule.PRODUCT_MODULE_NUMBER, "PM-DISCOUNT-001"); + addProperty(item, PROMO_CODE_PROPERTY, promoCode); + addProperty(item, "discountAmount", "25"); + addProperty(item, MIN_CART_TOTAL, "100"); + + return Response.ok(netlicensing).build(); + } + + private Response notFoundResponse() { + final Netlicensing netlicensing = objectFactory.createNetlicensing(); + SchemaFunction.addInfo(netlicensing, "NotFoundException", InfoEnum.ERROR, + "Requested discount does not exist."); + return Response.status(Response.Status.NOT_FOUND).entity(netlicensing).build(); + } + + private Response errorResponse(final String exceptionId, final String errorMessage) { + final Netlicensing netlicensing = objectFactory.createNetlicensing(); + SchemaFunction.addInfo(netlicensing, exceptionId, InfoEnum.ERROR, errorMessage); + return Response.status(Response.Status.BAD_REQUEST).entity(netlicensing).build(); + } + + private void addProperty(final Item item, final String name, final String value) { + final Property property = new Property(value, name); + item.getProperty().add(property); + } + } +} From 24d505608a3a7e90d70fe5b684ade6744c2fd657 Mon Sep 17 00:00:00 2001 From: Slava Rudkovskiy Date: Mon, 29 Jun 2026 17:07:22 +0300 Subject: [PATCH 2/2] move discount resolve API to ProductService --- .../netlicensing/service/DiscountService.java | 99 ---------- .../netlicensing/service/ProductService.java | 72 +++++++ .../service/DiscountServiceTest.java | 180 ------------------ .../service/ProductServiceTest.java | 119 ++++++++++++ 4 files changed, 191 insertions(+), 279 deletions(-) delete mode 100644 NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java delete mode 100644 NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java diff --git a/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java b/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java deleted file mode 100644 index de085fdf..00000000 --- a/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/DiscountService.java +++ /dev/null @@ -1,99 +0,0 @@ -/* Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.labs64.netlicensing.service; - -import java.math.BigDecimal; - -import org.apache.commons.lang3.StringUtils; - -import com.labs64.netlicensing.domain.Constants; -import com.labs64.netlicensing.domain.entity.LicenseTemplate; -import com.labs64.netlicensing.domain.vo.Context; -import com.labs64.netlicensing.exception.NetLicensingException; -import com.labs64.netlicensing.exception.ServiceException; -import com.labs64.netlicensing.provider.Form; -import com.labs64.netlicensing.util.CheckUtils; - -/** - * Provides discount handling routines. - */ -public class DiscountService { - - private static final int HTTP_STATUS_NOT_FOUND = 404; - private static final String NOT_FOUND_EXCEPTION_PREFIX = "NotFoundException:"; - - /** - * Resolves a discount license template by product number and promo code. - * - * @param context - * determines the vendor on whose behalf the call is performed - * @param productNumber - * product number that defines promo code uniqueness scope - * @param promoCode - * promo code to resolve - * @return resolved discount license template or null if discount was not found - * @throws NetLicensingException - * any non-not-found service error - */ - public static LicenseTemplate resolve(final Context context, final String productNumber, final String promoCode) - throws NetLicensingException { - return resolve(context, productNumber, promoCode, null); - } - - /** - * Resolves a discount license template by product number and promo code. - * - * @param context - * determines the vendor on whose behalf the call is performed - * @param productNumber - * product number that defines promo code uniqueness scope - * @param promoCode - * promo code to resolve - * @param cartTotal - * optional cart total used by the API to validate minimum cart total - * @return resolved discount license template or null if discount was not found - * @throws NetLicensingException - * any non-not-found service error - */ - public static LicenseTemplate resolve(final Context context, final String productNumber, final String promoCode, - final BigDecimal cartTotal) throws NetLicensingException { - CheckUtils.paramNotEmpty(productNumber, "productNumber"); - CheckUtils.paramNotEmpty(promoCode, "promoCode"); - - final Form form = new Form(); - if (cartTotal != null) { - form.param(Constants.Product.Discount.CART_TOTAL, cartTotal.toString()); - } - - try { - return NetLicensingService.getInstance().post(context, resolveEndpoint(productNumber, promoCode), form, - LicenseTemplate.class); - } catch (final ServiceException e) { - if (isNotFound(e)) { - return null; - } - throw e; - } - } - - private static String resolveEndpoint(final String productNumber, final String promoCode) { - return Constants.Product.ENDPOINT_PATH + "/" + productNumber + "/" - + Constants.Product.Discount.ENDPOINT_PATH + "/" + promoCode + "/" - + Constants.Product.Discount.ENDPOINT_RESOLVE_PATH; - } - - private static boolean isNotFound(final ServiceException e) { - return (e.getStatusCode() == HTTP_STATUS_NOT_FOUND) - || StringUtils.startsWith(e.getMessage(), NOT_FOUND_EXCEPTION_PREFIX); - } -} diff --git a/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/ProductService.java b/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/ProductService.java index a0eac02e..356cc473 100644 --- a/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/ProductService.java +++ b/NetLicensingClient/src/main/java/com/labs64/netlicensing/service/ProductService.java @@ -12,16 +12,20 @@ */ package com.labs64.netlicensing.service; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import com.labs64.netlicensing.domain.Constants; +import com.labs64.netlicensing.domain.entity.LicenseTemplate; import com.labs64.netlicensing.domain.entity.Product; import com.labs64.netlicensing.domain.vo.Context; import com.labs64.netlicensing.domain.vo.Page; import com.labs64.netlicensing.exception.NetLicensingException; +import com.labs64.netlicensing.exception.ServiceException; +import com.labs64.netlicensing.provider.Form; import com.labs64.netlicensing.util.CheckUtils; import com.labs64.netlicensing.util.ConvertUtils; @@ -38,6 +42,9 @@ */ public class ProductService { + private static final int HTTP_STATUS_NOT_FOUND = 404; + private static final String NOT_FOUND_EXCEPTION_PREFIX = "NotFoundException:"; + /** * Creates new product with given properties. * @@ -140,4 +147,69 @@ public static void delete(final Context context, final String number, final bool NetLicensingService.getInstance().delete(context, Constants.Product.ENDPOINT_PATH + "/" + number, params); } + /** + * Resolves a discount license template by product number and promo code. + * + * @param context + * determines the vendor on whose behalf the call is performed + * @param productNumber + * product number that defines promo code uniqueness scope + * @param promoCode + * promo code to resolve + * @return resolved discount license template or null if discount was not found + * @throws NetLicensingException + * any non-not-found service error + */ + public static LicenseTemplate resolveDiscount(final Context context, final String productNumber, + final String promoCode) throws NetLicensingException { + return resolveDiscount(context, productNumber, promoCode, null); + } + + /** + * Resolves a discount license template by product number and promo code. + * + * @param context + * determines the vendor on whose behalf the call is performed + * @param productNumber + * product number that defines promo code uniqueness scope + * @param promoCode + * promo code to resolve + * @param cartTotal + * optional cart total used by the API to validate minimum cart total + * @return resolved discount license template or null if discount was not found + * @throws NetLicensingException + * any non-not-found service error + */ + public static LicenseTemplate resolveDiscount(final Context context, final String productNumber, + final String promoCode, final BigDecimal cartTotal) throws NetLicensingException { + CheckUtils.paramNotEmpty(productNumber, "productNumber"); + CheckUtils.paramNotEmpty(promoCode, "promoCode"); + + final Form form = new Form(); + if (cartTotal != null) { + form.param(Constants.Product.Discount.CART_TOTAL, cartTotal.toString()); + } + + try { + return NetLicensingService.getInstance().post(context, resolveDiscountEndpoint(productNumber, promoCode), + form, LicenseTemplate.class); + } catch (final ServiceException e) { + if (isNotFound(e)) { + return null; + } + throw e; + } + } + + private static String resolveDiscountEndpoint(final String productNumber, final String promoCode) { + return Constants.Product.ENDPOINT_PATH + "/" + productNumber + "/" + + Constants.Product.Discount.ENDPOINT_PATH + "/" + promoCode + "/" + + Constants.Product.Discount.ENDPOINT_RESOLVE_PATH; + } + + private static boolean isNotFound(final ServiceException e) { + return (e.getStatusCode() == HTTP_STATUS_NOT_FOUND) + || StringUtils.startsWith(e.getMessage(), NOT_FOUND_EXCEPTION_PREFIX); + } + } diff --git a/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java b/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java deleted file mode 100644 index e5fd1fdd..00000000 --- a/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/DiscountServiceTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.labs64.netlicensing.service; - -import java.math.BigDecimal; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.core.Response; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.labs64.netlicensing.domain.Constants; -import com.labs64.netlicensing.domain.entity.LicenseTemplate; -import com.labs64.netlicensing.domain.vo.Context; -import com.labs64.netlicensing.domain.vo.LicenseType; -import com.labs64.netlicensing.exception.ServiceException; -import com.labs64.netlicensing.schema.SchemaFunction; -import com.labs64.netlicensing.schema.context.InfoEnum; -import com.labs64.netlicensing.schema.context.Item; -import com.labs64.netlicensing.schema.context.Netlicensing; -import com.labs64.netlicensing.schema.context.ObjectFactory; -import com.labs64.netlicensing.schema.context.Property; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Integration tests for {@link DiscountService}. - */ -public class DiscountServiceTest extends BaseServiceTest { - - private static final String PRODUCT_NUMBER = "P001-TEST"; - private static final String PROMO_CODE = "PROMO-001"; - private static final String LICENSE_TEMPLATE_NUMBER = "LT-DISCOUNT-001"; - private static final String MIN_CART_TOTAL = "minCartTotal"; - private static final String PROMO_CODE_PROPERTY = "promoCode"; - - private static Context context; - - @BeforeAll - public static void setup() { - context = createContext(); - } - - @BeforeEach - public void resetResourceState() { - DiscountServiceResource.lastProductNumber = null; - DiscountServiceResource.lastPromoCode = null; - DiscountServiceResource.lastCartTotal = null; - } - - @Test - public void testResolve() throws Exception { - final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, PROMO_CODE); - - assertNotNull(discount); - assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); - assertEquals("Promo discount", discount.getName()); - assertEquals(LicenseType.FEATURE, discount.getLicenseType()); - assertEquals("25", discount.getProperties().get("discountAmount")); - assertEquals(PROMO_CODE, discount.getProperties().get(PROMO_CODE_PROPERTY)); - assertEquals(PRODUCT_NUMBER, DiscountServiceResource.lastProductNumber); - assertEquals(PROMO_CODE, DiscountServiceResource.lastPromoCode); - assertNull(DiscountServiceResource.lastCartTotal); - } - - @Test - public void testResolveWithCartTotal() throws Exception { - final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, PROMO_CODE, - new BigDecimal("100.50")); - - assertNotNull(discount); - assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); - assertEquals("100.50", DiscountServiceResource.lastCartTotal); - } - - @Test - public void testResolveReturnsNullWhenDiscountDoesNotExist() throws Exception { - final LicenseTemplate discount = DiscountService.resolve(context, PRODUCT_NUMBER, "UNKNOWN"); - - assertNull(discount); - } - - @Test - public void testResolveRethrowsNonNotFoundServiceException() { - final ServiceException ex = assertThrows(ServiceException.class, - () -> DiscountService.resolve(context, PRODUCT_NUMBER, "BROKEN")); - - assertEquals("IllegalOperationException: Discount resolve failed", ex.getMessage()); - } - - @Override - protected Class getResourceClass() { - return DiscountServiceResource.class; - } - - @Path(REST_API_PATH + "/product/{productNumber}/discount/{promoCode}/resolve") - public static class DiscountServiceResource { - - private static String lastProductNumber; - private static String lastPromoCode; - private static String lastCartTotal; - - private final ObjectFactory objectFactory = new ObjectFactory(); - - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response resolve(@PathParam("productNumber") final String productNumber, - @PathParam("promoCode") final String promoCode, final MultivaluedMap formParams) { - lastProductNumber = productNumber; - lastPromoCode = promoCode; - lastCartTotal = formParams.getFirst(Constants.Product.Discount.CART_TOTAL); - - if ("UNKNOWN".equals(promoCode)) { - return notFoundResponse(); - } - if ("BROKEN".equals(promoCode)) { - return errorResponse("IllegalOperationException", "Discount resolve failed"); - } - - final Netlicensing netlicensing = objectFactory.createNetlicensing(); - netlicensing.setItems(objectFactory.createNetlicensingItems()); - - final Item item = objectFactory.createItem(); - item.setType("LicenseTemplate"); - netlicensing.getItems().getItem().add(item); - - addProperty(item, Constants.NUMBER, LICENSE_TEMPLATE_NUMBER); - addProperty(item, Constants.NAME, "Promo discount"); - addProperty(item, Constants.ACTIVE, Boolean.TRUE.toString()); - addProperty(item, Constants.LicenseTemplate.LICENSE_TYPE, LicenseType.FEATURE.value()); - addProperty(item, Constants.LicenseTemplate.AUTOMATIC, Boolean.FALSE.toString()); - addProperty(item, Constants.LicenseTemplate.HIDDEN, Boolean.TRUE.toString()); - addProperty(item, Constants.LicenseTemplate.HIDE_LICENSES, Boolean.TRUE.toString()); - addProperty(item, Constants.ProductModule.PRODUCT_MODULE_NUMBER, "PM-DISCOUNT-001"); - addProperty(item, PROMO_CODE_PROPERTY, promoCode); - addProperty(item, "discountAmount", "25"); - addProperty(item, MIN_CART_TOTAL, "100"); - - return Response.ok(netlicensing).build(); - } - - private Response notFoundResponse() { - final Netlicensing netlicensing = objectFactory.createNetlicensing(); - SchemaFunction.addInfo(netlicensing, "NotFoundException", InfoEnum.ERROR, - "Requested discount does not exist."); - return Response.status(Response.Status.NOT_FOUND).entity(netlicensing).build(); - } - - private Response errorResponse(final String exceptionId, final String errorMessage) { - final Netlicensing netlicensing = objectFactory.createNetlicensing(); - SchemaFunction.addInfo(netlicensing, exceptionId, InfoEnum.ERROR, errorMessage); - return Response.status(Response.Status.BAD_REQUEST).entity(netlicensing).build(); - } - - private void addProperty(final Item item, final String name, final String value) { - final Property property = new Property(value, name); - item.getProperty().add(property); - } - } -} diff --git a/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/ProductServiceTest.java b/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/ProductServiceTest.java index b96686bc..7d7d5809 100644 --- a/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/ProductServiceTest.java +++ b/NetLicensingClient/src/test/java/com/labs64/netlicensing/service/ProductServiceTest.java @@ -12,23 +12,36 @@ */ package com.labs64.netlicensing.service; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.labs64.netlicensing.domain.Constants; +import com.labs64.netlicensing.domain.entity.LicenseTemplate; import com.labs64.netlicensing.domain.entity.Product; import com.labs64.netlicensing.domain.entity.impl.ProductImpl; import com.labs64.netlicensing.domain.vo.Context; +import com.labs64.netlicensing.domain.vo.LicenseType; import com.labs64.netlicensing.domain.vo.Page; import com.labs64.netlicensing.exception.ServiceException; +import com.labs64.netlicensing.schema.SchemaFunction; +import com.labs64.netlicensing.schema.context.InfoEnum; +import com.labs64.netlicensing.schema.context.Item; +import com.labs64.netlicensing.schema.context.Netlicensing; +import com.labs64.netlicensing.schema.context.Property; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -43,6 +56,11 @@ public class ProductServiceTest extends BaseServiceTest { private static final String PRODUCT_CUSTOM_PROPERTY = "CustomProperty"; private static final String PRODUCT_DELETING_PROPERTY = "toBeDeleted"; + private static final String PRODUCT_NUMBER = "P001-TEST"; + private static final String PROMO_CODE = "PROMO-001"; + private static final String LICENSE_TEMPLATE_NUMBER = "LT-DISCOUNT-001"; + private static final String MIN_CART_TOTAL = "minCartTotal"; + private static final String PROMO_CODE_PROPERTY = "promoCode"; // *** NLIC Tests *** @@ -53,6 +71,13 @@ public static void setup() { context = createContext(); } + @BeforeEach + public void resetResourceState() { + ProductServiceResource.lastProductNumber = null; + ProductServiceResource.lastPromoCode = null; + ProductServiceResource.lastCartTotal = null; + } + @Test public void testCreate() throws Exception { final Product newProduct = new ProductImpl(); @@ -152,6 +177,46 @@ public void testDelete() throws Exception { assertEquals("NotFoundException: Requested product does not exist", e.getMessage()); } + @Test + public void testResolveDiscount() throws Exception { + final LicenseTemplate discount = ProductService.resolveDiscount(context, PRODUCT_NUMBER, PROMO_CODE); + + assertNotNull(discount); + assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); + assertEquals("Promo discount", discount.getName()); + assertEquals(LicenseType.FEATURE, discount.getLicenseType()); + assertEquals("25", discount.getProperties().get("discountAmount")); + assertEquals(PROMO_CODE, discount.getProperties().get(PROMO_CODE_PROPERTY)); + assertEquals(PRODUCT_NUMBER, ProductServiceResource.lastProductNumber); + assertEquals(PROMO_CODE, ProductServiceResource.lastPromoCode); + assertNull(ProductServiceResource.lastCartTotal); + } + + @Test + public void testResolveDiscountWithCartTotal() throws Exception { + final LicenseTemplate discount = ProductService.resolveDiscount(context, PRODUCT_NUMBER, PROMO_CODE, + new BigDecimal("100.50")); + + assertNotNull(discount); + assertEquals(LICENSE_TEMPLATE_NUMBER, discount.getNumber()); + assertEquals("100.50", ProductServiceResource.lastCartTotal); + } + + @Test + public void testResolveDiscountReturnsNullWhenDiscountDoesNotExist() throws Exception { + final LicenseTemplate discount = ProductService.resolveDiscount(context, PRODUCT_NUMBER, "UNKNOWN"); + + assertNull(discount); + } + + @Test + public void testResolveDiscountRethrowsNonNotFoundServiceException() { + final ServiceException ex = assertThrows(ServiceException.class, + () -> ProductService.resolveDiscount(context, PRODUCT_NUMBER, "BROKEN")); + + assertEquals("IllegalOperationException: Discount resolve failed", ex.getMessage()); + } + // *** NLIC test mock resource *** @Override @@ -162,6 +227,10 @@ protected java.lang.Class getResourceClass() { @Path(REST_API_PATH + "/product") public static class ProductServiceResource extends AbstractNLICServiceResource { + private static String lastProductNumber; + private static String lastPromoCode; + private static String lastCartTotal; + public ProductServiceResource() { super("product"); } @@ -183,6 +252,56 @@ public Response create(final MultivaluedMap formParams) { public Response delete(final String productNumber, final UriInfo uriInfo) { return delete(productNumber, "P001-TEST", uriInfo.getQueryParameters()); } + + @POST + @Path("{productNumber}/discount/{promoCode}/resolve") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response resolveDiscount(@PathParam("productNumber") final String productNumber, + @PathParam("promoCode") final String promoCode, final MultivaluedMap formParams) { + lastProductNumber = productNumber; + lastPromoCode = promoCode; + lastCartTotal = formParams.getFirst(Constants.Product.Discount.CART_TOTAL); + + if ("UNKNOWN".equals(promoCode)) { + return notFoundResponse(); + } + if ("BROKEN".equals(promoCode)) { + return errorResponse("IllegalOperationException", "Discount resolve failed"); + } + + final Netlicensing netlicensing = objectFactory.createNetlicensing(); + netlicensing.setItems(objectFactory.createNetlicensingItems()); + + final Item item = objectFactory.createItem(); + item.setType("LicenseTemplate"); + netlicensing.getItems().getItem().add(item); + + addProperty(item, Constants.NUMBER, LICENSE_TEMPLATE_NUMBER); + addProperty(item, Constants.NAME, "Promo discount"); + addProperty(item, Constants.ACTIVE, Boolean.TRUE.toString()); + addProperty(item, Constants.LicenseTemplate.LICENSE_TYPE, LicenseType.FEATURE.value()); + addProperty(item, Constants.LicenseTemplate.AUTOMATIC, Boolean.FALSE.toString()); + addProperty(item, Constants.LicenseTemplate.HIDDEN, Boolean.TRUE.toString()); + addProperty(item, Constants.LicenseTemplate.HIDE_LICENSES, Boolean.TRUE.toString()); + addProperty(item, Constants.ProductModule.PRODUCT_MODULE_NUMBER, "PM-DISCOUNT-001"); + addProperty(item, PROMO_CODE_PROPERTY, promoCode); + addProperty(item, "discountAmount", "25"); + addProperty(item, MIN_CART_TOTAL, "100"); + + return Response.ok(netlicensing).build(); + } + + private Response notFoundResponse() { + final Netlicensing netlicensing = objectFactory.createNetlicensing(); + SchemaFunction.addInfo(netlicensing, "NotFoundException", InfoEnum.ERROR, + "Requested discount does not exist."); + return Response.status(Response.Status.NOT_FOUND).entity(netlicensing).build(); + } + + private void addProperty(final Item item, final String name, final String value) { + final Property property = new Property(value, name); + item.getProperty().add(property); + } } }