Skip to content

Commit

Permalink
Merge cd3b128 into 3a23420
Browse files Browse the repository at this point in the history
  • Loading branch information
crisli001 authored Dec 22, 2024
2 parents 3a23420 + cd3b128 commit 804095d
Show file tree
Hide file tree
Showing 25 changed files with 690 additions and 11 deletions.
7 changes: 7 additions & 0 deletions projects/assets/src/translations/en/myAccount.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
"findProducts": "Find Products",
"status": "Status:",
"dialogTitle": "Coupon",
"claimCoupondialogTitle": "Add To Your Coupon List",
"claimCouponCode": {
"label": "Coupon Code",
"placeholder": "Enter the coupon code to claim a coupon"
},
"reset": "RESET",
"claim": "CLAIM",
"claimCustomerCoupon": "You have successfully claimed this coupon.",
"myCoupons": "My coupons",
"startDateAsc": "Start Date (ascending)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,14 @@ export interface FeatureTogglesInterface {
*/
enableConsecutiveCharactersPasswordRequirement?: boolean;

/**
* In CustomerCouponConnector, Enables claiming customer coupon with coupon code in httpRequest body with POST method.
*
* When set to `false`, claiming customer coupon works with coupon code as parameter in URL, which exposes sensitive data and has security risk.
* When set to `true`, claiming customer coupon works with coupon code in httpRequest body with POST method, which avoids security risk.
*/
enableClaimCustomerCouponWithCodeInRequestBody?: boolean;

