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/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/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); + } } }