diff --git a/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.spec.ts b/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.spec.ts index dfa282aaaec..ee8ab1f336e 100644 --- a/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.spec.ts +++ b/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.spec.ts @@ -225,22 +225,6 @@ describe('ConfigureCartEntryComponent', () => { }); }); - describe('getOwnerType', () => { - it('should find correct default owner type', () => { - component.cartEntry.orderCode = undefined; - expect(component.getOwnerType()).toBe( - CommonConfigurator.OwnerType.CART_ENTRY - ); - }); - - it('should find correct owner type for entry belonging to order', () => { - component.cartEntry.orderCode = orderCode; - expect(component.getOwnerType()).toBe( - CommonConfigurator.OwnerType.ORDER_ENTRY - ); - }); - }); - describe('retrieveOwnerTypeFromAbstractOrderType', () => { it('should find correct owner type in case entry knows order', () => { component.readOnly = true; @@ -333,25 +317,6 @@ describe('ConfigureCartEntryComponent', () => { }); }); - describe('getEntityKey', () => { - it('should find correct entity key for cart entry', () => { - component.cartEntry = { entryNumber: 0 }; - expect(component.getEntityKey()).toBe('0'); - }); - - it('should throw error if entry number not present in entry', () => { - component.cartEntry = {}; - expect(() => component.getEntityKey()).toThrowError(); - }); - - it('should find correct entity key for order entry', () => { - component.cartEntry = { entryNumber: 0, orderCode: orderCode }; - - component.readOnly = true; - expect(component.getEntityKey()).toBe(orderCode + '+0'); - }); - }); - describe('getDisplayOnly', () => { it('should derive result from component if available', () => { component.readOnly = true; @@ -491,65 +456,62 @@ describe('ConfigureCartEntryComponent', () => { }); }); - describe('getQueryParams', () => { - it('should set "forceReload" parameter', () => { - expect(component.getQueryParams().forceReload).toBe(true); - }); - it('should not set "resolveIssues" parameter in case no issues exist', () => { - component.readOnly = false; - component.msgBanner = false; - component.cartEntry = { - entryNumber: 0, - product: { configuratorType: configuratorType }, - statusSummaryList: [], - }; - expect(component.getQueryParams().resolveIssues).toBe(false); + describe('queryParam$', () => { + it('should contain "navigateToCheckout" parameter in case the navigation to the cart is relevant', (done) => { + mockRouterState.state.semanticRoute = 'checkoutReviewOrder'; + component.queryParams$ + .pipe(take(1), delay(0)) + .subscribe((queryParams) => { + expect(queryParams.navigateToCheckout).toBe(true); + done(); + }); }); - it('should set "resolveIssues" parameter in case issues exist', () => { - component.readOnly = false; - component.msgBanner = true; + + it('should contain "productCode" parameter in case product code is relevant', (done) => { component.cartEntry = { entryNumber: 0, - product: { configuratorType: configuratorType }, - statusSummaryList: [ - { status: OrderEntryStatus.Error, numberOfIssues: 3 }, - ], + product: { configuratorType: configuratorType, code: productCode }, }; - expect(component.getQueryParams().resolveIssues).toBe(true); + fixture.detectChanges(); + component.queryParams$ + .pipe(take(1), delay(0)) + .subscribe((queryParams) => { + expect(queryParams.productCode).toBe(productCode); + done(); + }); }); - it('should not set "resolveIssues" parameter in case issues exist but component is not rendered in the context of the resolve issues banner', () => { + + it('should not contain "resolveIssues" parameter in case no issues exist', (done) => { component.readOnly = false; component.msgBanner = false; component.cartEntry = { entryNumber: 0, - product: { configuratorType: configuratorType }, - statusSummaryList: [ - { status: OrderEntryStatus.Error, numberOfIssues: 3 }, - ], + product: { configuratorType: configuratorType, code: productCode }, }; - expect(component.getQueryParams().resolveIssues).toBe(false); - }); - - it('should set "navigateToCheckout" parameter in case the navigation to the cart is relevant', (done) => { - mockRouterState.state.semanticRoute = 'checkoutReviewOrder'; + fixture.detectChanges(); component.queryParams$ .pipe(take(1), delay(0)) .subscribe((queryParams) => { - expect(queryParams.navigateToCheckout).toBe(true); + expect(queryParams.resolveIssues).toBe(false); done(); }); }); - it('should set "productCode" parameter in case product code is relevant', (done) => { + it('should contain "resolveIssues" parameter in case issues exist', (done) => { + component.readOnly = false; + component.msgBanner = true; component.cartEntry = { entryNumber: 0, product: { configuratorType: configuratorType, code: productCode }, + statusSummaryList: [ + { status: OrderEntryStatus.Error, numberOfIssues: 3 }, + ], }; fixture.detectChanges(); component.queryParams$ .pipe(take(1), delay(0)) .subscribe((queryParams) => { - expect(queryParams.productCode).toBe(productCode); + expect(queryParams.resolveIssues).toBe(true); done(); }); }); diff --git a/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.ts b/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.ts index 79c0c26eba3..c7a33c48ce6 100644 --- a/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.ts +++ b/feature-libs/product-configurator/common/components/configure-cart-entry/configure-cart-entry.component.ts @@ -10,7 +10,6 @@ import { Input, inject, } from '@angular/core'; -import { Params } from '@angular/router'; import { AbstractOrderKey, AbstractOrderType, @@ -70,18 +69,6 @@ export class ConfigureCartEntryComponent { return this.commonConfigUtilsService.hasIssues(this.cartEntry); } - /** - * @deprecated Use retrieveOwnerTypeFromAbstractOrderType instead - * Verifies whether the cart entry has an order code and returns a corresponding owner type. - * - * @returns - an owner type - */ - getOwnerType(): CommonConfigurator.OwnerType { - return this.cartEntry.orderCode !== undefined - ? CommonConfigurator.OwnerType.ORDER_ENTRY - : CommonConfigurator.OwnerType.CART_ENTRY; - } - /** * Retrieves owner for an abstract order type * @@ -106,27 +93,6 @@ export class ConfigureCartEntryComponent { } } - /** - * @deprecated Use retrieveEntityKey instead - * Verifies whether the cart entry has an order code, retrieves a composed owner ID - * and concatenates a corresponding entry number. - * - * @returns - an entry key - */ - getEntityKey(): string { - const entryNumber = this.cartEntry.entryNumber; - if (entryNumber === undefined) { - throw new Error('No entryNumber present in entry'); - } - - return this.cartEntry.orderCode - ? this.commonConfigUtilsService.getComposedOwnerId( - this.cartEntry.orderCode, - entryNumber - ) - : entryNumber.toString(); - } - /** * Verifies whether the cart entry has an order code, retrieves a composed owner ID * and concatenates a corresponding entry number. @@ -191,22 +157,6 @@ export class ConfigureCartEntryComponent { return !this.getDisplayOnly() && this.msgBanner ? errorMsgId : undefined; } - /** - * @deprecated since 2211.24 use instead queryParams$ - * - * Compiles query parameters for the router link. - * 'resolveIssues' is only set if the component is - * rendered in the context of the message banner, and if issues exist at all - * - * @returns Query parameters - */ - getQueryParams(): Params { - return { - forceReload: true, - resolveIssues: this.msgBanner && this.hasIssues(), - }; - } - protected isInCheckout(): Observable { return this.routingService.getRouterState().pipe( map((routerState) => { diff --git a/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.spec.ts b/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.spec.ts index 706d81bd630..6e56fd74059 100644 --- a/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.spec.ts +++ b/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.spec.ts @@ -117,7 +117,6 @@ describe('ConfigAttributeHeaderComponent', () => { const testConfiguratorUISettings: ConfiguratorUISettingsConfig = { productConfigurator: { - enableNavigationToConflict: false, descriptions: { attributeDescriptionLength: 100, valueDescriptionLength: 70, @@ -690,27 +689,16 @@ describe('ConfigAttributeHeaderComponent', () => { }); describe('Get conflict message key', () => { - it("should return 'configurator.conflict.conflictDetected' conflict message key", () => { + it("should return 'configurator.conflict.viewConflictDetails' conflict message key for attribute groups", () => { component.groupType = Configurator.GroupType.ATTRIBUTE_GROUP; - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = false; - fixture.detectChanges(); - expect(component.getConflictMessageKey()).toEqual( - 'configurator.conflict.conflictDetected' - ); - }); - it("should return 'configurator.conflict.viewConflictDetails' conflict message key", () => { - component.groupType = Configurator.GroupType.ATTRIBUTE_GROUP; - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = true; fixture.detectChanges(); expect(component.getConflictMessageKey()).toEqual( 'configurator.conflict.viewConflictDetails' ); }); - it("should return 'configurator.conflict.viewConfigurationDetails' conflict message key", () => { + it("should return 'configurator.conflict.viewConfigurationDetails' conflict message key for conflict groups", () => { component.groupType = Configurator.GroupType.CONFLICT_GROUP; fixture.detectChanges(); expect(component.getConflictMessageKey()).toEqual( @@ -1076,32 +1064,11 @@ describe('ConfigAttributeHeaderComponent', () => { }); describe('isNavigationToConflictEnabled', () => { - it('should return false if productConfigurator setting is not provided', () => { - configuratorUISettingsConfig.productConfigurator = undefined; - expect(component.isNavigationToConflictEnabled()).toBeFalsy(); - }); - - it('should return false if enableNavigationToConflict setting is not provided', () => { - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = undefined; - expect(component.isNavigationToConflictEnabled()).toBeFalsy(); - }); - - it('should return true if enableNavigationToConflict setting is true', () => { - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = true; + it('should return true if isNavigationToGroupEnabled is true', () => { expect(component.isNavigationToConflictEnabled()).toBe(true); }); - it('should return false if enableNavigationToConflict setting is false', () => { - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = false; - expect(component.isNavigationToConflictEnabled()).toBe(false); - }); - - it('should return false if enableNavigationToConflict setting is true and isNavigationToGroupEnabled is false', () => { - (configuratorUISettingsConfig.productConfigurator ??= - {}).enableNavigationToConflict = true; + it('should return false if isNavigationToGroupEnabled is false', () => { component.isNavigationToGroupEnabled = false; fixture.detectChanges(); expect(component.isNavigationToConflictEnabled()).toBe(false); diff --git a/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.ts b/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.ts index 2f2506ef17d..65e9d3130e4 100644 --- a/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.ts +++ b/feature-libs/product-configurator/rulebased/components/attribute/header/configurator-attribute-header.component.ts @@ -216,7 +216,7 @@ export class ConfiguratorAttributeHeaderComponent this.scrollToAttribute(this.attribute.name); } else { this.logError( - 'Attribute was not found in any conflict group. Note that for this navigation, commerce 22.05 or later is required. Consider to disable setting "enableNavigationToConflict"' + 'Attribute was not found in any conflict group. Note that for this navigation, commerce 22.05 or later is required.' ); } }); @@ -293,12 +293,7 @@ export class ConfiguratorAttributeHeaderComponent * @returns {boolean} true only if navigation to conflict groups is enabled. */ isNavigationToConflictEnabled(): boolean { - return ( - (this.isNavigationToGroupEnabled && - this.configuratorUISettingsConfig.productConfigurator - ?.enableNavigationToConflict) ?? - false - ); + return this.isNavigationToGroupEnabled; } /** diff --git a/feature-libs/product-configurator/rulebased/components/config/configurator-ui-settings.config.ts b/feature-libs/product-configurator/rulebased/components/config/configurator-ui-settings.config.ts index 4c02dd700ee..d2cbd989f5e 100644 --- a/feature-libs/product-configurator/rulebased/components/config/configurator-ui-settings.config.ts +++ b/feature-libs/product-configurator/rulebased/components/config/configurator-ui-settings.config.ts @@ -14,7 +14,6 @@ export interface ProductConfiguratorUISettingsConfig { date?: number; }; addRetractOption?: boolean; - enableNavigationToConflict?: boolean; descriptions?: { attributeDescriptionLength?: number; valueDescriptionLength?: number; diff --git a/feature-libs/product-configurator/rulebased/components/config/default-configurator-ui-settings.config.ts b/feature-libs/product-configurator/rulebased/components/config/default-configurator-ui-settings.config.ts index cd107e3a217..1c3b6252dd8 100644 --- a/feature-libs/product-configurator/rulebased/components/config/default-configurator-ui-settings.config.ts +++ b/feature-libs/product-configurator/rulebased/components/config/default-configurator-ui-settings.config.ts @@ -15,7 +15,6 @@ export const defaultConfiguratorUISettingsConfig: ConfiguratorUISettingsConfig = date: 1500, }, addRetractOption: false, - enableNavigationToConflict: true, descriptions: { attributeDescriptionLength: 100, valueDescriptionLength: 70, diff --git a/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.spec.ts b/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.spec.ts index 9903f65d214..ca8b3b3d11d 100644 --- a/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.spec.ts +++ b/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.spec.ts @@ -190,20 +190,6 @@ describe('ConfigTabBarComponent', () => { expect(htmlElem.querySelectorAll('a').length).toEqual(0); }); - it('should tell from semantic route that we are on OV page', () => { - mockRouterState.state.semanticRoute = CONFIG_OVERVIEW_ROUTE; - component.isOverviewPage$ - .subscribe((isOv) => expect(isOv).toBe(true)) - .unsubscribe(); - }); - - it('should tell from semantic route that we are on config page', () => { - mockRouterState.state.semanticRoute = CONFIGURATOR_ROUTE; - component.isOverviewPage$ - .subscribe((isOv) => expect(isOv).toBe(false)) - .unsubscribe(); - }); - it('should return proper page type from route', () => { mockRouterState.state.semanticRoute = CONFIG_OVERVIEW_ROUTE; component.pageType$ @@ -378,18 +364,6 @@ describe('ConfigTabBarComponent', () => { }); }); - describe('getTabIndexOverviewTab', () => { - it('should return tabindex 0 if on overview page', () => { - mockRouterState.state.semanticRoute = CONFIG_OVERVIEW_ROUTE; - expect(component.getTabIndexOverviewTab()).toBe(0); - }); - - it('should return tabindex -1 if on configuration page', () => { - mockRouterState.state.semanticRoute = CONFIGURATOR_ROUTE; - expect(component.getTabIndexOverviewTab()).toBe(-1); - }); - }); - describe('getTabIndexForOverviewTab', () => { it('should return tabindex 0 if on overview page', () => { expect( @@ -408,18 +382,6 @@ describe('ConfigTabBarComponent', () => { }); }); - describe('getTabIndexConfigTab', () => { - it('should return tabindex -1 if on overview page', () => { - mockRouterState.state.semanticRoute = CONFIG_OVERVIEW_ROUTE; - expect(component.getTabIndexConfigTab()).toBe(-1); - }); - - it('should return tabindex 0 if on configuration page', () => { - mockRouterState.state.semanticRoute = CONFIGURATOR_ROUTE; - expect(component.getTabIndexConfigTab()).toBe(0); - }); - }); - describe('getTabIndeForConfigTab', () => { it('should return tabindex -1 if on overview page', () => { expect( diff --git a/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.ts b/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.ts index 39643e3eec0..093d8ea322f 100644 --- a/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.ts +++ b/feature-libs/product-configurator/rulebased/components/tab-bar/configurator-tab-bar.component.ts @@ -54,16 +54,6 @@ export class ConfiguratorTabBarComponent { ) ); - /** - * @deprecated Use getPageType$ and isOverviewPage(ConfiguratorRouter.PageType) - * instead - */ - isOverviewPage$: Observable = this.routerData$.pipe( - map( - (routerData) => - routerData.pageType === ConfiguratorRouter.PageType.OVERVIEW - ) - ); /** * Retrieves current page type. * @@ -176,24 +166,6 @@ export class ConfiguratorTabBarComponent { }); } - /** - * @deprecated Use getTabIndexForConfigTab instead. - * - * Returns the tabindex for the configuration tab. - * - * The configuration tab is excluded from the tab chain if currently the overview page is displayed. - * @returns tabindex of the configuration tab - */ - getTabIndexConfigTab(): number { - let tabIndex = 0; - this.isOverviewPage$.pipe(take(1)).subscribe((isOvPage) => { - if (isOvPage) { - tabIndex = -1; - } - }); - return tabIndex; - } - /** * Returns the tabindex for the configuration tab. * @@ -205,23 +177,6 @@ export class ConfiguratorTabBarComponent { return this.isOverviewPage(pageType) ? -1 : 0; } - /** - * @deprecated Use getTabIndexForOverviewTab instead. - * - * - * Returns the tabindex for the overview tab. - * The overview tab is excluded from the tab chain if currently the configuration page is displayed. - * @returns tabindex of the overview tab - */ - getTabIndexOverviewTab(): number { - let tabIndex = 0; - this.isOverviewPage$.pipe(take(1)).subscribe((isOvPage) => { - if (!isOvPage) { - tabIndex = -1; - } - }); - return tabIndex; - } /** * Returns the tabindex for the overview tab. * The overview tab is excluded from the tab chain if currently the configuration page is displayed. diff --git a/feature-libs/product-configurator/rulebased/core/config/configurator-core.config.ts b/feature-libs/product-configurator/rulebased/core/config/configurator-core.config.ts index 3288ceeef8c..8e05361a00e 100644 --- a/feature-libs/product-configurator/rulebased/core/config/configurator-core.config.ts +++ b/feature-libs/product-configurator/rulebased/core/config/configurator-core.config.ts @@ -9,7 +9,6 @@ import { Config } from '@spartacus/core'; export interface ProductConfiguratorCoreConfig { enableVariantSearch?: boolean; - cpqOverOcc?: boolean; } @Injectable({ diff --git a/feature-libs/product-configurator/rulebased/core/config/default-configurator-core.config.ts b/feature-libs/product-configurator/rulebased/core/config/default-configurator-core.config.ts index 23a16f62c69..8a0ec0db723 100644 --- a/feature-libs/product-configurator/rulebased/core/config/default-configurator-core.config.ts +++ b/feature-libs/product-configurator/rulebased/core/config/default-configurator-core.config.ts @@ -9,6 +9,5 @@ import { ConfiguratorCoreConfig } from './configurator-core.config'; export const defaultConfiguratorCoreConfig: ConfiguratorCoreConfig = { productConfigurator: { enableVariantSearch: false, - cpqOverOcc: true, }, }; diff --git a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.adapter.ts b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.adapter.ts index 7bb9345f5aa..5fc27314651 100644 --- a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.adapter.ts +++ b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.adapter.ts @@ -117,11 +117,6 @@ export abstract class RulebasedConfiguratorAdapter { */ abstract getConfiguratorType(): string; - /** - * Abstract method to check if the adapter supports to call CPQ over OCC. Only relevant for adapters supporting @see ConfiguratorType.CPQ - */ - abstract supportsCpqOverOcc?(): boolean; - /** * Searches for variants that are matching the configuration identified by its id. * Matches will be close to the attribute assignments of the configuration, but diff --git a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.spec.ts b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.spec.ts index 4153c47a553..31ac4467977 100644 --- a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.spec.ts +++ b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.spec.ts @@ -61,7 +61,6 @@ const cartModification: CartModification = {}; class MockRulebasedConfiguratorAdapter implements RulebasedConfiguratorAdapter { public configuratorType: string; - public cpqOverOcc: boolean | undefined; readConfigurationForCartEntry = createSpy().and.callFake(() => of(productConfiguration) @@ -108,42 +107,26 @@ class MockRulebasedConfiguratorAdapter implements RulebasedConfiguratorAdapter { getConfiguratorType(): string { return this.configuratorType ?? CONFIGURATOR_TYPE; } - supportsCpqOverOcc?(): boolean { - return this.cpqOverOcc ?? false; - } } describe('RulebasedConfiguratorConnector', () => { let service: RulebasedConfiguratorConnector; let configuratorUtils: CommonConfiguratorUtilsService; let adapter: RulebasedConfiguratorAdapter[]; - let configuratorCpqConfig: ConfiguratorCoreConfig; const GROUP_ID = 'GROUP1'; const QUANTITY = 1; const MockConfig: ConfiguratorCoreConfig = { - productConfigurator: { - cpqOverOcc: true, - }, + productConfigurator: {}, }; - function setCpqOverOcc(cpqOverOcc: boolean | undefined) { - (configuratorCpqConfig.productConfigurator ?? {}).cpqOverOcc = cpqOverOcc; - } - function createMockAdapter( - configuratorType?: string, - supportsCpqOverOcc?: boolean + configuratorType?: string ): RulebasedConfiguratorAdapter { let adapter = new MockRulebasedConfiguratorAdapter(); adapter.configuratorType = configuratorType ?? CONFIGURATOR_TYPE; - if (supportsCpqOverOcc) { - adapter.cpqOverOcc = supportsCpqOverOcc; - } else { - adapter.supportsCpqOverOcc = undefined; - } return adapter; } @@ -174,11 +157,8 @@ describe('RulebasedConfiguratorConnector', () => { adapter = TestBed.inject( RulebasedConfiguratorConnector.CONFIGURATOR_ADAPTER_LIST ); - configuratorCpqConfig = TestBed.inject( - ConfiguratorCoreConfig as Type - ); + configuratorUtils.setOwnerKey(productConfiguration.owner); - setCpqOverOcc(false); }); it('should be created', () => { @@ -415,29 +395,5 @@ describe('RulebasedConfiguratorConnector', () => { const adapter = createMockAdapter(ConfiguratorType.VARIANT); expect(isAdapterMatching(adapter, ConfiguratorType.CPQ)).toBe(false); }); - - it("shouldn't match if CPQ configurator type with cpqOverOcc flag is requested, but adapter doesn't support it", () => { - setCpqOverOcc(true); - const adapter = createMockAdapter(ConfiguratorType.CPQ, false); - expect(isAdapterMatching(adapter, ConfiguratorType.CPQ)).toBe(false); - }); - - it("shouldn't match if CPQ configurator type without cpqOverOcc flag is requested, but adapter supports it", () => { - setCpqOverOcc(false); - const adapter = createMockAdapter(ConfiguratorType.CPQ, true); - expect(isAdapterMatching(adapter, ConfiguratorType.CPQ)).toBe(false); - }); - - it('should match if CPQ configurator type with cpqOverOcc flag is requested and adapter supports it', () => { - setCpqOverOcc(true); - const adapter = createMockAdapter(ConfiguratorType.CPQ, true); - expect(isAdapterMatching(adapter, ConfiguratorType.CPQ)).toBe(true); - }); - - it("should match if CPQ configurator type without cpqOverOcc flag is requested and adapter doesn't support it", () => { - setCpqOverOcc(false); - const adapter = createMockAdapter(ConfiguratorType.CPQ, false); - expect(isAdapterMatching(adapter, ConfiguratorType.CPQ)).toBe(true); - }); }); }); diff --git a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.ts b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.ts index 609e2e6a947..2106a772b9e 100644 --- a/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.ts +++ b/feature-libs/product-configurator/rulebased/core/connectors/rulebased-configurator.connector.ts @@ -9,7 +9,6 @@ import { CartModification } from '@spartacus/cart/base/root'; import { CommonConfigurator, CommonConfiguratorUtilsService, - ConfiguratorType, } from '@spartacus/product-configurator/common'; import { Observable } from 'rxjs'; import { ConfiguratorCoreConfig } from '../config/configurator-core.config'; @@ -147,14 +146,6 @@ export class RulebasedConfiguratorConnector { adapter: RulebasedConfiguratorAdapter, configuratorType: string ): boolean { - let matching = adapter.getConfiguratorType() === configuratorType; - if (matching && ConfiguratorType.CPQ === configuratorType) { - const isCpqOverOccRequested = - this.config.productConfigurator?.cpqOverOcc ?? false; - const isCpqOverOccSupported = - !!adapter.supportsCpqOverOcc && adapter.supportsCpqOverOcc(); - matching = isCpqOverOccRequested === isCpqOverOccSupported; - } - return matching; + return adapter.getConfiguratorType() === configuratorType; } } diff --git a/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.spec.ts b/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.spec.ts index 3c5fb1f75dc..ccb19b022bc 100644 --- a/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.spec.ts +++ b/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.spec.ts @@ -170,10 +170,6 @@ describe('CpqConfiguratorOccAdapter', () => { ); }); - it('should state that this adapter supports CPQ API calls over OCC', () => { - expect(adapterUnderTest.supportsCpqOverOcc()).toBe(true); - }); - it('should delegate create configuration to OCC service and map owner', () => { adapterUnderTest.createConfiguration(owner).subscribe((config) => { expect(config.owner).toEqual(owner); diff --git a/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.ts b/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.ts index 4786a4be680..6f4b78f0a7a 100644 --- a/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.ts +++ b/feature-libs/product-configurator/rulebased/cpq/occ/cpq-configurator-occ.adapter.ts @@ -26,10 +26,6 @@ export class CpqConfiguratorOccAdapter implements RulebasedConfiguratorAdapter { return ConfiguratorType.CPQ; } - supportsCpqOverOcc(): boolean { - return true; - } - createConfiguration( owner: CommonConfigurator.Owner ): Observable { diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.config.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.config.ts deleted file mode 100644 index c8bd22d5654..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.config.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Injectable } from '@angular/core'; -import { Config } from '@spartacus/core'; - -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -interface CpqConfiguratorBackendConfig { - cpq?: { - endpoints: { - configurationInit: string; - configurationDisplay: string; - attributeUpdate: string; - valueUpdate: string; - }; - prefix: string; - }; -} - -@Injectable({ - providedIn: 'root', - useExisting: Config, -}) -export abstract class CpqConfiguratorEndpointConfig { - backend?: CpqConfiguratorBackendConfig; -} - -declare module '@spartacus/core' { - interface BackendConfig extends CpqConfiguratorBackendConfig {} -} diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.spec.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.spec.ts deleted file mode 100644 index d2c3685157f..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { MARKER_HEADER_CPQ_CONFIGURATOR } from '@spartacus/product-configurator/rulebased/root'; -import { CpqConfiguratorEndpointConfig } from './cpq-configurator-endpoint.config'; -import { CpqConfiguratorEndpointService } from './cpq-configurator-endpoint.service'; -import { defaultCpqConfiguratorEndpointConfig } from './default-cpq-configurator-endpoint.config'; - -describe('CpqConfiguratorEndpointService', () => { - let classUnderTest: CpqConfiguratorEndpointService; - const CONFIG_ID = 'c-123'; - const ATTR_ID = 'a-456'; - const VALUE_ID = 'v-789'; - const TAB_ID = '5'; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - { - provide: CpqConfiguratorEndpointConfig, - useValue: defaultCpqConfiguratorEndpointConfig, - }, - ], - }); - classUnderTest = TestBed.inject(CpqConfiguratorEndpointService); - }); - - it('should be created', () => { - expect(classUnderTest).toBeTruthy(); - }); - - it('should return CPQ Marker header attribute', () => { - expect( - classUnderTest.CPQ_MARKER_HEADER.headers.has( - MARKER_HEADER_CPQ_CONFIGURATOR - ) - ).toBe(true); - }); - - it('should build configurations init url', () => { - expect(classUnderTest.buildUrl('configurationInit')).toBe( - '/api/configuration/v1/configurations' - ); - }); - - it('should build configurations display url', () => { - expect( - classUnderTest.buildUrl('configurationDisplay', { configId: CONFIG_ID }) - ).toBe(`/api/configuration/v1/configurations/${CONFIG_ID}/display`); - }); - - it('should build configurations display url with tab id', () => { - expect( - classUnderTest.buildUrl('configurationDisplay', { configId: CONFIG_ID }, [ - { name: 'tabId', value: TAB_ID }, - ]) - ).toBe( - `/api/configuration/v1/configurations/${CONFIG_ID}/display?tabId=${TAB_ID}` - ); - }); - - it('should build configurations update attribute url', () => { - expect( - classUnderTest.buildUrl('attributeUpdate', { - configId: CONFIG_ID, - attributeCode: ATTR_ID, - }) - ).toBe( - `/api/configuration/v1/configurations/${CONFIG_ID}/attributes/${ATTR_ID}` - ); - }); - - it('should build configurations update value url', () => { - expect( - classUnderTest.buildUrl('valueUpdate', { - configId: CONFIG_ID, - attributeCode: ATTR_ID, - valueCode: VALUE_ID, - }) - ).toBe( - `/api/configuration/v1/configurations/${CONFIG_ID}/attributes/${ATTR_ID}/attributeValues/${VALUE_ID}` - ); - }); -}); diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.ts deleted file mode 100644 index 48d899c5a34..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-endpoint.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { HttpHeaders } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; -import { LoggerService, StringTemplate } from '@spartacus/core'; -import { MARKER_HEADER_CPQ_CONFIGURATOR } from '@spartacus/product-configurator/rulebased/root'; -import { CpqConfiguratorEndpointConfig } from './cpq-configurator-endpoint.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable({ providedIn: 'root' }) -export class CpqConfiguratorEndpointService { - protected logger = inject(LoggerService); - - constructor(protected config: CpqConfiguratorEndpointConfig) {} - - /** - * header attribute to a mark cpq related requests, so that they can be picked up by the {@link CpqConfiguratorRestInterceptor} - */ - readonly CPQ_MARKER_HEADER = { - headers: new HttpHeaders({ - [MARKER_HEADER_CPQ_CONFIGURATOR]: 'x', - }), - }; - - buildUrl( - endpointName: string, - urlParams?: Object, - queryParams?: [{ name: string; value: string }] - ): string { - const endpoints = this.config.backend?.cpq?.endpoints; - let endpoint; - switch (endpointName) { - case 'configurationInit': - endpoint = endpoints?.configurationInit; - break; - case 'configurationDisplay': - endpoint = endpoints?.configurationDisplay; - break; - case 'attributeUpdate': - endpoint = endpoints?.attributeUpdate; - break; - case 'valueUpdate': - endpoint = endpoints?.valueUpdate; - } - - if (!endpoint) { - endpoint = 'configurations'; - this.logger.warn( - `${endpointName} endpoint configuration missing for cpq backend, please provide it via key: "backend.cpq.endpoints.${endpointName}"` - ); - } - let url = this.config.backend?.cpq?.prefix + endpoint; - url = urlParams ? StringTemplate.resolve(url, urlParams) : url; - url = queryParams ? this.appendQueryParameters(url, queryParams) : url; - return url; - } - - protected appendQueryParameters( - url: string, - parameters: [{ name: string; value: string }] - ): string { - let urlWithParameters = url + '?'; - parameters.forEach((param, idx: number) => { - urlWithParameters = idx > 0 ? urlWithParameters + '&' : urlWithParameters; - urlWithParameters = `${urlWithParameters}${param.name}=${param.value}`; - }); - return urlWithParameters; - } -} diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.spec.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.spec.ts deleted file mode 100644 index 438fd055195..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.spec.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { Type } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { CartModification } from '@spartacus/cart/base/root'; -import { - CommonConfigurator, - ConfiguratorModelUtils, - ConfiguratorType, -} from '@spartacus/product-configurator/common'; -import { Configurator } from '@spartacus/product-configurator/rulebased'; -import { of } from 'rxjs'; -import { ConfiguratorTestUtils } from '../../testing/configurator-test-utils'; -import { CpqConfiguratorOccService } from './../occ/cpq-configurator-occ.service'; -import { CpqConfiguratorRestAdapter } from './cpq-configurator-rest.adapter'; -import { CpqConfiguratorRestService } from './cpq-configurator-rest.service'; -import { - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http'; - -const productCode = 'CONF_LAPTOP'; -const configId = '1234-56-7890'; -const userId = 'Anony'; -const documentId = '82736353'; - -const owner: CommonConfigurator.Owner = { - type: CommonConfigurator.OwnerType.PRODUCT, - id: productCode, - key: ConfiguratorModelUtils.getOwnerKey( - CommonConfigurator.OwnerType.PRODUCT, - productCode - ), - configuratorType: ConfiguratorType.CPQ, -}; - -const productConfiguration: Configurator.Configuration = { - ...ConfiguratorTestUtils.createConfiguration(configId, owner), - productCode: productCode, -}; - -const groupId = '123'; - -const inputForUpdateConfiguration: Configurator.Configuration = { - ...ConfiguratorTestUtils.createConfiguration(configId, owner), - productCode: productCode, -}; - -const addToCartParams: Configurator.AddToCartParameters = { - productCode: productCode, - quantity: 1, - configId: configId, - owner: owner, - userId: userId, - cartId: documentId, -}; - -const updateCartParams: Configurator.UpdateConfigurationForCartEntryParameters = - { - userId: userId, - cartId: documentId, - cartEntryNumber: '3', - configuration: ConfiguratorTestUtils.createConfiguration(configId, owner), - }; - -const cartResponse: CartModification = { - quantityAdded: 1, - entry: { entryNumber: 3 }, - statusCode: '201', -}; - -const readConfigCartParams: CommonConfigurator.ReadConfigurationFromCartEntryParameters = - { - userId: userId, - cartId: documentId, - cartEntryNumber: '3', - owner: owner, - }; - -const readConfigOrderEntryParams: CommonConfigurator.ReadConfigurationFromOrderEntryParameters = - { - userId: userId, - orderId: documentId, - orderEntryNumber: '3', - owner: owner, - }; - -const asSpy = (f: any) => f; - -describe('CpqConfiguratorRestAdapter', () => { - let adapterUnderTest: CpqConfiguratorRestAdapter; - let mockedRestService: CpqConfiguratorRestService; - let mockedOccService: CpqConfiguratorOccService; - - beforeEach(() => { - mockedRestService = jasmine.createSpyObj('mockedRestService', [ - 'createConfiguration', - 'readConfiguration', - 'updateAttribute', - 'updateValueQuantity', - 'readConfigurationOverview', - ]); - mockedOccService = jasmine.createSpyObj('mockedOccService', [ - 'addToCart', - 'getConfigIdForCartEntry', - 'getConfigIdForOrderEntry', - 'updateCartEntry', - ]); - - asSpy(mockedRestService.createConfiguration).and.callFake(() => { - return of(productConfiguration); - }); - - asSpy(mockedRestService.readConfiguration).and.callFake(() => { - return of(productConfiguration); - }); - - asSpy(mockedRestService.updateAttribute).and.callFake(() => { - return of(productConfiguration); - }); - asSpy(mockedRestService.updateValueQuantity).and.callFake(() => { - return of(productConfiguration); - }); - asSpy(mockedRestService.readConfigurationOverview).and.callFake(() => { - return of(productConfiguration); - }); - asSpy(mockedOccService.addToCart).and.callFake(() => { - return of(cartResponse); - }); - asSpy(mockedOccService.getConfigIdForCartEntry).and.callFake(() => { - return of(productConfiguration.configId); - }); - asSpy(mockedOccService.getConfigIdForOrderEntry).and.callFake(() => { - return of(productConfiguration.configId); - }); - asSpy(mockedOccService.updateCartEntry).and.callFake(() => { - return of(cartResponse); - }); - asSpy(mockedOccService.getConfigIdForCartEntry).and.callFake(() => { - return of(productConfiguration.configId); - }); - - TestBed.configureTestingModule({ - imports: [], - providers: [ - CpqConfiguratorRestAdapter, - { - provide: CpqConfiguratorRestService, - useValue: mockedRestService, - }, - { - provide: CpqConfiguratorOccService, - useValue: mockedOccService, - }, - provideHttpClient(withInterceptorsFromDi()), - provideHttpClientTesting(), - ], - }); - - adapterUnderTest = TestBed.inject( - CpqConfiguratorRestAdapter as Type - ); - - inputForUpdateConfiguration.updateType = Configurator.UpdateType.ATTRIBUTE; - }); - - it('should return correct configurator type', () => { - expect(adapterUnderTest.getConfiguratorType()).toEqual( - ConfiguratorType.CPQ - ); - }); - - it('should delegate create configuration to rest service and map owner', () => { - adapterUnderTest.createConfiguration(owner).subscribe((config) => { - expect(config.owner).toEqual(owner); - expect(mockedRestService.createConfiguration).toHaveBeenCalledWith( - productCode - ); - }); - }); - - it('should delegate read configuration to rest service and map owner', () => { - adapterUnderTest - .readConfiguration(productConfiguration.configId, groupId, owner) - .subscribe((config) => { - expect(config.owner).toEqual(owner); - expect(mockedRestService.readConfiguration).toHaveBeenCalledWith( - productConfiguration.configId, - groupId - ); - }); - }); - - // this ensures that there is a dummy response until the API is implemented, - // otherwise this leads to an NPE on the UI - it('should always return same configuration for price summary', () => { - adapterUnderTest - .readPriceSummary(productConfiguration) - .subscribe((config) => { - expect(config).toBe(productConfiguration); - }); - }); - - it('should delegate update configuration to rest service and map owner', () => { - adapterUnderTest - .updateConfiguration(inputForUpdateConfiguration) - .subscribe((config) => { - expect(config.owner).toEqual(owner); - expect(mockedRestService.updateAttribute).toHaveBeenCalledWith( - inputForUpdateConfiguration - ); - }); - }); - - it('should throw error in case overview is to be updated', () => { - expect(() => adapterUnderTest.updateConfigurationOverview()).toThrowError(); - }); - - it('should delegate update quantity configuration to rest service and map owner', () => { - inputForUpdateConfiguration.updateType = - Configurator.UpdateType.VALUE_QUANTITY; - adapterUnderTest - .updateConfiguration(inputForUpdateConfiguration) - .subscribe((config) => { - expect(config.owner).toEqual(owner); - expect(mockedRestService.updateValueQuantity).toHaveBeenCalledWith( - inputForUpdateConfiguration - ); - }); - }); - - it('should delegate read configuration overview to rest service', () => { - adapterUnderTest - .getConfigurationOverview(productConfiguration.configId) - .subscribe((config) => { - expect(config.configId).toEqual(configId); - expect( - mockedRestService.readConfigurationOverview - ).toHaveBeenCalledWith(productConfiguration.configId); - }); - }); - - it('should throw exception if variant search is attempted', () => { - expect(() => adapterUnderTest.searchVariants()).toThrowError(); - }); - - it('should delegate addToCart to OCC service', () => { - adapterUnderTest.addToCart(addToCartParams).subscribe((response) => { - expect(response).toEqual(cartResponse); - expect(mockedOccService.addToCart).toHaveBeenCalledWith(addToCartParams); - }); - }); - - it('should delegate readConfigurationForCartEntry to both OCC and rest service', () => { - adapterUnderTest - .readConfigurationForCartEntry(readConfigCartParams) - .subscribe((response) => { - expect(response).toBe(productConfiguration); - expect(response.owner).toBe(readConfigCartParams.owner); - expect(mockedOccService.getConfigIdForCartEntry).toHaveBeenCalledWith( - readConfigCartParams - ); - expect(mockedRestService.readConfiguration).toHaveBeenCalledWith( - configId - ); - }); - }); - - it('should delegate readConfigurationForOrderEntry to both OCC and rest service', () => { - adapterUnderTest - .readConfigurationForOrderEntry(readConfigOrderEntryParams) - .subscribe((response) => { - expect(response).toBe(productConfiguration); - expect(response.owner).toBe(readConfigOrderEntryParams.owner); - expect(mockedOccService.getConfigIdForOrderEntry).toHaveBeenCalledWith( - readConfigOrderEntryParams - ); - expect(mockedRestService.readConfiguration).toHaveBeenCalledWith( - configId - ); - }); - }); - - it('should delegate updateCart to OCC service', () => { - adapterUnderTest - .updateConfigurationForCartEntry(updateCartParams) - .subscribe((response) => { - expect(response).toEqual(cartResponse); - expect(mockedOccService.updateCartEntry).toHaveBeenCalledWith( - updateCartParams - ); - }); - }); - - it("shouldn't support CPQ over OCC mode", () => { - expect(adapterUnderTest.supportsCpqOverOcc()).toBe(false); - }); -}); diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.ts deleted file mode 100644 index 4316e3e4b4e..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.adapter.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Injectable } from '@angular/core'; -import { CartModification } from '@spartacus/cart/base/root'; -import { - CommonConfigurator, - ConfiguratorType, -} from '@spartacus/product-configurator/common'; -import { - Configurator, - RulebasedConfiguratorAdapter, -} from '@spartacus/product-configurator/rulebased'; -import { Observable, of } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; -import { CpqConfiguratorOccService } from './../occ/cpq-configurator-occ.service'; -import { CpqConfiguratorRestService } from './cpq-configurator-rest.service'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable() -export class CpqConfiguratorRestAdapter - implements RulebasedConfiguratorAdapter -{ - constructor( - protected cpqRestService: CpqConfiguratorRestService, - protected cpqOccService: CpqConfiguratorOccService - ) {} - - getConfiguratorType(): string { - return ConfiguratorType.CPQ; - } - - supportsCpqOverOcc(): boolean { - return false; - } - - createConfiguration( - owner: CommonConfigurator.Owner - ): Observable { - // no error handling for missing owner id needed, as it's a - // mandatory attribute in owner - return this.cpqRestService.createConfiguration(owner.id).pipe( - map((configResponse) => { - configResponse.owner = owner; - return configResponse; - }) - ); - } - - readConfiguration( - configId: string, - groupId: string, - owner: CommonConfigurator.Owner - ): Observable { - return this.cpqRestService.readConfiguration(configId, groupId).pipe( - map((configResponse) => { - configResponse.owner = owner; - return configResponse; - }) - ); - } - - updateConfiguration( - configuration: Configurator.Configuration - ): Observable { - const updateMethod = - configuration.updateType === Configurator.UpdateType.VALUE_QUANTITY - ? this.cpqRestService.updateValueQuantity - : this.cpqRestService.updateAttribute; - return updateMethod.call(this.cpqRestService, configuration).pipe( - map((configResponse: Configurator.Configuration) => { - configResponse.owner = configuration.owner; - return configResponse; - }) - ); - } - - updateConfigurationOverview(): Observable { - throw new Error( - 'Update the configuration overview is not supported for the CPQ configurator' - ); - } - - addToCart( - parameters: Configurator.AddToCartParameters - ): Observable { - return this.cpqOccService.addToCart(parameters); - } - - readConfigurationForCartEntry( - parameters: CommonConfigurator.ReadConfigurationFromCartEntryParameters - ): Observable { - return this.cpqOccService.getConfigIdForCartEntry(parameters).pipe( - switchMap((configId) => { - return this.cpqRestService.readConfiguration(configId).pipe( - map((configResponse) => { - configResponse.owner = parameters.owner; - return configResponse; - }) - ); - }) - ); - } - - updateConfigurationForCartEntry( - parameters: Configurator.UpdateConfigurationForCartEntryParameters - ): Observable { - return this.cpqOccService.updateCartEntry(parameters); - } - - readConfigurationForOrderEntry( - parameters: CommonConfigurator.ReadConfigurationFromOrderEntryParameters - ): Observable { - return this.cpqOccService.getConfigIdForOrderEntry(parameters).pipe( - switchMap((configId) => { - return this.cpqRestService.readConfiguration(configId).pipe( - map((configResponse) => { - configResponse.owner = parameters.owner; - return configResponse; - }) - ); - }) - ); - } - - readPriceSummary( - configuration: Configurator.Configuration - ): Observable { - return of(configuration); // so that UI does not run into exception - } - - getConfigurationOverview( - configId: string - ): Observable { - return this.cpqRestService.readConfigurationOverview(configId); - } - - searchVariants(): Observable { - throw new Error('searchVariants is not supported for the CPQ configurator'); - } -} diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.module.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.module.ts deleted file mode 100644 index 0bfed4648c6..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { provideDefaultConfig } from '@spartacus/core'; -import { RulebasedConfiguratorConnector } from '@spartacus/product-configurator/rulebased'; -import { CpqConfiguratorRestAdapter } from './cpq-configurator-rest.adapter'; -import { defaultCpqConfiguratorEndpointConfig } from './default-cpq-configurator-endpoint.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@NgModule({ - imports: [CommonModule], - - providers: [ - { - provide: RulebasedConfiguratorConnector.CONFIGURATOR_ADAPTER_LIST, - useClass: CpqConfiguratorRestAdapter, - multi: true, - }, - provideDefaultConfig(defaultCpqConfiguratorEndpointConfig), - ], -}) -export class CpqConfiguratorRestModule {} diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.spec.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.spec.ts deleted file mode 100644 index a760a37e5ab..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.spec.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { - HttpTestingController, - provideHttpClientTesting, -} from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; -import { ConverterService, OccEndpointsService } from '@spartacus/core'; -import { ConfiguratorModelUtils } from '@spartacus/product-configurator/common'; -import { Configurator } from '@spartacus/product-configurator/rulebased'; -import { MockOccEndpointsService } from 'projects/core/src/occ/adapters/user/unit-test.helper'; -import { ConfiguratorTestUtils } from '../../testing/configurator-test-utils'; -import { - CPQ_CONFIGURATOR_NORMALIZER, - CPQ_CONFIGURATOR_OVERVIEW_NORMALIZER, - CPQ_CONFIGURATOR_QUANTITY_SERIALIZER, - CPQ_CONFIGURATOR_SERIALIZER, -} from '../common/converters/cpq-configurator.converters'; -import { CpqConfiguratorEndpointConfig } from './cpq-configurator-endpoint.config'; -import { CpqConfiguratorRestAdapter } from './cpq-configurator-rest.adapter'; -import { CpqConfiguratorRestService } from './cpq-configurator-rest.service'; -import { Cpq } from '../common/cpq.models'; -import { defaultCpqConfiguratorEndpointConfig } from './default-cpq-configurator-endpoint.config'; -import { - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http'; - -const productCode = 'CONF_LAPTOP'; -const tabId = '2'; -const tabIdUpdatedAttribute = '1'; -const configId = '1234-56-7890'; - -const configCreatedResponse: Cpq.ConfigurationCreatedResponseData = { - configurationId: configId, -}; -const tabId1SelectedTrue = { id: 1, isSelected: true }; -const tabId2SelectedTrue = { id: 2, isSelected: true }; -const tabId3SelectedTrue = { id: 3, isSelected: true }; - -const configResponseOnlyOneTab: Cpq.Configuration = { - productSystemId: productCode, - currencyISOCode: 'USD', - completed: false, - tabs: [tabId1SelectedTrue], - attributes: [{ pA_ID: 11, stdAttrCode: 111 }], -}; - -const configResponseNoTab: Cpq.Configuration = { - productSystemId: productCode, - currencyISOCode: 'USD', - completed: false, - tabs: [], - attributes: [{ pA_ID: 11, stdAttrCode: 111 }], -}; - -const configResponseTab1: Cpq.Configuration = { - productSystemId: productCode, - currencyISOCode: 'USD', - completed: false, - errorMessages: [], - tabs: [ - tabId1SelectedTrue, - { id: 2, isSelected: false }, - { id: 3, isSelected: false }, - ], - attributes: [{ pA_ID: 11, stdAttrCode: 111 }], -}; - -const configResponseTab2: Cpq.Configuration = { - productSystemId: productCode, - currencyISOCode: 'USD', - completed: false, - errorMessages: [], - tabs: [ - { id: 1, isSelected: false }, - tabId2SelectedTrue, - { id: 3, isSelected: false }, - ], - attributes: [{ pA_ID: 21, stdAttrCode: 211 }], -}; - -const configResponseTab3: Cpq.Configuration = { - ...configResponseTab2, - tabs: [ - { id: 1, isSelected: false }, - { id: 2, isSelected: false }, - tabId3SelectedTrue, - ], - attributes: [{ pA_ID: 31, stdAttrCode: 311 }], -}; - -const configResponsesByTab = [ - configResponseTab1, - configResponseTab2, - configResponseTab3, -]; -const configUpdateResponse = {}; -const attrCode = '111'; -const attrValueId = 'abc'; -const configuration: Configurator.Configuration = { - ...ConfiguratorTestUtils.createConfiguration( - configId, - ConfiguratorModelUtils.createInitialOwner() - ), - productCode: productCode, -}; -const updateAttribute: Cpq.UpdateAttribute = { - configurationId: configId, - standardAttributeCode: attrCode, - changeAttributeValue: { - attributeValueIds: attrValueId, - }, - tabId: tabIdUpdatedAttribute, -}; -const updateValue: Cpq.UpdateValue = { - configurationId: configId, - standardAttributeCode: attrCode, - attributeValueId: attrValueId, - quantity: 5, - tabId: tabIdUpdatedAttribute, -}; - -describe('CpqConfiguratorRestService', () => { - let serviceUnderTest: CpqConfiguratorRestService; - let httpMock: HttpTestingController; - let converterService: ConverterService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - providers: [ - CpqConfiguratorRestAdapter, - { provide: OccEndpointsService, useClass: MockOccEndpointsService }, - { - provide: CpqConfiguratorEndpointConfig, - useValue: defaultCpqConfiguratorEndpointConfig, - }, - provideHttpClient(withInterceptorsFromDi()), - provideHttpClientTesting(), - ], - }); - - httpMock = TestBed.inject(HttpTestingController); - converterService = TestBed.inject(ConverterService); - serviceUnderTest = TestBed.inject(CpqConfiguratorRestService); - - configResponseTab1.errorMessages = []; - configResponseTab2.errorMessages = []; - configResponseTab3.errorMessages = []; - }); - - afterEach(() => { - httpMock.verify(); - }); - - it('should create a configuration and call normalizer', () => { - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.createConfiguration(productCode).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_NORMALIZER - ); - }); - - const mockReq = httpMock.expectOne((req) => { - return ( - req.method === 'POST' && - req.url === `/api/configuration/v1/configurations` - ); - }); - mockReq.flush(configCreatedResponse); - - mockDisplayConfig(); - }); - - it('should read a configuration and call normalizer', () => { - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.readConfiguration(configId).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_NORMALIZER - ); - }); - - mockDisplayConfig(); - }); - - it('should read all tabs individually for OV and merge results', () => { - serviceUnderTest['getConfigurationWithAllTabsAndAttributes']( - configId - ).subscribe((mergedConfig) => { - const expectedInput: Cpq.Configuration = { - ...configResponseTab1, - attributes: undefined, - tabs: [ - { - ...tabId1SelectedTrue, - attributes: configResponseTab1.attributes, - }, - { - ...tabId2SelectedTrue, - attributes: configResponseTab2.attributes, - }, - { - ...tabId3SelectedTrue, - attributes: configResponseTab3.attributes, - }, - ], - }; - expect(mergedConfig).toEqual(expectedInput); - }); - - mockDisplayConfig(); - mockDisplayConfigWithTabId('2'); - mockDisplayConfigWithTabId('3'); - }); - - it('should read all tabs individually for OV and merge results for only one group', () => { - serviceUnderTest['getConfigurationWithAllTabsAndAttributes']( - configId - ).subscribe((mergedConfig) => { - const expectedInput: Cpq.Configuration = { - ...configResponseOnlyOneTab, - attributes: undefined, - tabs: [ - { - ...tabId1SelectedTrue, - attributes: configResponseOnlyOneTab.attributes, - }, - ], - }; - expect(mergedConfig).toEqual(expectedInput); - }); - - mockDisplayConfig(configResponseOnlyOneTab); - }); - - it('should not read error messages from first tab ', () => { - configResponseTab1.errorMessages = []; - configResponseTab2.errorMessages = ['error']; - configResponseTab3.errorMessages = ['error']; - serviceUnderTest['getConfigurationWithAllTabsAndAttributes']( - configId - ).subscribe((mergedConfig) => { - expect(mergedConfig.errorMessages?.length).toEqual(1); - }); - - mockDisplayConfig(); - mockDisplayConfigWithTabId('2'); - mockDisplayConfigWithTabId('3'); - }); - - it('should merge results for configuration without group', () => { - serviceUnderTest['getConfigurationWithAllTabsAndAttributes']( - configId - ).subscribe((mergedConfig) => { - const expectedInput: Cpq.Configuration = { - ...configResponseNoTab, - attributes: undefined, - tabs: [ - { - id: 0, - attributes: configResponseOnlyOneTab.attributes, - }, - ], - }; - expect(mergedConfig).toEqual(expectedInput); - }); - - mockDisplayConfig(configResponseNoTab); - }); - - it('should read overview and call normalizer for config with only one group', () => { - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.readConfigurationOverview(configId).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_OVERVIEW_NORMALIZER - ); - }); - mockDisplayConfig(configResponseOnlyOneTab); - }); - - it('should read a configuration with tab id and call normalizer', () => { - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.readConfiguration(configId, tabId).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_NORMALIZER - ); - }); - - mockDisplayConfigWithTabId(tabId); - }); - - it('should call serializer, update attribute and call normalizer', () => { - spyOn(converterService, 'convert').and.returnValue(updateAttribute); - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.updateAttribute(configuration).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.convert).toHaveBeenCalledWith( - configuration, - CPQ_CONFIGURATOR_SERIALIZER - ); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_NORMALIZER - ); - }); - - const mockReq = httpMock.expectOne((req) => { - return ( - req.method === 'PATCH' && - req.url === - `/api/configuration/v1/configurations/${configId}/attributes/${attrCode}` && - req.body === updateAttribute.changeAttributeValue - ); - }); - mockReq.flush(configUpdateResponse); - mockDisplayConfigWithTabId(tabIdUpdatedAttribute); - }); - - it('should call serializer, update value quantity and call normalizer', () => { - spyOn(converterService, 'convert').and.returnValue(updateValue); - spyOn(converterService, 'pipeable').and.callThrough(); - serviceUnderTest.updateValueQuantity(configuration).subscribe((config) => { - expect(config.configId).toEqual(configId); - expect(converterService.convert).toHaveBeenCalledWith( - configuration, - CPQ_CONFIGURATOR_QUANTITY_SERIALIZER - ); - expect(converterService.pipeable).toHaveBeenCalledWith( - CPQ_CONFIGURATOR_NORMALIZER - ); - }); - - const mockReq = httpMock.expectOne((req) => { - return ( - req.method === 'PATCH' && - req.url === - `/api/configuration/v1/configurations/${configId}/attributes/${attrCode}/attributeValues/${attrValueId}` && - req.body.Quantity === 5 - ); - }); - mockReq.flush(configUpdateResponse); - mockDisplayConfigWithTabId(tabIdUpdatedAttribute); - }); - - function mockDisplayConfig(response?: Cpq.Configuration) { - const mockReq = httpMock.expectOne((req) => { - return ( - req.method === 'GET' && - req.url === `/api/configuration/v1/configurations/${configId}/display` - ); - }); - if (!response) { - response = configResponseTab1; - } - mockReq.flush(response); - } - - function mockDisplayConfigWithTabId(currentTabId: string) { - const mockReq = httpMock.expectOne((req) => { - return ( - req.method === 'GET' && - req.url === - `/api/configuration/v1/configurations/${configId}/display?tabId=${currentTabId}` - ); - }); - const index: number = parseInt(currentTabId, 10) - 1; - mockReq.flush(configResponsesByTab[index]); - } -}); diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.ts deleted file mode 100644 index f75860d51da..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-rest.service.ts +++ /dev/null @@ -1,281 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { ConverterService } from '@spartacus/core'; -import { Configurator } from '@spartacus/product-configurator/rulebased'; -import { forkJoin, Observable, of } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; -import { - CPQ_CONFIGURATOR_NORMALIZER, - CPQ_CONFIGURATOR_OVERVIEW_NORMALIZER, - CPQ_CONFIGURATOR_QUANTITY_SERIALIZER, - CPQ_CONFIGURATOR_SERIALIZER, -} from '../common/converters/cpq-configurator.converters'; -import { CpqConfiguratorEndpointService } from './cpq-configurator-endpoint.service'; -import { Cpq } from '../common/cpq.models'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable({ providedIn: 'root' }) -export class CpqConfiguratorRestService { - constructor( - protected http: HttpClient, - protected converterService: ConverterService, - protected endpointService: CpqConfiguratorEndpointService - ) {} - - /** - * Creates a new runtime configuration for the given product id - * and read this default configuration from the CPQ system. - * - * @param {string} productSystemId - Product system ID - * @returns {Observable} - Created configuration - */ - createConfiguration( - productSystemId: string - ): Observable { - return this.callConfigurationInit(productSystemId).pipe( - switchMap((configCreatedResponse) => { - return this.callConfigurationDisplay( - configCreatedResponse.configurationId - ).pipe( - this.converterService.pipeable(CPQ_CONFIGURATOR_NORMALIZER), - map((resultConfiguration) => { - return { - ...resultConfiguration, - configId: configCreatedResponse.configurationId, - }; - }) - ); - }) - ); - } - - /** - * Retrieves a configuration from the CPQ system by its configuration ID and for a certain tab. - * - * @param {string} configId - Configuration ID - * @param {string} tabId - Tab ID - * @returns {Observable} - Retrieved configuration - */ - readConfiguration( - configId: string, - tabId?: string - ): Observable { - return this.callConfigurationDisplay(configId, tabId).pipe( - this.converterService.pipeable(CPQ_CONFIGURATOR_NORMALIZER), - map((resultConfiguration) => { - return { - ...resultConfiguration, - configId: configId, - }; - }) - ); - } - - /** - * Retrieves an overview for a certain configuration by its configuration ID. - * - * @param {string} configId - Configuration ID - * @returns {Observable} - Retrieved overview - */ - readConfigurationOverview( - configId: string - ): Observable { - return this.getConfigurationWithAllTabsAndAttributes(configId).pipe( - this.converterService.pipeable(CPQ_CONFIGURATOR_OVERVIEW_NORMALIZER), - map((resultConfiguration) => { - return { - ...resultConfiguration, - configId: configId, - }; - }) - ); - } - - /** - * This method is actually a workaround until CPQ provides and API to fetch - * all selected attributes / attribute values grouped by all tabs. - * It will fire a request for each tab to collect all required data. - */ - protected getConfigurationWithAllTabsAndAttributes( - configId: string - ): Observable { - return this.callConfigurationDisplay(configId).pipe( - switchMap((currentTab) => { - const tabRequests: Observable[] = []; - if (currentTab.tabs && currentTab.tabs.length > 0) { - // prepare requests for remaining tabs - currentTab.tabs.forEach((tab) => { - if (tab.isSelected) { - // details of the currently selected tab are already fetched - tabRequests.push(of(currentTab)); - } else { - tabRequests.push( - this.callConfigurationDisplay(configId, tab.id.toString()) - ); - } - }); - } else { - // tabs are not defined in model, general tab is used - tabRequests.push(of(currentTab)); - } - // fire requests for remaining tabs and wait until all are finished - return forkJoin(tabRequests); - }), - map(this.mergeTabResults) - ); - } - - protected mergeTabResults( - tabReqResultList: Cpq.Configuration[] - ): Cpq.Configuration { - const config = { - // first tab will be the current tab. It might not contain all error messages (bug in CPQ). So we just use the last tab. - // this whole logic will be obsolete, as soon as CPQ provides and API to fetch everything. - ...tabReqResultList[tabReqResultList.length - 1], - }; - config.attributes = undefined; - config.tabs = []; - tabReqResultList.forEach((tabReqResult) => { - let tab: Cpq.Tab; - const currentTab = tabReqResult.tabs?.find((tabEl) => tabEl.isSelected); - if (currentTab && tabReqResult.tabs && tabReqResult.tabs.length > 0) { - tab = { - ...currentTab, - }; - } else { - tab = { - id: 0, - }; - } - tab.attributes = tabReqResult.attributes; - config.tabs?.push(tab); - }); - return config; - } - - /** - * Updates an attribute of the runtime configuration for the given configuration id and attribute code - * and read this default configuration from the CPQ system. - * - * @param {Configurator.Configuration} configuration - Configuration - * @returns {Observable} - Updated configuration - */ - updateAttribute( - configuration: Configurator.Configuration - ): Observable { - const updateAttribute: Cpq.UpdateAttribute = this.converterService.convert( - configuration, - CPQ_CONFIGURATOR_SERIALIZER - ); - return this.callUpdateAttribute(updateAttribute).pipe( - switchMap(() => { - return this.callConfigurationDisplay( - configuration.configId, - updateAttribute.tabId - ).pipe( - this.converterService.pipeable(CPQ_CONFIGURATOR_NORMALIZER), - map((resultConfiguration) => { - return { - ...resultConfiguration, - configId: configuration.configId, - }; - }) - ); - }) - ); - } - - /** - * Updates a quantity for an attribute of the runtime configuration for the given configuration id and attribute code - * and read this default configuration from the CPQ system. - * - * @param {Configurator.Configuration} configuration - Configuration - * @returns {Observable} - Updated configuration - */ - updateValueQuantity( - configuration: Configurator.Configuration - ): Observable { - const updateValue: Cpq.UpdateValue = this.converterService.convert( - configuration, - CPQ_CONFIGURATOR_QUANTITY_SERIALIZER - ); - return this.callUpdateValue(updateValue).pipe( - switchMap(() => { - return this.callConfigurationDisplay( - configuration.configId, - updateValue.tabId - ).pipe( - this.converterService.pipeable(CPQ_CONFIGURATOR_NORMALIZER), - map((resultConfiguration) => { - return { - ...resultConfiguration, - configId: configuration.configId, - }; - }) - ); - }) - ); - } - - protected callUpdateValue(updateValue: Cpq.UpdateValue): Observable { - return this.http.patch( - this.endpointService.buildUrl('valueUpdate', { - configId: updateValue.configurationId, - attributeCode: updateValue.standardAttributeCode, - valueCode: updateValue.attributeValueId, - }), - { - Quantity: updateValue.quantity, - }, - this.endpointService.CPQ_MARKER_HEADER - ); - } - - protected callConfigurationInit( - productSystemId: string - ): Observable { - return this.http.post( - this.endpointService.buildUrl('configurationInit'), - { - ProductSystemId: productSystemId, - }, - this.endpointService.CPQ_MARKER_HEADER - ); - } - - protected callConfigurationDisplay( - configId: string, - tabId?: string - ): Observable { - return this.http.get( - this.endpointService.buildUrl( - 'configurationDisplay', - { configId: configId }, - tabId ? [{ name: 'tabId', value: tabId }] : undefined - ), - this.endpointService.CPQ_MARKER_HEADER - ); - } - - protected callUpdateAttribute( - updateAttribute: Cpq.UpdateAttribute - ): Observable { - return this.http.patch( - this.endpointService.buildUrl('attributeUpdate', { - configId: updateAttribute.configurationId, - attributeCode: updateAttribute.standardAttributeCode, - }), - updateAttribute.changeAttributeValue, - this.endpointService.CPQ_MARKER_HEADER - ); - } -} diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-utils.spec.ts b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-utils.spec.ts index 9af33122e19..5bc6d894fec 100644 --- a/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-utils.spec.ts +++ b/feature-libs/product-configurator/rulebased/cpq/rest/cpq-configurator-utils.spec.ts @@ -8,7 +8,6 @@ import { productConfiguration, } from '../../testing/configurator-test-data'; import { ConfiguratorTestUtils } from '../../testing/configurator-test-utils'; -import { CpqConfiguratorEndpointService } from './cpq-configurator-endpoint.service'; import { CpqConfiguratorUtils } from './cpq-configurator-utils'; describe('CpqConfiguratorUtils', () => { @@ -16,7 +15,7 @@ describe('CpqConfiguratorUtils', () => { beforeEach(() => { TestBed.configureTestingModule({}); - classUnderTest = TestBed.inject(CpqConfiguratorEndpointService); + classUnderTest = new CpqConfiguratorUtils(); }); it('should be created', () => { diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/default-cpq-configurator-endpoint.config.ts b/feature-libs/product-configurator/rulebased/cpq/rest/default-cpq-configurator-endpoint.config.ts deleted file mode 100644 index cf59ba54090..00000000000 --- a/feature-libs/product-configurator/rulebased/cpq/rest/default-cpq-configurator-endpoint.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CpqConfiguratorEndpointConfig } from './cpq-configurator-endpoint.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -export const defaultCpqConfiguratorEndpointConfig: CpqConfiguratorEndpointConfig = - { - backend: { - cpq: { - endpoints: { - configurationInit: 'configurations', - configurationDisplay: 'configurations/${configId}/display', - attributeUpdate: - 'configurations/${configId}/attributes/${attributeCode}', - valueUpdate: - 'configurations/${configId}/attributes/${attributeCode}/attributeValues/${valueCode}', - }, - prefix: '/api/configuration/v1/', - }, - }, - }; diff --git a/feature-libs/product-configurator/rulebased/cpq/rest/index.ts b/feature-libs/product-configurator/rulebased/cpq/rest/index.ts index c4ed0d6960d..c9d650671aa 100644 --- a/feature-libs/product-configurator/rulebased/cpq/rest/index.ts +++ b/feature-libs/product-configurator/rulebased/cpq/rest/index.ts @@ -4,9 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './cpq-configurator-endpoint.config'; -export * from './cpq-configurator-endpoint.service'; -export * from './cpq-configurator-rest.adapter'; -export * from './cpq-configurator-rest.module'; -export * from './cpq-configurator-rest.service'; export * from './cpq-configurator-utils'; diff --git a/feature-libs/product-configurator/rulebased/cpq/rulebased-cpq-configurator.module.ts b/feature-libs/product-configurator/rulebased/cpq/rulebased-cpq-configurator.module.ts index 11ce00d69b4..1777dd47560 100644 --- a/feature-libs/product-configurator/rulebased/cpq/rulebased-cpq-configurator.module.ts +++ b/feature-libs/product-configurator/rulebased/cpq/rulebased-cpq-configurator.module.ts @@ -7,17 +7,11 @@ import { NgModule } from '@angular/core'; import { CpqConfiguratorCommonModule } from './common/cpq-configurator-common.module'; import { CpqConfiguratorOccModule } from './occ/cpq-configurator-occ.module'; -import { CpqConfiguratorRestModule } from './rest/cpq-configurator-rest.module'; /** - * Exposes the CPQ flavor of rulebase configurator, which connects to CPQ directly via - * REST APIs and to commerce via OCC + * Exposes the CPQ flavor of rulebase configurator */ @NgModule({ - imports: [ - CpqConfiguratorCommonModule, - CpqConfiguratorOccModule, - CpqConfiguratorRestModule, - ], + imports: [CpqConfiguratorCommonModule, CpqConfiguratorOccModule], }) export class RulebasedCpqConfiguratorModule {} diff --git a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-normalizer.ts b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-normalizer.ts index 444be2ef2af..1c4ccfc3685 100644 --- a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-normalizer.ts +++ b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-normalizer.ts @@ -17,11 +17,6 @@ export class OccConfiguratorVariantNormalizer implements Converter { - /** - * @deprecated since 6.2 - */ - static readonly RETRACT_VALUE_CODE = '###RETRACT_VALUE_CODE###'; - constructor( protected config: OccConfig, protected translation: TranslationService, diff --git a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.spec.ts b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.spec.ts index a65116b0d30..ec9fafbb0f4 100644 --- a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.spec.ts +++ b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.spec.ts @@ -299,7 +299,7 @@ describe('OccConfiguratorVariantSerializer', () => { const attributeWithRetraction: Configurator.Attribute = { name: 'attr', uiType: Configurator.UiType.DROPDOWN, - selectedSingleValue: OccConfiguratorVariantSerializer.RETRACT_VALUE_CODE, + selectedSingleValue: Configurator.RetractValueCode, }; const occAttributes: OccConfigurator.Attribute[] = []; occConfiguratorVariantSerializer.convertAttribute( @@ -326,8 +326,7 @@ describe('OccConfiguratorVariantSerializer', () => { const attribute: Configurator.Attribute = { name: 'attr', uiType: Configurator.UiType.DROPDOWN, - selectedSingleValue: - OccConfiguratorVariantSerializer.RETRACT_VALUE_CODE, + selectedSingleValue: Configurator.RetractValueCode, }; expect( @@ -419,8 +418,7 @@ describe('OccConfiguratorVariantSerializer', () => { const attribute: Configurator.Attribute = { name: 'attr', uiType: Configurator.UiType.DROPDOWN, - selectedSingleValue: - OccConfiguratorVariantSerializer.RETRACT_VALUE_CODE, + selectedSingleValue: Configurator.RetractValueCode, values: [value1, value2, value3, value4], }; diff --git a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.ts b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.ts index 5d61fa9f410..ef06abf34ad 100644 --- a/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.ts +++ b/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-serializer.ts @@ -14,11 +14,6 @@ export class OccConfiguratorVariantSerializer implements Converter { - /** - * @deprecated since 6.2 - */ - static readonly RETRACT_VALUE_CODE = '###RETRACT_VALUE_CODE###'; - convert( source: Configurator.Configuration, target?: OccConfigurator.Configuration diff --git a/feature-libs/product-configurator/rulebased/root/cpq/cpq-configurator-root.module.ts b/feature-libs/product-configurator/rulebased/root/cpq/cpq-configurator-root.module.ts index 6f0c1c488e2..7d1c1c5ad4e 100644 --- a/feature-libs/product-configurator/rulebased/root/cpq/cpq-configurator-root.module.ts +++ b/feature-libs/product-configurator/rulebased/root/cpq/cpq-configurator-root.module.ts @@ -8,18 +8,13 @@ import { NgModule } from '@angular/core'; import { provideDefaultConfig } from '@spartacus/core'; import { CpqConfiguratorInteractiveModule } from './cpq-configurator-interactive.module'; import { CpqConfiguratorOverviewModule } from './cpq-configurator-overview.module'; -import { CpqConfiguratorInterceptorModule } from './interceptor/cpq-configurator-interceptor.module'; /** * Exposes the CPQ aspects that we need to load eagerly, like page mappings, routing * and interceptor related entities */ @NgModule({ - imports: [ - CpqConfiguratorInteractiveModule, - CpqConfiguratorOverviewModule, - CpqConfiguratorInterceptorModule, - ], + imports: [CpqConfiguratorInteractiveModule, CpqConfiguratorOverviewModule], //force early login providers: [provideDefaultConfig({ routing: { protected: true } })], }) diff --git a/feature-libs/product-configurator/rulebased/root/cpq/index.ts b/feature-libs/product-configurator/rulebased/root/cpq/index.ts index 40af6796c57..aea0425abe5 100644 --- a/feature-libs/product-configurator/rulebased/root/cpq/index.ts +++ b/feature-libs/product-configurator/rulebased/root/cpq/index.ts @@ -8,4 +8,3 @@ export * from './cpq-configurator-interactive.module'; export * from './cpq-configurator-overview.module'; export * from './cpq-configurator-root.module'; export * from './cpq-configurator-layout.module'; -export * from './interceptor/index'; diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-data.models.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-data.models.ts deleted file mode 100644 index afe90ee9df6..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-data.models.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * - * Authorization data required for communicating with CPQ - * - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -export interface CpqAccessData { - /** - * CPQ Access token - */ - accessToken: string; - /** - * Token expiration time in milliseconds - */ - accessTokenExpirationTime: number; - /** - * CPQ endpoint url - */ - endpoint: string; -} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.spec.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.spec.ts deleted file mode 100644 index 70747af2484..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - HttpTestingController, - provideHttpClientTesting, -} from '@angular/common/http/testing'; -import { Type } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { OccEndpointsService, UserIdService } from '@spartacus/core'; -import { MockOccEndpointsService } from 'projects/core/src/occ/adapters/user/unit-test.helper'; -import { of } from 'rxjs'; -import { CpqAccessData } from './cpq-access-data.models'; -import { CpqAccessLoaderService } from './cpq-access-loader.service'; -import { - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http'; - -const accessData: CpqAccessData = { - accessToken: '8273635', - endpoint: 'https://cpq', - accessTokenExpirationTime: 1605004667020, -}; -const USER_ID = 'actualUser'; -class MockUserIdService { - takeUserId() { - return of(USER_ID); - } -} - -describe('CpqAccessLoaderService', () => { - let serviceUnderTest: CpqAccessLoaderService; - let httpMock: HttpTestingController; - - let occEnpointsService: OccEndpointsService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - providers: [ - CpqAccessLoaderService, - { provide: UserIdService, useClass: MockUserIdService }, - { provide: OccEndpointsService, useClass: MockOccEndpointsService }, - provideHttpClient(withInterceptorsFromDi()), - provideHttpClientTesting(), - ], - }); - - httpMock = TestBed.inject( - HttpTestingController as Type - ); - - occEnpointsService = TestBed.inject( - OccEndpointsService as Type - ); - - serviceUnderTest = TestBed.inject( - CpqAccessLoaderService as Type - ); - - spyOn(occEnpointsService, 'buildUrl').and.callThrough(); - }); - - afterEach(() => { - httpMock.verify(); - }); - - it('should create service', () => { - expect(serviceUnderTest).toBeDefined(); - }); - - it('should call getCpqAccessData endpoint', () => { - serviceUnderTest.getCpqAccessData().subscribe(); - - const mockReq = httpMock.expectOne((req) => { - return req.method === 'GET' && req.url === '/getCpqAccessData'; - }); - - expect(occEnpointsService.buildUrl).toHaveBeenCalledWith( - 'getCpqAccessData', - { urlParams: { userId: USER_ID } } - ); - - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - - mockReq.flush(accessData); - }); -}); diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.ts deleted file mode 100644 index a98a11c2fcb..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-loader.service.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { OccEndpointsService, UserIdService } from '@spartacus/core'; -import { Observable } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; -import { CpqAccessData } from './cpq-access-data.models'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable({ providedIn: 'root' }) -export class CpqAccessLoaderService { - constructor( - protected http: HttpClient, - protected occEndpointsService: OccEndpointsService, - protected userIdService: UserIdService - ) {} - - getCpqAccessData(): Observable { - return this.userIdService.takeUserId(true).pipe( - switchMap((userId) => - this.http.get( - this.occEndpointsService.buildUrl('getCpqAccessData', { - urlParams: { userId: userId }, - }) - ) - ) - ); - } -} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-storage.service.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-storage.service.ts deleted file mode 100644 index 3df04d00f4d..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-access-storage.service.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Injectable, OnDestroy } from '@angular/core'; -import { AuthService } from '@spartacus/core'; -import { BehaviorSubject, Observable, Subscription, timer } from 'rxjs'; -import { - distinctUntilChanged, - expand, - filter, - switchMap, - take, -} from 'rxjs/operators'; -import { CpqAccessData } from './cpq-access-data.models'; -import { CpqAccessLoaderService } from './cpq-access-loader.service'; -import { CpqConfiguratorAuthConfig } from './cpq-configurator-auth.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable({ providedIn: 'root' }) -export class CpqAccessStorageService implements OnDestroy { - protected readonly EXPIRED_TOKEN: CpqAccessData = { - accessToken: 'INVALID DUMMY', - accessTokenExpirationTime: 0, - endpoint: '', - }; - - constructor( - protected cpqAccessLoaderService: CpqAccessLoaderService, - protected authService: AuthService, - protected config: CpqConfiguratorAuthConfig - ) {} - - ngOnDestroy(): void { - this.currentCpqAccessSubscription?.unsubscribe(); - this.currentAuthServiceSubscription?.unsubscribe(); - this._cpqAccessDataErrorSubscription?.unsubscribe(); - } - - protected cpqAccessData$: Observable; - protected currentCpqAccessSubscription: Subscription; - protected currentAuthServiceSubscription: Subscription; - protected _cpqAccessData$: BehaviorSubject; - protected _cpqAccessDataError = false; - protected _cpqAccessDataErrorSubscription: Subscription | undefined; - - getCpqAccessData(): Observable { - if (!this.cpqAccessData$ || this._cpqAccessDataError) { - this.initCpqAccessData(); - } - return this.cpqAccessData$; - } - - /** - * Renews the current access data. All subscribers of getCachedCpqAccessData() - * will receive the new data. Will only have an effect, if there are any subscribers - * and the user is logged in. - */ - renewCpqAccessData() { - // only force token refresh if initialized. - if (this.cpqAccessData$) { - this.stopAutoFetchingCpqAccessData(); - this._cpqAccessData$.next(this.EXPIRED_TOKEN); // invalidate cache - this.authService - .isUserLoggedIn() - .pipe(take(1)) // get current login state - .subscribe((loggedIn) => { - // only fetch new token if user is logged in. - if (loggedIn) { - this.startAutoFetchingCpqAccessData(); - } - }); - } - } - - protected initCpqAccessData() { - this._cpqAccessData$ = new BehaviorSubject(this.EXPIRED_TOKEN); - this._cpqAccessDataError = false; - this._cpqAccessDataErrorSubscription?.unsubscribe(); - this._cpqAccessDataErrorSubscription = this._cpqAccessData$.subscribe({ - error: () => (this._cpqAccessDataError = true), - }); - this.cpqAccessData$ = this._cpqAccessData$.pipe( - // Never expose expired tokens - either cache was invalidated with expired token, - // or the cached one expired before a new one was fetched. - filter((data) => !this.isTokenExpired(data)) - ); - this.currentAuthServiceSubscription?.unsubscribe(); // cancel subscriptions created for old - - this.currentAuthServiceSubscription = this.authService - .isUserLoggedIn() - .pipe(distinctUntilChanged()) // only react if user login status changes - .subscribe((loggedIn) => { - if (loggedIn) { - // user logged in - start/stop to ensure token is refreshed - this.stopAutoFetchingCpqAccessData(); - this.startAutoFetchingCpqAccessData(); - } else { - // user logged out - cancel token fetching - this.stopAutoFetchingCpqAccessData(); - this._cpqAccessData$.next(this.EXPIRED_TOKEN); // invalidate cache - } - }); - } - - protected stopAutoFetchingCpqAccessData() { - this.currentCpqAccessSubscription?.unsubscribe(); - } - - protected startAutoFetchingCpqAccessData() { - this.currentCpqAccessSubscription = this.cpqAccessLoaderService - .getCpqAccessData() - .pipe( - expand((data) => - timer(this.fetchNextTokenIn(data)).pipe( - switchMap(() => this.cpqAccessLoaderService.getCpqAccessData()) - ) - ) - ) - .subscribe(this._cpqAccessData$); // also propagate errors - } - - protected fetchNextTokenIn(data: CpqAccessData) { - const authSettings = this.config.productConfigurator.cpq?.authentication; - if (authSettings) { - // we schedule a request to update our cache some time before expiration - let fetchNextIn: number = - data.accessTokenExpirationTime - - Date.now() - - authSettings.tokenExpirationBuffer; - if (fetchNextIn < authSettings.tokenMinValidity) { - fetchNextIn = authSettings.tokenMinValidity; - } else if (fetchNextIn > authSettings.tokenMaxValidity) { - fetchNextIn = authSettings.tokenMaxValidity; - } - return fetchNextIn; - } else { - throw new Error('CPQ authentication configuration not present'); - } - } - - protected isTokenExpired(tokenData: CpqAccessData): boolean { - const authSettings = this.config.productConfigurator.cpq?.authentication; - if (authSettings) { - return ( - Date.now() > - tokenData.accessTokenExpirationTime - authSettings.tokenExpirationBuffer - ); - } else { - throw new Error('CPQ authentication configuration not present'); - } - } -} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-auth.config.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-auth.config.ts deleted file mode 100644 index 7a1224cc8e6..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-auth.config.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Injectable } from '@angular/core'; -import { Config } from '@spartacus/core'; -import '@spartacus/product-configurator/common'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -export interface ProductConfiguratorCpqAuthConfig { - cpq?: { - authentication: { - /** We should stop using/sending a token shortly before expiration, - * to avoid that it is actually expired when evaluated in the target system. - * Time given in ms. */ - tokenExpirationBuffer: number; - /** max time in ms to pass until a token is considered expired and re-fetched, - * even if token expiration time is longer */ - tokenMaxValidity: number; - /** min time to pass until a token is re-fetched, even if token expiration time is shorter */ - tokenMinValidity: number; - }; - }; -} - -@Injectable({ - providedIn: 'root', - useExisting: Config, -}) -export abstract class CpqConfiguratorAuthConfig { - productConfigurator: ProductConfiguratorCpqAuthConfig; -} - -declare module '@spartacus/product-configurator/common' { - interface ProductConfiguratorConfig - extends ProductConfiguratorCpqAuthConfig {} -} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-interceptor.module.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-interceptor.module.ts deleted file mode 100644 index 367d8684756..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-interceptor.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CommonModule } from '@angular/common'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; -import { NgModule } from '@angular/core'; -import { provideDefaultConfig } from '@spartacus/core'; -import { CpqConfiguratorRestInterceptor } from './cpq-configurator-rest.interceptor'; -import { defaultCpqConfiguratorAuthConfig } from './default-cpq-configurator-auth.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@NgModule({ - imports: [CommonModule], - providers: [ - { - provide: HTTP_INTERCEPTORS, - useClass: CpqConfiguratorRestInterceptor, - multi: true, - }, - provideDefaultConfig(defaultCpqConfiguratorAuthConfig), - ], -}) -export class CpqConfiguratorInterceptorModule {} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.spec.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.spec.ts deleted file mode 100644 index 7d288a522eb..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.spec.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { - HttpErrorResponse, - HttpHandler, - HttpHeaders, - HttpRequest, - HttpResponse, - HttpResponseBase, - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { Type } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { of, throwError } from 'rxjs'; -import { CpqAccessData } from './cpq-access-data.models'; -import { CpqAccessStorageService } from './cpq-access-storage.service'; -import { - CpqConfiguratorRestInterceptor, - MARKER_HEADER_CPQ_CONFIGURATOR, -} from './cpq-configurator-rest.interceptor'; - -describe('CpqConfiguratorRestInterceptor', () => { - let interceptorUnderTest: CpqConfiguratorRestInterceptor; - let cpqAccessStorageServiceMock: CpqAccessStorageService; - let mockedNextHandler: HttpHandler; - let capturedRequestsStack: HttpRequest[]; - - const nonCPQRequest: HttpRequest = new HttpRequest( - 'GET', - 'https://www.example.com' - ); - - const cpqRequest: HttpRequest = new HttpRequest('GET', `/api/whatever`, { - headers: new HttpHeaders({ [MARKER_HEADER_CPQ_CONFIGURATOR]: 'x' }), - }); - - const responseWithoutSessionId = new HttpResponse({ - headers: new HttpHeaders({}), - }); - - const validCpqResponse = new HttpResponse({ - headers: new HttpHeaders({ 'x-cpq-session-id': '123' }), - }); - - const asSpy = (f: any) => f; - - let cpqAccessDataStack: CpqAccessData[]; - let cpqResponseStack: HttpResponseBase[]; - - beforeEach(() => { - cpqAccessStorageServiceMock = jasmine.createSpyObj('mockedAccessService', [ - 'getCpqAccessData', - 'renewCpqAccessData', - ]); - mockedNextHandler = jasmine.createSpyObj('mockedNextHandler', ['handle']); - - TestBed.configureTestingModule({ - imports: [], - providers: [ - CpqConfiguratorRestInterceptor, - { - provide: CpqAccessStorageService, - useValue: cpqAccessStorageServiceMock, - }, - provideHttpClient(withInterceptorsFromDi()), - provideHttpClientTesting(), - ], - }); - - interceptorUnderTest = TestBed.inject( - CpqConfiguratorRestInterceptor as Type - ); - - cpqAccessDataStack = []; - - cpqAccessDataStack.push({ - accessToken: 'TOKEN', - endpoint: 'https://cpq', - accessTokenExpirationTime: 0, - }); - - cpqResponseStack = []; - capturedRequestsStack = []; - cpqResponseStack.push(validCpqResponse); - cpqResponseStack.push(validCpqResponse); - - asSpy(cpqAccessStorageServiceMock.getCpqAccessData).and.callFake(() => - of(cpqAccessDataStack[cpqAccessDataStack.length - 1]) - ); - asSpy(cpqAccessStorageServiceMock.renewCpqAccessData).and.callFake(() => - cpqAccessDataStack.pop() - ); - - asSpy(mockedNextHandler.handle).and.callFake( - (request: HttpRequest) => { - capturedRequestsStack.push(request); - const cpqResponse = cpqResponseStack.pop(); - return cpqResponse instanceof HttpErrorResponse - ? throwError(() => cpqResponse) - : of(cpqResponse); - } - ); - }); - - it('should create service', () => { - expect(interceptorUnderTest).toBeDefined(); - }); - - it('should not intercept non-cpq related requests', () => { - interceptorUnderTest.intercept(nonCPQRequest, mockedNextHandler); - expect(mockedNextHandler.handle).toHaveBeenCalledWith(nonCPQRequest); - }); - - it('should intercept cpq related requests', (done) => { - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect(mockedNextHandler.handle).not.toHaveBeenCalledWith(cpqRequest); - expect(mockedNextHandler.handle).toHaveBeenCalled(); - done(); - }); - }); - - it('should call CPQ only once per invocation', (done) => { - asSpy(cpqAccessStorageServiceMock.getCpqAccessData).and.callFake(() => - of(cpqAccessDataStack[0], cpqAccessDataStack[0]) - ); - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect(mockedNextHandler.handle).toHaveBeenCalledTimes(1); - done(); - }); - }); - - it('should replace url of cpq related requests', (done) => { - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect(capturedRequestsStack.pop()?.url).toBe( - cpqAccessDataStack[0].endpoint + '/api/whatever' - ); - done(); - }); - }); - - it('should add authorization header to cpq related requests', (done) => { - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect(capturedRequestsStack.pop()?.headers.get('Authorization')).toBe( - 'Bearer TOKEN' - ); - done(); - }); - }); - - it('should add cookieless header to cpq related requests', (done) => { - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - capturedRequestsStack.pop()?.headers.get('x-cpq-disable-cookies') - ).toBe('true'); - done(); - }); - }); - - it('should extract CPQ session id and append it to following requests', (done) => { - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - capturedRequestsStack.pop()?.headers.has('x-cpq-session-id') - ).toBeFalsy(); - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - capturedRequestsStack.pop()?.headers.get('x-cpq-session-id') - ).toBe('123'); - done(); - }); - }); - }); - - it('should only extract CPQ session id and append it to following requests if existing', (done) => { - cpqResponseStack = []; - cpqResponseStack.push(responseWithoutSessionId); - cpqResponseStack.push(responseWithoutSessionId); - - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - capturedRequestsStack.pop()?.headers.has('x-cpq-session-id') - ).toBeFalsy(); - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - capturedRequestsStack.pop()?.headers.has('x-cpq-session-id') - ).toBeFalsy(); - done(); - }); - }); - }); - - it('should retry a CPQ request with fresh token and session on 403', (done) => { - cpqAccessDataStack.push({ - accessToken: 'EXPIRED_TOKEN', - endpoint: 'https://cpq', - accessTokenExpirationTime: 0, - }); - cpqResponseStack = []; - cpqResponseStack.push(validCpqResponse); // second request should succeed - cpqResponseStack.push(new HttpErrorResponse({ status: 403 })); // first error - interceptorUnderTest['cpqSessionId'] = '123'; - interceptorUnderTest - .intercept(cpqRequest, mockedNextHandler) - .subscribe(() => { - expect( - cpqAccessStorageServiceMock.getCpqAccessData - ).toHaveBeenCalledTimes(2); - expect( - cpqAccessStorageServiceMock.renewCpqAccessData - ).toHaveBeenCalled(); - expect(mockedNextHandler.handle).toHaveBeenCalledTimes(2); - // last request with new token and no session id to create fresh session - let lastReq = capturedRequestsStack.pop(); - expect(lastReq?.headers.get('Authorization')).toBe('Bearer TOKEN'); - expect(lastReq?.headers.get('x-cpq-session-id')).toBeNull(); - let firstReq = capturedRequestsStack.pop(); - // initial requests with expired token and existing session id - expect(firstReq?.headers.get('Authorization')).toBe( - 'Bearer EXPIRED_TOKEN' - ); - expect(firstReq?.headers.get('x-cpq-session-id')).toBe('123'); - done(); - }); - }); - - it('should not handle other errors', (done) => { - cpqResponseStack = []; - cpqResponseStack.push(new HttpErrorResponse({ status: 401 })); // first error - interceptorUnderTest.intercept(cpqRequest, mockedNextHandler).subscribe({ - next: () => fail('error should be propagated'), - error: () => { - expect( - cpqAccessStorageServiceMock.getCpqAccessData - ).toHaveBeenCalledTimes(1); - expect( - cpqAccessStorageServiceMock.renewCpqAccessData - ).not.toHaveBeenCalled(); - expect(mockedNextHandler.handle).toHaveBeenCalledTimes(1); - done(); - }, - }); - }); -}); diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.ts deleted file mode 100644 index 26c7144a2be..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/cpq-configurator-rest.interceptor.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - HttpErrorResponse, - HttpEvent, - HttpHandler, - HttpHeaders, - HttpInterceptor, - HttpRequest, - HttpResponse, -} from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable, throwError } from 'rxjs'; -import { catchError, switchMap, take, tap } from 'rxjs/operators'; -import { CpqAccessData } from './cpq-access-data.models'; -import { CpqAccessStorageService } from './cpq-access-storage.service'; - -/** - * This header attribute shall be used to mark any request made to the CPQ System. - * The presence of it enables this interceptor to actually intercept - * this request and to decorate it with the authentication related attributes. - */ -export const MARKER_HEADER_CPQ_CONFIGURATOR = 'x-cpq-configurator'; - -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -@Injectable({ - providedIn: 'root', -}) -export class CpqConfiguratorRestInterceptor implements HttpInterceptor { - protected readonly HEADER_ATTR_CPQ_SESSION_ID = 'x-cpq-session-id'; - protected readonly HEADER_ATTR_CPQ_NO_COOKIES = 'x-cpq-disable-cookies'; - - /** - * Although CPQ API is stateless and can work without session id, it's recommended to always append the CPQ session id to any request. - * It enables CPQ load balancer to redirect the request always to the same node, so that configuration related data is already in memory - * and does not need to be reloaded from DB. This can have a significant impact on performance nd reduce load in the CPQ system. - */ - protected cpqSessionId: string | null = null; - - constructor(protected cpqAccessStorageService: CpqAccessStorageService) {} - - intercept( - request: HttpRequest, - next: HttpHandler - ): Observable> { - if (!request.headers.has(MARKER_HEADER_CPQ_CONFIGURATOR)) { - return next.handle(request); - } - return this.cpqAccessStorageService.getCpqAccessData().pipe( - take(1), // avoid request being re-executed when token expires - switchMap((cpqData) => { - return next.handle(this.enrichHeaders(request, cpqData)).pipe( - catchError((errorResponse: any) => { - return this.handleError(errorResponse, next, request); - }), - tap((response) => this.extractCpqSessionId(response)) - ); - }) - ); - } - - protected handleError( - errorResponse: any, - next: HttpHandler, - request: HttpRequest - ): Observable> { - if (errorResponse instanceof HttpErrorResponse) { - if (errorResponse.status === 403) { - this.cpqSessionId = null; - this.cpqAccessStorageService.renewCpqAccessData(); - return this.cpqAccessStorageService.getCpqAccessData().pipe( - take(1), - switchMap((newCpqData) => { - return next - .handle(this.enrichHeaders(request, newCpqData)) - .pipe(tap((response) => this.extractCpqSessionId(response))); - }) - ); - } - } - return throwError(() => errorResponse); //propagate error - } - - protected extractCpqSessionId(response: HttpEvent) { - if ( - response instanceof HttpResponse || - response instanceof HttpErrorResponse - ) { - if (response.headers.has(this.HEADER_ATTR_CPQ_SESSION_ID)) { - this.cpqSessionId = response.headers.get( - this.HEADER_ATTR_CPQ_SESSION_ID - ); - } - } - } - - protected enrichHeaders( - request: HttpRequest, - cpqData: CpqAccessData - ): HttpRequest { - let newRequest = request.clone({ - url: cpqData.endpoint + request.url, - headers: new HttpHeaders({ - Authorization: 'Bearer ' + cpqData.accessToken, - [this.HEADER_ATTR_CPQ_NO_COOKIES]: 'true', - }), - }); - if (this.cpqSessionId) { - newRequest = newRequest.clone({ - setHeaders: { - [this.HEADER_ATTR_CPQ_SESSION_ID]: this.cpqSessionId, - }, - }); - } - return newRequest; - } -} diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/default-cpq-configurator-auth.config.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/default-cpq-configurator-auth.config.ts deleted file mode 100644 index e6cd3d50252..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/default-cpq-configurator-auth.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CpqConfiguratorAuthConfig } from './cpq-configurator-auth.config'; -/** - * @deprecated since 2211.25. Not needed for commerce based CPQ orchestration (which is the default communication flavour). - * Refer to configuration setting ConfiguratorCoreConfig.productConfigurator.cpqOverOcc = true. - * The other flavour (performing direct calls from composable storefront to CPQ) is technically no longer supported. - */ -export const defaultCpqConfiguratorAuthConfig: CpqConfiguratorAuthConfig = { - productConfigurator: { - cpq: { - authentication: { - tokenExpirationBuffer: 10000, - tokenMaxValidity: 24 * 60 * 60 * 1000, - tokenMinValidity: 5000, // five seconds - }, - }, - }, -}; diff --git a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/index.ts b/feature-libs/product-configurator/rulebased/root/cpq/interceptor/index.ts deleted file mode 100644 index 000b03e362b..00000000000 --- a/feature-libs/product-configurator/rulebased/root/cpq/interceptor/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -export * from './cpq-access-data.models'; -export * from './cpq-access-loader.service'; -export * from './cpq-access-storage.service'; -export * from './cpq-configurator-auth.config'; -export * from './cpq-configurator-interceptor.module'; -export * from './cpq-configurator-rest.interceptor'; diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-cart.e2e-flaky.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-cart.e2e-flaky.cy.ts index 9c87b36c6e7..bcf677a59ec 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-cart.e2e-flaky.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-cart.e2e-flaky.cy.ts @@ -56,7 +56,6 @@ const Conflict_msg_gaming_console = 'Gaming console cannot be selected with LCD projector'; context('Product Configuration', () => { - let configUISettings: any; const commerceRelease: configurationVc.CommerceRelease = {}; before(() => { @@ -68,22 +67,12 @@ context('Product Configuration', () => { }); beforeEach(() => { - configUISettings = { - productConfigurator: { - enableNavigationToConflict: true, - }, - }; configurationVc.registerConfigurationRoute(); configurationVc.registerConfigurationUpdateRoute(); configurationOverviewVc.registerConfigurationOverviewRoute(); - cy.cxConfig(configUISettings); cy.visit('/'); }); - afterEach(() => { - configUISettings.productConfigurator.enableNavigationToConflict = false; - }); - describe('Navigate to product configuration page', () => { it('should be able to navigate from the cart', () => { clickAllowAllFromBanner(); diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-interactive.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-interactive.e2e.cy.ts index a3bb00b1e3e..ab7f4cc9a7e 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-interactive.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/product_configurator/product-configurator-vc-interactive.e2e.cy.ts @@ -430,22 +430,10 @@ context('Product Configuration', () => { }); describe('Conflict solver', () => { - let configUISettings: any; - beforeEach(() => { - configUISettings = { - productConfigurator: { - enableNavigationToConflict: true, - }, - }; - cy.cxConfig(configUISettings); cy.visit('/'); }); - afterEach(() => { - configUISettings.productConfigurator.enableNavigationToConflict = false; - }); - it('should support the conflict solving process', () => { configurationVc.goToConfigurationPage( electronicsShop, diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.ccv2-e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.ccv2-e2e.cy.ts index c2e2d59265f..85ba1555f3a 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.ccv2-e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.ccv2-e2e.cy.ts @@ -123,16 +123,14 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_COF_CUPS, RADGRP, - VAL_COF_CUPS_300, - true + VAL_COF_CUPS_300 ); configuration.checkValueSelected(RADGRP, ATTR_COF_CUPS, VAL_COF_CUPS_300); configurationCpq.selectAttributeAndWait( ATTR_COF_CUPS, RADGRP, - VAL_COF_CUPS_500, - true + VAL_COF_CUPS_500 ); configuration.checkValueSelected(RADGRP, ATTR_COF_CUPS, VAL_COF_CUPS_500); }); @@ -151,16 +149,14 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_COF_MODE, CHKBOX, - VAL_COF_MODE, - true + VAL_COF_MODE ); configuration.checkValueSelected(CHKBOX, ATTR_COF_MODE, VAL_COF_MODE); configurationCpq.selectAttributeAndWait( ATTR_COF_MODE, CHKBOX, - VAL_COF_MODE, - true + VAL_COF_MODE ); configurationCpq.checkValueNotSelected( CHKBOX, @@ -204,8 +200,7 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_CAM_BODY, RADGRP_PROD, - VAL_CAM_BODY_EOS80D, - true + VAL_CAM_BODY_EOS80D ); configurationCpq.checkValueNotSelected( RADGRP_PROD, @@ -236,8 +231,7 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_CAM_INS, DDLB_PROD, - VAL_CB_INS_Y2, - true + VAL_CB_INS_Y2 ); configurationCpq.checkValueNotSelected( DDLB_PROD, @@ -265,8 +259,7 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_CAM_MC, CHKBOX_PROD, - VAL_CAM_MC_64, - true + VAL_CAM_MC_64 ); configuration.checkValueSelected( CHKBOX_PROD, @@ -278,14 +271,12 @@ context('CPQ Configuration', () => { configurationCpq.selectAttributeAndWait( ATTR_CAM_MC, CHKBOX_PROD, - VAL_CAM_MC_128, - true + VAL_CAM_MC_128 ); configurationCpq.checkValueNotSelected( CHKBOX_PROD, ATTR_CAM_MC, - VAL_CAM_MC_128, - true + VAL_CAM_MC_128 ); configuration.checkValueSelected(CHKBOX_PROD, ATTR_CAM_MC, VAL_CAM_MC_64); }); @@ -364,8 +355,7 @@ context('CPQ Configuration', () => { configurationCpq.selectProductCard( RADGRP, ATTR_CAM_BODY, - VAL_CAM_BODY_D850, - true + VAL_CAM_BODY_D850 ); configurationCpq.checkPrice( RADGRP_PROD, @@ -380,13 +370,7 @@ context('CPQ Configuration', () => { ATTR_CAM_MC, VAL_CAM_MC_128 ); - configurationCpq.setQuantity( - CHKBOX_PROD, - 2, - ATTR_CAM_MC, - VAL_CAM_MC_128, - true - ); + configurationCpq.setQuantity(CHKBOX_PROD, 2, ATTR_CAM_MC, VAL_CAM_MC_128); configurationCpq.checkPrice( CHKBOX_PROD, '2x($100.00) +$200.00', @@ -394,18 +378,8 @@ context('CPQ Configuration', () => { VAL_CAM_MC_128 ); - configurationCpq.selectProductCard( - CHKBOX, - ATTR_CAM_LEN, - VAL_CAM_LEN_SI, - true - ); - configurationCpq.selectProductCard( - CHKBOX, - ATTR_CAM_LEN, - VAL_CAM_LEN_NI, - true - ); + configurationCpq.selectProductCard(CHKBOX, ATTR_CAM_LEN, VAL_CAM_LEN_SI); + configurationCpq.selectProductCard(CHKBOX, ATTR_CAM_LEN, VAL_CAM_LEN_NI); configurationCpq.checkPrice( CHKBOX_PROD, '$800.00', @@ -423,16 +397,14 @@ context('CPQ Configuration', () => { configurationCpq.deSelectProductCard( RADGRP, ATTR_CAM_BAG, - VAL_CAM_BAG_LP, - true + VAL_CAM_BAG_LP ); configuration.clickOnNextBtn(GRP_CAM_IAW); configurationCpq.selectAttributeAndWait( ATTR_CAM_PROF, RADGRP, - VAL_CAM_PROF_Y, - true + VAL_CAM_PROF_Y ); //wait for this option to disappear configuration.checkAttrValueNotDisplayed( @@ -441,12 +413,7 @@ context('CPQ Configuration', () => { VAL_CB_INS_Y2 ); - configurationCpq.selectProductCard( - DDLB, - ATTR_CAM_INS, - VAL_CB_INS_P4, - true - ); + configurationCpq.selectProductCard(DDLB, ATTR_CAM_INS, VAL_CB_INS_P4); configurationCpq.checkPrice( DDLB_PROD, '$600.00', diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.core-e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.core-e2e.cy.ts index 56e99f1e9e9..efe33407311 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.core-e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/vendor/cpq/cpq-configuration.core-e2e.cy.ts @@ -93,12 +93,9 @@ const ATTR_NAMES = { ], }; -//only test with cpqOverOcc = true. For now we leave the option to run the obsolete flavor -//but delete it finally with the next breaking major release const testConfig = [ { - name: 'CPQ Configuration - cpqOverOcc Mode', - cpqOverOcc: true, + name: 'CPQ Configuration', backendURL: `${Cypress.env('OCC_PREFIX')}/${POWERTOOLS}/cpqconfigurator/**`, }, ]; @@ -106,9 +103,7 @@ const testConfig = [ testConfig.forEach((config) => { context(config.name, () => { const cpqSettings: any = { - productConfigurator: { - cpqOverOcc: config.cpqOverOcc, - }, + productConfigurator: {}, }; beforeEach(() => { cy.cxConfig(cpqSettings); @@ -140,8 +135,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_COF_CUPS, RADGRP, - VAL_COF_CUPS_300, - config.cpqOverOcc + VAL_COF_CUPS_300 ); configuration.checkValueSelected( RADGRP, @@ -152,8 +146,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_COF_CUPS, RADGRP, - VAL_COF_CUPS_500, - config.cpqOverOcc + VAL_COF_CUPS_500 ); configuration.checkValueSelected( RADGRP, @@ -176,16 +169,14 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_COF_MODE, CHKBOX, - VAL_COF_MODE, - config.cpqOverOcc + VAL_COF_MODE ); configuration.checkValueSelected(CHKBOX, ATTR_COF_MODE, VAL_COF_MODE); configurationCpq.selectAttributeAndWait( ATTR_COF_MODE, CHKBOX, - VAL_COF_MODE, - config.cpqOverOcc + VAL_COF_MODE ); configurationCpq.checkValueNotSelected( CHKBOX, @@ -213,8 +204,7 @@ testConfig.forEach((config) => { configuration.selectAttribute( ATTR_CAM_BODY, RADGRP_PROD, - VAL_CAM_BODY_D850, - config.cpqOverOcc + VAL_CAM_BODY_D850 ); configuration.checkValueSelected( RADGRP_PROD, @@ -230,8 +220,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_BODY, RADGRP_PROD, - VAL_CAM_BODY_EOS80D, - config.cpqOverOcc + VAL_CAM_BODY_EOS80D ); configurationCpq.checkValueNotSelected( RADGRP_PROD, @@ -266,8 +255,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_INS, DDLB_PROD, - VAL_CB_INS_Y2, - config.cpqOverOcc + VAL_CB_INS_Y2 ); configurationCpq.checkValueNotSelected( DDLB_PROD, @@ -299,8 +287,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_MC, CHKBOX_PROD, - VAL_CAM_MC_64, - config.cpqOverOcc + VAL_CAM_MC_64 ); configuration.checkValueSelected( CHKBOX_PROD, @@ -316,8 +303,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_MC, CHKBOX_PROD, - VAL_CAM_MC_128, - config.cpqOverOcc + VAL_CAM_MC_128 ); configurationCpq.checkValueNotSelected( CHKBOX_PROD, @@ -405,8 +391,7 @@ testConfig.forEach((config) => { configurationCpq.selectProductCard( RADGRP, ATTR_CAM_BODY, - VAL_CAM_BODY_D850, - config.cpqOverOcc + VAL_CAM_BODY_D850 ); configurationCpq.checkPrice( RADGRP_PROD, @@ -425,8 +410,7 @@ testConfig.forEach((config) => { CHKBOX_PROD, 2, ATTR_CAM_MC, - VAL_CAM_MC_128, - config.cpqOverOcc + VAL_CAM_MC_128 ); configurationCpq.checkPrice( CHKBOX_PROD, @@ -438,14 +422,12 @@ testConfig.forEach((config) => { configurationCpq.selectProductCard( CHKBOX, ATTR_CAM_LEN, - VAL_CAM_LEN_SI, - config.cpqOverOcc + VAL_CAM_LEN_SI ); configurationCpq.selectProductCard( CHKBOX, ATTR_CAM_LEN, - VAL_CAM_LEN_NI, - config.cpqOverOcc + VAL_CAM_LEN_NI ); configurationCpq.checkPrice( CHKBOX_PROD, @@ -464,16 +446,14 @@ testConfig.forEach((config) => { configurationCpq.deSelectProductCard( RADGRP, ATTR_CAM_BAG, - VAL_CAM_BAG_LP, - config.cpqOverOcc + VAL_CAM_BAG_LP ); configuration.clickOnNextBtn(GRP_CAM_IAW); configurationCpq.selectAttributeAndWait( ATTR_CAM_PROF, RADGRP, - VAL_CAM_PROF_Y, - config.cpqOverOcc + VAL_CAM_PROF_Y ); //wait for this option to disappear configuration.checkAttrValueNotDisplayed( @@ -482,12 +462,7 @@ testConfig.forEach((config) => { VAL_CB_INS_Y2 ); - configurationCpq.selectProductCard( - DDLB, - ATTR_CAM_INS, - VAL_CB_INS_P4, - config.cpqOverOcc - ); + configurationCpq.selectProductCard(DDLB, ATTR_CAM_INS, VAL_CB_INS_P4); configurationCpq.checkPrice( DDLB_PROD, '$600.00', @@ -626,8 +601,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_BODY, RADGRP_PROD, - VAL_CAM_BODY_D850, - config.cpqOverOcc + VAL_CAM_BODY_D850 ); configuration.checkValueSelected( RADGRP_PROD, @@ -637,8 +611,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_BODY, RADGRP_PROD, - VAL_CAM_BODY_EOS80D, - config.cpqOverOcc + VAL_CAM_BODY_EOS80D ); configuration.checkValueSelected( RADGRP_PROD, @@ -648,8 +621,7 @@ testConfig.forEach((config) => { configurationCpq.selectAttributeAndWait( ATTR_CAM_LEN, CHKBOX_PROD, - VAL_CAM_LEN_SI, - config.cpqOverOcc + VAL_CAM_LEN_SI ); configuration.checkValueSelected( CHKBOX_PROD, diff --git a/projects/storefrontapp-e2e-cypress/cypress/helpers/product-configurator-cpq.ts b/projects/storefrontapp-e2e-cypress/cypress/helpers/product-configurator-cpq.ts index 1032fb13c52..7338b58d524 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/helpers/product-configurator-cpq.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/helpers/product-configurator-cpq.ts @@ -118,11 +118,10 @@ export function checkAttributeHeaderDisplayed( export function selectProductCard( cardType: cardType, attributeName: string, - valueName: string, - cpqOverOcc?: boolean + valueName: string ) { const uiType: configuration.uiType = convertCardTypeToUiType(cardType); - selectAttributeAndWait(attributeName, uiType, valueName, cpqOverOcc); + selectAttributeAndWait(attributeName, uiType, valueName); configuration.checkValueSelected(uiType, attributeName, valueName); } @@ -135,11 +134,10 @@ export function selectProductCard( export function deSelectProductCard( cardType: cardType, attributeName: string, - valueName: string, - cpqOverOcc?: boolean + valueName: string ) { const uiType: configuration.uiType = convertCardTypeToUiType(cardType); - selectAttributeAndWait(attributeName, uiType, valueName, cpqOverOcc); + selectAttributeAndWait(attributeName, uiType, valueName); checkValueNotSelected(uiType, attributeName, valueName); } @@ -173,14 +171,10 @@ export function convertCardTypeToUiType(cardType: cardType) { export function selectAttributeAndWait( attributeName: string, uiType: configuration.uiType, - valueName: string, - cpqOverOcc?: boolean + valueName: string ): void { configuration.selectAttribute(attributeName, uiType, valueName); cy.wait('@updateConfig'); - if (!cpqOverOcc) { - cy.wait('@readConfig'); - } } /** @@ -222,8 +216,7 @@ export function setQuantity( uiType: configuration.uiType, quantity: number, attributeName: string, - valueName?: string, - cpqOverOcc?: boolean + valueName?: string ): void { let containerId = configuration.getAttributeId(attributeName, uiType); if (valueName) { @@ -235,9 +228,6 @@ export function setQuantity( ); configuration.checkUpdatingMessageNotDisplayed(); cy.wait('@updateConfig'); - if (!cpqOverOcc) { - cy.wait('@readConfig'); - } } /**