This is page 13 of 43. Use http://codebase.md/taurgis/sfcc-dev-mcp?page={x} to view the full context.
# Directory Structure
```
├── .DS_Store
├── .github
│ ├── dependabot.yml
│ ├── instructions
│ │ ├── mcp-node-tests.instructions.md
│ │ └── mcp-yml-tests.instructions.md
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── documentation.yml
│ │ ├── feature_request.yml
│ │ └── question.yml
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bug_fix.md
│ │ ├── documentation.md
│ │ └── new_tool.md
│ ├── pull_request_template.md
│ └── workflows
│ ├── ci.yml
│ ├── deploy-pages.yml
│ ├── publish.yml
│ └── update-docs.yml
├── .gitignore
├── .husky
│ └── pre-commit
├── aegis.config.docs-only.json
├── aegis.config.json
├── aegis.config.with-dw.json
├── AGENTS.md
├── ai-instructions
│ ├── claude-desktop
│ │ └── claude_custom_instructions.md
│ ├── cursor
│ │ └── .cursor
│ │ └── rules
│ │ ├── debugging-workflows.mdc
│ │ ├── hooks-development.mdc
│ │ ├── isml-templates.mdc
│ │ ├── job-framework.mdc
│ │ ├── performance-optimization.mdc
│ │ ├── scapi-endpoints.mdc
│ │ ├── security-patterns.mdc
│ │ ├── sfcc-development.mdc
│ │ ├── sfra-controllers.mdc
│ │ ├── sfra-models.mdc
│ │ ├── system-objects.mdc
│ │ └── testing-patterns.mdc
│ └── github-copilot
│ └── copilot-instructions.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│ ├── best-practices
│ │ ├── cartridge_creation.md
│ │ ├── isml_templates.md
│ │ ├── job_framework.md
│ │ ├── localserviceregistry.md
│ │ ├── ocapi_hooks.md
│ │ ├── performance.md
│ │ ├── scapi_custom_endpoint.md
│ │ ├── scapi_hooks.md
│ │ ├── security.md
│ │ ├── sfra_client_side_js.md
│ │ ├── sfra_controllers.md
│ │ ├── sfra_models.md
│ │ └── sfra_scss.md
│ ├── dw_campaign
│ │ ├── ABTest.md
│ │ ├── ABTestMgr.md
│ │ ├── ABTestSegment.md
│ │ ├── AmountDiscount.md
│ │ ├── ApproachingDiscount.md
│ │ ├── BonusChoiceDiscount.md
│ │ ├── BonusDiscount.md
│ │ ├── Campaign.md
│ │ ├── CampaignMgr.md
│ │ ├── CampaignStatusCodes.md
│ │ ├── Coupon.md
│ │ ├── CouponMgr.md
│ │ ├── CouponRedemption.md
│ │ ├── CouponStatusCodes.md
│ │ ├── Discount.md
│ │ ├── DiscountPlan.md
│ │ ├── FixedPriceDiscount.md
│ │ ├── FixedPriceShippingDiscount.md
│ │ ├── FreeDiscount.md
│ │ ├── FreeShippingDiscount.md
│ │ ├── PercentageDiscount.md
│ │ ├── PercentageOptionDiscount.md
│ │ ├── PriceBookPriceDiscount.md
│ │ ├── Promotion.md
│ │ ├── PromotionMgr.md
│ │ ├── PromotionPlan.md
│ │ ├── SlotContent.md
│ │ ├── SourceCodeGroup.md
│ │ ├── SourceCodeInfo.md
│ │ ├── SourceCodeStatusCodes.md
│ │ └── TotalFixedPriceDiscount.md
│ ├── dw_catalog
│ │ ├── Catalog.md
│ │ ├── CatalogMgr.md
│ │ ├── Category.md
│ │ ├── CategoryAssignment.md
│ │ ├── CategoryLink.md
│ │ ├── PriceBook.md
│ │ ├── PriceBookMgr.md
│ │ ├── Product.md
│ │ ├── ProductActiveData.md
│ │ ├── ProductAttributeModel.md
│ │ ├── ProductAvailabilityLevels.md
│ │ ├── ProductAvailabilityModel.md
│ │ ├── ProductInventoryList.md
│ │ ├── ProductInventoryMgr.md
│ │ ├── ProductInventoryRecord.md
│ │ ├── ProductLink.md
│ │ ├── ProductMgr.md
│ │ ├── ProductOption.md
│ │ ├── ProductOptionModel.md
│ │ ├── ProductOptionValue.md
│ │ ├── ProductPriceInfo.md
│ │ ├── ProductPriceModel.md
│ │ ├── ProductPriceTable.md
│ │ ├── ProductSearchHit.md
│ │ ├── ProductSearchModel.md
│ │ ├── ProductSearchRefinementDefinition.md
│ │ ├── ProductSearchRefinements.md
│ │ ├── ProductSearchRefinementValue.md
│ │ ├── ProductVariationAttribute.md
│ │ ├── ProductVariationAttributeValue.md
│ │ ├── ProductVariationModel.md
│ │ ├── Recommendation.md
│ │ ├── SearchModel.md
│ │ ├── SearchRefinementDefinition.md
│ │ ├── SearchRefinements.md
│ │ ├── SearchRefinementValue.md
│ │ ├── SortingOption.md
│ │ ├── SortingRule.md
│ │ ├── Store.md
│ │ ├── StoreGroup.md
│ │ ├── StoreInventoryFilter.md
│ │ ├── StoreInventoryFilterValue.md
│ │ ├── StoreMgr.md
│ │ ├── Variant.md
│ │ └── VariationGroup.md
│ ├── dw_content
│ │ ├── Content.md
│ │ ├── ContentMgr.md
│ │ ├── ContentSearchModel.md
│ │ ├── ContentSearchRefinementDefinition.md
│ │ ├── ContentSearchRefinements.md
│ │ ├── ContentSearchRefinementValue.md
│ │ ├── Folder.md
│ │ ├── Library.md
│ │ ├── MarkupText.md
│ │ └── MediaFile.md
│ ├── dw_crypto
│ │ ├── CertificateRef.md
│ │ ├── CertificateUtils.md
│ │ ├── Cipher.md
│ │ ├── Encoding.md
│ │ ├── JWE.md
│ │ ├── JWEHeader.md
│ │ ├── JWS.md
│ │ ├── JWSHeader.md
│ │ ├── KeyRef.md
│ │ ├── Mac.md
│ │ ├── MessageDigest.md
│ │ ├── SecureRandom.md
│ │ ├── Signature.md
│ │ ├── WeakCipher.md
│ │ ├── WeakMac.md
│ │ ├── WeakMessageDigest.md
│ │ ├── WeakSignature.md
│ │ └── X509Certificate.md
│ ├── dw_customer
│ │ ├── AddressBook.md
│ │ ├── AgentUserMgr.md
│ │ ├── AgentUserStatusCodes.md
│ │ ├── AuthenticationStatus.md
│ │ ├── Credentials.md
│ │ ├── Customer.md
│ │ ├── CustomerActiveData.md
│ │ ├── CustomerAddress.md
│ │ ├── CustomerCDPData.md
│ │ ├── CustomerContextMgr.md
│ │ ├── CustomerGroup.md
│ │ ├── CustomerList.md
│ │ ├── CustomerMgr.md
│ │ ├── CustomerPasswordConstraints.md
│ │ ├── CustomerPaymentInstrument.md
│ │ ├── CustomerStatusCodes.md
│ │ ├── EncryptedObject.md
│ │ ├── ExternalProfile.md
│ │ ├── OrderHistory.md
│ │ ├── ProductList.md
│ │ ├── ProductListItem.md
│ │ ├── ProductListItemPurchase.md
│ │ ├── ProductListMgr.md
│ │ ├── ProductListRegistrant.md
│ │ ├── Profile.md
│ │ └── Wallet.md
│ ├── dw_extensions.applepay
│ │ ├── ApplePayHookResult.md
│ │ └── ApplePayHooks.md
│ ├── dw_extensions.facebook
│ │ ├── FacebookFeedHooks.md
│ │ └── FacebookProduct.md
│ ├── dw_extensions.paymentrequest
│ │ ├── PaymentRequestHookResult.md
│ │ └── PaymentRequestHooks.md
│ ├── dw_extensions.payments
│ │ ├── SalesforceBancontactPaymentDetails.md
│ │ ├── SalesforceCardPaymentDetails.md
│ │ ├── SalesforceEpsPaymentDetails.md
│ │ ├── SalesforceIdealPaymentDetails.md
│ │ ├── SalesforceKlarnaPaymentDetails.md
│ │ ├── SalesforcePaymentDetails.md
│ │ ├── SalesforcePaymentIntent.md
│ │ ├── SalesforcePaymentMethod.md
│ │ ├── SalesforcePaymentRequest.md
│ │ ├── SalesforcePaymentsHooks.md
│ │ ├── SalesforcePaymentsMgr.md
│ │ ├── SalesforcePaymentsSiteConfiguration.md
│ │ ├── SalesforcePayPalOrder.md
│ │ ├── SalesforcePayPalOrderAddress.md
│ │ ├── SalesforcePayPalOrderPayer.md
│ │ ├── SalesforcePayPalPaymentDetails.md
│ │ ├── SalesforceSepaDebitPaymentDetails.md
│ │ └── SalesforceVenmoPaymentDetails.md
│ ├── dw_extensions.pinterest
│ │ ├── PinterestAvailability.md
│ │ ├── PinterestFeedHooks.md
│ │ ├── PinterestOrder.md
│ │ ├── PinterestOrderHooks.md
│ │ └── PinterestProduct.md
│ ├── dw_io
│ │ ├── CSVStreamReader.md
│ │ ├── CSVStreamWriter.md
│ │ ├── File.md
│ │ ├── FileReader.md
│ │ ├── FileWriter.md
│ │ ├── InputStream.md
│ │ ├── OutputStream.md
│ │ ├── PrintWriter.md
│ │ ├── RandomAccessFileReader.md
│ │ ├── Reader.md
│ │ ├── StringWriter.md
│ │ ├── Writer.md
│ │ ├── XMLIndentingStreamWriter.md
│ │ ├── XMLStreamConstants.md
│ │ ├── XMLStreamReader.md
│ │ └── XMLStreamWriter.md
│ ├── dw_job
│ │ ├── JobExecution.md
│ │ └── JobStepExecution.md
│ ├── dw_net
│ │ ├── FTPClient.md
│ │ ├── FTPFileInfo.md
│ │ ├── HTTPClient.md
│ │ ├── HTTPRequestPart.md
│ │ ├── Mail.md
│ │ ├── SFTPClient.md
│ │ ├── SFTPFileInfo.md
│ │ ├── WebDAVClient.md
│ │ └── WebDAVFileInfo.md
│ ├── dw_object
│ │ ├── ActiveData.md
│ │ ├── CustomAttributes.md
│ │ ├── CustomObject.md
│ │ ├── CustomObjectMgr.md
│ │ ├── Extensible.md
│ │ ├── ExtensibleObject.md
│ │ ├── Note.md
│ │ ├── ObjectAttributeDefinition.md
│ │ ├── ObjectAttributeGroup.md
│ │ ├── ObjectAttributeValueDefinition.md
│ │ ├── ObjectTypeDefinition.md
│ │ ├── PersistentObject.md
│ │ ├── SimpleExtensible.md
│ │ └── SystemObjectMgr.md
│ ├── dw_order
│ │ ├── AbstractItem.md
│ │ ├── AbstractItemCtnr.md
│ │ ├── Appeasement.md
│ │ ├── AppeasementItem.md
│ │ ├── Basket.md
│ │ ├── BasketMgr.md
│ │ ├── BonusDiscountLineItem.md
│ │ ├── CouponLineItem.md
│ │ ├── CreateAgentBasketLimitExceededException.md
│ │ ├── CreateBasketFromOrderException.md
│ │ ├── CreateCouponLineItemException.md
│ │ ├── CreateOrderException.md
│ │ ├── CreateTemporaryBasketLimitExceededException.md
│ │ ├── GiftCertificate.md
│ │ ├── GiftCertificateLineItem.md
│ │ ├── GiftCertificateMgr.md
│ │ ├── GiftCertificateStatusCodes.md
│ │ ├── Invoice.md
│ │ ├── InvoiceItem.md
│ │ ├── LineItem.md
│ │ ├── LineItemCtnr.md
│ │ ├── Order.md
│ │ ├── OrderAddress.md
│ │ ├── OrderItem.md
│ │ ├── OrderMgr.md
│ │ ├── OrderPaymentInstrument.md
│ │ ├── OrderProcessStatusCodes.md
│ │ ├── PaymentCard.md
│ │ ├── PaymentInstrument.md
│ │ ├── PaymentMethod.md
│ │ ├── PaymentMgr.md
│ │ ├── PaymentProcessor.md
│ │ ├── PaymentStatusCodes.md
│ │ ├── PaymentTransaction.md
│ │ ├── PriceAdjustment.md
│ │ ├── PriceAdjustmentLimitTypes.md
│ │ ├── ProductLineItem.md
│ │ ├── ProductShippingCost.md
│ │ ├── ProductShippingLineItem.md
│ │ ├── ProductShippingModel.md
│ │ ├── Return.md
│ │ ├── ReturnCase.md
│ │ ├── ReturnCaseItem.md
│ │ ├── ReturnItem.md
│ │ ├── Shipment.md
│ │ ├── ShipmentShippingCost.md
│ │ ├── ShipmentShippingModel.md
│ │ ├── ShippingLineItem.md
│ │ ├── ShippingLocation.md
│ │ ├── ShippingMethod.md
│ │ ├── ShippingMgr.md
│ │ ├── ShippingOrder.md
│ │ ├── ShippingOrderItem.md
│ │ ├── SumItem.md
│ │ ├── TaxGroup.md
│ │ ├── TaxItem.md
│ │ ├── TaxMgr.md
│ │ ├── TrackingInfo.md
│ │ └── TrackingRef.md
│ ├── dw_order.hooks
│ │ ├── CalculateHooks.md
│ │ ├── OrderHooks.md
│ │ ├── PaymentHooks.md
│ │ ├── ReturnHooks.md
│ │ └── ShippingOrderHooks.md
│ ├── dw_rpc
│ │ ├── SOAPUtil.md
│ │ ├── Stub.md
│ │ └── WebReference.md
│ ├── dw_suggest
│ │ ├── BrandSuggestions.md
│ │ ├── CategorySuggestions.md
│ │ ├── ContentSuggestions.md
│ │ ├── CustomSuggestions.md
│ │ ├── ProductSuggestions.md
│ │ ├── SearchPhraseSuggestions.md
│ │ ├── SuggestedCategory.md
│ │ ├── SuggestedContent.md
│ │ ├── SuggestedPhrase.md
│ │ ├── SuggestedProduct.md
│ │ ├── SuggestedTerm.md
│ │ ├── SuggestedTerms.md
│ │ ├── Suggestions.md
│ │ └── SuggestModel.md
│ ├── dw_svc
│ │ ├── FTPService.md
│ │ ├── FTPServiceDefinition.md
│ │ ├── HTTPFormService.md
│ │ ├── HTTPFormServiceDefinition.md
│ │ ├── HTTPService.md
│ │ ├── HTTPServiceDefinition.md
│ │ ├── LocalServiceRegistry.md
│ │ ├── Result.md
│ │ ├── Service.md
│ │ ├── ServiceCallback.md
│ │ ├── ServiceConfig.md
│ │ ├── ServiceCredential.md
│ │ ├── ServiceDefinition.md
│ │ ├── ServiceProfile.md
│ │ ├── ServiceRegistry.md
│ │ ├── SOAPService.md
│ │ └── SOAPServiceDefinition.md
│ ├── dw_system
│ │ ├── AgentUserStatusCodes.md
│ │ ├── Cache.md
│ │ ├── CacheMgr.md
│ │ ├── HookMgr.md
│ │ ├── InternalObject.md
│ │ ├── JobProcessMonitor.md
│ │ ├── Log.md
│ │ ├── Logger.md
│ │ ├── LogNDC.md
│ │ ├── OrganizationPreferences.md
│ │ ├── Pipeline.md
│ │ ├── PipelineDictionary.md
│ │ ├── RemoteInclude.md
│ │ ├── Request.md
│ │ ├── RequestHooks.md
│ │ ├── Response.md
│ │ ├── RESTErrorResponse.md
│ │ ├── RESTResponseMgr.md
│ │ ├── RESTSuccessResponse.md
│ │ ├── SearchStatus.md
│ │ ├── Session.md
│ │ ├── Site.md
│ │ ├── SitePreferences.md
│ │ ├── Status.md
│ │ ├── StatusItem.md
│ │ ├── System.md
│ │ └── Transaction.md
│ ├── dw_util
│ │ ├── ArrayList.md
│ │ ├── Assert.md
│ │ ├── BigInteger.md
│ │ ├── Bytes.md
│ │ ├── Calendar.md
│ │ ├── Collection.md
│ │ ├── Currency.md
│ │ ├── DateUtils.md
│ │ ├── Decimal.md
│ │ ├── FilteringCollection.md
│ │ ├── Geolocation.md
│ │ ├── HashMap.md
│ │ ├── HashSet.md
│ │ ├── Iterator.md
│ │ ├── LinkedHashMap.md
│ │ ├── LinkedHashSet.md
│ │ ├── List.md
│ │ ├── Locale.md
│ │ ├── Map.md
│ │ ├── MapEntry.md
│ │ ├── MappingKey.md
│ │ ├── MappingMgr.md
│ │ ├── PropertyComparator.md
│ │ ├── SecureEncoder.md
│ │ ├── SecureFilter.md
│ │ ├── SeekableIterator.md
│ │ ├── Set.md
│ │ ├── SortedMap.md
│ │ ├── SortedSet.md
│ │ ├── StringUtils.md
│ │ ├── Template.md
│ │ └── UUIDUtils.md
│ ├── dw_value
│ │ ├── EnumValue.md
│ │ ├── MimeEncodedText.md
│ │ ├── Money.md
│ │ └── Quantity.md
│ ├── dw_web
│ │ ├── ClickStream.md
│ │ ├── ClickStreamEntry.md
│ │ ├── Cookie.md
│ │ ├── Cookies.md
│ │ ├── CSRFProtection.md
│ │ ├── Form.md
│ │ ├── FormAction.md
│ │ ├── FormElement.md
│ │ ├── FormElementValidationResult.md
│ │ ├── FormField.md
│ │ ├── FormFieldOption.md
│ │ ├── FormFieldOptions.md
│ │ ├── FormGroup.md
│ │ ├── FormList.md
│ │ ├── FormListItem.md
│ │ ├── Forms.md
│ │ ├── HttpParameter.md
│ │ ├── HttpParameterMap.md
│ │ ├── LoopIterator.md
│ │ ├── PageMetaData.md
│ │ ├── PageMetaTag.md
│ │ ├── PagingModel.md
│ │ ├── Resource.md
│ │ ├── URL.md
│ │ ├── URLAction.md
│ │ ├── URLParameter.md
│ │ ├── URLRedirect.md
│ │ ├── URLRedirectMgr.md
│ │ └── URLUtils.md
│ ├── sfra
│ │ ├── account.md
│ │ ├── address.md
│ │ ├── billing.md
│ │ ├── cart.md
│ │ ├── categories.md
│ │ ├── content.md
│ │ ├── locale.md
│ │ ├── order.md
│ │ ├── payment.md
│ │ ├── price-default.md
│ │ ├── price-range.md
│ │ ├── price-tiered.md
│ │ ├── product-bundle.md
│ │ ├── product-full.md
│ │ ├── product-line-items.md
│ │ ├── product-search.md
│ │ ├── product-tile.md
│ │ ├── querystring.md
│ │ ├── render.md
│ │ ├── request.md
│ │ ├── response.md
│ │ ├── server.md
│ │ ├── shipping.md
│ │ ├── store.md
│ │ ├── stores.md
│ │ └── totals.md
│ └── TopLevel
│ ├── APIException.md
│ ├── arguments.md
│ ├── Array.md
│ ├── ArrayBuffer.md
│ ├── BigInt.md
│ ├── Boolean.md
│ ├── ConversionError.md
│ ├── DataView.md
│ ├── Date.md
│ ├── Error.md
│ ├── ES6Iterator.md
│ ├── EvalError.md
│ ├── Fault.md
│ ├── Float32Array.md
│ ├── Float64Array.md
│ ├── Function.md
│ ├── Generator.md
│ ├── global.md
│ ├── Int16Array.md
│ ├── Int32Array.md
│ ├── Int8Array.md
│ ├── InternalError.md
│ ├── IOError.md
│ ├── Iterable.md
│ ├── Iterator.md
│ ├── JSON.md
│ ├── Map.md
│ ├── Math.md
│ ├── Module.md
│ ├── Namespace.md
│ ├── Number.md
│ ├── Object.md
│ ├── QName.md
│ ├── RangeError.md
│ ├── ReferenceError.md
│ ├── RegExp.md
│ ├── Set.md
│ ├── StopIteration.md
│ ├── String.md
│ ├── Symbol.md
│ ├── SyntaxError.md
│ ├── SystemError.md
│ ├── TypeError.md
│ ├── Uint16Array.md
│ ├── Uint32Array.md
│ ├── Uint8Array.md
│ ├── Uint8ClampedArray.md
│ ├── URIError.md
│ ├── WeakMap.md
│ ├── WeakSet.md
│ ├── XML.md
│ ├── XMLList.md
│ └── XMLStreamError.md
├── docs-site
│ ├── .gitignore
│ ├── App.tsx
│ ├── components
│ │ ├── Badge.tsx
│ │ ├── BreadcrumbSchema.tsx
│ │ ├── CodeBlock.tsx
│ │ ├── Collapsible.tsx
│ │ ├── ConfigBuilder.tsx
│ │ ├── ConfigHero.tsx
│ │ ├── ConfigModeTabs.tsx
│ │ ├── icons.tsx
│ │ ├── Layout.tsx
│ │ ├── LightCodeContainer.tsx
│ │ ├── NewcomerCTA.tsx
│ │ ├── NextStepsStrip.tsx
│ │ ├── OnThisPage.tsx
│ │ ├── Search.tsx
│ │ ├── SEO.tsx
│ │ ├── Sidebar.tsx
│ │ ├── StructuredData.tsx
│ │ ├── ToolCard.tsx
│ │ ├── ToolFilters.tsx
│ │ ├── Typography.tsx
│ │ └── VersionBadge.tsx
│ ├── constants.tsx
│ ├── index.html
│ ├── main.tsx
│ ├── metadata.json
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ │ ├── AIInterfacesPage.tsx
│ │ ├── ConfigurationPage.tsx
│ │ ├── DevelopmentPage.tsx
│ │ ├── ExamplesPage.tsx
│ │ ├── FeaturesPage.tsx
│ │ ├── HomePage.tsx
│ │ ├── SecurityPage.tsx
│ │ ├── ToolsPage.tsx
│ │ └── TroubleshootingPage.tsx
│ ├── postcss.config.js
│ ├── public
│ │ ├── .well-known
│ │ │ └── security.txt
│ │ ├── 404.html
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── explain-product-pricing-methods-no-mcp.png
│ │ ├── explain-product-pricing-methods.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── llms.txt
│ │ ├── robots.txt
│ │ ├── site.webmanifest
│ │ └── sitemap.xml
│ ├── README.md
│ ├── scripts
│ │ ├── generate-search-index.js
│ │ ├── generate-sitemap.js
│ │ └── search-dev.js
│ ├── src
│ │ └── styles
│ │ ├── input.css
│ │ └── prism-theme.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── types.ts
│ ├── utils
│ │ ├── search.ts
│ │ └── toolsData.ts
│ └── vite.config.ts
├── eslint.config.js
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│ └── convert-docs.js
├── SECURITY.md
├── server.json
├── src
│ ├── clients
│ │ ├── base
│ │ │ ├── http-client.ts
│ │ │ ├── oauth-token.ts
│ │ │ └── ocapi-auth-client.ts
│ │ ├── best-practices-client.ts
│ │ ├── cartridge-generation-client.ts
│ │ ├── docs
│ │ │ ├── class-content-parser.ts
│ │ │ ├── class-name-resolver.ts
│ │ │ ├── documentation-scanner.ts
│ │ │ ├── index.ts
│ │ │ └── referenced-types-extractor.ts
│ │ ├── docs-client.ts
│ │ ├── log-client.ts
│ │ ├── logs
│ │ │ ├── index.ts
│ │ │ ├── log-analyzer.ts
│ │ │ ├── log-client.ts
│ │ │ ├── log-constants.ts
│ │ │ ├── log-file-discovery.ts
│ │ │ ├── log-file-reader.ts
│ │ │ ├── log-formatter.ts
│ │ │ ├── log-processor.ts
│ │ │ ├── log-types.ts
│ │ │ └── webdav-client-manager.ts
│ │ ├── ocapi
│ │ │ ├── code-versions-client.ts
│ │ │ ├── site-preferences-client.ts
│ │ │ └── system-objects-client.ts
│ │ ├── ocapi-client.ts
│ │ └── sfra-client.ts
│ ├── config
│ │ ├── configuration-factory.ts
│ │ └── dw-json-loader.ts
│ ├── core
│ │ ├── handlers
│ │ │ ├── abstract-log-tool-handler.ts
│ │ │ ├── base-handler.ts
│ │ │ ├── best-practices-handler.ts
│ │ │ ├── cartridge-handler.ts
│ │ │ ├── client-factory.ts
│ │ │ ├── code-version-handler.ts
│ │ │ ├── docs-handler.ts
│ │ │ ├── job-log-handler.ts
│ │ │ ├── job-log-tool-config.ts
│ │ │ ├── log-handler.ts
│ │ │ ├── log-tool-config.ts
│ │ │ ├── sfra-handler.ts
│ │ │ ├── system-object-handler.ts
│ │ │ └── validation-helpers.ts
│ │ ├── server.ts
│ │ └── tool-definitions.ts
│ ├── index.ts
│ ├── main.ts
│ ├── services
│ │ ├── file-system-service.ts
│ │ ├── index.ts
│ │ └── path-service.ts
│ ├── tool-configs
│ │ ├── best-practices-tool-config.ts
│ │ ├── cartridge-tool-config.ts
│ │ ├── code-version-tool-config.ts
│ │ ├── docs-tool-config.ts
│ │ ├── job-log-tool-config.ts
│ │ ├── log-tool-config.ts
│ │ ├── sfra-tool-config.ts
│ │ └── system-object-tool-config.ts
│ ├── types
│ │ └── types.ts
│ └── utils
│ ├── cache.ts
│ ├── job-log-tool-config.ts
│ ├── job-log-utils.ts
│ ├── log-cache.ts
│ ├── log-tool-config.ts
│ ├── log-tool-constants.ts
│ ├── log-tool-utils.ts
│ ├── logger.ts
│ ├── ocapi-url-builder.ts
│ ├── path-resolver.ts
│ ├── query-builder.ts
│ ├── utils.ts
│ └── validator.ts
├── tests
│ ├── __mocks__
│ │ ├── docs-client.ts
│ │ ├── src
│ │ │ └── clients
│ │ │ └── base
│ │ │ └── http-client.js
│ │ └── webdav.js
│ ├── base-handler.test.ts
│ ├── base-http-client.test.ts
│ ├── best-practices-handler.test.ts
│ ├── cache.test.ts
│ ├── cartridge-handler.test.ts
│ ├── class-content-parser.test.ts
│ ├── class-name-resolver.test.ts
│ ├── client-factory.test.ts
│ ├── code-version-handler.test.ts
│ ├── code-versions-client.test.ts
│ ├── config.test.ts
│ ├── configuration-factory.test.ts
│ ├── docs-handler.test.ts
│ ├── documentation-scanner.test.ts
│ ├── file-system-service.test.ts
│ ├── job-log-handler.test.ts
│ ├── job-log-utils.test.ts
│ ├── log-client.test.ts
│ ├── log-handler.test.ts
│ ├── log-processor.test.ts
│ ├── logger.test.ts
│ ├── mcp
│ │ ├── AGENTS.md
│ │ ├── node
│ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js
│ │ │ ├── code-versions.full-mode.programmatic.test.js
│ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js
│ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js
│ │ │ ├── get-available-sfra-documents.programmatic.test.js
│ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js
│ │ │ ├── get-hook-reference.docs-only.programmatic.test.js
│ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js
│ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js
│ │ │ ├── get-latest-debug.full-mode.programmatic.test.js
│ │ │ ├── get-latest-error.full-mode.programmatic.test.js
│ │ │ ├── get-latest-info.full-mode.programmatic.test.js
│ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js
│ │ │ ├── get-latest-warn.full-mode.programmatic.test.js
│ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js
│ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js
│ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js
│ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js
│ │ │ ├── get-sfra-document.programmatic.test.js
│ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js
│ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js
│ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js
│ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js
│ │ │ ├── list-log-files.full-mode.programmatic.test.js
│ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js
│ │ │ ├── search-best-practices.docs-only.programmatic.test.js
│ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js
│ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js
│ │ │ ├── search-job-logs.full-mode.programmatic.test.js
│ │ │ ├── search-logs.full-mode.programmatic.test.js
│ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js
│ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js
│ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js
│ │ │ ├── search-site-preferences.full-mode.programmatic.test.js
│ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js
│ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js
│ │ │ ├── summarize-logs.full-mode.programmatic.test.js
│ │ │ ├── tools.docs-only.programmatic.test.js
│ │ │ └── tools.full-mode.programmatic.test.js
│ │ ├── README.md
│ │ ├── test-fixtures
│ │ │ └── dw.json
│ │ └── yaml
│ │ ├── activate-code-version.docs-only.test.mcp.yml
│ │ ├── activate-code-version.full-mode.test.mcp.yml
│ │ ├── get_latest_error.test.mcp.yml
│ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml
│ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml
│ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml
│ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml
│ │ ├── get-best-practice-guide.docs-only.test.mcp.yml
│ │ ├── get-best-practice-guide.full-mode.test.mcp.yml
│ │ ├── get-code-versions.docs-only.test.mcp.yml
│ │ ├── get-code-versions.full-mode.test.mcp.yml
│ │ ├── get-hook-reference.docs-only.test.mcp.yml
│ │ ├── get-hook-reference.full-mode.test.mcp.yml
│ │ ├── get-job-execution-summary.full-mode.test.mcp.yml
│ │ ├── get-job-log-entries.full-mode.test.mcp.yml
│ │ ├── get-latest-debug.full-mode.test.mcp.yml
│ │ ├── get-latest-error.full-mode.test.mcp.yml
│ │ ├── get-latest-info.full-mode.test.mcp.yml
│ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml
│ │ ├── get-latest-warn.full-mode.test.mcp.yml
│ │ ├── get-log-file-contents.full-mode.test.mcp.yml
│ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml
│ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml
│ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml
│ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml
│ │ ├── get-sfra-categories.docs-only.test.mcp.yml
│ │ ├── get-sfra-categories.full-mode.test.mcp.yml
│ │ ├── get-sfra-document.docs-only.test.mcp.yml
│ │ ├── get-sfra-document.full-mode.test.mcp.yml
│ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml
│ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml
│ │ ├── get-system-object-definition.docs-only.test.mcp.yml
│ │ ├── get-system-object-definition.full-mode.test.mcp.yml
│ │ ├── get-system-object-definitions.docs-only.test.mcp.yml
│ │ ├── get-system-object-definitions.full-mode.test.mcp.yml
│ │ ├── list-log-files.full-mode.test.mcp.yml
│ │ ├── list-sfcc-classes.docs-only.test.mcp.yml
│ │ ├── list-sfcc-classes.full-mode.test.mcp.yml
│ │ ├── search-best-practices.docs-only.test.mcp.yml
│ │ ├── search-best-practices.full-mode.test.mcp.yml
│ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml
│ │ ├── search-custom-object-attribute-definitions.test.mcp.yml
│ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml
│ │ ├── search-job-logs.full-mode.test.mcp.yml
│ │ ├── search-logs.full-mode.test.mcp.yml
│ │ ├── search-sfcc-classes.docs-only.test.mcp.yml
│ │ ├── search-sfcc-classes.full-mode.test.mcp.yml
│ │ ├── search-sfcc-methods.docs-only.test.mcp.yml
│ │ ├── search-sfcc-methods.full-mode.test.mcp.yml
│ │ ├── search-sfra-documentation.docs-only.test.mcp.yml
│ │ ├── search-sfra-documentation.full-mode.test.mcp.yml
│ │ ├── search-site-preferences.docs-only.test.mcp.yml
│ │ ├── search-site-preferences.full-mode.test.mcp.yml
│ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml
│ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml
│ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml
│ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml
│ │ ├── summarize-logs.full-mode.test.mcp.yml
│ │ ├── tools.docs-only.test.mcp.yml
│ │ └── tools.full-mode.test.mcp.yml
│ ├── oauth-token.test.ts
│ ├── ocapi-auth-client.test.ts
│ ├── ocapi-client.test.ts
│ ├── path-service.test.ts
│ ├── query-builder.test.ts
│ ├── referenced-types-extractor.test.ts
│ ├── servers
│ │ ├── sfcc-mock-server
│ │ │ ├── mock-data
│ │ │ │ └── ocapi
│ │ │ │ ├── code-versions.json
│ │ │ │ ├── custom-object-attributes-customapi.json
│ │ │ │ ├── custom-object-attributes-globalsettings.json
│ │ │ │ ├── custom-object-attributes-versionhistory.json
│ │ │ │ ├── site-preferences-ccv.json
│ │ │ │ ├── site-preferences-fastforward.json
│ │ │ │ ├── site-preferences-sfra.json
│ │ │ │ ├── site-preferences-storefront.json
│ │ │ │ ├── site-preferences-system.json
│ │ │ │ ├── system-object-attribute-groups-campaign.json
│ │ │ │ ├── system-object-attribute-groups-category.json
│ │ │ │ ├── system-object-attribute-groups-order.json
│ │ │ │ ├── system-object-attribute-groups-product.json
│ │ │ │ ├── system-object-attribute-groups-sitepreferences.json
│ │ │ │ ├── system-object-attributes-customeraddress.json
│ │ │ │ ├── system-object-attributes-product-expanded.json
│ │ │ │ ├── system-object-attributes-product.json
│ │ │ │ ├── system-object-definition-category.json
│ │ │ │ ├── system-object-definition-customer.json
│ │ │ │ ├── system-object-definition-customeraddress.json
│ │ │ │ ├── system-object-definition-order.json
│ │ │ │ ├── system-object-definition-product.json
│ │ │ │ ├── system-object-definitions-old.json
│ │ │ │ └── system-object-definitions.json
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ ├── README.md
│ │ │ ├── scripts
│ │ │ │ └── setup-logs.js
│ │ │ ├── server.js
│ │ │ └── src
│ │ │ ├── app.js
│ │ │ ├── config
│ │ │ │ └── server-config.js
│ │ │ ├── middleware
│ │ │ │ ├── auth.js
│ │ │ │ ├── cors.js
│ │ │ │ └── logging.js
│ │ │ ├── routes
│ │ │ │ ├── ocapi
│ │ │ │ │ ├── code-versions-handler.js
│ │ │ │ │ ├── oauth-handler.js
│ │ │ │ │ ├── ocapi-error-utils.js
│ │ │ │ │ ├── ocapi-utils.js
│ │ │ │ │ ├── site-preferences-handler.js
│ │ │ │ │ └── system-objects-handler.js
│ │ │ │ ├── ocapi.js
│ │ │ │ └── webdav.js
│ │ │ └── utils
│ │ │ ├── mock-data-loader.js
│ │ │ └── webdav-xml.js
│ │ └── sfcc-mock-server-manager.ts
│ ├── sfcc-mock-server.test.ts
│ ├── site-preferences-client.test.ts
│ ├── system-objects-client.test.ts
│ ├── utils.test.ts
│ ├── validation-helpers.test.ts
│ └── validator.test.ts
├── tsconfig.json
└── tsconfig.test.json
```
# Files
--------------------------------------------------------------------------------
/docs/dw_order.hooks/ShippingOrderHooks.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.order.hooks
# Class ShippingOrderHooks
## Inheritance Hierarchy
- dw.order.hooks.ShippingOrderHooks
## Description
This interface represents all script hooks that can be registered around shipping order lifecycle. It contains the extension points (hook names), and the functions that are called by each extension point. A function must be defined inside a JavaScript source and must be exported. The script with the exported hook function must be located inside a site cartridge. Inside the site cartridge a 'package.json' file with a 'hooks' entry must exist. "hooks": "./hooks.json" The hooks entry links to a json file, relative to the 'package.json' file. This file lists all registered hooks inside the hooks property: "hooks": [ {"name": "dw.order.shippingorder.updateShippingOrderItem", "script": "./shippingOrderUpdate.ds"}, ] A hook entry has a 'name' and a 'script' property. The 'name' contains the extension point, the hook name. The 'script' contains the script relative to the hooks file, with the exported hook function. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice.
## Constants
## Properties
## Constructor Summary
## Method Summary
### afterStatusChange
**Signature:** `afterStatusChange(shippingOrder : ShippingOrder) : Status`
After Status change hook.
### changeStatus
**Signature:** `changeStatus(shippingOrder : ShippingOrder, updateData : ShippingOrder) : Status`
Change the status of a shipping order.
### createShippingOrders
**Signature:** `createShippingOrders(order : Order) : Status`
Called during shipping order creation for an order.
### notifyStatusChange
**Signature:** `notifyStatusChange(shippingOrder : ShippingOrder) : Status`
Notify Status change hook.
### prepareCreateShippingOrders
**Signature:** `prepareCreateShippingOrders(order : Order) : Status`
Called before shipping order creation for an order takes place.
### resolveShippingOrder
**Signature:** `resolveShippingOrder(updateData : ShippingOrder) : ShippingOrder`
Resolve the shipping order.
### setShippingOrderCancelled
**Signature:** `setShippingOrderCancelled(updateData : ShippingOrder) : Order`
Change the status of a shipping order to cancelled.
### setShippingOrderShipped
**Signature:** `setShippingOrderShipped(updateData : ShippingOrder) : Order`
Change the status of a shipping order to shipped.
### setShippingOrderWarehouse
**Signature:** `setShippingOrderWarehouse(updateData : ShippingOrder) : Order`
Change the status of a shipping order to warehouse.
### updateShippingOrderItem
**Signature:** `updateShippingOrderItem(shippingOrder : ShippingOrder, updateItem : ShippingOrderItem) : Status`
Updates the status of a shipping order item.
## Method Detail
## Method Details
### afterStatusChange
**Signature:** `afterStatusChange(shippingOrder : ShippingOrder) : Status`
**Description:** After Status change hook. The function is called by extension point extensionPointAfterStatusChange. The implementation of this hook is optional. If defined the hook is called after extensionPointChangeStatus or respectively after extensionPointShippingOrderShipped, extensionPointShippingOrderCancelled or extensionPointShippingOrderWarehouse Runs inside of a transaction.
**Parameters:**
- `shippingOrder`: the shipping order to be updated
**Returns:**
the resulting status
---
### changeStatus
**Signature:** `changeStatus(shippingOrder : ShippingOrder, updateData : ShippingOrder) : Status`
**Description:** Change the status of a shipping order. The function is called by extension point extensionPointChangeStatus. Runs inside a transaction together with the hooks extensionPointResolveShippingOrder extensionPointUpdateShippingOrderItem. Runs after the iteration over the input's items collection as the last step in this transaction. The implementation of this hook is mandatory.
**Parameters:**
- `shippingOrder`: the shipping order to be updated
- `updateData`: the input data
**Returns:**
the resulting status
---
### createShippingOrders
**Signature:** `createShippingOrders(order : Order) : Status`
**Description:** Called during shipping order creation for an order. The function is called by extension point extensionPointCreateShippingOrders. It is responsible for creating shipping orders and its items for the order. Preparations for shipping order creation can be done before in hook extensionPointPrepareCreateShippingOrders. Runs inside of a transaction. The implementation of this hook is mandatory.
**Parameters:**
- `order`: the order to create shipping orders for
**Returns:**
the resulting status
---
### notifyStatusChange
**Signature:** `notifyStatusChange(shippingOrder : ShippingOrder) : Status`
**Description:** Notify Status change hook. The function is called by extension point extensionPointNotifyStatusChange. The implementation of this hook is optional. If defined the hook is called after extensionPointAfterStatusChange as the last step in the shipping order update process. Runs outside of a transaction.
**Parameters:**
- `shippingOrder`: the shipping order to be updated
**Returns:**
the resulting status
---
### prepareCreateShippingOrders
**Signature:** `prepareCreateShippingOrders(order : Order) : Status`
**Description:** Called before shipping order creation for an order takes place. Typically the hook is used to check the payment authorization status of the order. The function is called by extension point extensionPointPrepareCreateShippingOrders. Runs inside its own transaction. The value of the return status is used to control whether hook createShippingOrders(Order) is called for the order or not. The implementation of this hook is mandatory.
**Parameters:**
- `order`: the order to create shipping orders for
**Returns:**
Status.OK successful preparation - continue with shipping order creation for this order. Status.ERROR failed preparation - skip shipping order creation for this order.
---
### resolveShippingOrder
**Signature:** `resolveShippingOrder(updateData : ShippingOrder) : ShippingOrder`
**Description:** Resolve the shipping order. Will be called as first step of the update. The function is called by extension point extensionPointResolveShippingOrder. Runs inside a transaction together with the hooks extensionPointUpdateShippingOrderItem extensionPointChangeStatus. The implementation of this hook is mandatory.
**Parameters:**
- `updateData`: the input data
**Returns:**
the shipping order to update
---
### setShippingOrderCancelled
**Signature:** `setShippingOrderCancelled(updateData : ShippingOrder) : Order`
**Description:** Change the status of a shipping order to cancelled. The function is called by extension point extensionPointShippingOrderCancelled. This is an optional hook that can be implemented to have full control over status change to destination status Cancelled. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder, extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction.
**Parameters:**
- `updateData`: the input data
**Returns:**
the changed order or {code}null{code}
---
### setShippingOrderShipped
**Signature:** `setShippingOrderShipped(updateData : ShippingOrder) : Order`
**Description:** Change the status of a shipping order to shipped. The function is called by extension point extensionPointShippingOrderShipped. This is an optional hook that can be implemented to have full control over status change to destination status Shipped. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction.
**Parameters:**
- `updateData`: the input data
**Returns:**
the changed order or {code}null{code}
---
### setShippingOrderWarehouse
**Signature:** `setShippingOrderWarehouse(updateData : ShippingOrder) : Order`
**Description:** Change the status of a shipping order to warehouse. The function is called by extension point extensionPointShippingOrderWarehouse. This is an optional hook that can be implemented to have full control over status change to destination status Warehouse. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder, extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction.
**Parameters:**
- `updateData`: the input data
**Returns:**
the changed order or {code}null{code}
---
### updateShippingOrderItem
**Signature:** `updateShippingOrderItem(shippingOrder : ShippingOrder, updateItem : ShippingOrderItem) : Status`
**Description:** Updates the status of a shipping order item. The function is called by extension point extensionPointUpdateShippingOrderItem. Runs inside a transaction together with the hooks extensionPointResolveShippingOrder extensionPointChangeStatus. The implementation of this hook is mandatory.
**Parameters:**
- `shippingOrder`: the shipping order
- `updateItem`: the input data
**Returns:**
the resulting status
---
```
--------------------------------------------------------------------------------
/tests/mcp/yaml/search-site-preferences.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------
```yaml
# ==================================================================================
# SFCC MCP Server - search_site_preferences Tool YAML Tests (Docs-Only Mode)
# Validates that site preferences search tools are NOT available in docs-only mode
# This tool requires SFCC credentials and should not be available without them
#
# Quick Test Commands:
# aegis "tests/mcp/yaml/search-site-preferences.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose
# aegis query search_site_preferences '{"groupId": "Storefront", "instanceType": "sandbox", "searchRequest": {"query": {"match_all_query": {}}}}' --config "aegis.config.docs-only.json"
# ==================================================================================
description: "search_site_preferences tool docs-only mode tests - Tool unavailability validation"
tests:
# ==================================================================================
# TOOL UNAVAILABILITY IN DOCS-ONLY MODE
# ==================================================================================
- it: "should NOT list search_site_preferences tool in docs-only mode"
request:
jsonrpc: "2.0"
id: "tool-not-available-docs"
method: "tools/list"
params: {}
expect:
response:
jsonrpc: "2.0"
id: "tool-not-available-docs"
result:
tools:
match:arrayElements:
match:partial:
name: "match:type:string"
description: "match:type:string"
match:extractField: "tools.*.name"
value: "match:not:arrayContains:search_site_preferences"
stderr: "toBeEmpty"
# ==================================================================================
# AUTHENTICATION ERROR TESTS (Tool Can Be Called But Returns Error)
# ==================================================================================
- it: "should return authentication error when calling search_site_preferences in docs-only mode"
request:
jsonrpc: "2.0"
id: "auth-error-storefront"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "Storefront"
instanceType: "sandbox"
searchRequest:
query:
match_all_query: {}
expect:
response:
jsonrpc: "2.0"
id: "auth-error-storefront"
result:
content:
- type: "text"
text: "match:contains:OCAPI client not configured"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
- it: "should return authentication error for text query search requests"
request:
jsonrpc: "2.0"
id: "auth-error-text-query"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "System"
instanceType: "development"
searchRequest:
query:
text_query:
fields: ["id", "display_name"]
search_phrase: "test"
expect:
response:
jsonrpc: "2.0"
id: "auth-error-text-query"
result:
content:
- type: "text"
text: "match:contains:credentials are provided"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
- it: "should return authentication error for term query requests"
request:
jsonrpc: "2.0"
id: "auth-error-term-query"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "SFRA"
instanceType: "staging"
searchRequest:
query:
term_query:
fields: ["value_type"]
operator: "is"
values: ["string"]
expect:
response:
jsonrpc: "2.0"
id: "auth-error-term-query"
result:
content:
- type: "text"
text: "match:contains:OCAPI client not configured"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
- it: "should return authentication error for boolean query requests"
request:
jsonrpc: "2.0"
id: "auth-error-bool-query"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "Integration"
instanceType: "production"
searchRequest:
query:
bool_query:
must:
- text_query:
fields: ["id"]
search_phrase: "api"
expect:
response:
jsonrpc: "2.0"
id: "auth-error-bool-query"
result:
content:
- type: "text"
text: "match:contains:ensure credentials are provided"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
- it: "should return authentication error regardless of instance type"
request:
jsonrpc: "2.0"
id: "auth-error-sandbox"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "CCV"
instanceType: "sandbox"
searchRequest:
query:
match_all_query: {}
count: 5
expect:
response:
jsonrpc: "2.0"
id: "auth-error-sandbox"
result:
content:
- type: "text"
text: "match:contains:OCAPI client not configured"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
- it: "should return authentication error with options parameter"
request:
jsonrpc: "2.0"
id: "auth-error-with-options"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "Storefront"
instanceType: "sandbox"
searchRequest:
query:
match_all_query: {}
options:
maskPasswords: false
expand: "value"
expect:
response:
jsonrpc: "2.0"
id: "auth-error-with-options"
result:
content:
- type: "text"
text: "match:contains:OCAPI client not configured"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
# ==================================================================================
# PARAMETER VALIDATION IN DOCS-ONLY MODE
# ==================================================================================
- it: "should return authentication error even with missing required parameters"
request:
jsonrpc: "2.0"
id: "auth-error-missing-params"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "Storefront"
# Missing instanceType and searchRequest - but should get auth error first
expect:
response:
jsonrpc: "2.0"
id: "auth-error-missing-params"
result:
content:
- type: "text"
text: "match:regex:(OCAPI client not configured|required)"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
# ==================================================================================
# CONSISTENT ERROR MESSAGING
# ==================================================================================
- it: "should provide consistent error message across different preference groups"
request:
jsonrpc: "2.0"
id: "consistent-error-message"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "FastForward"
instanceType: "development"
searchRequest:
query:
text_query:
fields: ["description"]
search_phrase: "feature"
expect:
response:
jsonrpc: "2.0"
id: "consistent-error-message"
result:
content:
- type: "text"
text: "match:regex:(OCAPI client not configured|ensure credentials are provided)"
isError: true
performance:
maxResponseTime: "500ms"
stderr: "toBeEmpty"
# ==================================================================================
# FAST ERROR RESPONSE VALIDATION
# ==================================================================================
- it: "should return authentication errors quickly without OCAPI calls"
request:
jsonrpc: "2.0"
id: "fast-auth-error"
method: "tools/call"
params:
name: "search_site_preferences"
arguments:
groupId: "Storefront"
instanceType: "sandbox"
searchRequest:
query:
match_all_query: {}
count: 100
start: 0
expect:
response:
jsonrpc: "2.0"
id: "fast-auth-error"
result:
content:
- type: "text"
text: "match:contains:OCAPI client not configured"
isError: true
performance:
maxResponseTime: "300ms" # Should be very fast since no actual API call
stderr: "toBeEmpty"
```
--------------------------------------------------------------------------------
/tests/config.test.ts:
--------------------------------------------------------------------------------
```typescript
import { loadSecureDwJson } from '../src/config/dw-json-loader.js';
import { ConfigurationFactory } from '../src/config/configuration-factory.js';
import { DwJsonConfig } from '../src/types/types.js';
import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
describe('dw-json-loader.ts and configuration-factory.ts', () => {
// Create a temporary directory for test files
const testDir = join(tmpdir(), 'sfcc-config-tests');
beforeAll(() => {
if (!existsSync(testDir)) {
mkdirSync(testDir, { recursive: true });
}
});
afterAll(() => {
// Clean up test files
try {
const testFiles = [
'valid-dw.json',
'invalid-json.json',
'incomplete-dw.json',
'oauth-dw.json',
'invalid-hostname-dw.json',
'localhost-port-dw.json',
'custom-port-dw.json',
];
testFiles.forEach(file => {
const filePath = join(testDir, file);
if (existsSync(filePath)) {
unlinkSync(filePath);
}
});
} catch {
// Ignore cleanup errors
}
});
afterEach(() => {
// Restore all mocks after each test
jest.restoreAllMocks();
});
describe('loadSecureDwJson', () => {
it('should load a valid dw.json file with basic auth', () => {
const validDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
'client-id': '',
'client-secret': '',
};
const testFile = join(testDir, 'valid-dw.json');
writeFileSync(testFile, JSON.stringify(validDwJson, null, 2));
const dwConfig = loadSecureDwJson(testFile);
expect(dwConfig).toEqual(validDwJson);
});
it('should load a valid dw.json file with OAuth credentials', () => {
const oauthDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
'client-id': 'test-client-id',
'client-secret': 'test-client-secret',
};
const testFile = join(testDir, 'oauth-dw.json');
writeFileSync(testFile, JSON.stringify(oauthDwJson, null, 2));
const dwConfig = loadSecureDwJson(testFile);
expect(dwConfig).toEqual(oauthDwJson);
});
it('should handle relative paths correctly', () => {
const validDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'valid-dw.json');
writeFileSync(testFile, JSON.stringify(validDwJson, null, 2));
// Test with the actual test file path (not relative)
const dwConfig = loadSecureDwJson(testFile);
expect(dwConfig.hostname).toBe('test-instance.demandware.net');
});
it('should throw error for non-existent file', () => {
const nonExistentFile = join(testDir, 'non-existent.json');
expect(() => {
loadSecureDwJson(nonExistentFile);
}).toThrow('Configuration file not found');
});
it('should throw error for invalid JSON', () => {
const invalidJsonFile = join(testDir, 'invalid-json.json');
writeFileSync(invalidJsonFile, '{ invalid json }');
expect(() => {
loadSecureDwJson(invalidJsonFile);
}).toThrow('Invalid JSON in configuration file');
});
it('should throw error for incomplete configuration', () => {
const incompleteDwJson = {
hostname: 'test-instance.demandware.net',
// Missing username and password
};
const testFile = join(testDir, 'incomplete-dw.json');
writeFileSync(testFile, JSON.stringify(incompleteDwJson, null, 2));
expect(() => {
loadSecureDwJson(testFile);
}).toThrow('Configuration file must contain hostname, username, and password fields');
});
it('should throw error for invalid hostname format', () => {
const invalidHostnameDwJson: DwJsonConfig = {
hostname: 'invalid_hostname_with_underscores',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'invalid-hostname-dw.json');
writeFileSync(testFile, JSON.stringify(invalidHostnameDwJson, null, 2));
expect(() => {
loadSecureDwJson(testFile);
}).toThrow('Invalid hostname format in configuration');
});
it('should load dw.json file with hostname containing port', () => {
const dwJsonWithPort: DwJsonConfig = {
hostname: 'localhost:3000',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'localhost-port-dw.json');
writeFileSync(testFile, JSON.stringify(dwJsonWithPort, null, 2));
const dwConfig = loadSecureDwJson(testFile);
expect(dwConfig.hostname).toBe('localhost:3000');
expect(dwConfig.username).toBe('testuser');
expect(dwConfig.password).toBe('testpass');
});
it('should load dw.json file with custom hostname and port', () => {
const dwJsonWithCustomPort: DwJsonConfig = {
hostname: 'test-instance.demandware.net:8080',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'custom-port-dw.json');
writeFileSync(testFile, JSON.stringify(dwJsonWithCustomPort, null, 2));
const dwConfig = loadSecureDwJson(testFile);
expect(dwConfig.hostname).toBe('test-instance.demandware.net:8080');
expect(dwConfig.username).toBe('testuser');
expect(dwConfig.password).toBe('testpass');
});
});
describe('ConfigurationFactory', () => {
it('should create config from dw.json file', () => {
const validDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'valid-dw.json');
writeFileSync(testFile, JSON.stringify(validDwJson, null, 2));
const config = ConfigurationFactory.create({
dwJsonPath: testFile,
});
expect(config.hostname).toBe('test-instance.demandware.net');
expect(config.username).toBe('testuser');
expect(config.password).toBe('testpass');
});
it('should map OAuth credentials from dw.json', () => {
const oauthDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
'client-id': 'test-client-id',
'client-secret': 'test-client-secret',
};
const testFile = join(testDir, 'oauth-dw.json');
writeFileSync(testFile, JSON.stringify(oauthDwJson, null, 2));
const config = ConfigurationFactory.create({
dwJsonPath: testFile,
});
expect(config.clientId).toBe('test-client-id');
expect(config.clientSecret).toBe('test-client-secret');
});
it('should override dw.json with command-line options', () => {
const validDwJson: DwJsonConfig = {
hostname: 'test-instance.demandware.net',
username: 'testuser',
password: 'testpass',
};
const testFile = join(testDir, 'valid-dw.json');
writeFileSync(testFile, JSON.stringify(validDwJson, null, 2));
const config = ConfigurationFactory.create({
dwJsonPath: testFile,
hostname: 'override-hostname.demandware.net',
username: 'override-user',
});
expect(config.hostname).toBe('override-hostname.demandware.net');
expect(config.username).toBe('override-user');
expect(config.password).toBe('testpass'); // Should keep from dw.json
});
it('should create config from options only', () => {
const config = ConfigurationFactory.create({
hostname: 'test-hostname.demandware.net',
username: 'testuser',
password: 'testpass',
});
expect(config.hostname).toBe('test-hostname.demandware.net');
expect(config.username).toBe('testuser');
expect(config.password).toBe('testpass');
});
it('should create local mode config', () => {
const config = ConfigurationFactory.createLocalMode();
expect(config.hostname).toBe('');
expect(config.username).toBeUndefined();
expect(config.password).toBeUndefined();
expect(config.clientId).toBeUndefined();
expect(config.clientSecret).toBeUndefined();
});
it('should validate configuration capabilities', () => {
const config = ConfigurationFactory.create({
hostname: 'test-hostname.demandware.net',
username: 'testuser',
password: 'testpass',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
});
const capabilities = ConfigurationFactory.getCapabilities(config);
expect(capabilities.canAccessLogs).toBe(true);
expect(capabilities.canAccessOCAPI).toBe(true);
expect(capabilities.canAccessWebDAV).toBe(true);
expect(capabilities.isLocalMode).toBe(false);
});
it('should detect local mode', () => {
const config = ConfigurationFactory.createLocalMode();
const capabilities = ConfigurationFactory.getCapabilities(config);
expect(capabilities.canAccessLogs).toBe(false);
expect(capabilities.canAccessOCAPI).toBe(false);
expect(capabilities.canAccessWebDAV).toBe(false);
expect(capabilities.isLocalMode).toBe(true);
});
});
});
```
--------------------------------------------------------------------------------
/docs/sfra/render.md:
--------------------------------------------------------------------------------
```markdown
# Module render
## Description
The SFRA render module is the core rendering engine that processes and outputs different types of response content in Salesforce Commerce Cloud's Storefront Reference Architecture (SFRA). This module handles the execution of rendering operations that have been queued in the Response object's renderings array during the request lifecycle. It supports multiple output formats including ISML templates, JSON responses, XML output, Page Designer pages, and direct text output. The render module serves as the final step in the SFRA request processing pipeline, converting accumulated view data and rendering instructions into the actual HTTP response content.
## Functions
### template
**Signature:** `template(view, viewData) : void`
Renders an ISML template with the provided data.
### json
**Signature:** `json(data, response) : void`
Renders an object as JSON output.
### xml
**Signature:** `xml(viewData, response) : void`
Renders an object as XML output with proper escaping.
### page
**Signature:** `page(pageID, aspectAttributes, data, response) : void`
Renders a Page Designer page with optional aspect attributes.
### applyRenderings
**Signature:** `applyRenderings(res) : void`
Processes all rendering instructions in the response object.
## Function Detail
### template
**Signature:** `template(view, viewData) : void`
**Description:** Renders an ISML template using the provided view data. Creates a shallow copy of the view data for isolation and uses SFCC's ISML rendering engine to process the template.
**Parameters:**
- `view` (String) - Path to the ISML template file
- `viewData` (Object) - Data object to be passed as pdict to the template
**Returns:**
void
**Throws:**
Error with enhanced message including Java stack trace, file name, and line number information.
**Processing:**
1. Creates shallow copy of view data to prevent modification
2. Calls `isml.renderTemplate()` with view path and data
3. Wraps any rendering errors with enhanced error information
### json
**Signature:** `json(data, response) : void`
**Description:** Renders the provided data as JSON output with proper content type headers and formatting.
**Parameters:**
- `data` (Object) - Object to be serialized as JSON
- `response` (Response) - SFRA Response object
**Returns:**
void
**Processing:**
1. Sets content type to `application/json`
2. Serializes data with JSON.stringify using 2-space indentation
3. Writes formatted JSON to response writer
### xml
**Signature:** `xml(viewData, response) : void`
**Description:** Renders the provided view data as XML output with proper character escaping and root element wrapping.
**Parameters:**
- `viewData` (Object) - Object containing data to be rendered as XML
- `response` (Response) - SFRA Response object
**Returns:**
void
**Throws:**
Error with enhanced message including stack trace, file name, and line number information.
**XML Character Escaping:**
- `<` → `<`
- `>` → `>`
- `&` → `&`
- `"` → `"`
- `'` → `'`
**Processing:**
1. Creates root `<response>` element
2. Processes each key in viewData:
- If key is 'xml', includes raw XML content
- Otherwise, creates element with escaped content
3. Closes root element
4. Sets content type to `application/xml`
5. Creates XML object and writes to response
### page
**Signature:** `page(pageID, aspectAttributes, data, response) : void`
**Description:** Renders a Page Designer page using SFCC's PageMgr with optional aspect attributes for advanced page management.
**Parameters:**
- `pageID` (String) - ID of the Page Designer page to render
- `aspectAttributes` (dw.util.HashMap) - Optional aspect attributes for PageMgr
- `data` (Object) - Data to be passed to the page
- `response` (Response) - SFRA Response object
**Returns:**
void
**Processing:**
1. Checks if aspect attributes exist and are not empty
2. Calls appropriate PageMgr.renderPage() method:
- With aspect attributes: `PageMgr.renderPage(pageID, aspectAttributes, JSON.stringify(data))`
- Without aspect attributes: `PageMgr.renderPage(pageID, JSON.stringify(data))`
3. Writes rendered page content to response writer
### applyRenderings
**Signature:** `applyRenderings(res) : void`
**Description:** Processes all queued rendering instructions in the response object's renderings array. This is the main entry point called by the Server during route completion.
**Parameters:**
- `res` (Response) - SFRA Response object containing renderings array
**Returns:**
void
**Throws:**
Error if no renderings are present or if invalid rendering type is encountered.
**Rendering Types Processed:**
**Render Instructions (`type: 'render'`):**
- `subType: 'isml'` - Calls `template()` with view and viewData
- `subType: 'json'` - Calls `json()` with viewData and response
- `subType: 'xml'` - Calls `xml()` with viewData and response
- `subType: 'page'` - Calls `page()` with page, aspectAttributes, viewData, and response
**Print Instructions (`type: 'print'`):**
- Directly writes message to response writer
**Validation:**
- Throws error if renderings array is empty
- Throws error for unknown rendering types
- Throws error for render instructions without valid subType
## Rendering Pipeline Integration
### Response Object Integration
The render module works closely with the SFRA Response object:
**Rendering Queue:**
```javascript
res.renderings = [
{ type: 'render', subType: 'isml', view: 'template/path' },
{ type: 'print', message: 'Debug output' },
{ type: 'render', subType: 'json' }
];
```
**View Data Accumulation:**
```javascript
res.viewData = {
title: 'Page Title',
products: [...],
customer: {...}
};
```
### Server Integration
The Server class calls `applyRenderings()` during route completion:
```javascript
route.on('route:Complete', function onRouteCompleteHandler(req, res) {
// Cache and personalization handling
if (res.redirectUrl) {
// Handle redirects
return;
}
render.applyRenderings(res); // Execute rendering pipeline
});
```
## Content Type Management
### Automatic Content Types
**JSON Responses:**
- Sets `application/json` content type
- Formats output with 2-space indentation
**XML Responses:**
- Sets `application/xml` content type
- Automatically escapes special characters
- Wraps content in root `<response>` element
**ISML Templates:**
- Content type determined by template output
- Typically `text/html` for web pages
**Page Designer:**
- Content type managed by PageMgr
- Usually `text/html` for rendered pages
## Error Handling
### Template Rendering Errors
**ISML Template Errors:**
- Catches Java exceptions from template engine
- Enhances error with file name and line number
- Includes original Java stack trace
**XML Rendering Errors:**
- Catches XML parsing/creation errors
- Provides enhanced error messaging
- Includes stack trace information
### Validation Errors
**Missing Renderings:**
- Throws error if renderings array is empty
- Prevents silent failures in rendering pipeline
**Invalid Rendering Types:**
- Validates rendering instruction format
- Throws descriptive errors for unknown types
## Usage Examples
### Basic Template Rendering
```javascript
// In controller middleware
res.render('product/productDetail', {
product: productModel,
breadcrumbs: breadcrumbsModel
});
// Results in rendering instruction:
{
type: 'render',
subType: 'isml',
view: 'product/productDetail'
}
```
### JSON API Response
```javascript
// In API controller
res.json({
success: true,
data: responseData,
message: 'Operation completed'
});
// Results in JSON output with proper headers
```
### XML Response
```javascript
// In XML endpoint
res.xml({
status: 'success',
xml: '<customData>...</customData>',
timestamp: new Date().toISOString()
});
// Results in properly escaped XML with root element
```
### Page Designer Integration
```javascript
// In Page Designer controller
res.page('homepage', aspectAttributes, {
banners: bannerData,
featured: featuredProducts
});
// Results in Page Designer page rendering
```
### Mixed Output
```javascript
// Debug output followed by template
res.print('Debug: Processing complete');
res.render('checkout/confirmation', orderData);
// Results in multiple rendering instructions executed in order
```
## Performance Considerations
### Data Copying
**Template Rendering:**
- Creates shallow copy of view data for isolation
- Prevents template modifications from affecting response object
- Minimal performance impact for typical data sizes
### Memory Management
**Large Data Sets:**
- JSON serialization with formatting may increase memory usage
- XML escaping creates additional string copies
- Consider streaming for very large responses
### Caching Integration
**Template Caching:**
- ISML templates are cached by SFCC platform
- View data is not cached at render level
- Page Designer pages benefit from platform caching
## Security Considerations
### XML Security
**Character Escaping:**
- Prevents XML injection attacks
- Escapes all potentially dangerous characters
- Safe handling of user-generated content
**Content Validation:**
- XML content must be well-formed
- Error handling prevents partial output
### Template Security
**Data Isolation:**
- Shallow copy prevents template-induced data corruption
- Original response data remains unchanged
- Safe passing of sensitive data to templates
---
```
--------------------------------------------------------------------------------
/tests/job-log-handler.test.ts:
--------------------------------------------------------------------------------
```typescript
import { JobLogToolHandler } from '../src/core/handlers/job-log-handler.js';
import { HandlerContext } from '../src/core/handlers/base-handler.js';
import { SFCCLogClient } from '../src/clients/log-client.js';
import { Logger } from '../src/utils/logger.js';
// Mock dependencies
jest.mock('../src/clients/log-client.js');
jest.mock('../src/utils/logger.js');
describe('JobLogToolHandler', () => {
let mockContext: HandlerContext;
let handler: JobLogToolHandler;
let mockLogger: jest.Mocked<Logger>;
let mockLogClient: jest.Mocked<SFCCLogClient>;
beforeEach(() => {
jest.clearAllMocks();
mockLogger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
methodEntry: jest.fn(),
methodExit: jest.fn(),
timing: jest.fn(),
log: jest.fn(),
} as any;
mockLogClient = {
getLatestJobLogFiles: jest.fn(),
searchJobLogsByName: jest.fn(),
getJobLogEntries: jest.fn(),
searchJobLogs: jest.fn(),
getJobExecutionSummary: jest.fn(),
} as any;
// Mock the SFCCLogClient constructor
(SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockImplementation(() => mockLogClient);
// Mock Logger.getChildLogger
jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
mockContext = {
logger: mockLogger,
config: {
hostname: 'test.demandware.net',
username: 'test',
password: 'test',
clientId: 'test',
clientSecret: 'test',
},
capabilities: {
canAccessLogs: true,
canAccessOCAPI: true,
},
};
handler = new JobLogToolHandler(mockContext, 'job-log-handler');
});
afterEach(() => {
jest.restoreAllMocks();
});
// Helper function to initialize handler
const initializeHandler = async () => {
await (handler as any).initialize();
};
describe('canHandle', () => {
it('should handle job log tool names', () => {
const jobLogTools = [
'get_latest_job_log_files',
'search_job_logs_by_name',
'get_job_log_entries',
'search_job_logs',
'get_job_execution_summary',
];
jobLogTools.forEach(toolName => {
expect(handler.canHandle(toolName)).toBe(true);
});
});
it('should not handle non-job-log tool names', () => {
const nonJobLogTools = [
'get_latest_error',
'search_logs',
'get_log_file_contents',
'summarize_logs',
'list_log_files',
'unknown_tool',
'',
];
nonJobLogTools.forEach(toolName => {
expect(handler.canHandle(toolName)).toBe(false);
});
});
it('should handle case-sensitive tool names only', () => {
expect(handler.canHandle('GET_LATEST_JOB_LOG_FILES')).toBe(false);
expect(handler.canHandle('get_Latest_Job_Log_Files')).toBe(false);
expect(handler.canHandle('get_latest_job_log_files')).toBe(true);
});
});
describe('handle method - get_latest_job_log_files', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle get_latest_job_log_files with default limit', async () => {
const mockResult = 'Latest job log files result';
mockLogClient.getLatestJobLogFiles.mockResolvedValue(mockResult);
const result = await handler.handle('get_latest_job_log_files', {}, Date.now());
expect(mockLogClient.getLatestJobLogFiles).toHaveBeenCalledWith(10); // default limit
expect(result.content[0].text).toContain(mockResult);
});
it('should handle get_latest_job_log_files with custom limit', async () => {
const mockResult = 'Latest job log files result';
mockLogClient.getLatestJobLogFiles.mockResolvedValue(mockResult);
const result = await handler.handle('get_latest_job_log_files', { limit: 25 }, Date.now());
expect(mockLogClient.getLatestJobLogFiles).toHaveBeenCalledWith(25);
expect(result.content[0].text).toContain(mockResult);
});
it('should validate limit parameter', async () => {
const result = await handler.handle('get_latest_job_log_files', { limit: -1 }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid limit');
});
});
describe('handle method - search_job_logs_by_name', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle search_job_logs_by_name with required jobName', async () => {
const mockResult = 'Search job logs by name result';
mockLogClient.searchJobLogsByName.mockResolvedValue(mockResult);
const result = await handler.handle('search_job_logs_by_name', {
jobName: 'TestJob',
limit: 15,
}, Date.now());
expect(mockLogClient.searchJobLogsByName).toHaveBeenCalledWith('TestJob', 15);
expect(result.content[0].text).toContain(mockResult);
});
it('should validate required jobName parameter', async () => {
const result = await handler.handle('search_job_logs_by_name', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('jobName must be a non-empty string');
});
});
describe('handle method - get_job_log_entries', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle get_job_log_entries with default parameters', async () => {
const mockResult = 'Job log entries result';
mockLogClient.getJobLogEntries.mockResolvedValue(mockResult);
const result = await handler.handle('get_job_log_entries', {}, Date.now());
expect(mockLogClient.getJobLogEntries).toHaveBeenCalledWith('all', 50, undefined); // default limit is 50 for job entries
expect(result.content[0].text).toContain(mockResult);
});
it('should validate log level parameter', async () => {
const result = await handler.handle('get_job_log_entries', { level: 'invalid' }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid log level: invalid');
});
});
describe('handle method - search_job_logs', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle search_job_logs with required pattern', async () => {
const mockResult = 'Search job logs result';
mockLogClient.searchJobLogs.mockResolvedValue(mockResult);
const result = await handler.handle('search_job_logs', {
pattern: 'error-pattern',
}, Date.now());
expect(mockLogClient.searchJobLogs).toHaveBeenCalledWith('error-pattern', 'all', 20, undefined);
expect(result.content[0].text).toContain(mockResult);
});
it('should validate required pattern parameter', async () => {
const result = await handler.handle('search_job_logs', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('pattern must be a non-empty string');
});
});
describe('handle method - get_job_execution_summary', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle get_job_execution_summary with required jobName', async () => {
const mockResult = 'Job execution summary result';
mockLogClient.getJobExecutionSummary.mockResolvedValue(mockResult);
const result = await handler.handle('get_job_execution_summary', {
jobName: 'SummaryJob',
}, Date.now());
expect(mockLogClient.getJobExecutionSummary).toHaveBeenCalledWith('SummaryJob');
expect(result.content[0].text).toContain(mockResult);
});
it('should validate required jobName parameter', async () => {
const result = await handler.handle('get_job_execution_summary', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('jobName must be a non-empty string');
});
});
describe('error handling', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle client errors gracefully', async () => {
const clientError = new Error('Client connection failed');
mockLogClient.getLatestJobLogFiles.mockRejectedValue(clientError);
const result = await handler.handle('get_latest_job_log_files', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Client connection failed');
});
it('should handle unsupported tool names', async () => {
await expect(handler.handle('unsupported_tool', {}, Date.now()))
.rejects.toThrow('Unsupported tool: unsupported_tool');
});
});
describe('initialization', () => {
it('should initialize log client when capabilities allow', async () => {
await initializeHandler();
expect(SFCCLogClient).toHaveBeenCalledWith(mockContext.config);
expect(mockLogger.debug).toHaveBeenCalledWith('Log client initialized');
});
it('should handle missing capabilities gracefully', async () => {
const contextWithoutLogs = {
...mockContext,
capabilities: { canAccessLogs: false, canAccessOCAPI: false },
};
const handlerWithoutLogs = new JobLogToolHandler(contextWithoutLogs, 'job-log');
const result = await handlerWithoutLogs.handle('get_latest_job_log_files', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Log client not configured');
});
});
});
```
--------------------------------------------------------------------------------
/src/clients/logs/log-file-discovery.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Log file discovery, filtering, and metadata operations
*/
import type { WebDAVClient } from 'webdav';
import { Logger } from '../../utils/logger.js';
import { getCurrentDate, normalizeFilePath } from '../../utils/utils.js';
import { LOG_CONSTANTS, LOG_FILE_PATTERNS, JOB_LOG_CONSTANTS } from './log-constants.js';
import type { LogFileMetadata, LogFileInfo, LogLevel, LogFileFilter, JobLogInfo, JobLogFilter } from './log-types.js';
export class LogFileDiscovery {
private logger: Logger;
private webdavClient: WebDAVClient;
constructor(webdavClient: WebDAVClient, logger: Logger) {
this.webdavClient = webdavClient;
this.logger = logger;
}
/**
* Get list of log files for a specific date
*/
async getLogFiles(date?: string): Promise<LogFileMetadata[]> {
const targetDate = date ?? getCurrentDate();
this.logger.methodEntry('getLogFiles', { date: targetDate });
const startTime = Date.now();
const contents = await this.webdavClient.getDirectoryContents('/');
this.logger.timing('webdav_getDirectoryContents', startTime);
const logFiles = (contents as any[])
.filter((item: any) =>
item.type === 'file' &&
item.filename.includes(targetDate) &&
item.filename.endsWith('.log'),
)
.map((item: any) => ({
filename: item.filename,
lastmod: item.lastmod ?? new Date().toISOString(), // Fallback to current time if no lastmod
}));
this.logger.debug(`Found ${logFiles.length} log files for date ${targetDate}:`, logFiles.map((f: LogFileMetadata) => f.filename));
this.logger.methodExit('getLogFiles', { count: logFiles.length });
return logFiles;
}
/**
* Filter log files by level and other criteria
*/
filterLogFiles(files: LogFileMetadata[], filter: LogFileFilter): LogFileMetadata[] {
if (!filter.level) {
return files;
}
const { level, includeCustom = true } = filter;
return files.filter(file => {
const filename = normalizeFilePath(file.filename);
const standardPattern = LOG_FILE_PATTERNS.STANDARD(level);
const customPattern = LOG_FILE_PATTERNS.CUSTOM(level);
if (filename.startsWith(standardPattern)) {
return true;
}
if (includeCustom && filename.startsWith(customPattern)) {
return true;
}
return false;
});
}
/**
* Get log files filtered by level with detailed logging
*/
async getLogFilesByLevel(level: LogLevel, date?: string): Promise<LogFileMetadata[]> {
const targetDate = date ?? getCurrentDate();
const allFiles = await this.getLogFiles(targetDate);
const filteredFiles = this.filterLogFiles(allFiles, { level, includeCustom: true });
this.logger.debug(`Filtered to ${filteredFiles.length} ${level} log files (including custom logs):`,
filteredFiles.map(f => f.filename));
return filteredFiles;
}
/**
* Sort log files by modification date
*/
sortFilesByDate(files: LogFileMetadata[], descending = true): LogFileMetadata[] {
return files.sort((a, b) => {
const dateA = new Date(a.lastmod).getTime();
const dateB = new Date(b.lastmod).getTime();
return descending ? dateB - dateA : dateA - dateB;
});
}
/**
* Get all available log files with detailed metadata
*/
async getAllLogFiles(): Promise<LogFileInfo[]> {
try {
const contents = await this.webdavClient.getDirectoryContents('/');
const allLogFiles = (contents as any[])
.filter((item: any) => item.type === 'file' && item.filename.endsWith('.log'));
const logFiles: LogFileInfo[] = allLogFiles
.map((item: any) => ({
name: item.filename,
size: item.size,
lastModified: item.lastmod,
}))
// Sort by lastModified date in descending order (newest first)
.sort((a: LogFileInfo, b: LogFileInfo) => {
const dateA = new Date(a.lastModified).getTime();
const dateB = new Date(b.lastModified).getTime();
return dateB - dateA;
})
// Limit to 50 most recent files
.slice(0, LOG_CONSTANTS.MAX_LOG_FILES_DISPLAY);
// Store total count for formatting
(logFiles as any).totalCount = allLogFiles.length;
return logFiles;
} catch (error) {
throw new Error(`Failed to list log files: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get unique log levels available for a specific date
*/
async getAvailableLogLevels(date?: string): Promise<LogLevel[]> {
const files = await this.getLogFiles(date);
const levels = new Set<LogLevel>();
for (const file of files) {
const filename = normalizeFilePath(file.filename);
for (const level of LOG_CONSTANTS.LOG_LEVELS) {
if (filename.startsWith(LOG_FILE_PATTERNS.STANDARD(level)) ||
filename.startsWith(LOG_FILE_PATTERNS.CUSTOM(level))) {
levels.add(level);
}
}
}
return Array.from(levels);
}
/**
* Get log file statistics for a date
*/
async getLogFileStats(date?: string): Promise<{
totalFiles: number;
filesByLevel: Record<LogLevel, number>;
totalSize: number;
oldestFile?: string;
newestFile?: string;
}> {
const files = await this.getLogFiles(date);
const sortedFiles = this.sortFilesByDate(files, true);
const stats = {
totalFiles: files.length,
filesByLevel: {} as Record<LogLevel, number>,
totalSize: 0,
oldestFile: sortedFiles[sortedFiles.length - 1]?.filename,
newestFile: sortedFiles[0]?.filename,
};
// Initialize level counts
for (const level of LOG_CONSTANTS.LOG_LEVELS) {
stats.filesByLevel[level] = 0;
}
// Count files by level
for (const file of files) {
for (const level of LOG_CONSTANTS.LOG_LEVELS) {
const filteredFiles = this.filterLogFiles([file], { level });
if (filteredFiles.length > 0) {
stats.filesByLevel[level]++;
}
}
}
return stats;
}
/**
* Get job log files from the /jobs/ folder structure
*/
async getJobLogFiles(filter?: JobLogFilter): Promise<JobLogInfo[]> {
this.logger.methodEntry('getJobLogFiles', filter);
try {
// List all directories in the jobs folder
const jobsContents = await this.webdavClient.getDirectoryContents(JOB_LOG_CONSTANTS.JOBS_FOLDER);
const jobDirs = (jobsContents as any[])
.filter((item: any) => item.type === 'directory')
.map((item: any) => ({
name: item.filename.replace(JOB_LOG_CONSTANTS.JOBS_FOLDER, ''),
path: item.filename,
lastModified: item.lastmod ?? new Date().toISOString(),
}));
this.logger.debug(`Found ${jobDirs.length} job directories`);
const jobLogInfos: JobLogInfo[] = [];
// Process each job directory to find log files
for (const jobDir of jobDirs) {
try {
const jobContents = await this.webdavClient.getDirectoryContents(jobDir.path);
const logFiles = (jobContents as any[])
.filter((item: any) =>
item.type === 'file' &&
JOB_LOG_CONSTANTS.JOB_LOG_PATTERN.test(item.filename.split('/').pop() ?? ''),
);
for (const logFile of logFiles) {
const fileName = logFile.filename.split('/').pop() ?? '';
jobLogInfos.push({
jobName: decodeURIComponent(jobDir.name),
jobId: this.extractJobIdFromFilename(fileName),
logFile: logFile.filename,
lastModified: logFile.lastmod ?? jobDir.lastModified,
size: logFile.size,
});
}
} catch (error) {
this.logger.warn(`Failed to read job directory ${jobDir.name}: ${error}`);
}
}
// Apply filtering
let filteredLogs = jobLogInfos;
if (filter?.jobName) {
const searchName = filter.jobName.toLowerCase();
filteredLogs = filteredLogs.filter(log =>
log.jobName.toLowerCase().includes(searchName),
);
}
// Sort by most recent first if requested (default behavior)
if (filter?.sortByRecent !== false) {
filteredLogs.sort((a, b) =>
new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime(),
);
}
// Apply limit
if (filter?.limit) {
filteredLogs = filteredLogs.slice(0, filter.limit);
}
this.logger.methodExit('getJobLogFiles', { count: filteredLogs.length });
return filteredLogs;
} catch (error) {
this.logger.error(`Failed to get job log files: ${error}`);
throw new Error(`Failed to access job logs: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get the latest job log files, sorted by modification date
*/
async getLatestJobLogFiles(limit?: number): Promise<JobLogInfo[]> {
return this.getJobLogFiles({
limit: limit ?? JOB_LOG_CONSTANTS.DEFAULT_JOB_LOG_LIMIT,
sortByRecent: true,
});
}
/**
* Search for job logs by job name
*/
async searchJobLogsByName(jobName: string, limit?: number): Promise<JobLogInfo[]> {
return this.getJobLogFiles({
jobName,
limit: limit ?? JOB_LOG_CONSTANTS.DEFAULT_JOB_LOG_LIMIT,
sortByRecent: true,
});
}
/**
* Extract job ID from filename (Job-JobName-ID.log format)
*/
private extractJobIdFromFilename(filename: string): string {
const match = filename.match(/Job-.+-([^.]+)\.log$/);
return match ? match[1] : 'unknown';
}
}
```
--------------------------------------------------------------------------------
/docs/dw_catalog/StoreMgr.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.catalog
# Class StoreMgr
## Inheritance Hierarchy
- Object
- dw.catalog.StoreMgr
## Description
Provides helper methods for getting stores based on id and querying for stores based on geolocation.
## Properties
### allStoreGroups
**Type:** Collection (Read Only)
All the store groups of the current site.
### storeIDFromSession
**Type:** String (Read Only)
Get the store id associated with the current session. By default, the session store id is null.
## Constructor Summary
## Method Summary
### getAllStoreGroups
**Signature:** `static getAllStoreGroups() : Collection`
Returns all the store groups of the current site.
### getStore
**Signature:** `static getStore(storeID : String) : Store`
Returns the store object with the specified id or null if store with this id does not exist in the site.
### getStoreGroup
**Signature:** `static getStoreGroup(storeGroupID : String) : StoreGroup`
Returns the store group with the specified id or null if the store group with this id does not exist in the current site.
### getStoreIDFromSession
**Signature:** `static getStoreIDFromSession() : String`
Get the store id associated with the current session.
### searchStoresByCoordinates
**Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap`
Search for stores based on geo-coordinates.
### searchStoresByCoordinates
**Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number) : LinkedHashMap`
Convenience method.
### searchStoresByPostalCode
**Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap`
Search for stores by country/postal code and optionally by additional filter criteria.
### searchStoresByPostalCode
**Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number) : LinkedHashMap`
Convenience method.
### setStoreIDToSession
**Signature:** `static setStoreIDToSession(storeID : String) : void`
Set the store id for the current session.
## Method Detail
## Method Details
### getAllStoreGroups
**Signature:** `static getAllStoreGroups() : Collection`
**Description:** Returns all the store groups of the current site.
**Returns:**
The store groups of the current site.
---
### getStore
**Signature:** `static getStore(storeID : String) : Store`
**Description:** Returns the store object with the specified id or null if store with this id does not exist in the site.
**Parameters:**
- `storeID`: the store identifier.
**Returns:**
Store for specified id or null.
---
### getStoreGroup
**Signature:** `static getStoreGroup(storeGroupID : String) : StoreGroup`
**Description:** Returns the store group with the specified id or null if the store group with this id does not exist in the current site.
**Parameters:**
- `storeGroupID`: the store group identifier.
**Returns:**
The store group for specified id or null.
---
### getStoreIDFromSession
**Signature:** `static getStoreIDFromSession() : String`
**Description:** Get the store id associated with the current session. By default, the session store id is null.
**Returns:**
store id, null is returned and means no store id is set on session
---
### searchStoresByCoordinates
**Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap`
**Description:** Search for stores based on geo-coordinates. The method returns a list of stores for the current site that are within a specified distance of a location on the earth and which optionally satisfy additional filter criteria. If no additional criteria are specified, then this method behaves similar to GetNearestStores pipelet. The criteria are specified as a querystring, using the same syntax as SystemObjectMgr.querySystemObjects(String, String, String, Object...) The stores and their distance from the specified location are returned as a LinkedHashMap of Store objects to distances, sorting in ascending order by distance. The distance is interpreted either in miles or kilometers depending on the "distanceUnit" parameter. The latitude and longitude of each store is determined by first looking at Store.getLatitude() and Store.getLongitude(). If these are not set, then the system deduces the location of the store by looking for a configured geolocation matching the store's postal and country codes.
**Parameters:**
- `latitude`: Latitude coordinate which is the center of the search area. Must not be null or an exception is thrown.
- `longitude`: Longitude coordinate which is the center of the search area. Must not be null or an exception is thrown.
- `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used.
- `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used.
- `queryString`: optional filter criteria specified as querystring.
- `args`: the arguments to fill in the values in the querystring.
**Returns:**
The stores and their distance from the specified location, sorted in ascending order by distance.
---
### searchStoresByCoordinates
**Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number) : LinkedHashMap`
**Description:** Convenience method. Same as searchStoresByCoordinates(latitude, longitude, distanceUnit, maxDistance, null).
**Parameters:**
- `latitude`: Latitude coordinate which is the center of the search area. Must not be null or an exception is thrown.
- `longitude`: Longitude coordinate which is the center of the search area. Must not be null or an exception is thrown.
- `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used.
- `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used.
**Returns:**
The stores and their distance from the specified location, sorted in ascending order by distance.
---
### searchStoresByPostalCode
**Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap`
**Description:** Search for stores by country/postal code and optionally by additional filter criteria. This method is analagous to searchStoresByCoordinates(Number, Number, String, Number, String, Object...) but identifies a location on the earth indirectly using country and postal code. The method will look first in the saved geolocations of the system to find a geolocation matching the passed country and postal code. If none is found, this method will return an empty map. If one is found, it will use the latitude/longitude of the found geolocation as the center of the search.
**Parameters:**
- `countryCode`: The country code for the search area, must not be null.
- `postalCode`: The postal code for the center of the search area, must not be null.
- `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used.
- `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used.
- `queryString`: An optional search querystring which provides additional criteria to filter stores by.
- `args`: The arguments providing the dynamic values to the querystring.
**Returns:**
The stores and their distance from the specified location, sorted in ascending order by distance.
---
### searchStoresByPostalCode
**Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number) : LinkedHashMap`
**Description:** Convenience method. Same as searchStoresByPostalCode(countryCode, postalCode, distanceUnit, maxDistance, null).
**Parameters:**
- `countryCode`: The country code for the search area, must not be null.
- `postalCode`: The postal code for the center of the search area, must not be null.
- `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used.
- `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used.
**Returns:**
The stores and their distance from the specified location, sorted in ascending order by distance.
---
### setStoreIDToSession
**Signature:** `static setStoreIDToSession(storeID : String) : void`
**Description:** Set the store id for the current session. The store id is also saved on the cookie with the cookie name "dw_store" with no expiration time. Null is allowed to remove store id from session, when null is passed in, the cookie will be removed when browser exits.
**Parameters:**
- `storeID`: the id of the store. The leading and trailing white spaces are removed by system from storeID
---
```
--------------------------------------------------------------------------------
/docs/dw_content/Folder.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.content
# Class Folder
## Inheritance Hierarchy
- Object
- dw.object.PersistentObject
- dw.object.ExtensibleObject
- dw.content.Folder
## Description
Class representing a folder for organizing content assets in Commerce Cloud Digital.
## Properties
### content
**Type:** Collection (Read Only)
The content objects for this folder, sorted by position.
### description
**Type:** String (Read Only)
The description for the folder as known in the current
locale or null if it cannot be found.
### displayName
**Type:** String (Read Only)
The display name for the folder as known in the current
locale or null if it cannot be found.
### ID
**Type:** String (Read Only)
The ID of the folder. The ID can be used to uniquely
identify a folder within any given library. This folder ID provides
an alternative lookup mechanism for folders frequently used in
the storefront.
### online
**Type:** boolean (Read Only)
Indicates if the folder is set online or
offline. Initially, all folders are set online.
### onlineContent
**Type:** Collection (Read Only)
The online content objects for this folder, sorted by position.
### onlineSubFolders
**Type:** Collection (Read Only)
The online subfolders of this folder, sorted by position.
### pageDescription
**Type:** String (Read Only)
The page description for this folder using the value in
the current locale, or returns null if no value was found.
### pageKeywords
**Type:** String (Read Only)
The page keywords for this folder using the value in
the current locale, or returns null if no value was found.
### pageTitle
**Type:** String (Read Only)
The page title for this folder using the value in
the current locale, or returns null if no value was found.
### pageURL
**Type:** String (Read Only)
The page URL for this folder using the value in
the current locale, or returns null if no value was found.
### parent
**Type:** Folder (Read Only)
The parent folder of this folder.
### root
**Type:** boolean (Read Only)
Indicates if this is the root folder.
### siteMapChangeFrequency
**Type:** String (Read Only)
The folder's sitemap change frequency.
### siteMapIncluded
**Type:** Number (Read Only)
The folder's sitemap inclusion.
### siteMapPriority
**Type:** Number (Read Only)
The folder's sitemap priority.
### subFolders
**Type:** Collection (Read Only)
The subfolders of this folder, sorted by position.
### template
**Type:** String (Read Only)
The name of the template used to render the folder
in the store front.
## Constructor Summary
## Method Summary
### getContent
**Signature:** `getContent() : Collection`
Returns the content objects for this folder, sorted by position.
### getDescription
**Signature:** `getDescription() : String`
Returns the description for the folder as known in the current locale or null if it cannot be found.
### getDisplayName
**Signature:** `getDisplayName() : String`
Returns the display name for the folder as known in the current locale or null if it cannot be found.
### getID
**Signature:** `getID() : String`
Returns the ID of the folder.
### getOnlineContent
**Signature:** `getOnlineContent() : Collection`
Returns the online content objects for this folder, sorted by position.
### getOnlineSubFolders
**Signature:** `getOnlineSubFolders() : Collection`
Returns the online subfolders of this folder, sorted by position.
### getPageDescription
**Signature:** `getPageDescription() : String`
Returns the page description for this folder using the value in the current locale, or returns null if no value was found.
### getPageKeywords
**Signature:** `getPageKeywords() : String`
Returns the page keywords for this folder using the value in the current locale, or returns null if no value was found.
### getPageTitle
**Signature:** `getPageTitle() : String`
Returns the page title for this folder using the value in the current locale, or returns null if no value was found.
### getPageURL
**Signature:** `getPageURL() : String`
Returns the page URL for this folder using the value in the current locale, or returns null if no value was found.
### getParent
**Signature:** `getParent() : Folder`
Returns the parent folder of this folder.
### getSiteMapChangeFrequency
**Signature:** `getSiteMapChangeFrequency() : String`
Returns the folder's sitemap change frequency.
### getSiteMapIncluded
**Signature:** `getSiteMapIncluded() : Number`
Returns the folder's sitemap inclusion.
### getSiteMapPriority
**Signature:** `getSiteMapPriority() : Number`
Returns the folder's sitemap priority.
### getSubFolders
**Signature:** `getSubFolders() : Collection`
Returns the subfolders of this folder, sorted by position.
### getTemplate
**Signature:** `getTemplate() : String`
Returns the name of the template used to render the folder in the store front.
### isOnline
**Signature:** `isOnline() : boolean`
Indicates if the folder is set online or offline.
### isRoot
**Signature:** `isRoot() : boolean`
Indicates if this is the root folder.
## Method Detail
## Method Details
### getContent
**Signature:** `getContent() : Collection`
**Description:** Returns the content objects for this folder, sorted by position.
**Returns:**
the content objects for this folder, sorted by position.
---
### getDescription
**Signature:** `getDescription() : String`
**Description:** Returns the description for the folder as known in the current locale or null if it cannot be found.
**Returns:**
the description for the folder as known in the current locale or null if it cannot be found.
---
### getDisplayName
**Signature:** `getDisplayName() : String`
**Description:** Returns the display name for the folder as known in the current locale or null if it cannot be found.
**Returns:**
the display name for the folder as known in the current locale or null if it cannot be found.
---
### getID
**Signature:** `getID() : String`
**Description:** Returns the ID of the folder. The ID can be used to uniquely identify a folder within any given library. This folder ID provides an alternative lookup mechanism for folders frequently used in the storefront.
**Returns:**
the ID of the folder.
---
### getOnlineContent
**Signature:** `getOnlineContent() : Collection`
**Description:** Returns the online content objects for this folder, sorted by position.
**Returns:**
the online content objects for this folder, sorted by position.
---
### getOnlineSubFolders
**Signature:** `getOnlineSubFolders() : Collection`
**Description:** Returns the online subfolders of this folder, sorted by position.
**Returns:**
the online subfolders of this folder, sorted by position.
---
### getPageDescription
**Signature:** `getPageDescription() : String`
**Description:** Returns the page description for this folder using the value in the current locale, or returns null if no value was found.
**Returns:**
the page description for this folder using the value in the current locale, or returns null if no value was found.
---
### getPageKeywords
**Signature:** `getPageKeywords() : String`
**Description:** Returns the page keywords for this folder using the value in the current locale, or returns null if no value was found.
**Returns:**
the page keywords for this folder using the value in the current locale, or returns null if no value was found.
---
### getPageTitle
**Signature:** `getPageTitle() : String`
**Description:** Returns the page title for this folder using the value in the current locale, or returns null if no value was found.
**Returns:**
the page title for this folder using the value in the current locale, or returns null if no value was found.
---
### getPageURL
**Signature:** `getPageURL() : String`
**Description:** Returns the page URL for this folder using the value in the current locale, or returns null if no value was found.
**Returns:**
the page URL for this folder using the value in the current locale, or returns null if no value was found.
---
### getParent
**Signature:** `getParent() : Folder`
**Description:** Returns the parent folder of this folder.
**Returns:**
the parent folder of this folder.
---
### getSiteMapChangeFrequency
**Signature:** `getSiteMapChangeFrequency() : String`
**Description:** Returns the folder's sitemap change frequency.
**Returns:**
the value of the attribute 'siteMapChangeFrequency'.
---
### getSiteMapIncluded
**Signature:** `getSiteMapIncluded() : Number`
**Description:** Returns the folder's sitemap inclusion.
**Returns:**
the value of the attribute 'siteMapIncluded'.
---
### getSiteMapPriority
**Signature:** `getSiteMapPriority() : Number`
**Description:** Returns the folder's sitemap priority.
**Returns:**
the value of the attribute 'siteMapPriority'.
---
### getSubFolders
**Signature:** `getSubFolders() : Collection`
**Description:** Returns the subfolders of this folder, sorted by position.
**Returns:**
the subfolders of this folder, sorted by position.
---
### getTemplate
**Signature:** `getTemplate() : String`
**Description:** Returns the name of the template used to render the folder in the store front.
**Returns:**
the name of the template used to render the folder.
---
### isOnline
**Signature:** `isOnline() : boolean`
**Description:** Indicates if the folder is set online or offline. Initially, all folders are set online.
**Returns:**
true if the folder is online, false otherwise.
---
### isRoot
**Signature:** `isRoot() : boolean`
**Description:** Indicates if this is the root folder.
**Returns:**
true if this is the root folder, false otherwise.
---
```
--------------------------------------------------------------------------------
/src/clients/docs/class-content-parser.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Class Content Parser
*
* Responsible for parsing markdown documentation content and extracting
* structured class information including constants, properties, methods, and inheritance.
*
* Single Responsibility: Converting markdown content to structured data
*/
import { Logger } from '../../utils/logger.js';
export interface SFCCMethod {
name: string;
signature: string;
description: string;
parameters?: string[];
returnType?: string;
deprecated?: boolean;
deprecationMessage?: string;
}
export interface SFCCProperty {
name: string;
type: string;
description: string;
modifiers?: string[];
deprecated?: boolean;
deprecationMessage?: string;
}
export interface SFCCConstant {
name: string;
type: string;
value?: string;
description: string;
deprecated?: boolean;
deprecationMessage?: string;
}
export interface SFCCClassDetails {
className: string;
packageName: string;
description: string;
constants: SFCCConstant[];
properties: SFCCProperty[];
methods: SFCCMethod[];
inheritance?: string[];
constructorInfo?: string;
}
export class ClassContentParser {
private logger: Logger;
constructor() {
this.logger = Logger.getChildLogger('ClassContentParser');
}
/**
* Parse markdown content and extract structured class information
*/
parseClassContent(content: string): SFCCClassDetails {
const lines = content.split('\n');
let currentSection = '';
let className = '';
let packageName = '';
let description = '';
const constants: SFCCConstant[] = [];
const properties: SFCCProperty[] = [];
const methods: SFCCMethod[] = [];
const inheritance: string[] = [];
let constructorInfo = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// Extract package name
if (line.startsWith('## Package:')) {
packageName = line.replace('## Package:', '').trim();
}
// Extract class name
if (line.startsWith('# ') && !line.startsWith('## ')) {
className = line.replace('# ', '').replace('Class ', '').trim();
}
// Track current section
if (line.startsWith('## ')) {
currentSection = line.replace('## ', '').trim();
}
// Extract description
if (currentSection === 'Description' && line && !line.startsWith('#')) {
description += `${line} `;
}
// Extract inheritance hierarchy
if (currentSection === 'Inheritance Hierarchy' && line.includes('-')) {
const hierarchyItem = line.replace(/^[\s-]*/, '').trim();
if (hierarchyItem) {
inheritance.push(hierarchyItem);
}
}
// Extract constants
if (currentSection === 'Constants' && line.startsWith('### ')) {
const constant = this.parseConstant(line, lines, i);
if (constant) {
constants.push(constant);
}
}
// Extract properties
if (currentSection === 'Properties' && line.startsWith('### ')) {
const property = this.parseProperty(line, lines, i);
if (property) {
properties.push(property);
}
}
// Extract methods
if ((currentSection === 'Method Summary' || currentSection === 'Method Details') && line.startsWith('### ')) {
const method = this.parseMethod(line, lines, i);
if (method) {
methods.push(method);
}
}
// Extract constructor info
if (currentSection === 'Constructor Summary' && line && !line.startsWith('#')) {
constructorInfo += `${line} `;
}
}
return {
className: className.trim(),
packageName: packageName.trim(),
description: description.trim(),
constants,
properties,
methods,
inheritance: inheritance.length > 0 ? inheritance : undefined,
constructorInfo: constructorInfo.trim() || undefined,
};
}
/**
* Parse a constant definition from markdown content
*/
private parseConstant(headerLine: string, lines: string[], startIndex: number): SFCCConstant | null {
const constName = headerLine.replace('### ', '').trim();
let constType = '';
let constValue = '';
let constDesc = '';
let deprecated = false;
let deprecationMessage = '';
// Look for type, value and description in following lines
for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
const nextLine = lines[j].trim();
if (nextLine.startsWith('**Type:**')) {
const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
if (typeMatch) {
const typeInfo = typeMatch[1];
// Extract type and value if present (e.g., "String = 'COMPLETED'" or "Number = 8")
const valueMatch = typeInfo.match(/^(\w+)\s*=\s*(.+)$/);
if (valueMatch) {
constType = valueMatch[1];
constValue = valueMatch[2];
} else {
constType = typeInfo.trim();
}
}
} else if (nextLine.startsWith('**Deprecated:**')) {
const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
deprecated = true;
deprecationMessage = deprecationInfo.message;
} else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
constDesc += `${nextLine} `;
}
}
return {
name: constName,
type: constType,
value: constValue || undefined,
description: constDesc.trim(),
deprecated: deprecated || undefined,
deprecationMessage: deprecationMessage || undefined,
};
}
/**
* Parse a property definition from markdown content
*/
private parseProperty(headerLine: string, lines: string[], startIndex: number): SFCCProperty | null {
const propName = headerLine.replace('### ', '').trim();
let propType = '';
let propDesc = '';
const modifiers: string[] = [];
let deprecated = false;
let deprecationMessage = '';
// Look for type and description in following lines
for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
const nextLine = lines[j].trim();
if (nextLine.startsWith('**Type:**')) {
const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
if (typeMatch) {
const typeInfo = typeMatch[1];
propType = typeInfo.split(' ')[0];
if (typeInfo.includes('(Read Only)')) {
modifiers.push('Read Only');
}
if (typeInfo.includes('(Static)')) {
modifiers.push('Static');
}
}
} else if (nextLine.startsWith('**Deprecated:**')) {
const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
deprecated = true;
deprecationMessage = deprecationInfo.message;
} else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
propDesc += `${nextLine} `;
}
}
return {
name: propName,
type: propType,
description: propDesc.trim(),
modifiers: modifiers.length > 0 ? modifiers : undefined,
deprecated: deprecated || undefined,
deprecationMessage: deprecationMessage || undefined,
};
}
/**
* Parse a method definition from markdown content
*/
private parseMethod(headerLine: string, lines: string[], startIndex: number): SFCCMethod | null {
const methodName = headerLine.replace('### ', '').trim();
let signature = '';
let methodDesc = '';
let deprecated = false;
let deprecationMessage = '';
// Look for signature and description in following lines
for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
const nextLine = lines[j].trim();
if (nextLine.startsWith('**Signature:**')) {
const sigMatch = nextLine.match(/\*\*Signature:\*\*\s*`(.+)`/);
if (sigMatch) {
signature = sigMatch[1];
}
} else if (nextLine.startsWith('**Description:**')) {
methodDesc = nextLine.replace('**Description:**', '').trim();
} else if (nextLine.startsWith('**Deprecated:**')) {
const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
deprecated = true;
deprecationMessage = deprecationInfo.message;
} else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#') && !nextLine.startsWith('---')) {
if (!methodDesc && !nextLine.includes('Signature:')) {
methodDesc += `${nextLine} `;
}
}
}
return {
name: methodName,
signature: signature || methodName,
description: methodDesc.trim(),
deprecated: deprecated || undefined,
deprecationMessage: deprecationMessage || undefined,
};
}
/**
* Parse deprecation information from markdown content
*/
private parseDeprecationInfo(deprecationLine: string, lines: string[], startIndex: number): { message: string } {
let deprecationMessage = '';
// Check if there's a message on the same line
const sameLineMessage = deprecationLine.replace('**Deprecated:**', '').trim();
if (sameLineMessage) {
deprecationMessage = sameLineMessage;
} else {
// Look for the deprecation message on subsequent lines until next ** marker
const depLines: string[] = [];
for (let k = startIndex + 1; k < lines.length && !lines[k].startsWith('#'); k++) {
const depLine = lines[k].trim();
if (depLine.startsWith('**') && !depLine.startsWith('**Deprecated:**')) {
break; // Stop at next ** marker
}
if (depLine && !depLine.startsWith('---')) {
depLines.push(depLine);
}
}
deprecationMessage = depLines.join(' ').trim();
}
return { message: deprecationMessage };
}
}
```
--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/server.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
/**
* SFCC Mock Server
*
* Unified mock server combining WebDAV and OCAPI functionality for SFCC development testing.
* Provides a single endpoint for both log file access and OCAPI simulation.
*
* Usage:
* node server.js [options]
*
* Options:
* --port <number> Server port (default: 3000)
* --host <string> Server host (default: localhost)
* --dev Enable development mode with verbose logging
* --no-webdav Disable WebDAV functionality
* --no-ocapi Disable OCAPI functionality
* --no-cors Disable CORS headers
* --mock-data <path> Custom path to mock data directory
* --help Show this help message
*/
const ServerConfig = require('./src/config/server-config');
const SFCCMockApp = require('./src/app');
class SFCCMockServer {
constructor(options = {}) {
this.config = new ServerConfig(options);
this.app = new SFCCMockApp(this.config);
this.server = null;
}
async start() {
try {
// Validate configuration
const configErrors = this.config.validate();
if (configErrors.length > 0) {
throw new Error(`Configuration errors: ${configErrors.join(', ')}`);
}
const expressApp = this.app.getExpressApp();
return new Promise((resolve, reject) => {
this.server = expressApp.listen(this.config.port, this.config.host, (err) => {
if (err) {
reject(err);
return;
}
this.printStartupMessage();
resolve(this.server);
});
this.server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`❌ Port ${this.config.port} is already in use`);
console.error(' Try using a different port with --port <number>');
} else {
console.error('❌ Server error:', err.message);
}
reject(err);
});
});
} catch (error) {
console.error('❌ Failed to start server:', error.message);
throw error;
}
}
async stop() {
if (this.server) {
return new Promise((resolve) => {
this.server.close(() => {
console.log('🛑 SFCC Mock Server stopped');
resolve();
});
});
}
}
printStartupMessage() {
const summary = this.config.getSummary();
console.log('🚀 SFCC Mock Server started successfully!');
console.log('');
console.log(`📊 Server Info:`);
console.log(` Host: ${summary.server}`);
console.log(` Mode: ${summary.mode}`);
console.log(` Features: ${summary.features.join(', ')}`);
console.log('');
console.log('📋 Available Endpoints:');
Object.entries(summary.endpoints).forEach(([name, url]) => {
if (typeof url === 'string') {
console.log(` ${name}: ${url}`);
} else if (typeof url === 'object') {
console.log(` ${name}:`);
Object.entries(url).forEach(([subName, subUrl]) => {
console.log(` ${subName}: ${subUrl}`);
});
}
});
if (this.config.features.webdav) {
console.log('');
console.log('📁 WebDAV Endpoints:');
console.log(` Logs (SFCC path): ${this.config.getWebdavLogsUrl()}`);
console.log(` Logs (direct): ${this.config.getServerUrl()}/Logs/`);
console.log(` Job Logs: ${this.config.getServerUrl()}/Logs/jobs/`);
}
if (this.config.features.ocapi) {
console.log('');
console.log('🔐 OCAPI Endpoints:');
console.log(` OAuth: ${this.config.getServerUrl()}/dw/oauth2/access_token`);
console.log(` System Objects: ${this.config.getOcapiBaseUrl()}/system_object_definitions`);
console.log(` Code Versions: ${this.config.getOcapiBaseUrl()}/code_versions`);
console.log('');
console.log('🔑 Test Credentials:');
console.log(` Client ID: ${this.config.validCredentials.clientId}`);
console.log(` Client Secret: ${this.config.validCredentials.clientSecret}`);
}
if (this.config.isDevMode) {
console.log('');
console.log('🔧 Development mode enabled - verbose logging active');
}
console.log('');
console.log('✅ Server is ready for connections');
}
getConfig() {
return this.config;
}
getApp() {
return this.app;
}
}
/**
* Parse command line arguments
*/
function parseArgs() {
const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case '--help':
printHelp();
process.exit(0);
break;
case '--dev':
options.dev = true;
break;
case '--no-webdav':
options.enableWebdav = false;
break;
case '--no-ocapi':
options.enableOcapi = false;
break;
case '--no-cors':
options.enableCors = false;
break;
case '--enable-random-errors':
options.enableRandomErrors = true;
break;
case '--port':
if (i + 1 < args.length) {
options.port = parseInt(args[i + 1]);
i++;
} else {
console.error('❌ --port requires a value');
process.exit(1);
}
break;
case '--host':
if (i + 1 < args.length) {
options.host = args[i + 1];
i++;
} else {
console.error('❌ --host requires a value');
process.exit(1);
}
break;
case '--mock-data':
if (i + 1 < args.length) {
options.mockDataPath = args[i + 1];
i++;
} else {
console.error('❌ --mock-data requires a path');
process.exit(1);
}
break;
default:
if (arg.startsWith('--port=')) {
options.port = parseInt(arg.split('=')[1]);
} else if (arg.startsWith('--host=')) {
options.host = arg.split('=')[1];
} else if (arg.startsWith('--mock-data=')) {
options.mockDataPath = arg.split('=')[1];
} else {
console.warn(`⚠️ Unknown argument: ${arg}`);
}
}
}
return options;
}
/**
* Print help message
*/
function printHelp() {
console.log('SFCC Mock Server - Unified WebDAV and OCAPI mock server');
console.log('');
console.log('Usage:');
console.log(' node server.js [options]');
console.log('');
console.log('Options:');
console.log(' --port <number> Server port (default: 3000)');
console.log(' --host <string> Server host (default: localhost)');
console.log(' --dev Enable development mode with verbose logging');
console.log(' --no-webdav Disable WebDAV functionality');
console.log(' --no-ocapi Disable OCAPI functionality');
console.log(' --no-cors Disable CORS headers');
console.log(' --enable-random-errors Enable random 500 errors (1% chance) for error handling testing');
console.log(' --mock-data <path> Custom path to mock data directory');
console.log(' --help Show this help message');
console.log('');
console.log('Examples:');
console.log(' node server.js --dev # Start in development mode');
console.log(' node server.js --port 4000 # Start on port 4000');
console.log(' node server.js --no-webdav # Only OCAPI endpoints');
console.log(' node server.js --host 0.0.0.0 --port 3001 # Bind to all interfaces');
}
// CLI execution
if (require.main === module) {
const options = parseArgs();
const server = new SFCCMockServer(options);
// Graceful shutdown handlers
const gracefulShutdown = async (signal) => {
console.log(`\\n🔄 Received ${signal}, shutting down gracefully...`);
try {
await server.stop();
process.exit(0);
} catch (error) {
console.error('❌ Error during shutdown:', error);
process.exit(1);
}
};
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('❌ Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('❌ Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
// Start the server
server.start().catch((error) => {
console.error('💥 Failed to start server:', error.message);
process.exit(1);
});
}
module.exports = SFCCMockServer;
```
--------------------------------------------------------------------------------
/docs/dw_order/ShippingOrderItem.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.order
# Class ShippingOrderItem
## Inheritance Hierarchy
- Object
- dw.object.Extensible
- dw.order.AbstractItem
- dw.order.ShippingOrderItem
## Description
One or more ShippingOrderItems are contained in a ShippingOrder, created using ShippingOrder.createShippingOrderItem(OrderItem, Quantity) and can be retrieved by ShippingOrder.getItems(). A ShippingOrderItem references a single OrderItem which in turn references a LineItem associated with an Order. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice.
## Constants
### STATUS_CANCELLED
**Type:** String = "CANCELLED"
Constant for Order Item Status CANCELLED
### STATUS_CONFIRMED
**Type:** String = "CONFIRMED"
Constant for Order Item Status CONFIRMED
### STATUS_SHIPPED
**Type:** String = "SHIPPED"
Constant for Order Item Status SHIPPED
### STATUS_WAREHOUSE
**Type:** String = "WAREHOUSE"
Constant for Order Item Status WAREHOUSE
## Properties
### basePrice
**Type:** Money (Read Only)
Price of a single unit before discount application.
### parentItem
**Type:** ShippingOrderItem
Returns null or the parent item.
### quantity
**Type:** Quantity (Read Only)
The quantity of the shipping order item.
The Quantity is equal to the related line item quantity.
### shippingOrderNumber
**Type:** String (Read Only)
The mandatory shipping order number of the related
ShippingOrder.
### status
**Type:** EnumValue
Gets the order item status.
The possible values are STATUS_CONFIRMED,
STATUS_WAREHOUSE, STATUS_SHIPPED,
STATUS_CANCELLED.
### trackingRefs
**Type:** FilteringCollection (Read Only)
Gets the tracking refs (tracking infos) the shipping order item is
assigned to.
## Constructor Summary
## Method Summary
### addTrackingRef
**Signature:** `addTrackingRef(trackingInfoID : String, quantity : Quantity) : TrackingRef`
A shipping order item can be assigned to one or many tracking infos with different quantities.
### applyPriceRate
**Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void`
Apply a rate of (factor / divisor) to the prices in this item, with the option to half round up or half round down to the nearest cent if necessary.
### getBasePrice
**Signature:** `getBasePrice() : Money`
Price of a single unit before discount application.
### getParentItem
**Signature:** `getParentItem() : ShippingOrderItem`
Returns null or the parent item.
### getQuantity
**Signature:** `getQuantity() : Quantity`
The quantity of the shipping order item.
### getShippingOrderNumber
**Signature:** `getShippingOrderNumber() : String`
The mandatory shipping order number of the related ShippingOrder.
### getStatus
**Signature:** `getStatus() : EnumValue`
Gets the order item status.
### getTrackingRefs
**Signature:** `getTrackingRefs() : FilteringCollection`
Gets the tracking refs (tracking infos) the shipping order item is assigned to.
### setParentItem
**Signature:** `setParentItem(parentItem : ShippingOrderItem) : void`
Set a parent item.
### setStatus
**Signature:** `setStatus(status : String) : void`
Sets the status.
### split
**Signature:** `split(quantity : Quantity) : ShippingOrderItem`
Split the shipping order item.
### split
**Signature:** `split(quantity : Quantity, splitOrderItem : boolean) : ShippingOrderItem`
Split the shipping order item.
## Method Detail
## Method Details
### addTrackingRef
**Signature:** `addTrackingRef(trackingInfoID : String, quantity : Quantity) : TrackingRef`
**Description:** A shipping order item can be assigned to one or many tracking infos with different quantities. For example an item with quantity 3 may have been shipped in 2 packages, each represented by its own tracking info - 2 TrackingRefs would exist with quantities 1 and 2. This method creates and adds a new tracking reference to this shipping order item for a given tracking info and quantity. The new instance is returned.
**Parameters:**
- `trackingInfoID`: the id of the tracking info
- `quantity`: the quantity the which is assigned to the tracking info for this shipping order item. Optional (null is allowed).
**Returns:**
the new tracking reference
**See Also:**
TrackingRef
---
### applyPriceRate
**Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void`
**Description:** Apply a rate of (factor / divisor) to the prices in this item, with the option to half round up or half round down to the nearest cent if necessary. Examples: TaxBasis beforefactordivisorroundupCalculationTaxBasis after $10.0012true10*1/2=$5.00 $10.00910true10*9/10=$9.00 $10.0013true10*1/3=3.3333=$3.33 $2.4712true2.47*1/2=1.235=$1.24 $2.4712false2.47*1/2=1.235=$1.23 Which prices are updated?: The rate described above is applied to tax-basis and tax then the net-price and gross-price are recalculated by adding / subtracting depending on whether the order is based on net price. Example (order based on net price) New TaxBasis:$10.00, Tax:$1.00, NetPrice=TaxBasis=$10.00, GrossPrice=TaxBasis+Tax=$11.00 Example (order based on gross price) New TaxBasis:$10.00, Tax:$1.00, NetPrice=TaxBasis-tax=$9.00, GrossPrice=TaxBasis=$10.00
**Parameters:**
- `factor`: factor used to calculate rate
- `divisor`: divisor used to calculate rate
- `roundUp`: whether to round up or down on 0.5
**See Also:**
AbstractItem.getTaxBasis()
AbstractItem.getTax()
AbstractItem.getNetPrice()
AbstractItem.getGrossPrice()
TaxMgr.getTaxationPolicy()
---
### getBasePrice
**Signature:** `getBasePrice() : Money`
**Description:** Price of a single unit before discount application.
**Returns:**
Price of a single unit before discount application.
---
### getParentItem
**Signature:** `getParentItem() : ShippingOrderItem`
**Description:** Returns null or the parent item.
**Returns:**
null or the parent item.
---
### getQuantity
**Signature:** `getQuantity() : Quantity`
**Description:** The quantity of the shipping order item. The Quantity is equal to the related line item quantity.
**Returns:**
the quantity
---
### getShippingOrderNumber
**Signature:** `getShippingOrderNumber() : String`
**Description:** The mandatory shipping order number of the related ShippingOrder.
**Returns:**
the shipping order number.
---
### getStatus
**Signature:** `getStatus() : EnumValue`
**Description:** Gets the order item status. The possible values are STATUS_CONFIRMED, STATUS_WAREHOUSE, STATUS_SHIPPED, STATUS_CANCELLED.
**Returns:**
the status
---
### getTrackingRefs
**Signature:** `getTrackingRefs() : FilteringCollection`
**Description:** Gets the tracking refs (tracking infos) the shipping order item is assigned to.
**Returns:**
the tracking refs ( tracking infos - TrackingRef ) the shipping order item is assigned to.
**See Also:**
TrackingRef
---
### setParentItem
**Signature:** `setParentItem(parentItem : ShippingOrderItem) : void`
**Description:** Set a parent item. The parent item must belong to the same ShippingOrder. An infinite parent-child loop is disallowed as is a parent-child depth greater than 10. Setting a parent item indicates a dependency of the child item on the parent item, and can be used to form a parallel structure to that accessed using ProductLineItem.getParent().
**Parameters:**
- `parentItem`: The parent item, null is allowed
---
### setStatus
**Signature:** `setStatus(status : String) : void`
**Description:** Sets the status. See ShippingOrder for details of shipping order status transitions. Do not use this method to set a shipping order to status WAREHOUSE, instead use ShippingOrder.setStatusWarehouse() This also triggers the setting of the status of the LineItem when appropriate. Setting this status can also have an impact on the order status, accessed using Order.getStatus() and the shipping order status, accessed using ShippingOrder.getStatus().
**Parameters:**
- `status`: the status
**Throws:**
NullPointerException - if status is null
IllegalArgumentException - if the status transition to the status is not allowed
---
### split
**Signature:** `split(quantity : Quantity) : ShippingOrderItem`
**Description:** Split the shipping order item. This will also lead to a split of the related LineItem. Split means that for the passed quantity a new item is created with this quantity as an exact copy of this item. The remaining amount will stay in this item. If quantity is equal to getQuantity() no split is done and this item is returned itself. This method is equivalent to split(Quantity, Boolean) called with splitOrderItem equals to true.
**Parameters:**
- `quantity`: the quantity for the newly created item
**Returns:**
the newly created item or this item
**Throws:**
IllegalArgumentException - if quantity is greater than getQuantity()
---
### split
**Signature:** `split(quantity : Quantity, splitOrderItem : boolean) : ShippingOrderItem`
**Description:** Split the shipping order item. This will also lead to a split of the related LineItem when splitOrderItem is true. Split means that for the passed quantity a new item is created with this quantity as an exact copy of this item. The remaining amount will stay in this item. If quantity is equal to getQuantity() no split is done and this item is returned itself.
**Parameters:**
- `quantity`: the quantity for the newly created item
- `splitOrderItem`: true the related LineItem will be splitted too false the related LineItem will not be splitted
**Returns:**
the newly created item or this item
**Throws:**
IllegalArgumentException - if quantity is greater than getQuantity()
---
```
--------------------------------------------------------------------------------
/docs/dw_system/RESTResponseMgr.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.system
# Class RESTResponseMgr
## Inheritance Hierarchy
- Object
- dw.system.RESTResponseMgr
## Description
This class provides helper methods for creating REST error and success responses. It is mainly intended to be used to build Custom REST APIs. But, any controller implementation planning to provide REST-like responses can use these methods. If these methods are being used in the controllers, note that a few defaults like URL prefix for type in createError methods will correspond to Custom REST APIs.
## Constructor Summary
RESTResponseMgr()
## Method Summary
### createEmptySuccess
**Signature:** `static createEmptySuccess(statusCode : Number) : RESTSuccessResponse`
Constructs a new RESTSuccessResponse object.
### createError
**Signature:** `static createError(statusCode : Number) : RESTErrorResponse`
Constructs a new RESTErrorResponse object.
### createError
**Signature:** `static createError(statusCode : Number, type : String) : RESTErrorResponse`
Constructs a new RESTErrorResponse object.
### createError
**Signature:** `static createError(statusCode : Number, type : String, title : String) : RESTErrorResponse`
Constructs a new RESTErrorResponse object.
### createError
**Signature:** `static createError(statusCode : Number, type : String, title : String, detail : String) : RESTErrorResponse`
Constructs a new RESTErrorResponse object.
### createScapiRemoteInclude
**Signature:** `static createScapiRemoteInclude(apiFamily : String, apiName : String, apiVersion : String, resourcePath : String, params : URLParameter...) : RemoteInclude`
Constructs a new RemoteInclude object specific for the SCAPI include path.
### createStorefrontControllerRemoteInclude
**Signature:** `static createStorefrontControllerRemoteInclude(action : URLAction, params : URLParameter...) : RemoteInclude`
Constructs a new RemoteInclude object specific for the Storefront Controller include path.
### createSuccess
**Signature:** `static createSuccess(body : Object, statusCode : Number) : RESTSuccessResponse`
Constructs a new RESTSuccessResponse object.
### createSuccess
**Signature:** `static createSuccess(body : Object) : RESTSuccessResponse`
Constructs a new RESTSuccessResponse object.
## Constructor Detail
## Method Detail
## Method Details
### createEmptySuccess
**Signature:** `static createEmptySuccess(statusCode : Number) : RESTSuccessResponse`
**Description:** Constructs a new RESTSuccessResponse object. This method is to be used in scenarios where response body is not expected (e.g. statusCode is 204).
**Parameters:**
- `statusCode`: The http status code of the response. The statusCode parameter should conform to RFC standards for a success.
**Returns:**
A new RESTSuccessResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (100..299) range.
---
### createError
**Signature:** `static createError(statusCode : Number) : RESTErrorResponse`
**Description:** Constructs a new RESTErrorResponse object. This method should be used when you have just the statusCode of the error and want the type of error to be inferred. 'type' of the error is inferred from the status code as follows: 400 - bad-request 401 - unauthorized 403 - forbidden 404 - resource-not-found 409 - conflict 412 - precondition-failed 429 - too-many-requests 500 - internal-server-error default - about:blank
**Parameters:**
- `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
**Returns:**
A new RESTErrorResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (400..599) range.
---
### createError
**Signature:** `static createError(statusCode : Number, type : String) : RESTErrorResponse`
**Description:** Constructs a new RESTErrorResponse object. This method should be used when you want to omit 'title' and 'detail' of the error. With this method, custom error codes and types apart from the standard ones can be constructed.
**Parameters:**
- `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
- `type`: Type of the error according to RFC 9457. We enforce the following restrictions on top of the RFC: If the provided type is not an absolute URL, it will be prepended with https://api.commercecloud.salesforce.com/documentation/error/v1/custom-errors/. Custom error types are not allowed to have SYSTEM error type prefix: https://api.commercecloud.salesforce.com/documentation/error/v1/errors/.
**Returns:**
A new RESTErrorResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (400..599) range or if the error type is not a valid URI or conflicts with the SYSTEM error type namespace.
---
### createError
**Signature:** `static createError(statusCode : Number, type : String, title : String) : RESTErrorResponse`
**Description:** Constructs a new RESTErrorResponse object. This method should be used when you want to omit 'detail' of the error but want to have valid 'statusCode', 'type' and 'title'.
**Parameters:**
- `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
- `type`: Type of the error according to RFC 9457. We enforce the following restrictions on top of the RFC: If the provided type is not an absolute URL, it will be prepended with https://api.commercecloud.salesforce.com/documentation/error/v1/custom-errors/. Custom error types are not allowed to have SYSTEM error type prefix: https://api.commercecloud.salesforce.com/documentation/error/v1/errors/.
- `title`: Human-readable summary of the error type.
**Returns:**
A new RESTErrorResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (400..599) range or if the error type is not a valid URI or conflicts with SYSTEM error type namespace.
---
### createError
**Signature:** `static createError(statusCode : Number, type : String, title : String, detail : String) : RESTErrorResponse`
**Description:** Constructs a new RESTErrorResponse object. This method can be used to construct error responses with valid 'statusCode', 'type', 'title' and 'detail'. If you want to omit title or detail, you can pass in null.
**Parameters:**
- `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
- `type`: Type of the error according to RFC 9457. We enforce the following restrictions on top of the RFC: If the provided type is not an absolute URL, it will be prepended with https://api.commercecloud.salesforce.com/documentation/error/v1/custom-errors/. Custom error types are not allowed to have SYSTEM error type prefix: https://api.commercecloud.salesforce.com/documentation/error/v1/errors/.
- `title`: Human-readable summary of the error type.
- `detail`: Human-readable explanation of the specific occurrence of the error.
**Returns:**
A new RESTErrorResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (400..599) range or if the error type is not a valid URI or conflicts with SYSTEM error type namespace.
---
### createScapiRemoteInclude
**Signature:** `static createScapiRemoteInclude(apiFamily : String, apiName : String, apiVersion : String, resourcePath : String, params : URLParameter...) : RemoteInclude`
**Description:** Constructs a new RemoteInclude object specific for the SCAPI include path. Usage: SCAPI remote include URL have following form: BASE_PATH/{apiFamily}/{apiName}/{apiVersion}/organizations/ORG_ID/{resourcePath}[?params] For the given SCAPI resource path: BASE_PATH/product/shopper-products/v1/organizations/ORG_ID/categories/root?siteId=YourShopHere RemoteInclude object can be constructed in a script like following: let include = dw.system.RESTResponseMgr.createScapiRemoteInclude("product", "shopper-products", "v1", "categories/root", dw.web.URLParameter("siteId", "YourShopHere")); Please notice that 'BASE_PATH' and 'ORG_ID' are automatically resolved.
**Parameters:**
- `apiFamily`: an API Family name. Example: 'product'.
- `apiName`: an API Name. Example: 'shopper-products'.
- `apiVersion`: an API Version. Example: 'v1'.
- `resourcePath`: a Resource path. Example: 'categories/root'
- `params`: a query parameters (optional)
**Returns:**
a new instance of RemoteInclude.
---
### createStorefrontControllerRemoteInclude
**Signature:** `static createStorefrontControllerRemoteInclude(action : URLAction, params : URLParameter...) : RemoteInclude`
**Description:** Constructs a new RemoteInclude object specific for the Storefront Controller include path.
**Parameters:**
- `action`: a container to specify target controller. Hostnames in URL actions are ignored.
- `params`: a query parameters (optional).
**Returns:**
a new instance of RemoteInclude.
---
### createSuccess
**Signature:** `static createSuccess(body : Object, statusCode : Number) : RESTSuccessResponse`
**Description:** Constructs a new RESTSuccessResponse object.
**Parameters:**
- `body`: The body of the successful response. This should always be a valid JavaScript JSON object.
- `statusCode`: The http status code of the response. The statusCode parameter should conform to RFC standards for a success.
**Returns:**
A new RESTSuccessResponse object.
**Throws:**
IllegalArgumentException - If the statusCode is not in the (100..299) range.
---
### createSuccess
**Signature:** `static createSuccess(body : Object) : RESTSuccessResponse`
**Description:** Constructs a new RESTSuccessResponse object. HTTP status code of the response will be defaulted to 200.
**Parameters:**
- `body`: The body of the successful response. This should always be a valid JavaScript JSON object.
**Returns:**
A new RESTSuccessResponse object.
---
```
--------------------------------------------------------------------------------
/tests/cartridge-handler.test.ts:
--------------------------------------------------------------------------------
```typescript
import { CartridgeToolHandler } from '../src/core/handlers/cartridge-handler.js';
import { HandlerContext } from '../src/core/handlers/base-handler.js';
import { Logger } from '../src/utils/logger.js';
// Mock cartridge client
const mockCartridgeClient = {
generateCartridgeStructure: jest.fn(),
};
// Mock the ClientFactory to return our mock client
jest.mock('../src/core/handlers/client-factory.js', () => ({
ClientFactory: jest.fn().mockImplementation(() => ({
createCartridgeClient: jest.fn(() => mockCartridgeClient),
})),
}));
describe('CartridgeToolHandler', () => {
let mockLogger: jest.Mocked<Logger>;
let mockClient: typeof mockCartridgeClient;
let context: HandlerContext;
let handler: CartridgeToolHandler;
beforeEach(() => {
mockLogger = {
debug: jest.fn(),
log: jest.fn(),
error: jest.fn(),
timing: jest.fn(),
methodEntry: jest.fn(),
methodExit: jest.fn(),
} as any;
// Reset mocks
jest.clearAllMocks();
// Use the mock client directly and reset it
mockClient = mockCartridgeClient;
mockClient.generateCartridgeStructure.mockReset();
jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
context = {
logger: mockLogger,
config: null as any,
capabilities: { canAccessLogs: false, canAccessOCAPI: false },
};
handler = new CartridgeToolHandler(context, 'Cartridge');
});
afterEach(() => {
jest.restoreAllMocks();
});
// Helper function to initialize handler for tests that need it
const initializeHandler = async () => {
await (handler as any).initialize();
};
describe('canHandle', () => {
it('should handle cartridge tools', () => {
expect(handler.canHandle('generate_cartridge_structure')).toBe(true);
});
it('should not handle non-cartridge tools', () => {
expect(handler.canHandle('get_latest_error')).toBe(false);
expect(handler.canHandle('unknown_tool')).toBe(false);
});
});
describe('initialization', () => {
it('should initialize cartridge generation client', async () => {
await initializeHandler();
const MockedClientFactory = jest.requireMock('../src/core/handlers/client-factory.js').ClientFactory;
const mockFactoryInstance = MockedClientFactory.mock.results[0].value;
expect(mockFactoryInstance.createCartridgeClient).toHaveBeenCalled();
expect(mockLogger.debug).toHaveBeenCalledWith('Cartridge generation client initialized');
});
});
describe('disposal', () => {
it('should dispose cartridge generation client properly', async () => {
await initializeHandler();
await (handler as any).dispose();
expect(mockLogger.debug).toHaveBeenCalledWith('Cartridge generation client disposed');
});
});
describe('generate_cartridge_structure tool', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle generate_cartridge_structure with cartridgeName', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'plugin_example',
targetPath: '/path/to/project',
filesCreated: [
'cartridges/plugin_example/cartridge/controllers/Product.js',
'cartridges/plugin_example/cartridge/scripts/helpers/ProductHelper.js',
'package.json',
],
});
const args = { cartridgeName: 'plugin_example' };
const result = await handler.handle('generate_cartridge_structure', args, Date.now());
expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
cartridgeName: 'plugin_example',
targetPath: undefined,
fullProjectSetup: true,
});
expect(result.content[0].text).toContain('plugin_example');
});
it('should handle generate_cartridge_structure with all options', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'custom_cartridge',
targetPath: '/custom/path',
filesCreated: [
'cartridges/custom_cartridge/cartridge/controllers/Product.js',
],
});
const args = {
cartridgeName: 'custom_cartridge',
targetPath: '/custom/path',
fullProjectSetup: false,
};
const result = await handler.handle('generate_cartridge_structure', args, Date.now());
expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
cartridgeName: 'custom_cartridge',
targetPath: '/custom/path',
fullProjectSetup: false,
});
expect(result.content[0].text).toContain('custom_cartridge');
});
it('should use default fullProjectSetup when not provided', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'test_cartridge',
filesCreated: [],
});
const args = {
cartridgeName: 'test_cartridge',
targetPath: '/test/path',
};
await handler.handle('generate_cartridge_structure', args, Date.now());
expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
cartridgeName: 'test_cartridge',
targetPath: '/test/path',
fullProjectSetup: true,
});
});
it('should throw error when cartridgeName is missing', async () => {
const result = await handler.handle('generate_cartridge_structure', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
});
it('should throw error when cartridgeName is empty', async () => {
const result = await handler.handle('generate_cartridge_structure', { cartridgeName: '' }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
});
it('should throw error when cartridgeName is not a string', async () => {
const result = await handler.handle('generate_cartridge_structure', { cartridgeName: 123 }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
});
});
describe('error handling', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle client errors gracefully', async () => {
mockClient.generateCartridgeStructure.mockRejectedValue(new Error('Directory already exists'));
const result = await handler.handle('generate_cartridge_structure', { cartridgeName: 'existing_cartridge' }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Directory already exists');
});
it('should throw error for unsupported tools', async () => {
await expect(handler.handle('unsupported_tool', {}, Date.now()))
.rejects.toThrow('Unsupported tool');
});
});
describe('timing and logging', () => {
beforeEach(async () => {
await initializeHandler();
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'test_cartridge',
filesCreated: [],
});
});
it('should log timing information', async () => {
const startTime = Date.now();
await handler.handle('generate_cartridge_structure', { cartridgeName: 'test_cartridge' }, startTime);
expect(mockLogger.timing).toHaveBeenCalledWith('generate_cartridge_structure', startTime);
});
it('should log execution details', async () => {
await handler.handle('generate_cartridge_structure', { cartridgeName: 'test_cartridge' }, Date.now());
expect(mockLogger.debug).toHaveBeenCalledWith(
'generate_cartridge_structure completed successfully',
expect.any(Object),
);
});
it('should create appropriate log message', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'my_cartridge',
filesCreated: [],
});
await handler.handle('generate_cartridge_structure', { cartridgeName: 'my_cartridge' }, Date.now());
// Check that the debug log contains the execution details
expect(mockLogger.debug).toHaveBeenCalledWith(
'generate_cartridge_structure completed successfully',
expect.any(Object),
);
});
});
describe('client integration', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should pass correct parameters to client for minimal request', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'minimal_cartridge',
filesCreated: ['cartridges/minimal_cartridge/cartridge/controllers/Default.js'],
});
const args = { cartridgeName: 'minimal_cartridge' };
await handler.handle('generate_cartridge_structure', args, Date.now());
expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
cartridgeName: 'minimal_cartridge',
targetPath: undefined,
fullProjectSetup: true,
});
});
it('should pass correct parameters to client for full request', async () => {
mockClient.generateCartridgeStructure.mockResolvedValue({
success: true,
cartridgeName: 'full_cartridge',
filesCreated: ['cartridges/full_cartridge/cartridge/controllers/Default.js'],
});
const args = {
cartridgeName: 'full_cartridge',
targetPath: '/workspace/my-project',
fullProjectSetup: false,
};
await handler.handle('generate_cartridge_structure', args, Date.now());
expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
cartridgeName: 'full_cartridge',
targetPath: '/workspace/my-project',
fullProjectSetup: false,
});
});
});
});
```
--------------------------------------------------------------------------------
/docs/dw_svc/ServiceCallback.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.svc
# Class ServiceCallback
## Inheritance Hierarchy
- Object
- dw.svc.ServiceCallback
## Description
Defines callbacks for use with the LocalServiceRegistry. Note this class itself is not used directly, and is present only for documentation of the available callback methods. These methods are called in sequence when a service is called: initServiceClient(Service) -- Creates the underlying client that will be used to make the call. This is intended for SOAP Services. Other client types will be created automatically. createRequest(Service, Object...) -- Given arguments to the Service.call(Object...), configure the actual service request. This may include setting request headers, defining the message body, etc. execute(Service, Object) -- Perform the actual request. At this point the client has been configured with the relevant credentials, so the call should be made. This is required for SOAP services. parseResponse(Service, Object) -- Convert the result of the call into an object to be returned from the Service.call(Object...) method. If the service is mocked (see Service.isMock()), then mockFull(Service, Object...) takes the place of this entire sequence. If that is not implemented, then mockCall(Service, Object) takes the place of just the execute(Service, Object) method. The URL, request, and response objects may be logged. To avoid logging sensitive data, filterLogMessage(String) and/or getRequestLogMessage(Object) and getResponseLogMessage(Object) must be implemented. If they are not implemented then this logging will not be done on Production environments. There are some special considerations for the combination of service type and callback: Service Type initServiceClient createRequest execute parseResponse HTTP Not normally implemented. Must return a HTTPClient Required unless execute is provided. The return value is expected to be either a String or array of HTTPRequestPart, which will be used as the request body Not called unless a boolean "executeOverride:true" is set on the callback. This is a temporary limitation, a future release will always call this callback if it is present Required unless execute is provided. HTTPForm Not normally implemented. Must return a HTTPClient Not normally implemented. Default behavior constructs an "application/x-www-form-urlencoded" request based on a Map given as an argument. Not normally implemented. The same limitations as HTTP regarding the "executeOverride" flag apply here. Optional. Default behavior is to return the response body as a String. SOAP Optional. This must return the Webservice stub or port Required. If initServiceClient was not provided, then this function must call SOAPService.setServiceClient(Object) with the stub or port Required. A typical implementation will call the webservice via a method on the service client Optional. Default behavior returns the output of execute FTP Not normally implemented. Must return a FTPClient or SFTPClient Required unless execute is defined. If present, it should call FTPService.setOperation(String, Object...) Optional. An implementation may call any required methods on the given client. The default implementation calls the Operation that was set up and returns the result. Optional. Default behavior returns the output of execute GENERIC Optional. Optional. Required. The GENERIC type allows any code to be wrapped in the service framework layer, and it's up to this execute method to define what that logic is. Optional.
## Properties
### URL
**Type:** String (Read Only)
Allows overriding the URL provided by the service configuration.
It is usually better to call Service.setURL(String) within createRequest(Service, Object...)
because that allows you to modify the existing URL based on call parameters.
## Constructor Summary
## Method Summary
### createRequest
**Signature:** `createRequest(service : Service, params : Object...) : Object`
Creates a request object to be used when calling the service.
### execute
**Signature:** `execute(service : Service, request : Object) : Object`
Provides service-specific execution logic.
### filterLogMessage
**Signature:** `filterLogMessage(msg : String) : String`
Allows filtering communication URL, request, and response log messages.
### getRequestLogMessage
**Signature:** `getRequestLogMessage(request : Object) : String`
Creates a communication log message for the given request.
### getResponseLogMessage
**Signature:** `getResponseLogMessage(response : Object) : String`
Creates a response log message for the given request.
### getURL
**Signature:** `getURL() : String`
Allows overriding the URL provided by the service configuration.
### initServiceClient
**Signature:** `initServiceClient(service : Service) : Object`
Creates a protocol-specific client object.
### mockCall
**Signature:** `mockCall(service : Service, requestObj : Object) : Object`
Override this method to mock the remote portion of the service call.
### mockFull
**Signature:** `mockFull(service : Service, args : Object...) : Object`
Override this method to mock the entire service call, including the createRequest, execute, and parseResponse phases.
### parseResponse
**Signature:** `parseResponse(service : Service, response : Object) : Object`
Creates a response object from a successful service call.
## Method Detail
## Method Details
### createRequest
**Signature:** `createRequest(service : Service, params : Object...) : Object`
**Description:** Creates a request object to be used when calling the service. The type of the object expected is dependent on the service. For example, the HTTPService expects the HTTP request body to be returned. This is required unless the execute method is implemented. It is not recommended to have a service accept a single array or list as a parameter, since doing so requires some extra work when actually calling the service. See Service.call(Object...) for more details.
**Parameters:**
- `service`: Service being executed.
- `params`: Parameters given to the call method.
**Returns:**
Request object to give to the execute method.
---
### execute
**Signature:** `execute(service : Service, request : Object) : Object`
**Description:** Provides service-specific execution logic. This can be overridden to execute a chain of FTP commands in the FTPService, or perform the actual remote call on a webservice stub in the SOAPService.
**Parameters:**
- `service`: Service being executed.
- `request`: Request object returned by createRequest(Service, Object...).
**Returns:**
Response from the underlying call, to be sent to parseResponse(Service, Object).
**Throws:**
- Exception
---
### filterLogMessage
**Signature:** `filterLogMessage(msg : String) : String`
**Description:** Allows filtering communication URL, request, and response log messages. If not implemented, then no filtering will be performed and the message will be logged as-is.
**Parameters:**
- `msg`: Original log message.
**Returns:**
Message to be logged.
---
### getRequestLogMessage
**Signature:** `getRequestLogMessage(request : Object) : String`
**Description:** Creates a communication log message for the given request. If not implemented then the default logic will be used to convert the request into a log message.
**Parameters:**
- `request`: Request object.
**Returns:**
Log message, or null to create and use the default message.
---
### getResponseLogMessage
**Signature:** `getResponseLogMessage(response : Object) : String`
**Description:** Creates a response log message for the given request. If not implemented then the default logic will be used to convert the response into a log message.
**Parameters:**
- `response`: Response object.
**Returns:**
Log message, or null to create and use the default message.
---
### getURL
**Signature:** `getURL() : String`
**Description:** Allows overriding the URL provided by the service configuration. It is usually better to call Service.setURL(String) within createRequest(Service, Object...) because that allows you to modify the existing URL based on call parameters.
**Returns:**
URL to use. The default behavior is to use the URL from the service configuration.
---
### initServiceClient
**Signature:** `initServiceClient(service : Service) : Object`
**Description:** Creates a protocol-specific client object. This does not normally need to be implemented, except in the case of SOAP services. Example declaration: initServiceClient: function( svc:SOAPService ) { }
**Parameters:**
- `service`: the Service object.
**Returns:**
Client object
**Throws:**
- Exception
---
### mockCall
**Signature:** `mockCall(service : Service, requestObj : Object) : Object`
**Description:** Override this method to mock the remote portion of the service call. Other callbacks like createRequest and parseResponse are still called.
**Parameters:**
- `service`: Service being executed.
- `requestObj`: Request object returned by createRequest(Service, Object...).
**Returns:**
Mock response, to be sent to parseResponse(Service, Object).
**Throws:**
- Exception
---
### mockFull
**Signature:** `mockFull(service : Service, args : Object...) : Object`
**Description:** Override this method to mock the entire service call, including the createRequest, execute, and parseResponse phases.
**Parameters:**
- `service`: Service being executed.
- `args`: Arguments from the Service call method.
**Returns:**
Object to return in the service call's Result.
**Throws:**
- Exception
---
### parseResponse
**Signature:** `parseResponse(service : Service, response : Object) : Object`
**Description:** Creates a response object from a successful service call. This response object will be the output object of the call method's Result.
**Parameters:**
- `service`: Service being executed.
- `response`: Service-specific response object. For example, the HTTPService service provides the underlying HTTPClient object that made the HTTP call.
**Returns:**
Object to return in the service call's Result.
---
```
--------------------------------------------------------------------------------
/docs/dw_order/BonusDiscountLineItem.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.order
# Class BonusDiscountLineItem
## Inheritance Hierarchy
- Object
- dw.object.PersistentObject
- dw.object.ExtensibleObject
- dw.order.BonusDiscountLineItem
## Description
Line item representing an applied BonusChoiceDiscount in a LineItemCtnr. This type of line item can only be created by the B2C Commerce promotions engine when applying a BonusChoiceDiscount. A BonusDiscountLineItem is basically a placeholder in the cart which entitles a customer to add one or more bonus products to his basket from a configured list of products. Merchants typically display this type of line item in the cart by showing the corresponding promotion callout message. They typically provide links to the bonus products that the customer can choose from. This line item can be removed from the cart but will be re-added each time the promotions engine re-applies discounts. Merchants may however add custom logic to show/hide this line item since it just a placeholder and not an actual product line item. The number of products that a customer is allowed to choose from is determined by getMaxBonusItems(). The collection of products the customer can choose from is determined by getBonusProducts(). When a customer chooses a bonus product in the storefront, it is necessary to use the AddBonusProductToBasket pipelet instead of the usual AddProductToBasket pipelet, in order to associate this BonusDiscountLineItem with the newly created bonus ProductLineItem. Alternatively, the API method LineItemCtnr.createBonusProductLineItem(BonusDiscountLineItem, Product, ProductOptionModel, Shipment) can be used instead. The system does proper validations in order to prevent incorrect or too many bonus products from being associated with this BonusDiscountLineItem. Once a customer has selected bonus products, the product line items representing the chosen bonus products can be retrieved with getBonusProductLineItems().
## Properties
### bonusChoiceRuleBased
**Type:** getBonusProducts() (Read Only)
Returns whether the promotion that triggered the creation of this line item uses a rule to determine the list of
bonus products.
If the promotion is rule based, then a ProductSearchModel should be used to return the bonus products the
customer may choose from, as the methods that return lists will return nothing. See getBonusProducts()
### bonusProductLineItems
**Type:** List (Read Only)
Get the product line items in the current LineItemCtnr representing the
bonus products that the customer has selected for this discount.
### bonusProducts
**Type:** List (Read Only)
Get the list of bonus products which the customer is allowed to choose
from for this discount. This list is configured by a merchant entering a
list of SKUs for the discount. Products which do not exist in the system,
or are offline, or are not assigned to a category in the site catalog are
filtered out. Unavailable (i.e. out-of-stock) products are NOT filtered
out. This allows merchants to display out-of-stock bonus products with
appropriate messaging.
If the promotion which triggered this discount does not exist, or this
promotion is rule based, then this method returns an empty list.
If the promotion is rule based, then this method will return an empty list.
A ProductSearchModel should be used to return the bonus products the
customer may choose from instead. See
ProductSearchModel.PROMOTION_PRODUCT_TYPE_BONUS and
ProductSearchModel.setPromotionID(String)
If a returned product is a master product, the customer is entitled to
choose from any variant. If the product is an option product, the
customer is entitled to choose any value for each option. Since the
promotions engine does not touch the value of the product option line
items, it is the responsibility of custom code to set option prices.
### couponLineItem
**Type:** CouponLineItem (Read Only)
Get the coupon line item associated with this discount.
### maxBonusItems
**Type:** Number (Read Only)
Get the maximum number of bonus items that the customer is permitted to
select for this bonus discount.
If the promotion which triggered this discount does not exist, then this
method returns 0.
### promotion
**Type:** Promotion (Read Only)
Get the promotion associated with this discount.
### promotionID
**Type:** String (Read Only)
Get the promotion ID associated with this discount.
## Constructor Summary
## Method Summary
### getBonusProductLineItems
**Signature:** `getBonusProductLineItems() : List`
Get the product line items in the current LineItemCtnr representing the bonus products that the customer has selected for this discount.
### getBonusProductPrice
**Signature:** `getBonusProductPrice(product : Product) : Money`
Get the effective price for the passed bonus product.
### getBonusProducts
**Signature:** `getBonusProducts() : List`
Get the list of bonus products which the customer is allowed to choose from for this discount.
### getCouponLineItem
**Signature:** `getCouponLineItem() : CouponLineItem`
Get the coupon line item associated with this discount.
### getMaxBonusItems
**Signature:** `getMaxBonusItems() : Number`
Get the maximum number of bonus items that the customer is permitted to select for this bonus discount.
### getPromotion
**Signature:** `getPromotion() : Promotion`
Get the promotion associated with this discount.
### getPromotionID
**Signature:** `getPromotionID() : String`
Get the promotion ID associated with this discount.
### isBonusChoiceRuleBased
**Signature:** `isBonusChoiceRuleBased() : boolean`
Returns whether the promotion that triggered the creation of this line item uses a rule to determine the list of bonus products.
## Method Detail
## Method Details
### getBonusProductLineItems
**Signature:** `getBonusProductLineItems() : List`
**Description:** Get the product line items in the current LineItemCtnr representing the bonus products that the customer has selected for this discount.
**Returns:**
The selected product line items, never null.
**See Also:**
LineItemCtnr.createBonusProductLineItem(BonusDiscountLineItem, Product, ProductOptionModel, Shipment)
---
### getBonusProductPrice
**Signature:** `getBonusProductPrice(product : Product) : Money`
**Description:** Get the effective price for the passed bonus product. This is expected to be one of the products returned by getBonusProducts() with one exception: If a master product is configured as a bonus product, this implies that a customer may choose from any of its variants. In this case, it is allowed to pass in a variant to this method and a price will be returned. If the passed product is not a valid bonus product, this method throws an exception.
**Parameters:**
- `product`: The bonus product to retrieve a price for, must not be null.
**Returns:**
The price of the passed bonus product as a Number.
---
### getBonusProducts
**Signature:** `getBonusProducts() : List`
**Description:** Get the list of bonus products which the customer is allowed to choose from for this discount. This list is configured by a merchant entering a list of SKUs for the discount. Products which do not exist in the system, or are offline, or are not assigned to a category in the site catalog are filtered out. Unavailable (i.e. out-of-stock) products are NOT filtered out. This allows merchants to display out-of-stock bonus products with appropriate messaging. If the promotion which triggered this discount does not exist, or this promotion is rule based, then this method returns an empty list. If the promotion is rule based, then this method will return an empty list. A ProductSearchModel should be used to return the bonus products the customer may choose from instead. See ProductSearchModel.PROMOTION_PRODUCT_TYPE_BONUS and ProductSearchModel.setPromotionID(String) If a returned product is a master product, the customer is entitled to choose from any variant. If the product is an option product, the customer is entitled to choose any value for each option. Since the promotions engine does not touch the value of the product option line items, it is the responsibility of custom code to set option prices.
**Returns:**
An ordered list of bonus products that the customer may choose from for this discount.
---
### getCouponLineItem
**Signature:** `getCouponLineItem() : CouponLineItem`
**Description:** Get the coupon line item associated with this discount.
**Returns:**
The coupon line item associated with this discount, or null if it no longer exists or there is no one.
---
### getMaxBonusItems
**Signature:** `getMaxBonusItems() : Number`
**Description:** Get the maximum number of bonus items that the customer is permitted to select for this bonus discount. If the promotion which triggered this discount does not exist, then this method returns 0.
**Returns:**
The maximum number of bonus items that the customer is permitted to select for this bonus discount, or 0 if the promotion no longer exists.
---
### getPromotion
**Signature:** `getPromotion() : Promotion`
**Description:** Get the promotion associated with this discount.
**Returns:**
The promotion associated with this discount, or null if it no longer exists.
---
### getPromotionID
**Signature:** `getPromotionID() : String`
**Description:** Get the promotion ID associated with this discount.
**Returns:**
The promotion ID associated with this discount, never null.
---
### isBonusChoiceRuleBased
**Signature:** `isBonusChoiceRuleBased() : boolean`
**Description:** Returns whether the promotion that triggered the creation of this line item uses a rule to determine the list of bonus products. If the promotion is rule based, then a ProductSearchModel should be used to return the bonus products the customer may choose from, as the methods that return lists will return nothing. See getBonusProducts()
**Returns:**
If the promotion no longer exists, then null, otherwise, true if the promotion that triggered the creation of this line item uses a rule to determine the bonus products to choose from.
---
```
--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/mock-data/ocapi/system-object-definitions-old.json:
--------------------------------------------------------------------------------
```json
{
"_v": "24.4",
"_type": "system_object_definitions",
"count": 25,
"data": [
{
"_type": "system_object_definition",
"object_type": "Product",
"displayName": {
"default": "Product"
},
"description": {
"default": "Product"
},
"content_object": false,
"queryable": true,
"read_only": false,
"attribute_group_count": 35,
"attribute_definition_count": 113,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Product"
}
}
},
{
"_type": "system_object_definition",
"object_type": "Category",
"displayName": {
"default": "Category"
},
"description": {
"default": "Category"
},
"content_object": false,
"queryable": true,
"read_only": false,
"attribute_group_count": 12,
"attribute_definition_count": 45,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Category"
}
}
},
{
"_type": "system_object_definition",
"object_type": "CustomerAddress",
"displayName": {
"default": "Customer Address"
},
"description": {
"default": "Customer Address"
},
"content_object": false,
"queryable": true,
"read_only": false,
"attribute_group_count": 8,
"attribute_definition_count": 28,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/CustomerAddress"
}
}
},
{
"_type": "system_object_definition",
"object_type": "Customer",
"displayName": {
"default": "Customer"
},
"description": {
"default": "Customer"
},
"content_object": false,
"queryable": true,
"read_only": false,
"attribute_group_count": 15,
"attribute_definition_count": 67,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Customer"
}
}
},
{
"_type": "system_object_definition",
"object_type": "Order",
"displayName": {
"default": "Order"
},
"description": {
"default": "Order"
},
"content_object": false,
"queryable": true,
"read_only": false,
"attribute_group_count": 18,
"attribute_definition_count": 89,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Order"
}
}
},
{
"_type": "system_object_definition",
"object_type": "SitePreferences",
"displayName": {
"default": "Site Preferences"
},
"description": {
"default": "Site Preferences"
},
"content_object": false,
"queryable": false,
"read_only": false,
"attribute_group_count": 8,
"attribute_definition_count": 156,
"_links": {
"self": {
"href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/SitePreferences"
}
}
}
"key_attribute_id": "order_no",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Category",
"display_name": {
"default": "Category"
},
"description": {
"default": "Salesforce B2C Commerce category object definition"
},
"attribute_definition_count": 20,
"attribute_group_count": 4,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Site",
"display_name": {
"default": "Site"
},
"description": {
"default": "Salesforce B2C Commerce site object definition"
},
"attribute_definition_count": 15,
"attribute_group_count": 3,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Campaign",
"display_name": {
"default": "Campaign"
},
"description": {
"default": "Salesforce B2C Commerce campaign object definition"
},
"attribute_definition_count": 12,
"attribute_group_count": 3,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Coupon",
"display_name": {
"default": "Coupon"
},
"description": {
"default": "Salesforce B2C Commerce coupon object definition"
},
"attribute_definition_count": 18,
"attribute_group_count": 4,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Promotion",
"display_name": {
"default": "Promotion"
},
"description": {
"default": "Salesforce B2C Commerce promotion object definition"
},
"attribute_definition_count": 25,
"attribute_group_count": 5,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "PriceBook",
"display_name": {
"default": "Price Book"
},
"description": {
"default": "Salesforce B2C Commerce price book object definition"
},
"attribute_definition_count": 10,
"attribute_group_count": 2,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "CustomObject",
"display_name": {
"default": "Custom Object"
},
"description": {
"default": "Custom business object definition"
},
"attribute_definition_count": 8,
"attribute_group_count": 2,
"key_attribute_id": "key_property",
"content_object": true,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "Content",
"display_name": {
"default": "Content"
},
"description": {
"default": "Salesforce B2C Commerce content object definition"
},
"attribute_definition_count": 22,
"attribute_group_count": 4,
"key_attribute_id": "id",
"content_object": true,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "ContentFolder",
"display_name": {
"default": "Content Folder"
},
"description": {
"default": "Salesforce B2C Commerce content folder object definition"
},
"attribute_definition_count": 12,
"attribute_group_count": 3,
"key_attribute_id": "id",
"content_object": true,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "CustomerGroup",
"display_name": {
"default": "Customer Group"
},
"description": {
"default": "Salesforce B2C Commerce customer group object definition"
},
"attribute_definition_count": 8,
"attribute_group_count": 2,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "SourceCodeInfo",
"display_name": {
"default": "Source Code Info"
},
"description": {
"default": "Salesforce B2C Commerce source code information object definition"
},
"attribute_definition_count": 6,
"attribute_group_count": 1,
"key_attribute_id": "id",
"content_object": false,
"queryable": true,
"read_only": true,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
},
{
"_type": "object_type_definition",
"object_type": "GiftCertificate",
"display_name": {
"default": "Gift Certificate"
},
"description": {
"default": "Salesforce B2C Commerce gift certificate object definition"
},
"attribute_definition_count": 14,
"attribute_group_count": 3,
"key_attribute_id": "gift_certificate_code",
"content_object": false,
"queryable": true,
"read_only": false,
"creation_date": "2021-01-01T00:00:00.000Z",
"last_modified": "2024-01-01T00:00:00.000Z"
}
],
"next": null,
"previous": null,
"start": 0,
"total": 25
}
```
--------------------------------------------------------------------------------
/docs/dw_catalog/ProductSearchRefinements.md:
--------------------------------------------------------------------------------
```markdown
## Package: dw.catalog
# Class ProductSearchRefinements
## Inheritance Hierarchy
- Object
- dw.catalog.SearchRefinements
- dw.catalog.ProductSearchRefinements
## Description
This class provides an interface to refinement options for the product search. In a typical usage, the client application UI displays the search refinements along with the search results and allows customers to "refine" the results (i.e. limit the results that are shown) by specifying additional product criteria, or "relax" (i.e. broaden) the results after previously refining. The four types of product search refinements are: Refine By Category: Limit the products to those assigned to specific child/ancestor categories of the search category. Refine By Attribute: Limit the products to those with specific values for a given attribute. Values may be grouped into "buckets" so that a given set of values are represented as a single refinement option. Refine By Price: Limit the products to those whose prices fall in a specific range. Refine By Promotion: Limit the products to those which are related to a specific promotion. Rendering a product search refinement UI typically begins with iterating the refinement definitions for the search result. Call SearchRefinements.getRefinementDefinitions() or SearchRefinements.getAllRefinementDefinitions() to retrieve the appropriate collection of refinement definitions. For each definition, display the available refinement values by calling getAllRefinementValues(ProductSearchRefinementDefinition). Depending on the type of the refinement definition, the application must use slightly different logic to display the refinement widgets. For all 4 types, methods in ProductSearchModel are used to generate URLs to render hyperlinks in the UI. When clicked, these links trigger a call to the Search pipelet which in turn applies the appropriate filters to the native search result.
## Properties
### categoryRefinementDefinition
**Type:** ProductSearchRefinementDefinition (Read Only)
The appropriate category refinement definition based on the search
result. The category refinement definition returned will be the first that
can be found traversing the category tree upward starting at the deepest
common category of the search result.
### priceRefinementDefinition
**Type:** ProductSearchRefinementDefinition (Read Only)
The appropriate price refinement definition based on the search
result. The price refinement definition returned will be the first that
can be found traversing the category tree upward starting at the deepest
common category of the search result.
### promotionRefinementDefinition
**Type:** ProductSearchRefinementDefinition (Read Only)
The appropriate promotion refinement definition based on the search
result. The promotion refinement definition returned will be the first that
can be found traversing the category tree upward starting at the deepest
common category of the search result.
## Constructor Summary
## Method Summary
### getAllRefinementValues
**Signature:** `getAllRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
Returns a sorted collection of refinement values for the passed refinement definition.
### getCategoryRefinementDefinition
**Signature:** `getCategoryRefinementDefinition() : ProductSearchRefinementDefinition`
Returns the appropriate category refinement definition based on the search result.
### getNextLevelCategoryRefinementValues
**Signature:** `getNextLevelCategoryRefinementValues(category : Category) : Collection`
Returns category refinement values based on the current search result filtered such that only category refinements representing children of the given category are present.
### getPriceRefinementDefinition
**Signature:** `getPriceRefinementDefinition() : ProductSearchRefinementDefinition`
Returns the appropriate price refinement definition based on the search result.
### getPromotionRefinementDefinition
**Signature:** `getPromotionRefinementDefinition() : ProductSearchRefinementDefinition`
Returns the appropriate promotion refinement definition based on the search result.
### getRefinementValue
**Signature:** `getRefinementValue(definition : ProductSearchRefinementDefinition, value : String) : ProductSearchRefinementValue`
Returns the refinement value (incl.
### getRefinementValue
**Signature:** `getRefinementValue(name : String, value : String) : ProductSearchRefinementValue`
Returns the refinement value (incl.
### getRefinementValues
**Signature:** `getRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
Returns a collection of refinement values for the given refinement definition.
## Method Detail
## Method Details
### getAllRefinementValues
**Signature:** `getAllRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
**Description:** Returns a sorted collection of refinement values for the passed refinement definition. The returned collection includes all refinement values for which the hit count is greater than 0 within the search result when the passed refinement definition is excluded from filtering the search hits but all other refinement filters are still applied. This method is useful for rendering broadening options for definitions that the search is currently refined by. If the search is not currently restricted by the passed refinement definition, then this method will return the same result as getRefinementValues(ProductSearchRefinementDefinition). For attribute-based refinement definitions, the returned collection depends upon how the "value set" property is configured. (Category and price refinement definitions do not have such a property.) If this property is set to "search result values", the behavior is as described above. If this property is set to "all values of category", then the returned collection will also include all refinement values for products in the category subtree rooted at the search definition's category. This setting is useful for refinements whose visualization is supposed to remain constant for a certain subtree of a catalog (e.g. color pickers or size charts). These additional values are independent of the search result and do not contribute towards the value hit counts. If the search result is further refined by one of these values, it is possible to get an empty search result. Except for this one case this method does NOT return refinement values independent of the search result.
**Parameters:**
- `definition`: The refinement definition to return refinement values for. Must not be null.
**Returns:**
The collection of ProductSearchRefinementValue instances, sorted according to the settings of the refinement definition.
---
### getCategoryRefinementDefinition
**Signature:** `getCategoryRefinementDefinition() : ProductSearchRefinementDefinition`
**Description:** Returns the appropriate category refinement definition based on the search result. The category refinement definition returned will be the first that can be found traversing the category tree upward starting at the deepest common category of the search result.
**Returns:**
The category refinement definition or null if none can be found.
---
### getNextLevelCategoryRefinementValues
**Signature:** `getNextLevelCategoryRefinementValues(category : Category) : Collection`
**Description:** Returns category refinement values based on the current search result filtered such that only category refinements representing children of the given category are present. If no category is given, the method uses the catalog's root category. The refinement value product counts represent all hits contained in the catalog tree starting at the corresponding child category.
**Parameters:**
- `category`: The category to return child category refinement values for.
**Returns:**
The refinement values for all child categories of the given category.
---
### getPriceRefinementDefinition
**Signature:** `getPriceRefinementDefinition() : ProductSearchRefinementDefinition`
**Description:** Returns the appropriate price refinement definition based on the search result. The price refinement definition returned will be the first that can be found traversing the category tree upward starting at the deepest common category of the search result.
**Returns:**
The price refinement definition or null if none can be found.
---
### getPromotionRefinementDefinition
**Signature:** `getPromotionRefinementDefinition() : ProductSearchRefinementDefinition`
**Description:** Returns the appropriate promotion refinement definition based on the search result. The promotion refinement definition returned will be the first that can be found traversing the category tree upward starting at the deepest common category of the search result.
**Returns:**
The promotion refinement definition or null if none can be found.
---
### getRefinementValue
**Signature:** `getRefinementValue(definition : ProductSearchRefinementDefinition, value : String) : ProductSearchRefinementValue`
**Description:** Returns the refinement value (incl. product hit count) for the given refinement definition and the given (selected) value.
**Parameters:**
- `definition`: The definition to return the refinement for.
- `value`: The value to return the refinement for.
**Returns:**
The refinement value.
---
### getRefinementValue
**Signature:** `getRefinementValue(name : String, value : String) : ProductSearchRefinementValue`
**Description:** Returns the refinement value (incl. product hit count) for the given refinement attribute and the given (selected) value.
**Parameters:**
- `name`: The name of the refinement attribute.
- `value`: The value to return the refinement for.
**Returns:**
The refinement value.
---
### getRefinementValues
**Signature:** `getRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
**Description:** Returns a collection of refinement values for the given refinement definition. The returned refinement values only include those that are part of the actual search result (i.e. hit count will always be > 0).
**Parameters:**
- `definition`: The refinement definition to return refinement values for.
**Returns:**
The collection of refinement values sorted according to the settings of the definition.
---
```
--------------------------------------------------------------------------------
/tests/code-version-handler.test.ts:
--------------------------------------------------------------------------------
```typescript
import { CodeVersionToolHandler } from '../src/core/handlers/code-version-handler.js';
import { HandlerContext } from '../src/core/handlers/base-handler.js';
import { Logger } from '../src/utils/logger.js';
// Mock the OCAPICodeVersionsClient
const mockCodeVersionsClient = {
getCodeVersions: jest.fn(),
activateCodeVersion: jest.fn(),
};
jest.mock('../src/clients/ocapi/code-versions-client.js', () => ({
OCAPICodeVersionsClient: jest.fn(() => mockCodeVersionsClient),
}));
describe('CodeVersionToolHandler', () => {
let mockLogger: jest.Mocked<Logger>;
let mockClient: typeof mockCodeVersionsClient;
let context: HandlerContext;
let handler: CodeVersionToolHandler;
beforeEach(() => {
mockLogger = {
debug: jest.fn(),
log: jest.fn(),
error: jest.fn(),
timing: jest.fn(),
methodEntry: jest.fn(),
methodExit: jest.fn(),
} as any;
// Reset mocks
jest.clearAllMocks();
// Use the mock client directly and reset it
mockClient = mockCodeVersionsClient;
mockClient.getCodeVersions.mockReset();
mockClient.activateCodeVersion.mockReset();
jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
context = {
logger: mockLogger,
config: {
hostname: 'test.commercecloud.salesforce.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
siteId: 'test-site',
},
capabilities: { canAccessLogs: false, canAccessOCAPI: true },
};
handler = new CodeVersionToolHandler(context, 'CodeVersion');
});
afterEach(() => {
jest.restoreAllMocks();
});
// Helper function to initialize handler for tests that need it
const initializeHandler = async () => {
await (handler as any).initialize();
};
describe('canHandle', () => {
it('should handle code version tools', () => {
expect(handler.canHandle('get_code_versions')).toBe(true);
expect(handler.canHandle('activate_code_version')).toBe(true);
});
it('should not handle non-code-version tools', () => {
expect(handler.canHandle('get_latest_error')).toBe(false);
expect(handler.canHandle('unknown_tool')).toBe(false);
});
});
describe('initialization', () => {
it('should initialize code versions client when OCAPI access is available', async () => {
await initializeHandler();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).toHaveBeenCalledWith({
hostname: 'test.commercecloud.salesforce.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
version: 'v23_2',
});
expect(mockLogger.debug).toHaveBeenCalledWith('Code versions client initialized');
});
it('should not initialize client when OCAPI access is not available', async () => {
context.capabilities = { canAccessLogs: false, canAccessOCAPI: false };
const handlerWithoutOCAPI = new CodeVersionToolHandler(context, 'CodeVersion');
await (handlerWithoutOCAPI as any).initialize();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).not.toHaveBeenCalled();
});
it('should not initialize client when config is missing', async () => {
context.config = null as any;
const handlerWithoutConfig = new CodeVersionToolHandler(context, 'CodeVersion');
await (handlerWithoutConfig as any).initialize();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).not.toHaveBeenCalled();
});
});
describe('disposal', () => {
it('should dispose code versions client properly', async () => {
await initializeHandler();
await (handler as any).dispose();
expect(mockLogger.debug).toHaveBeenCalledWith('Code versions client disposed');
});
});
describe('get_code_versions tool', () => {
beforeEach(async () => {
await initializeHandler();
mockClient.getCodeVersions.mockResolvedValue([
{ id: 'version_1', active: true, lastModified: '2024-01-01T00:00:00Z' },
{ id: 'version_2', active: false, lastModified: '2024-01-02T00:00:00Z' },
]);
});
it('should handle get_code_versions', async () => {
const result = await handler.handle('get_code_versions', {}, Date.now());
expect(mockClient.getCodeVersions).toHaveBeenCalled();
expect(result.content[0].text).toContain('version_1');
expect(result.content[0].text).toContain('version_2');
});
});
describe('activate_code_version tool', () => {
beforeEach(async () => {
await initializeHandler();
mockClient.activateCodeVersion.mockResolvedValue({
success: true,
message: 'Code version activated successfully',
codeVersionId: 'version_2',
});
});
it('should handle activate_code_version with codeVersionId', async () => {
const args = { codeVersionId: 'version_2' };
const result = await handler.handle('activate_code_version', args, Date.now());
expect(mockClient.activateCodeVersion).toHaveBeenCalledWith('version_2');
expect(result.content[0].text).toContain('activated successfully');
});
it('should throw error when codeVersionId is missing', async () => {
const result = await handler.handle('activate_code_version', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('codeVersionId must be a non-empty string');
});
it('should throw error when codeVersionId is empty', async () => {
const result = await handler.handle('activate_code_version', { codeVersionId: '' }, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('codeVersionId must be a non-empty string');
});
});
describe('error handling', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should handle client errors gracefully', async () => {
mockClient.getCodeVersions.mockRejectedValue(new Error('OCAPI connection failed'));
const result = await handler.handle('get_code_versions', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('OCAPI connection failed');
});
it('should throw error for unsupported tools', async () => {
await expect(handler.handle('unsupported_tool', {}, Date.now()))
.rejects.toThrow('Unsupported tool');
});
it('should handle OCAPI client not configured error', async () => {
// Create handler without proper configuration
const contextWithoutOCAPI = {
...context,
capabilities: { canAccessLogs: false, canAccessOCAPI: false },
};
const handlerWithoutOCAPI = new CodeVersionToolHandler(contextWithoutOCAPI, 'CodeVersion');
const result = await handlerWithoutOCAPI.handle('get_code_versions', {}, Date.now());
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('OCAPI client not configured');
});
});
describe('timing and logging', () => {
beforeEach(async () => {
await initializeHandler();
mockClient.getCodeVersions.mockResolvedValue([]);
});
it('should log timing information', async () => {
const startTime = Date.now();
await handler.handle('get_code_versions', {}, startTime);
expect(mockLogger.timing).toHaveBeenCalledWith('get_code_versions', startTime);
});
it('should log execution details', async () => {
await handler.handle('get_code_versions', {}, Date.now());
expect(mockLogger.debug).toHaveBeenCalledWith(
'get_code_versions completed successfully',
expect.any(Object),
);
});
});
describe('client integration', () => {
beforeEach(async () => {
await initializeHandler();
});
it('should call getCodeVersions correctly', async () => {
mockClient.getCodeVersions.mockResolvedValue([
{ id: 'test_version', active: false },
]);
await handler.handle('get_code_versions', {}, Date.now());
expect(mockClient.getCodeVersions).toHaveBeenCalledWith();
});
it('should call activateCodeVersion correctly', async () => {
mockClient.activateCodeVersion.mockResolvedValue({
success: true,
codeVersionId: 'test_version',
});
await handler.handle('activate_code_version', { codeVersionId: 'test_version' }, Date.now());
expect(mockClient.activateCodeVersion).toHaveBeenCalledWith('test_version');
});
});
describe('configuration variations', () => {
it('should not initialize without hostname', async () => {
const contextWithoutHostname = {
...context,
config: { ...context.config, hostname: undefined },
};
const handlerWithoutHostname = new CodeVersionToolHandler(contextWithoutHostname, 'CodeVersion');
await (handlerWithoutHostname as any).initialize();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).not.toHaveBeenCalled();
});
it('should not initialize without clientId', async () => {
const contextWithoutClientId = {
...context,
config: { ...context.config, clientId: undefined },
};
const handlerWithoutClientId = new CodeVersionToolHandler(contextWithoutClientId, 'CodeVersion');
await (handlerWithoutClientId as any).initialize();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).not.toHaveBeenCalled();
});
it('should not initialize without clientSecret', async () => {
const contextWithoutClientSecret = {
...context,
config: { ...context.config, clientSecret: undefined },
};
const handlerWithoutClientSecret = new CodeVersionToolHandler(contextWithoutClientSecret, 'CodeVersion');
await (handlerWithoutClientSecret as any).initialize();
const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
expect(MockedConstructor).not.toHaveBeenCalled();
});
});
});
```