/**
* Enables a validation that prevents new passwords from matching the current password
* in the password update form.
Expand Down Expand Up @@ -1069,4 +1077,5 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
a11yScrollToTopPositioning: false,
enableSecurePasswordValidation: false,
enableCarouselCategoryProducts: false,
enableClaimCustomerCouponWithCodeInRequestBody: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const defaultOccUserConfig: OccConfig = {
addressVerification: 'users/${userId}/addresses/verification',
customerCoupons: 'users/${userId}/customercoupons',
claimCoupon: 'users/${userId}/customercoupons/${couponCode}/claim',
claimCustomerCoupon: 'users/${userId}/customercoupons/claim',
couponNotification:
'users/${userId}/customercoupons/${couponCode}/notification',
notificationPreference: 'users/${userId}/notificationpreferences',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,45 @@ describe('OccCustomerCouponAdapter', () => {
mockReq.flush(customerCoupon2Customer);
});
});
describe('claim customer coupon with code in body', () => {
it('should claim a customer coupon with code in request body for a given user id', () => {
const customerCoupon: CustomerCoupon = {
couponId: couponCode,
name: 'coupon 1',
startDate: '',
endDate: '',
status: 'Effective',
description: '',
notificationOn: true,
};
const customerCoupon2Customer: CustomerCoupon2Customer = {
coupon: customerCoupon,
customer: {},
};

occCustomerCouponAdapter
.claimCustomerCouponWithCodeInBody(userId, couponCode)
.subscribe((result) => {
expect(result).toEqual(customerCoupon2Customer);
});

const mockReq = httpMock.expectOne((req) => {
return req.method === 'POST';
});

expect(occEnpointsService.buildUrl).toHaveBeenCalledWith(
'claimCustomerCoupon',
{
urlParams: {
userId: userId,
},
}
);

expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.body).toEqual({ couponCode: couponCode });
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush(customerCoupon2Customer);
});
});
});
15 changes: 15 additions & 0 deletions projects/core/src/occ/adapters/user/occ-customer-coupon.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ export class OccCustomerCouponAdapter implements CustomerCouponAdapter {
return this.http.post(url, { headers });
}

claimCustomerCouponWithCodeInBody(
userId: string,
codeVal: string
): Observable<CustomerCoupon2Customer> {
const url = this.occEndpoints.buildUrl('claimCustomerCoupon', {
urlParams: { userId },
});
const toClaim = {
couponCode: codeVal,
};
const headers = this.newHttpHeader();

return this.http.post(url, toClaim, { headers });
}

claimCustomerCoupon(
userId: string,
couponCode: string
Expand Down
6 changes: 6 additions & 0 deletions projects/core/src/occ/occ-models/occ-endpoints.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ export interface OccEndpoints {
* @member {string}
*/
claimCoupon?: string | OccEndpoint;
/**
* Endpoint for claiming coupon with code in request body
*
* @member {string}
*/
claimCustomerCoupon?: string | OccEndpoint;
/**
* Endpoint for coupons
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export abstract class CustomerCouponAdapter {
couponCode: string
): Observable<{}>;

abstract claimCustomerCouponWithCodeInBody(
userId: string,
couponVal: string
): Observable<CustomerCoupon2Customer>;

abstract claimCustomerCoupon(
userId: string,
couponCode: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { of } from 'rxjs';
import { CustomerCouponAdapter } from './customer-coupon.adapter';
import { CustomerCouponConnector } from './customer-coupon.connector';
import createSpy = jasmine.createSpy;
import { FeatureConfigService } from '../../../features-config/services/feature-config.service';

const PAGE_SIZE = 5;
const currentPage = 1;
Expand All @@ -21,6 +22,9 @@ class MockUserAdapter implements CustomerCouponAdapter {
claimCustomerCoupon = createSpy('claimCustomerCoupon').and.callFake(
(userId) => of(`claim-${userId}`)
);
claimCustomerCouponWithCodeInBody = createSpy(
'claimCustomerCouponWithCodeInBody'
).and.callFake((userId) => of(`claim-${userId}`));
disclaimCustomerCoupon = createSpy('disclaimCustomerCoupon').and.callFake(
(userId) => of(`disclaim-${userId}`)
);
Expand All @@ -29,6 +33,7 @@ class MockUserAdapter implements CustomerCouponAdapter {
describe('CustomerCouponConnector', () => {
let service: CustomerCouponConnector;
let adapter: CustomerCouponAdapter;
let featureConfigService: FeatureConfigService;

beforeEach(() => {
TestBed.configureTestingModule({
Expand All @@ -39,6 +44,7 @@ describe('CustomerCouponConnector', () => {

service = TestBed.inject(CustomerCouponConnector);
adapter = TestBed.inject(CustomerCouponAdapter);
featureConfigService = TestBed.inject(FeatureConfigService);
});

it('should be created', () => {
Expand Down Expand Up @@ -83,8 +89,9 @@ describe('CustomerCouponConnector', () => {
);
});

it('claimCustomerCoupon should call adapter', () => {
it('claimCustomerCoupon should call adapter.claimCustomerCoupon in case enableClaimCustomerCouponWithCodeInRequestBody is disabled', () => {
let result;
spyOn(featureConfigService, 'isEnabled').and.returnValue(false);
service
.claimCustomerCoupon('userId', 'couponCode')
.subscribe((res) => (result = res));
Expand All @@ -95,6 +102,19 @@ describe('CustomerCouponConnector', () => {
);
});

it('claimCustomerCoupon should call adapter.claimCustomerCouponWithCodeInBody in case enableClaimCustomerCouponWithCodeInRequestBody is enabled', () => {
let result;
spyOn(featureConfigService, 'isEnabled').and.returnValue(true);
service
.claimCustomerCoupon('userId', 'couponCode')
.subscribe((res) => (result = res));
expect(result).toEqual('claim-userId');
expect(adapter.claimCustomerCouponWithCodeInBody).toHaveBeenCalledWith(
'userId',
'couponCode'
);
});

it('disclaimCustomerCoupon should call adapter', () => {
let result;
service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import {
CustomerCoupon2Customer,
CustomerCouponNotification,
CustomerCouponSearchResult,
} from '../../../model/customer-coupon.model';
import { CustomerCouponAdapter } from './customer-coupon.adapter';
import { FeatureConfigService } from '../../../features-config/services/feature-config.service';

@Injectable({
providedIn: 'root',
})
export class CustomerCouponConnector {
private featureConfigService = inject(FeatureConfigService);
constructor(protected adapter: CustomerCouponAdapter) {}

getCustomerCoupons(
Expand All @@ -43,7 +45,15 @@ export class CustomerCouponConnector {
userId: string,
couponCode: string
): Observable<CustomerCoupon2Customer> {
return this.adapter.claimCustomerCoupon(userId, couponCode);
if (
this.featureConfigService.isEnabled(
'enableClaimCustomerCouponWithCodeInRequestBody'
)
) {
return this.adapter.claimCustomerCouponWithCodeInBody(userId, couponCode);
} else {
return this.adapter.claimCustomerCoupon(userId, couponCode);
}
}

disclaimCustomerCoupon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ context('Assisted Service Module', () => {
it('should be able to sent customer coupon for customer coupon (CXSPA-3945)', () => {
interceptPost(
'claim_customer_coupon',
'/users/*/customercoupons/*/claim?*'
'/users/*/customercoupons/claim?*'
);
cy.get('.cx-asm-customer-360-promotion-listing-row')
.contains(customer_coupon.name)
Expand All @@ -125,8 +125,10 @@ context('Assisted Service Module', () => {
'not.contain',
customer_coupon.name
);
cy.wait(5000);
});
it('should be able to remove customer coupon for customer coupon (CXSPA-3945)', () => {
cy.wait(3000);
cy.get('.cx-tab-header').contains('Sent').click();
interceptDelete(
'disclaim_customer_coupon',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ context('Assisted Service Module', () => {
it('should be able to sent customer coupon for customer coupon (CXSPA-3945)', () => {
interceptPost(
'claim_customer_coupon',
'/users/*/customercoupons/*/claim?*'
'/users/*/customercoupons/claim?*'
);
cy.get('.cx-asm-customer-360-promotion-listing-row')
.contains('Buy over $1000 get 20% off on cart')
Expand All @@ -304,8 +304,10 @@ context('Assisted Service Module', () => {
'not.contain',
'Buy over $1000 get 20% off on cart'
);
cy.wait(5000);
});
it('should be able to remove customer coupon for customer coupon (CXSPA-3945)', () => {
cy.wait(3000);
cy.get('.cx-tab-header').contains('Sent').click();
interceptDelete(
'disclaim_customer_coupon',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ viewportContext(['mobile', 'desktop'], () => {
});
});

describe('My coupons - claim coupons with code in body using authenticated user', () => {
beforeEach(() => {
cy.window().then((win) => {
win.sessionStorage.clear();
});
cy.requireLoggedIn();
});
myCoupons.testClaimCustomerCouponWithCodeInBody();
});

describe('My coupons - Authenticated user', () => {
beforeEach(() => {
cy.window().then((win) => {
Expand Down
Loading

0 comments on commit 804095d

Please sign in to comment.