#
tokens: 49191/50000 13/825 files (page 19/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 19 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&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_content/Folder.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.content
  2 | 
  3 | # Class Folder
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.content.Folder
 11 | 
 12 | ## Description
 13 | 
 14 | Class representing a folder for organizing content assets in Commerce Cloud Digital.
 15 | 
 16 | ## Properties
 17 | 
 18 | ### content
 19 | 
 20 | **Type:** Collection (Read Only)
 21 | 
 22 | The content objects for this folder, sorted by position.
 23 | 
 24 | ### description
 25 | 
 26 | **Type:** String (Read Only)
 27 | 
 28 | The description for the folder as known in the current
 29 |  locale or null if it cannot be found.
 30 | 
 31 | ### displayName
 32 | 
 33 | **Type:** String (Read Only)
 34 | 
 35 | The display name for the folder as known in the current
 36 |  locale or null if it cannot be found.
 37 | 
 38 | ### ID
 39 | 
 40 | **Type:** String (Read Only)
 41 | 
 42 | The ID of the folder. The ID can be used to uniquely
 43 |  identify a folder within any given library. This folder ID provides
 44 |  an alternative lookup mechanism for folders frequently used in
 45 |  the storefront.
 46 | 
 47 | ### online
 48 | 
 49 | **Type:** boolean (Read Only)
 50 | 
 51 | Indicates if the folder is set online or
 52 |  offline. Initially, all folders are set online.
 53 | 
 54 | ### onlineContent
 55 | 
 56 | **Type:** Collection (Read Only)
 57 | 
 58 | The online content objects for this folder, sorted by position.
 59 | 
 60 | ### onlineSubFolders
 61 | 
 62 | **Type:** Collection (Read Only)
 63 | 
 64 | The online subfolders of this folder, sorted by position.
 65 | 
 66 | ### pageDescription
 67 | 
 68 | **Type:** String (Read Only)
 69 | 
 70 | The page description for this folder using the value in
 71 |  the current locale, or returns null if no value was found.
 72 | 
 73 | ### pageKeywords
 74 | 
 75 | **Type:** String (Read Only)
 76 | 
 77 | The page keywords for this folder using the value in
 78 |  the current locale, or returns null if no value was found.
 79 | 
 80 | ### pageTitle
 81 | 
 82 | **Type:** String (Read Only)
 83 | 
 84 | The page title for this folder using the value in
 85 |  the current locale, or returns null if no value was found.
 86 | 
 87 | ### pageURL
 88 | 
 89 | **Type:** String (Read Only)
 90 | 
 91 | The page URL for this folder using the value in
 92 |  the current locale, or returns null if no value was found.
 93 | 
 94 | ### parent
 95 | 
 96 | **Type:** Folder (Read Only)
 97 | 
 98 | The parent folder of this folder.
 99 | 
100 | ### root
101 | 
102 | **Type:** boolean (Read Only)
103 | 
104 | Indicates if this is the root folder.
105 | 
106 | ### siteMapChangeFrequency
107 | 
108 | **Type:** String (Read Only)
109 | 
110 | The folder's sitemap change frequency.
111 | 
112 | ### siteMapIncluded
113 | 
114 | **Type:** Number (Read Only)
115 | 
116 | The folder's sitemap inclusion.
117 | 
118 | ### siteMapPriority
119 | 
120 | **Type:** Number (Read Only)
121 | 
122 | The folder's sitemap priority.
123 | 
124 | ### subFolders
125 | 
126 | **Type:** Collection (Read Only)
127 | 
128 | The subfolders of this folder, sorted by position.
129 | 
130 | ### template
131 | 
132 | **Type:** String (Read Only)
133 | 
134 | The name of the template used to render the folder
135 |  in the store front.
136 | 
137 | ## Constructor Summary
138 | 
139 | ## Method Summary
140 | 
141 | ### getContent
142 | 
143 | **Signature:** `getContent() : Collection`
144 | 
145 | Returns the content objects for this folder, sorted by position.
146 | 
147 | ### getDescription
148 | 
149 | **Signature:** `getDescription() : String`
150 | 
151 | Returns the description for the folder as known in the current locale or null if it cannot be found.
152 | 
153 | ### getDisplayName
154 | 
155 | **Signature:** `getDisplayName() : String`
156 | 
157 | Returns the display name for the folder as known in the current locale or null if it cannot be found.
158 | 
159 | ### getID
160 | 
161 | **Signature:** `getID() : String`
162 | 
163 | Returns the ID of the folder.
164 | 
165 | ### getOnlineContent
166 | 
167 | **Signature:** `getOnlineContent() : Collection`
168 | 
169 | Returns the online content objects for this folder, sorted by position.
170 | 
171 | ### getOnlineSubFolders
172 | 
173 | **Signature:** `getOnlineSubFolders() : Collection`
174 | 
175 | Returns the online subfolders of this folder, sorted by position.
176 | 
177 | ### getPageDescription
178 | 
179 | **Signature:** `getPageDescription() : String`
180 | 
181 | Returns the page description for this folder using the value in the current locale, or returns null if no value was found.
182 | 
183 | ### getPageKeywords
184 | 
185 | **Signature:** `getPageKeywords() : String`
186 | 
187 | Returns the page keywords for this folder using the value in the current locale, or returns null if no value was found.
188 | 
189 | ### getPageTitle
190 | 
191 | **Signature:** `getPageTitle() : String`
192 | 
193 | Returns the page title for this folder using the value in the current locale, or returns null if no value was found.
194 | 
195 | ### getPageURL
196 | 
197 | **Signature:** `getPageURL() : String`
198 | 
199 | Returns the page URL for this folder using the value in the current locale, or returns null if no value was found.
200 | 
201 | ### getParent
202 | 
203 | **Signature:** `getParent() : Folder`
204 | 
205 | Returns the parent folder of this folder.
206 | 
207 | ### getSiteMapChangeFrequency
208 | 
209 | **Signature:** `getSiteMapChangeFrequency() : String`
210 | 
211 | Returns the folder's sitemap change frequency.
212 | 
213 | ### getSiteMapIncluded
214 | 
215 | **Signature:** `getSiteMapIncluded() : Number`
216 | 
217 | Returns the folder's sitemap inclusion.
218 | 
219 | ### getSiteMapPriority
220 | 
221 | **Signature:** `getSiteMapPriority() : Number`
222 | 
223 | Returns the folder's sitemap priority.
224 | 
225 | ### getSubFolders
226 | 
227 | **Signature:** `getSubFolders() : Collection`
228 | 
229 | Returns the subfolders of this folder, sorted by position.
230 | 
231 | ### getTemplate
232 | 
233 | **Signature:** `getTemplate() : String`
234 | 
235 | Returns the name of the template used to render the folder in the store front.
236 | 
237 | ### isOnline
238 | 
239 | **Signature:** `isOnline() : boolean`
240 | 
241 | Indicates if the folder is set online or offline.
242 | 
243 | ### isRoot
244 | 
245 | **Signature:** `isRoot() : boolean`
246 | 
247 | Indicates if this is the root folder.
248 | 
249 | ## Method Detail
250 | 
251 | ## Method Details
252 | 
253 | ### getContent
254 | 
255 | **Signature:** `getContent() : Collection`
256 | 
257 | **Description:** Returns the content objects for this folder, sorted by position.
258 | 
259 | **Returns:**
260 | 
261 | the content objects for this folder, sorted by position.
262 | 
263 | ---
264 | 
265 | ### getDescription
266 | 
267 | **Signature:** `getDescription() : String`
268 | 
269 | **Description:** Returns the description for the folder as known in the current locale or null if it cannot be found.
270 | 
271 | **Returns:**
272 | 
273 | the description for the folder as known in the current locale or null if it cannot be found.
274 | 
275 | ---
276 | 
277 | ### getDisplayName
278 | 
279 | **Signature:** `getDisplayName() : String`
280 | 
281 | **Description:** Returns the display name for the folder as known in the current locale or null if it cannot be found.
282 | 
283 | **Returns:**
284 | 
285 | the display name for the folder as known in the current locale or null if it cannot be found.
286 | 
287 | ---
288 | 
289 | ### getID
290 | 
291 | **Signature:** `getID() : String`
292 | 
293 | **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.
294 | 
295 | **Returns:**
296 | 
297 | the ID of the folder.
298 | 
299 | ---
300 | 
301 | ### getOnlineContent
302 | 
303 | **Signature:** `getOnlineContent() : Collection`
304 | 
305 | **Description:** Returns the online content objects for this folder, sorted by position.
306 | 
307 | **Returns:**
308 | 
309 | the online content objects for this folder, sorted by position.
310 | 
311 | ---
312 | 
313 | ### getOnlineSubFolders
314 | 
315 | **Signature:** `getOnlineSubFolders() : Collection`
316 | 
317 | **Description:** Returns the online subfolders of this folder, sorted by position.
318 | 
319 | **Returns:**
320 | 
321 | the online subfolders of this folder, sorted by position.
322 | 
323 | ---
324 | 
325 | ### getPageDescription
326 | 
327 | **Signature:** `getPageDescription() : String`
328 | 
329 | **Description:** Returns the page description for this folder using the value in the current locale, or returns null if no value was found.
330 | 
331 | **Returns:**
332 | 
333 | the page description for this folder using the value in the current locale, or returns null if no value was found.
334 | 
335 | ---
336 | 
337 | ### getPageKeywords
338 | 
339 | **Signature:** `getPageKeywords() : String`
340 | 
341 | **Description:** Returns the page keywords for this folder using the value in the current locale, or returns null if no value was found.
342 | 
343 | **Returns:**
344 | 
345 | the page keywords for this folder using the value in the current locale, or returns null if no value was found.
346 | 
347 | ---
348 | 
349 | ### getPageTitle
350 | 
351 | **Signature:** `getPageTitle() : String`
352 | 
353 | **Description:** Returns the page title for this folder using the value in the current locale, or returns null if no value was found.
354 | 
355 | **Returns:**
356 | 
357 | the page title for this folder using the value in the current locale, or returns null if no value was found.
358 | 
359 | ---
360 | 
361 | ### getPageURL
362 | 
363 | **Signature:** `getPageURL() : String`
364 | 
365 | **Description:** Returns the page URL for this folder using the value in the current locale, or returns null if no value was found.
366 | 
367 | **Returns:**
368 | 
369 | the page URL for this folder using the value in the current locale, or returns null if no value was found.
370 | 
371 | ---
372 | 
373 | ### getParent
374 | 
375 | **Signature:** `getParent() : Folder`
376 | 
377 | **Description:** Returns the parent folder of this folder.
378 | 
379 | **Returns:**
380 | 
381 | the parent folder of this folder.
382 | 
383 | ---
384 | 
385 | ### getSiteMapChangeFrequency
386 | 
387 | **Signature:** `getSiteMapChangeFrequency() : String`
388 | 
389 | **Description:** Returns the folder's sitemap change frequency.
390 | 
391 | **Returns:**
392 | 
393 | the value of the attribute 'siteMapChangeFrequency'.
394 | 
395 | ---
396 | 
397 | ### getSiteMapIncluded
398 | 
399 | **Signature:** `getSiteMapIncluded() : Number`
400 | 
401 | **Description:** Returns the folder's sitemap inclusion.
402 | 
403 | **Returns:**
404 | 
405 | the value of the attribute 'siteMapIncluded'.
406 | 
407 | ---
408 | 
409 | ### getSiteMapPriority
410 | 
411 | **Signature:** `getSiteMapPriority() : Number`
412 | 
413 | **Description:** Returns the folder's sitemap priority.
414 | 
415 | **Returns:**
416 | 
417 | the value of the attribute 'siteMapPriority'.
418 | 
419 | ---
420 | 
421 | ### getSubFolders
422 | 
423 | **Signature:** `getSubFolders() : Collection`
424 | 
425 | **Description:** Returns the subfolders of this folder, sorted by position.
426 | 
427 | **Returns:**
428 | 
429 | the subfolders of this folder, sorted by position.
430 | 
431 | ---
432 | 
433 | ### getTemplate
434 | 
435 | **Signature:** `getTemplate() : String`
436 | 
437 | **Description:** Returns the name of the template used to render the folder in the store front.
438 | 
439 | **Returns:**
440 | 
441 | the name of the template used to render the folder.
442 | 
443 | ---
444 | 
445 | ### isOnline
446 | 
447 | **Signature:** `isOnline() : boolean`
448 | 
449 | **Description:** Indicates if the folder is set online or offline. Initially, all folders are set online.
450 | 
451 | **Returns:**
452 | 
453 | true if the folder is online, false otherwise.
454 | 
455 | ---
456 | 
457 | ### isRoot
458 | 
459 | **Signature:** `isRoot() : boolean`
460 | 
461 | **Description:** Indicates if this is the root folder.
462 | 
463 | **Returns:**
464 | 
465 | true if this is the root folder, false otherwise.
466 | 
467 | ---
```

--------------------------------------------------------------------------------
/src/clients/docs/class-content-parser.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Class Content Parser
  3 |  *
  4 |  * Responsible for parsing markdown documentation content and extracting
  5 |  * structured class information including constants, properties, methods, and inheritance.
  6 |  *
  7 |  * Single Responsibility: Converting markdown content to structured data
  8 |  */
  9 | 
 10 | import { Logger } from '../../utils/logger.js';
 11 | 
 12 | export interface SFCCMethod {
 13 |   name: string;
 14 |   signature: string;
 15 |   description: string;
 16 |   parameters?: string[];
 17 |   returnType?: string;
 18 |   deprecated?: boolean;
 19 |   deprecationMessage?: string;
 20 | }
 21 | 
 22 | export interface SFCCProperty {
 23 |   name: string;
 24 |   type: string;
 25 |   description: string;
 26 |   modifiers?: string[];
 27 |   deprecated?: boolean;
 28 |   deprecationMessage?: string;
 29 | }
 30 | 
 31 | export interface SFCCConstant {
 32 |   name: string;
 33 |   type: string;
 34 |   value?: string;
 35 |   description: string;
 36 |   deprecated?: boolean;
 37 |   deprecationMessage?: string;
 38 | }
 39 | 
 40 | export interface SFCCClassDetails {
 41 |   className: string;
 42 |   packageName: string;
 43 |   description: string;
 44 |   constants: SFCCConstant[];
 45 |   properties: SFCCProperty[];
 46 |   methods: SFCCMethod[];
 47 |   inheritance?: string[];
 48 |   constructorInfo?: string;
 49 | }
 50 | 
 51 | export class ClassContentParser {
 52 |   private logger: Logger;
 53 | 
 54 |   constructor() {
 55 |     this.logger = Logger.getChildLogger('ClassContentParser');
 56 |   }
 57 | 
 58 |   /**
 59 |    * Parse markdown content and extract structured class information
 60 |    */
 61 |   parseClassContent(content: string): SFCCClassDetails {
 62 |     const lines = content.split('\n');
 63 | 
 64 |     let currentSection = '';
 65 |     let className = '';
 66 |     let packageName = '';
 67 |     let description = '';
 68 |     const constants: SFCCConstant[] = [];
 69 |     const properties: SFCCProperty[] = [];
 70 |     const methods: SFCCMethod[] = [];
 71 |     const inheritance: string[] = [];
 72 |     let constructorInfo = '';
 73 | 
 74 |     for (let i = 0; i < lines.length; i++) {
 75 |       const line = lines[i].trim();
 76 | 
 77 |       // Extract package name
 78 |       if (line.startsWith('## Package:')) {
 79 |         packageName = line.replace('## Package:', '').trim();
 80 |       }
 81 | 
 82 |       // Extract class name
 83 |       if (line.startsWith('# ') && !line.startsWith('## ')) {
 84 |         className = line.replace('# ', '').replace('Class ', '').trim();
 85 |       }
 86 | 
 87 |       // Track current section
 88 |       if (line.startsWith('## ')) {
 89 |         currentSection = line.replace('## ', '').trim();
 90 |       }
 91 | 
 92 |       // Extract description
 93 |       if (currentSection === 'Description' && line && !line.startsWith('#')) {
 94 |         description += `${line} `;
 95 |       }
 96 | 
 97 |       // Extract inheritance hierarchy
 98 |       if (currentSection === 'Inheritance Hierarchy' && line.includes('-')) {
 99 |         const hierarchyItem = line.replace(/^[\s-]*/, '').trim();
100 |         if (hierarchyItem) {
101 |           inheritance.push(hierarchyItem);
102 |         }
103 |       }
104 | 
105 |       // Extract constants
106 |       if (currentSection === 'Constants' && line.startsWith('### ')) {
107 |         const constant = this.parseConstant(line, lines, i);
108 |         if (constant) {
109 |           constants.push(constant);
110 |         }
111 |       }
112 | 
113 |       // Extract properties
114 |       if (currentSection === 'Properties' && line.startsWith('### ')) {
115 |         const property = this.parseProperty(line, lines, i);
116 |         if (property) {
117 |           properties.push(property);
118 |         }
119 |       }
120 | 
121 |       // Extract methods
122 |       if ((currentSection === 'Method Summary' || currentSection === 'Method Details') && line.startsWith('### ')) {
123 |         const method = this.parseMethod(line, lines, i);
124 |         if (method) {
125 |           methods.push(method);
126 |         }
127 |       }
128 | 
129 |       // Extract constructor info
130 |       if (currentSection === 'Constructor Summary' && line && !line.startsWith('#')) {
131 |         constructorInfo += `${line} `;
132 |       }
133 |     }
134 | 
135 |     return {
136 |       className: className.trim(),
137 |       packageName: packageName.trim(),
138 |       description: description.trim(),
139 |       constants,
140 |       properties,
141 |       methods,
142 |       inheritance: inheritance.length > 0 ? inheritance : undefined,
143 |       constructorInfo: constructorInfo.trim() || undefined,
144 |     };
145 |   }
146 | 
147 |   /**
148 |    * Parse a constant definition from markdown content
149 |    */
150 |   private parseConstant(headerLine: string, lines: string[], startIndex: number): SFCCConstant | null {
151 |     const constName = headerLine.replace('### ', '').trim();
152 |     let constType = '';
153 |     let constValue = '';
154 |     let constDesc = '';
155 |     let deprecated = false;
156 |     let deprecationMessage = '';
157 | 
158 |     // Look for type, value and description in following lines
159 |     for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
160 |       const nextLine = lines[j].trim();
161 | 
162 |       if (nextLine.startsWith('**Type:**')) {
163 |         const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
164 |         if (typeMatch) {
165 |           const typeInfo = typeMatch[1];
166 |           // Extract type and value if present (e.g., "String = 'COMPLETED'" or "Number = 8")
167 |           const valueMatch = typeInfo.match(/^(\w+)\s*=\s*(.+)$/);
168 |           if (valueMatch) {
169 |             constType = valueMatch[1];
170 |             constValue = valueMatch[2];
171 |           } else {
172 |             constType = typeInfo.trim();
173 |           }
174 |         }
175 |       } else if (nextLine.startsWith('**Deprecated:**')) {
176 |         const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
177 |         deprecated = true;
178 |         deprecationMessage = deprecationInfo.message;
179 |       } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
180 |         constDesc += `${nextLine} `;
181 |       }
182 |     }
183 | 
184 |     return {
185 |       name: constName,
186 |       type: constType,
187 |       value: constValue || undefined,
188 |       description: constDesc.trim(),
189 |       deprecated: deprecated || undefined,
190 |       deprecationMessage: deprecationMessage || undefined,
191 |     };
192 |   }
193 | 
194 |   /**
195 |    * Parse a property definition from markdown content
196 |    */
197 |   private parseProperty(headerLine: string, lines: string[], startIndex: number): SFCCProperty | null {
198 |     const propName = headerLine.replace('### ', '').trim();
199 |     let propType = '';
200 |     let propDesc = '';
201 |     const modifiers: string[] = [];
202 |     let deprecated = false;
203 |     let deprecationMessage = '';
204 | 
205 |     // Look for type and description in following lines
206 |     for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
207 |       const nextLine = lines[j].trim();
208 | 
209 |       if (nextLine.startsWith('**Type:**')) {
210 |         const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
211 |         if (typeMatch) {
212 |           const typeInfo = typeMatch[1];
213 |           propType = typeInfo.split(' ')[0];
214 |           if (typeInfo.includes('(Read Only)')) {
215 |             modifiers.push('Read Only');
216 |           }
217 |           if (typeInfo.includes('(Static)')) {
218 |             modifiers.push('Static');
219 |           }
220 |         }
221 |       } else if (nextLine.startsWith('**Deprecated:**')) {
222 |         const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
223 |         deprecated = true;
224 |         deprecationMessage = deprecationInfo.message;
225 |       } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
226 |         propDesc += `${nextLine} `;
227 |       }
228 |     }
229 | 
230 |     return {
231 |       name: propName,
232 |       type: propType,
233 |       description: propDesc.trim(),
234 |       modifiers: modifiers.length > 0 ? modifiers : undefined,
235 |       deprecated: deprecated || undefined,
236 |       deprecationMessage: deprecationMessage || undefined,
237 |     };
238 |   }
239 | 
240 |   /**
241 |    * Parse a method definition from markdown content
242 |    */
243 |   private parseMethod(headerLine: string, lines: string[], startIndex: number): SFCCMethod | null {
244 |     const methodName = headerLine.replace('### ', '').trim();
245 |     let signature = '';
246 |     let methodDesc = '';
247 |     let deprecated = false;
248 |     let deprecationMessage = '';
249 | 
250 |     // Look for signature and description in following lines
251 |     for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
252 |       const nextLine = lines[j].trim();
253 | 
254 |       if (nextLine.startsWith('**Signature:**')) {
255 |         const sigMatch = nextLine.match(/\*\*Signature:\*\*\s*`(.+)`/);
256 |         if (sigMatch) {
257 |           signature = sigMatch[1];
258 |         }
259 |       } else if (nextLine.startsWith('**Description:**')) {
260 |         methodDesc = nextLine.replace('**Description:**', '').trim();
261 |       } else if (nextLine.startsWith('**Deprecated:**')) {
262 |         const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
263 |         deprecated = true;
264 |         deprecationMessage = deprecationInfo.message;
265 |       } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#') && !nextLine.startsWith('---')) {
266 |         if (!methodDesc && !nextLine.includes('Signature:')) {
267 |           methodDesc += `${nextLine} `;
268 |         }
269 |       }
270 |     }
271 | 
272 |     return {
273 |       name: methodName,
274 |       signature: signature || methodName,
275 |       description: methodDesc.trim(),
276 |       deprecated: deprecated || undefined,
277 |       deprecationMessage: deprecationMessage || undefined,
278 |     };
279 |   }
280 | 
281 |   /**
282 |    * Parse deprecation information from markdown content
283 |    */
284 |   private parseDeprecationInfo(deprecationLine: string, lines: string[], startIndex: number): { message: string } {
285 |     let deprecationMessage = '';
286 | 
287 |     // Check if there's a message on the same line
288 |     const sameLineMessage = deprecationLine.replace('**Deprecated:**', '').trim();
289 |     if (sameLineMessage) {
290 |       deprecationMessage = sameLineMessage;
291 |     } else {
292 |       // Look for the deprecation message on subsequent lines until next ** marker
293 |       const depLines: string[] = [];
294 |       for (let k = startIndex + 1; k < lines.length && !lines[k].startsWith('#'); k++) {
295 |         const depLine = lines[k].trim();
296 |         if (depLine.startsWith('**') && !depLine.startsWith('**Deprecated:**')) {
297 |           break; // Stop at next ** marker
298 |         }
299 |         if (depLine && !depLine.startsWith('---')) {
300 |           depLines.push(depLine);
301 |         }
302 |       }
303 |       deprecationMessage = depLines.join(' ').trim();
304 |     }
305 | 
306 |     return { message: deprecationMessage };
307 |   }
308 | }
309 | 
```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * SFCC Mock Server
  5 |  * 
  6 |  * Unified mock server combining WebDAV and OCAPI functionality for SFCC development testing.
  7 |  * Provides a single endpoint for both log file access and OCAPI simulation.
  8 |  * 
  9 |  * Usage:
 10 |  *   node server.js [options]
 11 |  * 
 12 |  * Options:
 13 |  *   --port <number>        Server port (default: 3000)
 14 |  *   --host <string>        Server host (default: localhost)  
 15 |  *   --dev                  Enable development mode with verbose logging
 16 |  *   --no-webdav            Disable WebDAV functionality
 17 |  *   --no-ocapi             Disable OCAPI functionality
 18 |  *   --no-cors              Disable CORS headers
 19 |  *   --mock-data <path>     Custom path to mock data directory
 20 |  *   --help                 Show this help message
 21 |  */
 22 | 
 23 | const ServerConfig = require('./src/config/server-config');
 24 | const SFCCMockApp = require('./src/app');
 25 | 
 26 | class SFCCMockServer {
 27 |     constructor(options = {}) {
 28 |         this.config = new ServerConfig(options);
 29 |         this.app = new SFCCMockApp(this.config);
 30 |         this.server = null;
 31 |     }
 32 | 
 33 |     async start() {
 34 |         try {
 35 |             // Validate configuration
 36 |             const configErrors = this.config.validate();
 37 |             if (configErrors.length > 0) {
 38 |                 throw new Error(`Configuration errors: ${configErrors.join(', ')}`);
 39 |             }
 40 | 
 41 |             const expressApp = this.app.getExpressApp();
 42 | 
 43 |             return new Promise((resolve, reject) => {
 44 |                 this.server = expressApp.listen(this.config.port, this.config.host, (err) => {
 45 |                     if (err) {
 46 |                         reject(err);
 47 |                         return;
 48 |                     }
 49 | 
 50 |                     this.printStartupMessage();
 51 |                     resolve(this.server);
 52 |                 });
 53 | 
 54 |                 this.server.on('error', (err) => {
 55 |                     if (err.code === 'EADDRINUSE') {
 56 |                         console.error(`❌ Port ${this.config.port} is already in use`);
 57 |                         console.error('   Try using a different port with --port <number>');
 58 |                     } else {
 59 |                         console.error('❌ Server error:', err.message);
 60 |                     }
 61 |                     reject(err);
 62 |                 });
 63 |             });
 64 |         } catch (error) {
 65 |             console.error('❌ Failed to start server:', error.message);
 66 |             throw error;
 67 |         }
 68 |     }
 69 | 
 70 |     async stop() {
 71 |         if (this.server) {
 72 |             return new Promise((resolve) => {
 73 |                 this.server.close(() => {
 74 |                     console.log('🛑 SFCC Mock Server stopped');
 75 |                     resolve();
 76 |                 });
 77 |             });
 78 |         }
 79 |     }
 80 | 
 81 |     printStartupMessage() {
 82 |         const summary = this.config.getSummary();
 83 |         
 84 |         console.log('🚀 SFCC Mock Server started successfully!');
 85 |         console.log('');
 86 |         console.log(`📊 Server Info:`);
 87 |         console.log(`   Host: ${summary.server}`);
 88 |         console.log(`   Mode: ${summary.mode}`);
 89 |         console.log(`   Features: ${summary.features.join(', ')}`);
 90 |         console.log('');
 91 |         console.log('📋 Available Endpoints:');
 92 |         
 93 |         Object.entries(summary.endpoints).forEach(([name, url]) => {
 94 |             if (typeof url === 'string') {
 95 |                 console.log(`   ${name}: ${url}`);
 96 |             } else if (typeof url === 'object') {
 97 |                 console.log(`   ${name}:`);
 98 |                 Object.entries(url).forEach(([subName, subUrl]) => {
 99 |                     console.log(`     ${subName}: ${subUrl}`);
100 |                 });
101 |             }
102 |         });
103 | 
104 |         if (this.config.features.webdav) {
105 |             console.log('');
106 |             console.log('📁 WebDAV Endpoints:');
107 |             console.log(`   Logs (SFCC path): ${this.config.getWebdavLogsUrl()}`);
108 |             console.log(`   Logs (direct): ${this.config.getServerUrl()}/Logs/`);
109 |             console.log(`   Job Logs: ${this.config.getServerUrl()}/Logs/jobs/`);
110 |         }
111 | 
112 |         if (this.config.features.ocapi) {
113 |             console.log('');
114 |             console.log('🔐 OCAPI Endpoints:');
115 |             console.log(`   OAuth: ${this.config.getServerUrl()}/dw/oauth2/access_token`);
116 |             console.log(`   System Objects: ${this.config.getOcapiBaseUrl()}/system_object_definitions`);
117 |             console.log(`   Code Versions: ${this.config.getOcapiBaseUrl()}/code_versions`);
118 |             console.log('');
119 |             console.log('🔑 Test Credentials:');
120 |             console.log(`   Client ID: ${this.config.validCredentials.clientId}`);
121 |             console.log(`   Client Secret: ${this.config.validCredentials.clientSecret}`);
122 |         }
123 | 
124 |         if (this.config.isDevMode) {
125 |             console.log('');
126 |             console.log('🔧 Development mode enabled - verbose logging active');
127 |         }
128 |         
129 |         console.log('');
130 |         console.log('✅ Server is ready for connections');
131 |     }
132 | 
133 |     getConfig() {
134 |         return this.config;
135 |     }
136 | 
137 |     getApp() {
138 |         return this.app;
139 |     }
140 | }
141 | 
142 | /**
143 |  * Parse command line arguments
144 |  */
145 | function parseArgs() {
146 |     const args = process.argv.slice(2);
147 |     const options = {};
148 | 
149 |     for (let i = 0; i < args.length; i++) {
150 |         const arg = args[i];
151 |         
152 |         switch (arg) {
153 |             case '--help':
154 |                 printHelp();
155 |                 process.exit(0);
156 |                 break;
157 |             case '--dev':
158 |                 options.dev = true;
159 |                 break;
160 |             case '--no-webdav':
161 |                 options.enableWebdav = false;
162 |                 break;
163 |             case '--no-ocapi':
164 |                 options.enableOcapi = false;
165 |                 break;
166 |             case '--no-cors':
167 |                 options.enableCors = false;
168 |                 break;
169 |             case '--enable-random-errors':
170 |                 options.enableRandomErrors = true;
171 |                 break;
172 |             case '--port':
173 |                 if (i + 1 < args.length) {
174 |                     options.port = parseInt(args[i + 1]);
175 |                     i++;
176 |                 } else {
177 |                     console.error('❌ --port requires a value');
178 |                     process.exit(1);
179 |                 }
180 |                 break;
181 |             case '--host':
182 |                 if (i + 1 < args.length) {
183 |                     options.host = args[i + 1];
184 |                     i++;
185 |                 } else {
186 |                     console.error('❌ --host requires a value');
187 |                     process.exit(1);
188 |                 }
189 |                 break;
190 |             case '--mock-data':
191 |                 if (i + 1 < args.length) {
192 |                     options.mockDataPath = args[i + 1];
193 |                     i++;
194 |                 } else {
195 |                     console.error('❌ --mock-data requires a path');
196 |                     process.exit(1);
197 |                 }
198 |                 break;
199 |             default:
200 |                 if (arg.startsWith('--port=')) {
201 |                     options.port = parseInt(arg.split('=')[1]);
202 |                 } else if (arg.startsWith('--host=')) {
203 |                     options.host = arg.split('=')[1];
204 |                 } else if (arg.startsWith('--mock-data=')) {
205 |                     options.mockDataPath = arg.split('=')[1];
206 |                 } else {
207 |                     console.warn(`⚠️  Unknown argument: ${arg}`);
208 |                 }
209 |         }
210 |     }
211 | 
212 |     return options;
213 | }
214 | 
215 | /**
216 |  * Print help message
217 |  */
218 | function printHelp() {
219 |     console.log('SFCC Mock Server - Unified WebDAV and OCAPI mock server');
220 |     console.log('');
221 |     console.log('Usage:');
222 |     console.log('  node server.js [options]');
223 |     console.log('');
224 |     console.log('Options:');
225 |     console.log('  --port <number>        Server port (default: 3000)');
226 |     console.log('  --host <string>        Server host (default: localhost)');
227 |     console.log('  --dev                  Enable development mode with verbose logging');
228 |     console.log('  --no-webdav            Disable WebDAV functionality');
229 |     console.log('  --no-ocapi             Disable OCAPI functionality');
230 |     console.log('  --no-cors              Disable CORS headers');
231 |     console.log('  --enable-random-errors Enable random 500 errors (1% chance) for error handling testing');
232 |     console.log('  --mock-data <path>     Custom path to mock data directory');
233 |     console.log('  --help                 Show this help message');
234 |     console.log('');
235 |     console.log('Examples:');
236 |     console.log('  node server.js --dev                    # Start in development mode');
237 |     console.log('  node server.js --port 4000              # Start on port 4000');
238 |     console.log('  node server.js --no-webdav              # Only OCAPI endpoints');
239 |     console.log('  node server.js --host 0.0.0.0 --port 3001  # Bind to all interfaces');
240 | }
241 | 
242 | // CLI execution
243 | if (require.main === module) {
244 |     const options = parseArgs();
245 |     const server = new SFCCMockServer(options);
246 | 
247 |     // Graceful shutdown handlers
248 |     const gracefulShutdown = async (signal) => {
249 |         console.log(`\\n🔄 Received ${signal}, shutting down gracefully...`);
250 |         try {
251 |             await server.stop();
252 |             process.exit(0);
253 |         } catch (error) {
254 |             console.error('❌ Error during shutdown:', error);
255 |             process.exit(1);
256 |         }
257 |     };
258 | 
259 |     process.on('SIGINT', () => gracefulShutdown('SIGINT'));
260 |     process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
261 | 
262 |     // Handle uncaught exceptions
263 |     process.on('uncaughtException', (error) => {
264 |         console.error('❌ Uncaught Exception:', error);
265 |         process.exit(1);
266 |     });
267 | 
268 |     process.on('unhandledRejection', (reason, promise) => {
269 |         console.error('❌ Unhandled Rejection at:', promise, 'reason:', reason);
270 |         process.exit(1);
271 |     });
272 | 
273 |     // Start the server
274 |     server.start().catch((error) => {
275 |         console.error('💥 Failed to start server:', error.message);
276 |         process.exit(1);
277 |     });
278 | }
279 | 
280 | module.exports = SFCCMockServer;
```

--------------------------------------------------------------------------------
/docs/dw_order/ShippingOrderItem.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.order
  2 | 
  3 | # Class ShippingOrderItem
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.Extensible
  9 |   - dw.order.AbstractItem
 10 |     - dw.order.ShippingOrderItem
 11 | 
 12 | ## Description
 13 | 
 14 | 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.
 15 | 
 16 | ## Constants
 17 | 
 18 | ### STATUS_CANCELLED
 19 | 
 20 | **Type:** String = "CANCELLED"
 21 | 
 22 | Constant for Order Item Status CANCELLED
 23 | 
 24 | ### STATUS_CONFIRMED
 25 | 
 26 | **Type:** String = "CONFIRMED"
 27 | 
 28 | Constant for Order Item Status CONFIRMED
 29 | 
 30 | ### STATUS_SHIPPED
 31 | 
 32 | **Type:** String = "SHIPPED"
 33 | 
 34 | Constant for Order Item Status SHIPPED
 35 | 
 36 | ### STATUS_WAREHOUSE
 37 | 
 38 | **Type:** String = "WAREHOUSE"
 39 | 
 40 | Constant for Order Item Status WAREHOUSE
 41 | 
 42 | ## Properties
 43 | 
 44 | ### basePrice
 45 | 
 46 | **Type:** Money (Read Only)
 47 | 
 48 | Price of a single unit before discount application.
 49 | 
 50 | ### parentItem
 51 | 
 52 | **Type:** ShippingOrderItem
 53 | 
 54 | Returns null or the parent item.
 55 | 
 56 | ### quantity
 57 | 
 58 | **Type:** Quantity (Read Only)
 59 | 
 60 | The quantity of the shipping order item.
 61 |  
 62 |  The Quantity is equal to the related line item quantity.
 63 | 
 64 | ### shippingOrderNumber
 65 | 
 66 | **Type:** String (Read Only)
 67 | 
 68 | The mandatory shipping order number of the related
 69 |  ShippingOrder.
 70 | 
 71 | ### status
 72 | 
 73 | **Type:** EnumValue
 74 | 
 75 | Gets the order item status.
 76 |  
 77 |  The possible values are STATUS_CONFIRMED,
 78 |  STATUS_WAREHOUSE, STATUS_SHIPPED,
 79 |  STATUS_CANCELLED.
 80 | 
 81 | ### trackingRefs
 82 | 
 83 | **Type:** FilteringCollection (Read Only)
 84 | 
 85 | Gets the tracking refs (tracking infos) the shipping order item is
 86 |  assigned to.
 87 | 
 88 | ## Constructor Summary
 89 | 
 90 | ## Method Summary
 91 | 
 92 | ### addTrackingRef
 93 | 
 94 | **Signature:** `addTrackingRef(trackingInfoID : String, quantity : Quantity) : TrackingRef`
 95 | 
 96 | A shipping order item can be assigned to one or many tracking infos with different quantities.
 97 | 
 98 | ### applyPriceRate
 99 | 
100 | **Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void`
101 | 
102 | 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.
103 | 
104 | ### getBasePrice
105 | 
106 | **Signature:** `getBasePrice() : Money`
107 | 
108 | Price of a single unit before discount application.
109 | 
110 | ### getParentItem
111 | 
112 | **Signature:** `getParentItem() : ShippingOrderItem`
113 | 
114 | Returns null or the parent item.
115 | 
116 | ### getQuantity
117 | 
118 | **Signature:** `getQuantity() : Quantity`
119 | 
120 | The quantity of the shipping order item.
121 | 
122 | ### getShippingOrderNumber
123 | 
124 | **Signature:** `getShippingOrderNumber() : String`
125 | 
126 | The mandatory shipping order number of the related ShippingOrder.
127 | 
128 | ### getStatus
129 | 
130 | **Signature:** `getStatus() : EnumValue`
131 | 
132 | Gets the order item status.
133 | 
134 | ### getTrackingRefs
135 | 
136 | **Signature:** `getTrackingRefs() : FilteringCollection`
137 | 
138 | Gets the tracking refs (tracking infos) the shipping order item is assigned to.
139 | 
140 | ### setParentItem
141 | 
142 | **Signature:** `setParentItem(parentItem : ShippingOrderItem) : void`
143 | 
144 | Set a parent item.
145 | 
146 | ### setStatus
147 | 
148 | **Signature:** `setStatus(status : String) : void`
149 | 
150 | Sets the status.
151 | 
152 | ### split
153 | 
154 | **Signature:** `split(quantity : Quantity) : ShippingOrderItem`
155 | 
156 | Split the shipping order item.
157 | 
158 | ### split
159 | 
160 | **Signature:** `split(quantity : Quantity, splitOrderItem : boolean) : ShippingOrderItem`
161 | 
162 | Split the shipping order item.
163 | 
164 | ## Method Detail
165 | 
166 | ## Method Details
167 | 
168 | ### addTrackingRef
169 | 
170 | **Signature:** `addTrackingRef(trackingInfoID : String, quantity : Quantity) : TrackingRef`
171 | 
172 | **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.
173 | 
174 | **Parameters:**
175 | 
176 | - `trackingInfoID`: the id of the tracking info
177 | - `quantity`: the quantity the which is assigned to the tracking info for this shipping order item. Optional (null is allowed).
178 | 
179 | **Returns:**
180 | 
181 | the new tracking reference
182 | 
183 | **See Also:**
184 | 
185 | TrackingRef
186 | 
187 | ---
188 | 
189 | ### applyPriceRate
190 | 
191 | **Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void`
192 | 
193 | **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
194 | 
195 | **Parameters:**
196 | 
197 | - `factor`: factor used to calculate rate
198 | - `divisor`: divisor used to calculate rate
199 | - `roundUp`: whether to round up or down on 0.5
200 | 
201 | **See Also:**
202 | 
203 | AbstractItem.getTaxBasis()
204 | AbstractItem.getTax()
205 | AbstractItem.getNetPrice()
206 | AbstractItem.getGrossPrice()
207 | TaxMgr.getTaxationPolicy()
208 | 
209 | ---
210 | 
211 | ### getBasePrice
212 | 
213 | **Signature:** `getBasePrice() : Money`
214 | 
215 | **Description:** Price of a single unit before discount application.
216 | 
217 | **Returns:**
218 | 
219 | Price of a single unit before discount application.
220 | 
221 | ---
222 | 
223 | ### getParentItem
224 | 
225 | **Signature:** `getParentItem() : ShippingOrderItem`
226 | 
227 | **Description:** Returns null or the parent item.
228 | 
229 | **Returns:**
230 | 
231 | null or the parent item.
232 | 
233 | ---
234 | 
235 | ### getQuantity
236 | 
237 | **Signature:** `getQuantity() : Quantity`
238 | 
239 | **Description:** The quantity of the shipping order item. The Quantity is equal to the related line item quantity.
240 | 
241 | **Returns:**
242 | 
243 | the quantity
244 | 
245 | ---
246 | 
247 | ### getShippingOrderNumber
248 | 
249 | **Signature:** `getShippingOrderNumber() : String`
250 | 
251 | **Description:** The mandatory shipping order number of the related ShippingOrder.
252 | 
253 | **Returns:**
254 | 
255 | the shipping order number.
256 | 
257 | ---
258 | 
259 | ### getStatus
260 | 
261 | **Signature:** `getStatus() : EnumValue`
262 | 
263 | **Description:** Gets the order item status. The possible values are STATUS_CONFIRMED, STATUS_WAREHOUSE, STATUS_SHIPPED, STATUS_CANCELLED.
264 | 
265 | **Returns:**
266 | 
267 | the status
268 | 
269 | ---
270 | 
271 | ### getTrackingRefs
272 | 
273 | **Signature:** `getTrackingRefs() : FilteringCollection`
274 | 
275 | **Description:** Gets the tracking refs (tracking infos) the shipping order item is assigned to.
276 | 
277 | **Returns:**
278 | 
279 | the tracking refs ( tracking infos - TrackingRef ) the shipping order item is assigned to.
280 | 
281 | **See Also:**
282 | 
283 | TrackingRef
284 | 
285 | ---
286 | 
287 | ### setParentItem
288 | 
289 | **Signature:** `setParentItem(parentItem : ShippingOrderItem) : void`
290 | 
291 | **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().
292 | 
293 | **Parameters:**
294 | 
295 | - `parentItem`: The parent item, null is allowed
296 | 
297 | ---
298 | 
299 | ### setStatus
300 | 
301 | **Signature:** `setStatus(status : String) : void`
302 | 
303 | **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().
304 | 
305 | **Parameters:**
306 | 
307 | - `status`: the status
308 | 
309 | **Throws:**
310 | 
311 | NullPointerException - if status is null
312 | IllegalArgumentException - if the status transition to the status is not allowed
313 | 
314 | ---
315 | 
316 | ### split
317 | 
318 | **Signature:** `split(quantity : Quantity) : ShippingOrderItem`
319 | 
320 | **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.
321 | 
322 | **Parameters:**
323 | 
324 | - `quantity`: the quantity for the newly created item
325 | 
326 | **Returns:**
327 | 
328 | the newly created item or this item
329 | 
330 | **Throws:**
331 | 
332 | IllegalArgumentException - if quantity is greater than getQuantity()
333 | 
334 | ---
335 | 
336 | ### split
337 | 
338 | **Signature:** `split(quantity : Quantity, splitOrderItem : boolean) : ShippingOrderItem`
339 | 
340 | **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.
341 | 
342 | **Parameters:**
343 | 
344 | - `quantity`: the quantity for the newly created item
345 | - `splitOrderItem`: true the related LineItem will be splitted too false the related LineItem will not be splitted
346 | 
347 | **Returns:**
348 | 
349 | the newly created item or this item
350 | 
351 | **Throws:**
352 | 
353 | IllegalArgumentException - if quantity is greater than getQuantity()
354 | 
355 | ---
```

--------------------------------------------------------------------------------
/docs/dw_system/RESTResponseMgr.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.system
  2 | 
  3 | # Class RESTResponseMgr
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.system.RESTResponseMgr
  9 | 
 10 | ## Description
 11 | 
 12 | 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.
 13 | 
 14 | ## Constructor Summary
 15 | 
 16 | RESTResponseMgr()
 17 | 
 18 | ## Method Summary
 19 | 
 20 | ### createEmptySuccess
 21 | 
 22 | **Signature:** `static createEmptySuccess(statusCode : Number) : RESTSuccessResponse`
 23 | 
 24 | Constructs a new RESTSuccessResponse object.
 25 | 
 26 | ### createError
 27 | 
 28 | **Signature:** `static createError(statusCode : Number) : RESTErrorResponse`
 29 | 
 30 | Constructs a new RESTErrorResponse object.
 31 | 
 32 | ### createError
 33 | 
 34 | **Signature:** `static createError(statusCode : Number, type : String) : RESTErrorResponse`
 35 | 
 36 | Constructs a new RESTErrorResponse object.
 37 | 
 38 | ### createError
 39 | 
 40 | **Signature:** `static createError(statusCode : Number, type : String, title : String) : RESTErrorResponse`
 41 | 
 42 | Constructs a new RESTErrorResponse object.
 43 | 
 44 | ### createError
 45 | 
 46 | **Signature:** `static createError(statusCode : Number, type : String, title : String, detail : String) : RESTErrorResponse`
 47 | 
 48 | Constructs a new RESTErrorResponse object.
 49 | 
 50 | ### createScapiRemoteInclude
 51 | 
 52 | **Signature:** `static createScapiRemoteInclude(apiFamily : String, apiName : String, apiVersion : String, resourcePath : String, params : URLParameter...) : RemoteInclude`
 53 | 
 54 | Constructs a new RemoteInclude object specific for the SCAPI include path.
 55 | 
 56 | ### createStorefrontControllerRemoteInclude
 57 | 
 58 | **Signature:** `static createStorefrontControllerRemoteInclude(action : URLAction, params : URLParameter...) : RemoteInclude`
 59 | 
 60 | Constructs a new RemoteInclude object specific for the Storefront Controller include path.
 61 | 
 62 | ### createSuccess
 63 | 
 64 | **Signature:** `static createSuccess(body : Object, statusCode : Number) : RESTSuccessResponse`
 65 | 
 66 | Constructs a new RESTSuccessResponse object.
 67 | 
 68 | ### createSuccess
 69 | 
 70 | **Signature:** `static createSuccess(body : Object) : RESTSuccessResponse`
 71 | 
 72 | Constructs a new RESTSuccessResponse object.
 73 | 
 74 | ## Constructor Detail
 75 | 
 76 | ## Method Detail
 77 | 
 78 | ## Method Details
 79 | 
 80 | ### createEmptySuccess
 81 | 
 82 | **Signature:** `static createEmptySuccess(statusCode : Number) : RESTSuccessResponse`
 83 | 
 84 | **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).
 85 | 
 86 | **Parameters:**
 87 | 
 88 | - `statusCode`: The http status code of the response. The statusCode parameter should conform to RFC standards for a success.
 89 | 
 90 | **Returns:**
 91 | 
 92 | A new RESTSuccessResponse object.
 93 | 
 94 | **Throws:**
 95 | 
 96 | IllegalArgumentException - If the statusCode is not in the (100..299) range.
 97 | 
 98 | ---
 99 | 
100 | ### createError
101 | 
102 | **Signature:** `static createError(statusCode : Number) : RESTErrorResponse`
103 | 
104 | **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
105 | 
106 | **Parameters:**
107 | 
108 | - `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
109 | 
110 | **Returns:**
111 | 
112 | A new RESTErrorResponse object.
113 | 
114 | **Throws:**
115 | 
116 | IllegalArgumentException - If the statusCode is not in the (400..599) range.
117 | 
118 | ---
119 | 
120 | ### createError
121 | 
122 | **Signature:** `static createError(statusCode : Number, type : String) : RESTErrorResponse`
123 | 
124 | **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.
125 | 
126 | **Parameters:**
127 | 
128 | - `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
129 | - `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/.
130 | 
131 | **Returns:**
132 | 
133 | A new RESTErrorResponse object.
134 | 
135 | **Throws:**
136 | 
137 | 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.
138 | 
139 | ---
140 | 
141 | ### createError
142 | 
143 | **Signature:** `static createError(statusCode : Number, type : String, title : String) : RESTErrorResponse`
144 | 
145 | **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'.
146 | 
147 | **Parameters:**
148 | 
149 | - `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
150 | - `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/.
151 | - `title`: Human-readable summary of the error type.
152 | 
153 | **Returns:**
154 | 
155 | A new RESTErrorResponse object.
156 | 
157 | **Throws:**
158 | 
159 | 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.
160 | 
161 | ---
162 | 
163 | ### createError
164 | 
165 | **Signature:** `static createError(statusCode : Number, type : String, title : String, detail : String) : RESTErrorResponse`
166 | 
167 | **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.
168 | 
169 | **Parameters:**
170 | 
171 | - `statusCode`: The error code of the response. The statusCode parameter should conform to RFC standards for an error.
172 | - `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/.
173 | - `title`: Human-readable summary of the error type.
174 | - `detail`: Human-readable explanation of the specific occurrence of the error.
175 | 
176 | **Returns:**
177 | 
178 | A new RESTErrorResponse object.
179 | 
180 | **Throws:**
181 | 
182 | 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.
183 | 
184 | ---
185 | 
186 | ### createScapiRemoteInclude
187 | 
188 | **Signature:** `static createScapiRemoteInclude(apiFamily : String, apiName : String, apiVersion : String, resourcePath : String, params : URLParameter...) : RemoteInclude`
189 | 
190 | **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.
191 | 
192 | **Parameters:**
193 | 
194 | - `apiFamily`: an API Family name. Example: 'product'.
195 | - `apiName`: an API Name. Example: 'shopper-products'.
196 | - `apiVersion`: an API Version. Example: 'v1'.
197 | - `resourcePath`: a Resource path. Example: 'categories/root'
198 | - `params`: a query parameters (optional)
199 | 
200 | **Returns:**
201 | 
202 | a new instance of RemoteInclude.
203 | 
204 | ---
205 | 
206 | ### createStorefrontControllerRemoteInclude
207 | 
208 | **Signature:** `static createStorefrontControllerRemoteInclude(action : URLAction, params : URLParameter...) : RemoteInclude`
209 | 
210 | **Description:** Constructs a new RemoteInclude object specific for the Storefront Controller include path.
211 | 
212 | **Parameters:**
213 | 
214 | - `action`: a container to specify target controller. Hostnames in URL actions are ignored.
215 | - `params`: a query parameters (optional).
216 | 
217 | **Returns:**
218 | 
219 | a new instance of RemoteInclude.
220 | 
221 | ---
222 | 
223 | ### createSuccess
224 | 
225 | **Signature:** `static createSuccess(body : Object, statusCode : Number) : RESTSuccessResponse`
226 | 
227 | **Description:** Constructs a new RESTSuccessResponse object.
228 | 
229 | **Parameters:**
230 | 
231 | - `body`: The body of the successful response. This should always be a valid JavaScript JSON object.
232 | - `statusCode`: The http status code of the response. The statusCode parameter should conform to RFC standards for a success.
233 | 
234 | **Returns:**
235 | 
236 | A new RESTSuccessResponse object.
237 | 
238 | **Throws:**
239 | 
240 | IllegalArgumentException - If the statusCode is not in the (100..299) range.
241 | 
242 | ---
243 | 
244 | ### createSuccess
245 | 
246 | **Signature:** `static createSuccess(body : Object) : RESTSuccessResponse`
247 | 
248 | **Description:** Constructs a new RESTSuccessResponse object. HTTP status code of the response will be defaulted to 200.
249 | 
250 | **Parameters:**
251 | 
252 | - `body`: The body of the successful response. This should always be a valid JavaScript JSON object.
253 | 
254 | **Returns:**
255 | 
256 | A new RESTSuccessResponse object.
257 | 
258 | ---
```

--------------------------------------------------------------------------------
/tests/cartridge-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CartridgeToolHandler } from '../src/core/handlers/cartridge-handler.js';
  2 | import { HandlerContext } from '../src/core/handlers/base-handler.js';
  3 | import { Logger } from '../src/utils/logger.js';
  4 | 
  5 | // Mock cartridge client
  6 | const mockCartridgeClient = {
  7 |   generateCartridgeStructure: jest.fn(),
  8 | };
  9 | 
 10 | // Mock the ClientFactory to return our mock client
 11 | jest.mock('../src/core/handlers/client-factory.js', () => ({
 12 |   ClientFactory: jest.fn().mockImplementation(() => ({
 13 |     createCartridgeClient: jest.fn(() => mockCartridgeClient),
 14 |   })),
 15 | }));
 16 | 
 17 | describe('CartridgeToolHandler', () => {
 18 |   let mockLogger: jest.Mocked<Logger>;
 19 |   let mockClient: typeof mockCartridgeClient;
 20 |   let context: HandlerContext;
 21 |   let handler: CartridgeToolHandler;
 22 | 
 23 |   beforeEach(() => {
 24 |     mockLogger = {
 25 |       debug: jest.fn(),
 26 |       log: jest.fn(),
 27 |       error: jest.fn(),
 28 |       timing: jest.fn(),
 29 |       methodEntry: jest.fn(),
 30 |       methodExit: jest.fn(),
 31 |     } as any;
 32 | 
 33 |     // Reset mocks
 34 |     jest.clearAllMocks();
 35 | 
 36 |     // Use the mock client directly and reset it
 37 |     mockClient = mockCartridgeClient;
 38 |     mockClient.generateCartridgeStructure.mockReset();
 39 | 
 40 |     jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
 41 | 
 42 |     context = {
 43 |       logger: mockLogger,
 44 |       config: null as any,
 45 |       capabilities: { canAccessLogs: false, canAccessOCAPI: false },
 46 |     };
 47 | 
 48 |     handler = new CartridgeToolHandler(context, 'Cartridge');
 49 |   });
 50 | 
 51 |   afterEach(() => {
 52 |     jest.restoreAllMocks();
 53 |   });
 54 | 
 55 |   // Helper function to initialize handler for tests that need it
 56 |   const initializeHandler = async () => {
 57 |     await (handler as any).initialize();
 58 |   };
 59 | 
 60 |   describe('canHandle', () => {
 61 |     it('should handle cartridge tools', () => {
 62 |       expect(handler.canHandle('generate_cartridge_structure')).toBe(true);
 63 |     });
 64 | 
 65 |     it('should not handle non-cartridge tools', () => {
 66 |       expect(handler.canHandle('get_latest_error')).toBe(false);
 67 |       expect(handler.canHandle('unknown_tool')).toBe(false);
 68 |     });
 69 |   });
 70 | 
 71 |   describe('initialization', () => {
 72 |     it('should initialize cartridge generation client', async () => {
 73 |       await initializeHandler();
 74 | 
 75 |       const MockedClientFactory = jest.requireMock('../src/core/handlers/client-factory.js').ClientFactory;
 76 |       const mockFactoryInstance = MockedClientFactory.mock.results[0].value;
 77 |       expect(mockFactoryInstance.createCartridgeClient).toHaveBeenCalled();
 78 |       expect(mockLogger.debug).toHaveBeenCalledWith('Cartridge generation client initialized');
 79 |     });
 80 |   });
 81 | 
 82 |   describe('disposal', () => {
 83 |     it('should dispose cartridge generation client properly', async () => {
 84 |       await initializeHandler();
 85 |       await (handler as any).dispose();
 86 | 
 87 |       expect(mockLogger.debug).toHaveBeenCalledWith('Cartridge generation client disposed');
 88 |     });
 89 |   });
 90 | 
 91 |   describe('generate_cartridge_structure tool', () => {
 92 |     beforeEach(async () => {
 93 |       await initializeHandler();
 94 |     });
 95 | 
 96 |     it('should handle generate_cartridge_structure with cartridgeName', async () => {
 97 |       mockClient.generateCartridgeStructure.mockResolvedValue({
 98 |         success: true,
 99 |         cartridgeName: 'plugin_example',
100 |         targetPath: '/path/to/project',
101 |         filesCreated: [
102 |           'cartridges/plugin_example/cartridge/controllers/Product.js',
103 |           'cartridges/plugin_example/cartridge/scripts/helpers/ProductHelper.js',
104 |           'package.json',
105 |         ],
106 |       });
107 | 
108 |       const args = { cartridgeName: 'plugin_example' };
109 |       const result = await handler.handle('generate_cartridge_structure', args, Date.now());
110 | 
111 |       expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
112 |         cartridgeName: 'plugin_example',
113 |         targetPath: undefined,
114 |         fullProjectSetup: true,
115 |       });
116 |       expect(result.content[0].text).toContain('plugin_example');
117 |     });
118 | 
119 |     it('should handle generate_cartridge_structure with all options', async () => {
120 |       mockClient.generateCartridgeStructure.mockResolvedValue({
121 |         success: true,
122 |         cartridgeName: 'custom_cartridge',
123 |         targetPath: '/custom/path',
124 |         filesCreated: [
125 |           'cartridges/custom_cartridge/cartridge/controllers/Product.js',
126 |         ],
127 |       });
128 | 
129 |       const args = {
130 |         cartridgeName: 'custom_cartridge',
131 |         targetPath: '/custom/path',
132 |         fullProjectSetup: false,
133 |       };
134 |       const result = await handler.handle('generate_cartridge_structure', args, Date.now());
135 | 
136 |       expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
137 |         cartridgeName: 'custom_cartridge',
138 |         targetPath: '/custom/path',
139 |         fullProjectSetup: false,
140 |       });
141 |       expect(result.content[0].text).toContain('custom_cartridge');
142 |     });
143 | 
144 |     it('should use default fullProjectSetup when not provided', async () => {
145 |       mockClient.generateCartridgeStructure.mockResolvedValue({
146 |         success: true,
147 |         cartridgeName: 'test_cartridge',
148 |         filesCreated: [],
149 |       });
150 | 
151 |       const args = {
152 |         cartridgeName: 'test_cartridge',
153 |         targetPath: '/test/path',
154 |       };
155 |       await handler.handle('generate_cartridge_structure', args, Date.now());
156 | 
157 |       expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
158 |         cartridgeName: 'test_cartridge',
159 |         targetPath: '/test/path',
160 |         fullProjectSetup: true,
161 |       });
162 |     });
163 | 
164 |     it('should throw error when cartridgeName is missing', async () => {
165 |       const result = await handler.handle('generate_cartridge_structure', {}, Date.now());
166 |       expect(result.isError).toBe(true);
167 |       expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
168 |     });
169 | 
170 |     it('should throw error when cartridgeName is empty', async () => {
171 |       const result = await handler.handle('generate_cartridge_structure', { cartridgeName: '' }, Date.now());
172 |       expect(result.isError).toBe(true);
173 |       expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
174 |     });
175 | 
176 |     it('should throw error when cartridgeName is not a string', async () => {
177 |       const result = await handler.handle('generate_cartridge_structure', { cartridgeName: 123 }, Date.now());
178 |       expect(result.isError).toBe(true);
179 |       expect(result.content[0].text).toContain('cartridgeName must be a valid identifier');
180 |     });
181 |   });
182 | 
183 |   describe('error handling', () => {
184 |     beforeEach(async () => {
185 |       await initializeHandler();
186 |     });
187 | 
188 |     it('should handle client errors gracefully', async () => {
189 |       mockClient.generateCartridgeStructure.mockRejectedValue(new Error('Directory already exists'));
190 | 
191 |       const result = await handler.handle('generate_cartridge_structure', { cartridgeName: 'existing_cartridge' }, Date.now());
192 |       expect(result.isError).toBe(true);
193 |       expect(result.content[0].text).toContain('Directory already exists');
194 |     });
195 | 
196 |     it('should throw error for unsupported tools', async () => {
197 |       await expect(handler.handle('unsupported_tool', {}, Date.now()))
198 |         .rejects.toThrow('Unsupported tool');
199 |     });
200 |   });
201 | 
202 |   describe('timing and logging', () => {
203 |     beforeEach(async () => {
204 |       await initializeHandler();
205 |       mockClient.generateCartridgeStructure.mockResolvedValue({
206 |         success: true,
207 |         cartridgeName: 'test_cartridge',
208 |         filesCreated: [],
209 |       });
210 |     });
211 | 
212 |     it('should log timing information', async () => {
213 |       const startTime = Date.now();
214 |       await handler.handle('generate_cartridge_structure', { cartridgeName: 'test_cartridge' }, startTime);
215 | 
216 |       expect(mockLogger.timing).toHaveBeenCalledWith('generate_cartridge_structure', startTime);
217 |     });
218 | 
219 |     it('should log execution details', async () => {
220 |       await handler.handle('generate_cartridge_structure', { cartridgeName: 'test_cartridge' }, Date.now());
221 | 
222 |       expect(mockLogger.debug).toHaveBeenCalledWith(
223 |         'generate_cartridge_structure completed successfully',
224 |         expect.any(Object),
225 |       );
226 |     });
227 | 
228 |     it('should create appropriate log message', async () => {
229 |       mockClient.generateCartridgeStructure.mockResolvedValue({
230 |         success: true,
231 |         cartridgeName: 'my_cartridge',
232 |         filesCreated: [],
233 |       });
234 | 
235 |       await handler.handle('generate_cartridge_structure', { cartridgeName: 'my_cartridge' }, Date.now());
236 | 
237 |       // Check that the debug log contains the execution details
238 |       expect(mockLogger.debug).toHaveBeenCalledWith(
239 |         'generate_cartridge_structure completed successfully',
240 |         expect.any(Object),
241 |       );
242 |     });
243 |   });
244 | 
245 |   describe('client integration', () => {
246 |     beforeEach(async () => {
247 |       await initializeHandler();
248 |     });
249 | 
250 |     it('should pass correct parameters to client for minimal request', async () => {
251 |       mockClient.generateCartridgeStructure.mockResolvedValue({
252 |         success: true,
253 |         cartridgeName: 'minimal_cartridge',
254 |         filesCreated: ['cartridges/minimal_cartridge/cartridge/controllers/Default.js'],
255 |       });
256 | 
257 |       const args = { cartridgeName: 'minimal_cartridge' };
258 | 
259 |       await handler.handle('generate_cartridge_structure', args, Date.now());
260 | 
261 |       expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
262 |         cartridgeName: 'minimal_cartridge',
263 |         targetPath: undefined,
264 |         fullProjectSetup: true,
265 |       });
266 |     });
267 | 
268 |     it('should pass correct parameters to client for full request', async () => {
269 |       mockClient.generateCartridgeStructure.mockResolvedValue({
270 |         success: true,
271 |         cartridgeName: 'full_cartridge',
272 |         filesCreated: ['cartridges/full_cartridge/cartridge/controllers/Default.js'],
273 |       });
274 | 
275 |       const args = {
276 |         cartridgeName: 'full_cartridge',
277 |         targetPath: '/workspace/my-project',
278 |         fullProjectSetup: false,
279 |       };
280 | 
281 |       await handler.handle('generate_cartridge_structure', args, Date.now());
282 | 
283 |       expect(mockClient.generateCartridgeStructure).toHaveBeenCalledWith({
284 |         cartridgeName: 'full_cartridge',
285 |         targetPath: '/workspace/my-project',
286 |         fullProjectSetup: false,
287 |       });
288 |     });
289 |   });
290 | });
291 | 
```

--------------------------------------------------------------------------------
/docs/dw_svc/ServiceCallback.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.svc
  2 | 
  3 | # Class ServiceCallback
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.svc.ServiceCallback
  9 | 
 10 | ## Description
 11 | 
 12 | 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.
 13 | 
 14 | ## Properties
 15 | 
 16 | ### URL
 17 | 
 18 | **Type:** String (Read Only)
 19 | 
 20 | Allows overriding the URL provided by the service configuration.
 21 |  
 22 |  It is usually better to call Service.setURL(String) within createRequest(Service, Object...)
 23 |  because that allows you to modify the existing URL based on call parameters.
 24 | 
 25 | ## Constructor Summary
 26 | 
 27 | ## Method Summary
 28 | 
 29 | ### createRequest
 30 | 
 31 | **Signature:** `createRequest(service : Service, params : Object...) : Object`
 32 | 
 33 | Creates a request object to be used when calling the service.
 34 | 
 35 | ### execute
 36 | 
 37 | **Signature:** `execute(service : Service, request : Object) : Object`
 38 | 
 39 | Provides service-specific execution logic.
 40 | 
 41 | ### filterLogMessage
 42 | 
 43 | **Signature:** `filterLogMessage(msg : String) : String`
 44 | 
 45 | Allows filtering communication URL, request, and response log messages.
 46 | 
 47 | ### getRequestLogMessage
 48 | 
 49 | **Signature:** `getRequestLogMessage(request : Object) : String`
 50 | 
 51 | Creates a communication log message for the given request.
 52 | 
 53 | ### getResponseLogMessage
 54 | 
 55 | **Signature:** `getResponseLogMessage(response : Object) : String`
 56 | 
 57 | Creates a response log message for the given request.
 58 | 
 59 | ### getURL
 60 | 
 61 | **Signature:** `getURL() : String`
 62 | 
 63 | Allows overriding the URL provided by the service configuration.
 64 | 
 65 | ### initServiceClient
 66 | 
 67 | **Signature:** `initServiceClient(service : Service) : Object`
 68 | 
 69 | Creates a protocol-specific client object.
 70 | 
 71 | ### mockCall
 72 | 
 73 | **Signature:** `mockCall(service : Service, requestObj : Object) : Object`
 74 | 
 75 | Override this method to mock the remote portion of the service call.
 76 | 
 77 | ### mockFull
 78 | 
 79 | **Signature:** `mockFull(service : Service, args : Object...) : Object`
 80 | 
 81 | Override this method to mock the entire service call, including the createRequest, execute, and parseResponse phases.
 82 | 
 83 | ### parseResponse
 84 | 
 85 | **Signature:** `parseResponse(service : Service, response : Object) : Object`
 86 | 
 87 | Creates a response object from a successful service call.
 88 | 
 89 | ## Method Detail
 90 | 
 91 | ## Method Details
 92 | 
 93 | ### createRequest
 94 | 
 95 | **Signature:** `createRequest(service : Service, params : Object...) : Object`
 96 | 
 97 | **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.
 98 | 
 99 | **Parameters:**
100 | 
101 | - `service`: Service being executed.
102 | - `params`: Parameters given to the call method.
103 | 
104 | **Returns:**
105 | 
106 | Request object to give to the execute method.
107 | 
108 | ---
109 | 
110 | ### execute
111 | 
112 | **Signature:** `execute(service : Service, request : Object) : Object`
113 | 
114 | **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.
115 | 
116 | **Parameters:**
117 | 
118 | - `service`: Service being executed.
119 | - `request`: Request object returned by createRequest(Service, Object...).
120 | 
121 | **Returns:**
122 | 
123 | Response from the underlying call, to be sent to parseResponse(Service, Object).
124 | 
125 | **Throws:**
126 | 
127 | - Exception
128 | 
129 | ---
130 | 
131 | ### filterLogMessage
132 | 
133 | **Signature:** `filterLogMessage(msg : String) : String`
134 | 
135 | **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.
136 | 
137 | **Parameters:**
138 | 
139 | - `msg`: Original log message.
140 | 
141 | **Returns:**
142 | 
143 | Message to be logged.
144 | 
145 | ---
146 | 
147 | ### getRequestLogMessage
148 | 
149 | **Signature:** `getRequestLogMessage(request : Object) : String`
150 | 
151 | **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.
152 | 
153 | **Parameters:**
154 | 
155 | - `request`: Request object.
156 | 
157 | **Returns:**
158 | 
159 | Log message, or null to create and use the default message.
160 | 
161 | ---
162 | 
163 | ### getResponseLogMessage
164 | 
165 | **Signature:** `getResponseLogMessage(response : Object) : String`
166 | 
167 | **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.
168 | 
169 | **Parameters:**
170 | 
171 | - `response`: Response object.
172 | 
173 | **Returns:**
174 | 
175 | Log message, or null to create and use the default message.
176 | 
177 | ---
178 | 
179 | ### getURL
180 | 
181 | **Signature:** `getURL() : String`
182 | 
183 | **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.
184 | 
185 | **Returns:**
186 | 
187 | URL to use. The default behavior is to use the URL from the service configuration.
188 | 
189 | ---
190 | 
191 | ### initServiceClient
192 | 
193 | **Signature:** `initServiceClient(service : Service) : Object`
194 | 
195 | **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 ) { }
196 | 
197 | **Parameters:**
198 | 
199 | - `service`: the Service object.
200 | 
201 | **Returns:**
202 | 
203 | Client object
204 | 
205 | **Throws:**
206 | 
207 | - Exception
208 | 
209 | ---
210 | 
211 | ### mockCall
212 | 
213 | **Signature:** `mockCall(service : Service, requestObj : Object) : Object`
214 | 
215 | **Description:** Override this method to mock the remote portion of the service call. Other callbacks like createRequest and parseResponse are still called.
216 | 
217 | **Parameters:**
218 | 
219 | - `service`: Service being executed.
220 | - `requestObj`: Request object returned by createRequest(Service, Object...).
221 | 
222 | **Returns:**
223 | 
224 | Mock response, to be sent to parseResponse(Service, Object).
225 | 
226 | **Throws:**
227 | 
228 | - Exception
229 | 
230 | ---
231 | 
232 | ### mockFull
233 | 
234 | **Signature:** `mockFull(service : Service, args : Object...) : Object`
235 | 
236 | **Description:** Override this method to mock the entire service call, including the createRequest, execute, and parseResponse phases.
237 | 
238 | **Parameters:**
239 | 
240 | - `service`: Service being executed.
241 | - `args`: Arguments from the Service call method.
242 | 
243 | **Returns:**
244 | 
245 | Object to return in the service call's Result.
246 | 
247 | **Throws:**
248 | 
249 | - Exception
250 | 
251 | ---
252 | 
253 | ### parseResponse
254 | 
255 | **Signature:** `parseResponse(service : Service, response : Object) : Object`
256 | 
257 | **Description:** Creates a response object from a successful service call. This response object will be the output object of the call method's Result.
258 | 
259 | **Parameters:**
260 | 
261 | - `service`: Service being executed.
262 | - `response`: Service-specific response object. For example, the HTTPService service provides the underlying HTTPClient object that made the HTTP call.
263 | 
264 | **Returns:**
265 | 
266 | Object to return in the service call's Result.
267 | 
268 | ---
```

--------------------------------------------------------------------------------
/docs/dw_order/BonusDiscountLineItem.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.order
  2 | 
  3 | # Class BonusDiscountLineItem
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.order.BonusDiscountLineItem
 11 | 
 12 | ## Description
 13 | 
 14 | 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().
 15 | 
 16 | ## Properties
 17 | 
 18 | ### bonusChoiceRuleBased
 19 | 
 20 | **Type:** getBonusProducts() (Read Only)
 21 | 
 22 | Returns whether the promotion that triggered the creation of this line item uses a rule to determine the list of
 23 |  bonus products.
 24 |  
 25 |  If the promotion is rule based, then a ProductSearchModel should be used to return the bonus products the
 26 |  customer may choose from, as the methods that return lists will return nothing. See getBonusProducts()
 27 | 
 28 | ### bonusProductLineItems
 29 | 
 30 | **Type:** List (Read Only)
 31 | 
 32 | Get the product line items in the current LineItemCtnr representing the
 33 |  bonus products that the customer has selected for this discount.
 34 | 
 35 | ### bonusProducts
 36 | 
 37 | **Type:** List (Read Only)
 38 | 
 39 | Get the list of bonus products which the customer is allowed to choose
 40 |  from for this discount. This list is configured by a merchant entering a
 41 |  list of SKUs for the discount. Products which do not exist in the system,
 42 |  or are offline, or are not assigned to a category in the site catalog are
 43 |  filtered out. Unavailable (i.e. out-of-stock) products are NOT filtered
 44 |  out. This allows merchants to display out-of-stock bonus products with
 45 |  appropriate messaging.
 46 |  
 47 |  If the promotion which triggered this discount does not exist, or this
 48 |  promotion is rule based, then this method returns an empty list.
 49 |  
 50 |  If the promotion is rule based, then this method will return an empty list.
 51 |  A ProductSearchModel should be used to return the bonus products the
 52 |  customer may choose from instead. See
 53 |  ProductSearchModel.PROMOTION_PRODUCT_TYPE_BONUS and
 54 |  ProductSearchModel.setPromotionID(String)
 55 |  
 56 |  If a returned product is a master product, the customer is entitled to
 57 |  choose from any variant. If the product is an option product, the
 58 |  customer is entitled to choose any value for each option. Since the
 59 |  promotions engine does not touch the value of the product option line
 60 |  items, it is the responsibility of custom code to set option prices.
 61 | 
 62 | ### couponLineItem
 63 | 
 64 | **Type:** CouponLineItem (Read Only)
 65 | 
 66 | Get the coupon line item associated with this discount.
 67 | 
 68 | ### maxBonusItems
 69 | 
 70 | **Type:** Number (Read Only)
 71 | 
 72 | Get the maximum number of bonus items that the customer is permitted to
 73 |  select for this bonus discount.
 74 |  
 75 |  If the promotion which triggered this discount does not exist, then this
 76 |  method returns 0.
 77 | 
 78 | ### promotion
 79 | 
 80 | **Type:** Promotion (Read Only)
 81 | 
 82 | Get the promotion associated with this discount.
 83 | 
 84 | ### promotionID
 85 | 
 86 | **Type:** String (Read Only)
 87 | 
 88 | Get the promotion ID associated with this discount.
 89 | 
 90 | ## Constructor Summary
 91 | 
 92 | ## Method Summary
 93 | 
 94 | ### getBonusProductLineItems
 95 | 
 96 | **Signature:** `getBonusProductLineItems() : List`
 97 | 
 98 | Get the product line items in the current LineItemCtnr representing the bonus products that the customer has selected for this discount.
 99 | 
100 | ### getBonusProductPrice
101 | 
102 | **Signature:** `getBonusProductPrice(product : Product) : Money`
103 | 
104 | Get the effective price for the passed bonus product.
105 | 
106 | ### getBonusProducts
107 | 
108 | **Signature:** `getBonusProducts() : List`
109 | 
110 | Get the list of bonus products which the customer is allowed to choose from for this discount.
111 | 
112 | ### getCouponLineItem
113 | 
114 | **Signature:** `getCouponLineItem() : CouponLineItem`
115 | 
116 | Get the coupon line item associated with this discount.
117 | 
118 | ### getMaxBonusItems
119 | 
120 | **Signature:** `getMaxBonusItems() : Number`
121 | 
122 | Get the maximum number of bonus items that the customer is permitted to select for this bonus discount.
123 | 
124 | ### getPromotion
125 | 
126 | **Signature:** `getPromotion() : Promotion`
127 | 
128 | Get the promotion associated with this discount.
129 | 
130 | ### getPromotionID
131 | 
132 | **Signature:** `getPromotionID() : String`
133 | 
134 | Get the promotion ID associated with this discount.
135 | 
136 | ### isBonusChoiceRuleBased
137 | 
138 | **Signature:** `isBonusChoiceRuleBased() : boolean`
139 | 
140 | Returns whether the promotion that triggered the creation of this line item uses a rule to determine the list of bonus products.
141 | 
142 | ## Method Detail
143 | 
144 | ## Method Details
145 | 
146 | ### getBonusProductLineItems
147 | 
148 | **Signature:** `getBonusProductLineItems() : List`
149 | 
150 | **Description:** Get the product line items in the current LineItemCtnr representing the bonus products that the customer has selected for this discount.
151 | 
152 | **Returns:**
153 | 
154 | The selected product line items, never null.
155 | 
156 | **See Also:**
157 | 
158 | LineItemCtnr.createBonusProductLineItem(BonusDiscountLineItem, Product, ProductOptionModel, Shipment)
159 | 
160 | ---
161 | 
162 | ### getBonusProductPrice
163 | 
164 | **Signature:** `getBonusProductPrice(product : Product) : Money`
165 | 
166 | **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.
167 | 
168 | **Parameters:**
169 | 
170 | - `product`: The bonus product to retrieve a price for, must not be null.
171 | 
172 | **Returns:**
173 | 
174 | The price of the passed bonus product as a Number.
175 | 
176 | ---
177 | 
178 | ### getBonusProducts
179 | 
180 | **Signature:** `getBonusProducts() : List`
181 | 
182 | **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.
183 | 
184 | **Returns:**
185 | 
186 | An ordered list of bonus products that the customer may choose from for this discount.
187 | 
188 | ---
189 | 
190 | ### getCouponLineItem
191 | 
192 | **Signature:** `getCouponLineItem() : CouponLineItem`
193 | 
194 | **Description:** Get the coupon line item associated with this discount.
195 | 
196 | **Returns:**
197 | 
198 | The coupon line item associated with this discount, or null if it no longer exists or there is no one.
199 | 
200 | ---
201 | 
202 | ### getMaxBonusItems
203 | 
204 | **Signature:** `getMaxBonusItems() : Number`
205 | 
206 | **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.
207 | 
208 | **Returns:**
209 | 
210 | 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.
211 | 
212 | ---
213 | 
214 | ### getPromotion
215 | 
216 | **Signature:** `getPromotion() : Promotion`
217 | 
218 | **Description:** Get the promotion associated with this discount.
219 | 
220 | **Returns:**
221 | 
222 | The promotion associated with this discount, or null if it no longer exists.
223 | 
224 | ---
225 | 
226 | ### getPromotionID
227 | 
228 | **Signature:** `getPromotionID() : String`
229 | 
230 | **Description:** Get the promotion ID associated with this discount.
231 | 
232 | **Returns:**
233 | 
234 | The promotion ID associated with this discount, never null.
235 | 
236 | ---
237 | 
238 | ### isBonusChoiceRuleBased
239 | 
240 | **Signature:** `isBonusChoiceRuleBased() : boolean`
241 | 
242 | **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()
243 | 
244 | **Returns:**
245 | 
246 | 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.
247 | 
248 | ---
```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/mock-data/ocapi/system-object-definitions-old.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "_v": "24.4",
  3 |   "_type": "system_object_definitions",
  4 |   "count": 25,
  5 |   "data": [
  6 |     {
  7 |       "_type": "system_object_definition",
  8 |       "object_type": "Product",
  9 |       "displayName": {
 10 |         "default": "Product"
 11 |       },
 12 |       "description": {
 13 |         "default": "Product"
 14 |       },
 15 |       "content_object": false,
 16 |       "queryable": true,
 17 |       "read_only": false,
 18 |       "attribute_group_count": 35,
 19 |       "attribute_definition_count": 113,
 20 |       "_links": {
 21 |         "self": {
 22 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Product"
 23 |         }
 24 |       }
 25 |     },
 26 |     {
 27 |       "_type": "system_object_definition", 
 28 |       "object_type": "Category",
 29 |       "displayName": {
 30 |         "default": "Category"
 31 |       },
 32 |       "description": {
 33 |         "default": "Category"
 34 |       },
 35 |       "content_object": false,
 36 |       "queryable": true,
 37 |       "read_only": false,
 38 |       "attribute_group_count": 12,
 39 |       "attribute_definition_count": 45,
 40 |       "_links": {
 41 |         "self": {
 42 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Category"
 43 |         }
 44 |       }
 45 |     },
 46 |     {
 47 |       "_type": "system_object_definition",
 48 |       "object_type": "CustomerAddress",
 49 |       "displayName": {
 50 |         "default": "Customer Address"
 51 |       },
 52 |       "description": {
 53 |         "default": "Customer Address"
 54 |       },
 55 |       "content_object": false,
 56 |       "queryable": true,
 57 |       "read_only": false,
 58 |       "attribute_group_count": 8,
 59 |       "attribute_definition_count": 28,
 60 |       "_links": {
 61 |         "self": {
 62 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/CustomerAddress"
 63 |         }
 64 |       }
 65 |     },
 66 |     {
 67 |       "_type": "system_object_definition",
 68 |       "object_type": "Customer",
 69 |       "displayName": {
 70 |         "default": "Customer"
 71 |       },
 72 |       "description": {
 73 |         "default": "Customer"
 74 |       },
 75 |       "content_object": false,
 76 |       "queryable": true,
 77 |       "read_only": false,
 78 |       "attribute_group_count": 15,
 79 |       "attribute_definition_count": 67,
 80 |       "_links": {
 81 |         "self": {
 82 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Customer"
 83 |         }
 84 |       }
 85 |     },
 86 |     {
 87 |       "_type": "system_object_definition",
 88 |       "object_type": "Order",
 89 |       "displayName": {
 90 |         "default": "Order"
 91 |       },
 92 |       "description": {
 93 |         "default": "Order"
 94 |       },
 95 |       "content_object": false,
 96 |       "queryable": true,
 97 |       "read_only": false,
 98 |       "attribute_group_count": 18,
 99 |       "attribute_definition_count": 89,
100 |       "_links": {
101 |         "self": {
102 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/Order"
103 |         }
104 |       }
105 |     },
106 |     {
107 |       "_type": "system_object_definition",
108 |       "object_type": "SitePreferences",
109 |       "displayName": {
110 |         "default": "Site Preferences"
111 |       },
112 |       "description": {
113 |         "default": "Site Preferences"
114 |       },
115 |       "content_object": false,
116 |       "queryable": false,
117 |       "read_only": false,
118 |       "attribute_group_count": 8,
119 |       "attribute_definition_count": 156,
120 |       "_links": {
121 |         "self": {
122 |           "href": "https://{{hostname}}/s/-/dw/data/v24_4/system_object_definitions/SitePreferences"
123 |         }
124 |       }
125 |     }
126 |       "key_attribute_id": "order_no",
127 |       "content_object": false,
128 |       "queryable": true,
129 |       "read_only": false,
130 |       "creation_date": "2021-01-01T00:00:00.000Z",
131 |       "last_modified": "2024-01-01T00:00:00.000Z"
132 |     },
133 |     {
134 |       "_type": "object_type_definition",
135 |       "object_type": "Category",
136 |       "display_name": {
137 |         "default": "Category"
138 |       },
139 |       "description": {
140 |         "default": "Salesforce B2C Commerce category object definition"
141 |       },
142 |       "attribute_definition_count": 20,
143 |       "attribute_group_count": 4,
144 |       "key_attribute_id": "id",
145 |       "content_object": false,
146 |       "queryable": true,
147 |       "read_only": false,
148 |       "creation_date": "2021-01-01T00:00:00.000Z",
149 |       "last_modified": "2024-01-01T00:00:00.000Z"
150 |     },
151 |     {
152 |       "_type": "object_type_definition",
153 |       "object_type": "Site",
154 |       "display_name": {
155 |         "default": "Site"
156 |       },
157 |       "description": {
158 |         "default": "Salesforce B2C Commerce site object definition"
159 |       },
160 |       "attribute_definition_count": 15,
161 |       "attribute_group_count": 3,
162 |       "key_attribute_id": "id",
163 |       "content_object": false,
164 |       "queryable": true,
165 |       "read_only": false,
166 |       "creation_date": "2021-01-01T00:00:00.000Z",
167 |       "last_modified": "2024-01-01T00:00:00.000Z"
168 |     },
169 |     {
170 |       "_type": "object_type_definition",
171 |       "object_type": "Campaign",
172 |       "display_name": {
173 |         "default": "Campaign"
174 |       },
175 |       "description": {
176 |         "default": "Salesforce B2C Commerce campaign object definition"
177 |       },
178 |       "attribute_definition_count": 12,
179 |       "attribute_group_count": 3,
180 |       "key_attribute_id": "id",
181 |       "content_object": false,
182 |       "queryable": true,
183 |       "read_only": false,
184 |       "creation_date": "2021-01-01T00:00:00.000Z",
185 |       "last_modified": "2024-01-01T00:00:00.000Z"
186 |     },
187 |     {
188 |       "_type": "object_type_definition",
189 |       "object_type": "Coupon",
190 |       "display_name": {
191 |         "default": "Coupon"
192 |       },
193 |       "description": {
194 |         "default": "Salesforce B2C Commerce coupon object definition"
195 |       },
196 |       "attribute_definition_count": 18,
197 |       "attribute_group_count": 4,
198 |       "key_attribute_id": "id",
199 |       "content_object": false,
200 |       "queryable": true,
201 |       "read_only": false,
202 |       "creation_date": "2021-01-01T00:00:00.000Z",
203 |       "last_modified": "2024-01-01T00:00:00.000Z"
204 |     },
205 |     {
206 |       "_type": "object_type_definition",
207 |       "object_type": "Promotion",
208 |       "display_name": {
209 |         "default": "Promotion"
210 |       },
211 |       "description": {
212 |         "default": "Salesforce B2C Commerce promotion object definition"
213 |       },
214 |       "attribute_definition_count": 25,
215 |       "attribute_group_count": 5,
216 |       "key_attribute_id": "id",
217 |       "content_object": false,
218 |       "queryable": true,
219 |       "read_only": false,
220 |       "creation_date": "2021-01-01T00:00:00.000Z",
221 |       "last_modified": "2024-01-01T00:00:00.000Z"
222 |     },
223 |     {
224 |       "_type": "object_type_definition",
225 |       "object_type": "PriceBook",
226 |       "display_name": {
227 |         "default": "Price Book"
228 |       },
229 |       "description": {
230 |         "default": "Salesforce B2C Commerce price book object definition"
231 |       },
232 |       "attribute_definition_count": 10,
233 |       "attribute_group_count": 2,
234 |       "key_attribute_id": "id",
235 |       "content_object": false,
236 |       "queryable": true,
237 |       "read_only": false,
238 |       "creation_date": "2021-01-01T00:00:00.000Z",
239 |       "last_modified": "2024-01-01T00:00:00.000Z"
240 |     },
241 |     {
242 |       "_type": "object_type_definition",
243 |       "object_type": "CustomObject",
244 |       "display_name": {
245 |         "default": "Custom Object"
246 |       },
247 |       "description": {
248 |         "default": "Custom business object definition"
249 |       },
250 |       "attribute_definition_count": 8,
251 |       "attribute_group_count": 2,
252 |       "key_attribute_id": "key_property",
253 |       "content_object": true,
254 |       "queryable": true,
255 |       "read_only": false,
256 |       "creation_date": "2021-01-01T00:00:00.000Z",
257 |       "last_modified": "2024-01-01T00:00:00.000Z"
258 |     },
259 |     {
260 |       "_type": "object_type_definition",
261 |       "object_type": "Content",
262 |       "display_name": {
263 |         "default": "Content"
264 |       },
265 |       "description": {
266 |         "default": "Salesforce B2C Commerce content object definition"
267 |       },
268 |       "attribute_definition_count": 22,
269 |       "attribute_group_count": 4,
270 |       "key_attribute_id": "id",
271 |       "content_object": true,
272 |       "queryable": true,
273 |       "read_only": false,
274 |       "creation_date": "2021-01-01T00:00:00.000Z",
275 |       "last_modified": "2024-01-01T00:00:00.000Z"
276 |     },
277 |     {
278 |       "_type": "object_type_definition",
279 |       "object_type": "ContentFolder",
280 |       "display_name": {
281 |         "default": "Content Folder"
282 |       },
283 |       "description": {
284 |         "default": "Salesforce B2C Commerce content folder object definition"
285 |       },
286 |       "attribute_definition_count": 12,
287 |       "attribute_group_count": 3,
288 |       "key_attribute_id": "id",
289 |       "content_object": true,
290 |       "queryable": true,
291 |       "read_only": false,
292 |       "creation_date": "2021-01-01T00:00:00.000Z",
293 |       "last_modified": "2024-01-01T00:00:00.000Z"
294 |     },
295 |     {
296 |       "_type": "object_type_definition",
297 |       "object_type": "CustomerGroup",
298 |       "display_name": {
299 |         "default": "Customer Group"
300 |       },
301 |       "description": {
302 |         "default": "Salesforce B2C Commerce customer group object definition"
303 |       },
304 |       "attribute_definition_count": 8,
305 |       "attribute_group_count": 2,
306 |       "key_attribute_id": "id",
307 |       "content_object": false,
308 |       "queryable": true,
309 |       "read_only": false,
310 |       "creation_date": "2021-01-01T00:00:00.000Z",
311 |       "last_modified": "2024-01-01T00:00:00.000Z"
312 |     },
313 |     {
314 |       "_type": "object_type_definition",
315 |       "object_type": "SourceCodeInfo",
316 |       "display_name": {
317 |         "default": "Source Code Info"
318 |       },
319 |       "description": {
320 |         "default": "Salesforce B2C Commerce source code information object definition"
321 |       },
322 |       "attribute_definition_count": 6,
323 |       "attribute_group_count": 1,
324 |       "key_attribute_id": "id",
325 |       "content_object": false,
326 |       "queryable": true,
327 |       "read_only": true,
328 |       "creation_date": "2021-01-01T00:00:00.000Z",
329 |       "last_modified": "2024-01-01T00:00:00.000Z"
330 |     },
331 |     {
332 |       "_type": "object_type_definition",
333 |       "object_type": "GiftCertificate",
334 |       "display_name": {
335 |         "default": "Gift Certificate"
336 |       },
337 |       "description": {
338 |         "default": "Salesforce B2C Commerce gift certificate object definition"
339 |       },
340 |       "attribute_definition_count": 14,
341 |       "attribute_group_count": 3,
342 |       "key_attribute_id": "gift_certificate_code",
343 |       "content_object": false,
344 |       "queryable": true,
345 |       "read_only": false,
346 |       "creation_date": "2021-01-01T00:00:00.000Z",
347 |       "last_modified": "2024-01-01T00:00:00.000Z"
348 |     }
349 |   ],
350 |   "next": null,
351 |   "previous": null,
352 |   "start": 0,
353 |   "total": 25
354 | }
```

--------------------------------------------------------------------------------
/docs/dw_catalog/ProductSearchRefinements.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.catalog
  2 | 
  3 | # Class ProductSearchRefinements
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.catalog.SearchRefinements
  9 |   - dw.catalog.ProductSearchRefinements
 10 | 
 11 | ## Description
 12 | 
 13 | 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.
 14 | 
 15 | ## Properties
 16 | 
 17 | ### categoryRefinementDefinition
 18 | 
 19 | **Type:** ProductSearchRefinementDefinition (Read Only)
 20 | 
 21 | The appropriate category refinement definition based on the search
 22 |  result. The category refinement definition returned will be the first that
 23 |  can be found traversing the category tree upward starting at the deepest
 24 |  common category of the search result.
 25 | 
 26 | ### priceRefinementDefinition
 27 | 
 28 | **Type:** ProductSearchRefinementDefinition (Read Only)
 29 | 
 30 | The appropriate price refinement definition based on the search
 31 |  result. The price refinement definition returned will be the first that
 32 |  can be found traversing the category tree upward starting at the deepest
 33 |  common category of the search result.
 34 | 
 35 | ### promotionRefinementDefinition
 36 | 
 37 | **Type:** ProductSearchRefinementDefinition (Read Only)
 38 | 
 39 | The appropriate promotion refinement definition based on the search
 40 |  result. The promotion refinement definition returned will be the first that
 41 |  can be found traversing the category tree upward starting at the deepest
 42 |  common category of the search result.
 43 | 
 44 | ## Constructor Summary
 45 | 
 46 | ## Method Summary
 47 | 
 48 | ### getAllRefinementValues
 49 | 
 50 | **Signature:** `getAllRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
 51 | 
 52 | Returns a sorted collection of refinement values for the passed refinement definition.
 53 | 
 54 | ### getCategoryRefinementDefinition
 55 | 
 56 | **Signature:** `getCategoryRefinementDefinition() : ProductSearchRefinementDefinition`
 57 | 
 58 | Returns the appropriate category refinement definition based on the search result.
 59 | 
 60 | ### getNextLevelCategoryRefinementValues
 61 | 
 62 | **Signature:** `getNextLevelCategoryRefinementValues(category : Category) : Collection`
 63 | 
 64 | Returns category refinement values based on the current search result filtered such that only category refinements representing children of the given category are present.
 65 | 
 66 | ### getPriceRefinementDefinition
 67 | 
 68 | **Signature:** `getPriceRefinementDefinition() : ProductSearchRefinementDefinition`
 69 | 
 70 | Returns the appropriate price refinement definition based on the search result.
 71 | 
 72 | ### getPromotionRefinementDefinition
 73 | 
 74 | **Signature:** `getPromotionRefinementDefinition() : ProductSearchRefinementDefinition`
 75 | 
 76 | Returns the appropriate promotion refinement definition based on the search result.
 77 | 
 78 | ### getRefinementValue
 79 | 
 80 | **Signature:** `getRefinementValue(definition : ProductSearchRefinementDefinition, value : String) : ProductSearchRefinementValue`
 81 | 
 82 | Returns the refinement value (incl.
 83 | 
 84 | ### getRefinementValue
 85 | 
 86 | **Signature:** `getRefinementValue(name : String, value : String) : ProductSearchRefinementValue`
 87 | 
 88 | Returns the refinement value (incl.
 89 | 
 90 | ### getRefinementValues
 91 | 
 92 | **Signature:** `getRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
 93 | 
 94 | Returns a collection of refinement values for the given refinement definition.
 95 | 
 96 | ## Method Detail
 97 | 
 98 | ## Method Details
 99 | 
100 | ### getAllRefinementValues
101 | 
102 | **Signature:** `getAllRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
103 | 
104 | **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.
105 | 
106 | **Parameters:**
107 | 
108 | - `definition`: The refinement definition to return refinement values for. Must not be null.
109 | 
110 | **Returns:**
111 | 
112 | The collection of ProductSearchRefinementValue instances, sorted according to the settings of the refinement definition.
113 | 
114 | ---
115 | 
116 | ### getCategoryRefinementDefinition
117 | 
118 | **Signature:** `getCategoryRefinementDefinition() : ProductSearchRefinementDefinition`
119 | 
120 | **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.
121 | 
122 | **Returns:**
123 | 
124 | The category refinement definition or null if none can be found.
125 | 
126 | ---
127 | 
128 | ### getNextLevelCategoryRefinementValues
129 | 
130 | **Signature:** `getNextLevelCategoryRefinementValues(category : Category) : Collection`
131 | 
132 | **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.
133 | 
134 | **Parameters:**
135 | 
136 | - `category`: The category to return child category refinement values for.
137 | 
138 | **Returns:**
139 | 
140 | The refinement values for all child categories of the given category.
141 | 
142 | ---
143 | 
144 | ### getPriceRefinementDefinition
145 | 
146 | **Signature:** `getPriceRefinementDefinition() : ProductSearchRefinementDefinition`
147 | 
148 | **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.
149 | 
150 | **Returns:**
151 | 
152 | The price refinement definition or null if none can be found.
153 | 
154 | ---
155 | 
156 | ### getPromotionRefinementDefinition
157 | 
158 | **Signature:** `getPromotionRefinementDefinition() : ProductSearchRefinementDefinition`
159 | 
160 | **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.
161 | 
162 | **Returns:**
163 | 
164 | The promotion refinement definition or null if none can be found.
165 | 
166 | ---
167 | 
168 | ### getRefinementValue
169 | 
170 | **Signature:** `getRefinementValue(definition : ProductSearchRefinementDefinition, value : String) : ProductSearchRefinementValue`
171 | 
172 | **Description:** Returns the refinement value (incl. product hit count) for the given refinement definition and the given (selected) value.
173 | 
174 | **Parameters:**
175 | 
176 | - `definition`: The definition to return the refinement for.
177 | - `value`: The value to return the refinement for.
178 | 
179 | **Returns:**
180 | 
181 | The refinement value.
182 | 
183 | ---
184 | 
185 | ### getRefinementValue
186 | 
187 | **Signature:** `getRefinementValue(name : String, value : String) : ProductSearchRefinementValue`
188 | 
189 | **Description:** Returns the refinement value (incl. product hit count) for the given refinement attribute and the given (selected) value.
190 | 
191 | **Parameters:**
192 | 
193 | - `name`: The name of the refinement attribute.
194 | - `value`: The value to return the refinement for.
195 | 
196 | **Returns:**
197 | 
198 | The refinement value.
199 | 
200 | ---
201 | 
202 | ### getRefinementValues
203 | 
204 | **Signature:** `getRefinementValues(definition : ProductSearchRefinementDefinition) : Collection`
205 | 
206 | **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).
207 | 
208 | **Parameters:**
209 | 
210 | - `definition`: The refinement definition to return refinement values for.
211 | 
212 | **Returns:**
213 | 
214 | The collection of refinement values sorted according to the settings of the definition.
215 | 
216 | ---
```

--------------------------------------------------------------------------------
/tests/code-version-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CodeVersionToolHandler } from '../src/core/handlers/code-version-handler.js';
  2 | import { HandlerContext } from '../src/core/handlers/base-handler.js';
  3 | import { Logger } from '../src/utils/logger.js';
  4 | 
  5 | // Mock the OCAPICodeVersionsClient
  6 | const mockCodeVersionsClient = {
  7 |   getCodeVersions: jest.fn(),
  8 |   activateCodeVersion: jest.fn(),
  9 | };
 10 | 
 11 | jest.mock('../src/clients/ocapi/code-versions-client.js', () => ({
 12 |   OCAPICodeVersionsClient: jest.fn(() => mockCodeVersionsClient),
 13 | }));
 14 | 
 15 | describe('CodeVersionToolHandler', () => {
 16 |   let mockLogger: jest.Mocked<Logger>;
 17 |   let mockClient: typeof mockCodeVersionsClient;
 18 |   let context: HandlerContext;
 19 |   let handler: CodeVersionToolHandler;
 20 | 
 21 |   beforeEach(() => {
 22 |     mockLogger = {
 23 |       debug: jest.fn(),
 24 |       log: jest.fn(),
 25 |       error: jest.fn(),
 26 |       timing: jest.fn(),
 27 |       methodEntry: jest.fn(),
 28 |       methodExit: jest.fn(),
 29 |     } as any;
 30 | 
 31 |     // Reset mocks
 32 |     jest.clearAllMocks();
 33 | 
 34 |     // Use the mock client directly and reset it
 35 |     mockClient = mockCodeVersionsClient;
 36 |     mockClient.getCodeVersions.mockReset();
 37 |     mockClient.activateCodeVersion.mockReset();
 38 | 
 39 |     jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
 40 | 
 41 |     context = {
 42 |       logger: mockLogger,
 43 |       config: {
 44 |         hostname: 'test.commercecloud.salesforce.com',
 45 |         clientId: 'test-client-id',
 46 |         clientSecret: 'test-client-secret',
 47 |         siteId: 'test-site',
 48 |       },
 49 |       capabilities: { canAccessLogs: false, canAccessOCAPI: true },
 50 |     };
 51 | 
 52 |     handler = new CodeVersionToolHandler(context, 'CodeVersion');
 53 |   });
 54 | 
 55 |   afterEach(() => {
 56 |     jest.restoreAllMocks();
 57 |   });
 58 | 
 59 |   // Helper function to initialize handler for tests that need it
 60 |   const initializeHandler = async () => {
 61 |     await (handler as any).initialize();
 62 |   };
 63 | 
 64 |   describe('canHandle', () => {
 65 |     it('should handle code version tools', () => {
 66 |       expect(handler.canHandle('get_code_versions')).toBe(true);
 67 |       expect(handler.canHandle('activate_code_version')).toBe(true);
 68 |     });
 69 | 
 70 |     it('should not handle non-code-version tools', () => {
 71 |       expect(handler.canHandle('get_latest_error')).toBe(false);
 72 |       expect(handler.canHandle('unknown_tool')).toBe(false);
 73 |     });
 74 |   });
 75 | 
 76 |   describe('initialization', () => {
 77 |     it('should initialize code versions client when OCAPI access is available', async () => {
 78 |       await initializeHandler();
 79 | 
 80 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
 81 |       expect(MockedConstructor).toHaveBeenCalledWith({
 82 |         hostname: 'test.commercecloud.salesforce.com',
 83 |         clientId: 'test-client-id',
 84 |         clientSecret: 'test-client-secret',
 85 |         version: 'v23_2',
 86 |       });
 87 |       expect(mockLogger.debug).toHaveBeenCalledWith('Code versions client initialized');
 88 |     });
 89 | 
 90 |     it('should not initialize client when OCAPI access is not available', async () => {
 91 |       context.capabilities = { canAccessLogs: false, canAccessOCAPI: false };
 92 |       const handlerWithoutOCAPI = new CodeVersionToolHandler(context, 'CodeVersion');
 93 | 
 94 |       await (handlerWithoutOCAPI as any).initialize();
 95 | 
 96 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
 97 |       expect(MockedConstructor).not.toHaveBeenCalled();
 98 |     });
 99 | 
100 |     it('should not initialize client when config is missing', async () => {
101 |       context.config = null as any;
102 |       const handlerWithoutConfig = new CodeVersionToolHandler(context, 'CodeVersion');
103 | 
104 |       await (handlerWithoutConfig as any).initialize();
105 | 
106 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
107 |       expect(MockedConstructor).not.toHaveBeenCalled();
108 |     });
109 |   });
110 | 
111 |   describe('disposal', () => {
112 |     it('should dispose code versions client properly', async () => {
113 |       await initializeHandler();
114 |       await (handler as any).dispose();
115 | 
116 |       expect(mockLogger.debug).toHaveBeenCalledWith('Code versions client disposed');
117 |     });
118 |   });
119 | 
120 |   describe('get_code_versions tool', () => {
121 |     beforeEach(async () => {
122 |       await initializeHandler();
123 |       mockClient.getCodeVersions.mockResolvedValue([
124 |         { id: 'version_1', active: true, lastModified: '2024-01-01T00:00:00Z' },
125 |         { id: 'version_2', active: false, lastModified: '2024-01-02T00:00:00Z' },
126 |       ]);
127 |     });
128 | 
129 |     it('should handle get_code_versions', async () => {
130 |       const result = await handler.handle('get_code_versions', {}, Date.now());
131 | 
132 |       expect(mockClient.getCodeVersions).toHaveBeenCalled();
133 |       expect(result.content[0].text).toContain('version_1');
134 |       expect(result.content[0].text).toContain('version_2');
135 |     });
136 |   });
137 | 
138 |   describe('activate_code_version tool', () => {
139 |     beforeEach(async () => {
140 |       await initializeHandler();
141 |       mockClient.activateCodeVersion.mockResolvedValue({
142 |         success: true,
143 |         message: 'Code version activated successfully',
144 |         codeVersionId: 'version_2',
145 |       });
146 |     });
147 | 
148 |     it('should handle activate_code_version with codeVersionId', async () => {
149 |       const args = { codeVersionId: 'version_2' };
150 |       const result = await handler.handle('activate_code_version', args, Date.now());
151 | 
152 |       expect(mockClient.activateCodeVersion).toHaveBeenCalledWith('version_2');
153 |       expect(result.content[0].text).toContain('activated successfully');
154 |     });
155 | 
156 |     it('should throw error when codeVersionId is missing', async () => {
157 |       const result = await handler.handle('activate_code_version', {}, Date.now());
158 |       expect(result.isError).toBe(true);
159 |       expect(result.content[0].text).toContain('codeVersionId must be a non-empty string');
160 |     });
161 | 
162 |     it('should throw error when codeVersionId is empty', async () => {
163 |       const result = await handler.handle('activate_code_version', { codeVersionId: '' }, Date.now());
164 |       expect(result.isError).toBe(true);
165 |       expect(result.content[0].text).toContain('codeVersionId must be a non-empty string');
166 |     });
167 |   });
168 | 
169 |   describe('error handling', () => {
170 |     beforeEach(async () => {
171 |       await initializeHandler();
172 |     });
173 | 
174 |     it('should handle client errors gracefully', async () => {
175 |       mockClient.getCodeVersions.mockRejectedValue(new Error('OCAPI connection failed'));
176 | 
177 |       const result = await handler.handle('get_code_versions', {}, Date.now());
178 |       expect(result.isError).toBe(true);
179 |       expect(result.content[0].text).toContain('OCAPI connection failed');
180 |     });
181 | 
182 |     it('should throw error for unsupported tools', async () => {
183 |       await expect(handler.handle('unsupported_tool', {}, Date.now()))
184 |         .rejects.toThrow('Unsupported tool');
185 |     });
186 | 
187 |     it('should handle OCAPI client not configured error', async () => {
188 |       // Create handler without proper configuration
189 |       const contextWithoutOCAPI = {
190 |         ...context,
191 |         capabilities: { canAccessLogs: false, canAccessOCAPI: false },
192 |       };
193 |       const handlerWithoutOCAPI = new CodeVersionToolHandler(contextWithoutOCAPI, 'CodeVersion');
194 | 
195 |       const result = await handlerWithoutOCAPI.handle('get_code_versions', {}, Date.now());
196 |       expect(result.isError).toBe(true);
197 |       expect(result.content[0].text).toContain('OCAPI client not configured');
198 |     });
199 |   });
200 | 
201 |   describe('timing and logging', () => {
202 |     beforeEach(async () => {
203 |       await initializeHandler();
204 |       mockClient.getCodeVersions.mockResolvedValue([]);
205 |     });
206 | 
207 |     it('should log timing information', async () => {
208 |       const startTime = Date.now();
209 |       await handler.handle('get_code_versions', {}, startTime);
210 | 
211 |       expect(mockLogger.timing).toHaveBeenCalledWith('get_code_versions', startTime);
212 |     });
213 | 
214 |     it('should log execution details', async () => {
215 |       await handler.handle('get_code_versions', {}, Date.now());
216 | 
217 |       expect(mockLogger.debug).toHaveBeenCalledWith(
218 |         'get_code_versions completed successfully',
219 |         expect.any(Object),
220 |       );
221 |     });
222 |   });
223 | 
224 |   describe('client integration', () => {
225 |     beforeEach(async () => {
226 |       await initializeHandler();
227 |     });
228 | 
229 |     it('should call getCodeVersions correctly', async () => {
230 |       mockClient.getCodeVersions.mockResolvedValue([
231 |         { id: 'test_version', active: false },
232 |       ]);
233 | 
234 |       await handler.handle('get_code_versions', {}, Date.now());
235 | 
236 |       expect(mockClient.getCodeVersions).toHaveBeenCalledWith();
237 |     });
238 | 
239 |     it('should call activateCodeVersion correctly', async () => {
240 |       mockClient.activateCodeVersion.mockResolvedValue({
241 |         success: true,
242 |         codeVersionId: 'test_version',
243 |       });
244 | 
245 |       await handler.handle('activate_code_version', { codeVersionId: 'test_version' }, Date.now());
246 | 
247 |       expect(mockClient.activateCodeVersion).toHaveBeenCalledWith('test_version');
248 |     });
249 |   });
250 | 
251 |   describe('configuration variations', () => {
252 |     it('should not initialize without hostname', async () => {
253 |       const contextWithoutHostname = {
254 |         ...context,
255 |         config: { ...context.config, hostname: undefined },
256 |       };
257 |       const handlerWithoutHostname = new CodeVersionToolHandler(contextWithoutHostname, 'CodeVersion');
258 | 
259 |       await (handlerWithoutHostname as any).initialize();
260 | 
261 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
262 |       expect(MockedConstructor).not.toHaveBeenCalled();
263 |     });
264 | 
265 |     it('should not initialize without clientId', async () => {
266 |       const contextWithoutClientId = {
267 |         ...context,
268 |         config: { ...context.config, clientId: undefined },
269 |       };
270 |       const handlerWithoutClientId = new CodeVersionToolHandler(contextWithoutClientId, 'CodeVersion');
271 | 
272 |       await (handlerWithoutClientId as any).initialize();
273 | 
274 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
275 |       expect(MockedConstructor).not.toHaveBeenCalled();
276 |     });
277 | 
278 |     it('should not initialize without clientSecret', async () => {
279 |       const contextWithoutClientSecret = {
280 |         ...context,
281 |         config: { ...context.config, clientSecret: undefined },
282 |       };
283 |       const handlerWithoutClientSecret = new CodeVersionToolHandler(contextWithoutClientSecret, 'CodeVersion');
284 | 
285 |       await (handlerWithoutClientSecret as any).initialize();
286 | 
287 |       const MockedConstructor = jest.requireMock('../src/clients/ocapi/code-versions-client.js').OCAPICodeVersionsClient;
288 |       expect(MockedConstructor).not.toHaveBeenCalled();
289 |     });
290 |   });
291 | });
292 | 
```

--------------------------------------------------------------------------------
/tests/log-processor.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { LogProcessor } from '../src/clients/logs/log-processor';
  2 | import { LogEntry, LogFileMetadata, LogLevel } from '../src/clients/logs/log-types';
  3 | import { Logger } from '../src/utils/logger';
  4 | 
  5 | // Mock utils functions
  6 | jest.mock('../src/utils/utils', () => ({
  7 |   parseLogEntries: jest.fn(),
  8 |   extractUniqueErrors: jest.fn(),
  9 |   normalizeFilePath: jest.fn((path: string) => path),
 10 |   extractTimestampFromLogEntry: jest.fn(),
 11 | }));
 12 | 
 13 | import { parseLogEntries, extractTimestampFromLogEntry } from '../src/utils/utils';
 14 | const mockParseLogEntries = parseLogEntries as jest.MockedFunction<typeof parseLogEntries>;
 15 | const mockExtractTimestamp = extractTimestampFromLogEntry as jest.MockedFunction<
 16 |   typeof extractTimestampFromLogEntry
 17 | >;
 18 | 
 19 | describe('LogProcessor', () => {
 20 |   let logProcessor: LogProcessor;
 21 |   let mockLogger: Logger;
 22 | 
 23 |   beforeEach(() => {
 24 |     mockLogger = {
 25 |       methodEntry: jest.fn(),
 26 |       methodExit: jest.fn(),
 27 |       debug: jest.fn(),
 28 |       warn: jest.fn(),
 29 |       error: jest.fn(),
 30 |       timing: jest.fn(),
 31 |       log: jest.fn(),
 32 |       info: jest.fn(),
 33 |       createChildLogger: jest.fn(),
 34 |       setDebugEnabled: jest.fn(),
 35 |       getLogDirectory: jest.fn(),
 36 |     } as unknown as Logger;
 37 | 
 38 |     logProcessor = new LogProcessor(mockLogger);
 39 |     jest.clearAllMocks();
 40 |   });
 41 | 
 42 |   describe('processLogFiles', () => {
 43 |     it('should process files in chronological order by modification date (newest first)', async () => {
 44 |       const files: LogFileMetadata[] = [
 45 |         { filename: 'error-server1-20250819.log', lastmod: '2025-08-19T08:00:00.000Z' }, // oldest
 46 |         { filename: 'error-server2-20250819.log', lastmod: '2025-08-19T10:00:00.000Z' }, // newest
 47 |         { filename: 'error-server3-20250819.log', lastmod: '2025-08-19T09:00:00.000Z' }, // middle
 48 |       ];
 49 | 
 50 |       const fileContents = new Map([
 51 |         ['error-server1-20250819.log', '[2025-08-19T08:30:00.000 GMT] ERROR Class1 - Old error'],
 52 |         ['error-server2-20250819.log', '[2025-08-19T10:30:00.000 GMT] ERROR Class2 - New error'],
 53 |         ['error-server3-20250819.log', '[2025-08-19T09:30:00.000 GMT] ERROR Class3 - Middle error'],
 54 |       ]);
 55 | 
 56 |       // Mock parseLogEntries to return the messages as they appear in content
 57 |       mockParseLogEntries
 58 |         .mockReturnValueOnce(['[2025-08-19T10:30:00.000 GMT] ERROR Class2 - New error']) // newest file processed first
 59 |         .mockReturnValueOnce(['[2025-08-19T09:30:00.000 GMT] ERROR Class3 - Middle error']) // middle file
 60 |         .mockReturnValueOnce(['[2025-08-19T08:30:00.000 GMT] ERROR Class1 - Old error']); // oldest file processed last
 61 | 
 62 |       // Mock extractTimestampFromLogEntry to return appropriate timestamps
 63 |       mockExtractTimestamp
 64 |         .mockReturnValueOnce(new Date('2025-08-19T10:30:00.000Z')) // newest error
 65 |         .mockReturnValueOnce(new Date('2025-08-19T09:30:00.000Z')) // middle error
 66 |         .mockReturnValueOnce(new Date('2025-08-19T08:30:00.000Z')); // oldest error
 67 | 
 68 |       const result = await logProcessor.processLogFiles(files, 'error' as LogLevel, fileContents);
 69 | 
 70 |       // Verify files were processed in chronological order by modification date (newest first)
 71 |       expect(mockParseLogEntries).toHaveBeenNthCalledWith(1, fileContents.get('error-server2-20250819.log'), 'ERROR');
 72 |       expect(mockParseLogEntries).toHaveBeenNthCalledWith(2, fileContents.get('error-server3-20250819.log'), 'ERROR');
 73 |       expect(mockParseLogEntries).toHaveBeenNthCalledWith(3, fileContents.get('error-server1-20250819.log'), 'ERROR');
 74 | 
 75 |       // Verify entries have timestamps extracted and order values assigned
 76 |       expect(result).toHaveLength(3);
 77 |       expect(result[0].timestamp).toEqual(new Date('2025-08-19T10:30:00.000Z')); // newest timestamp
 78 |       expect(result[0].order).toBe(0); // newest file, first entry: 0 * 1000000 + 0 = 0
 79 |       expect(result[1].timestamp).toEqual(new Date('2025-08-19T09:30:00.000Z')); // middle timestamp
 80 |       expect(result[1].order).toBe(1000000); // middle file, first entry: 1 * 1000000 + 0 = 1000000
 81 |       expect(result[2].timestamp).toEqual(new Date('2025-08-19T08:30:00.000Z')); // oldest timestamp
 82 |       expect(result[2].order).toBe(2000000); // oldest file, first entry: 2 * 1000000 + 0 = 2000000
 83 |     });
 84 | 
 85 |     it('should assign correct order values for multiple entries within files', async () => {
 86 |       const files: LogFileMetadata[] = [
 87 |         { filename: 'error-new-20250819.log', lastmod: '2025-08-19T10:00:00.000Z' }, // newer
 88 |         { filename: 'error-old-20250819.log', lastmod: '2025-08-19T08:00:00.000Z' }, // older
 89 |       ];
 90 | 
 91 |       const fileContents = new Map([
 92 |         ['error-new-20250819.log', 'content1'],
 93 |         ['error-old-20250819.log', 'content2'],
 94 |       ]);
 95 | 
 96 |       // Mock parseLogEntries to return multiple entries per file
 97 |       mockParseLogEntries
 98 |         .mockReturnValueOnce([ // newer file (processed first, order starts at 0)
 99 |           '[2025-08-19T10:30:00.000 GMT] ERROR Class1 - First new error',
100 |           '[2025-08-19T10:25:00.000 GMT] ERROR Class2 - Second new error',
101 |         ])
102 |         .mockReturnValueOnce([ // older file (processed second, order starts at 1000)
103 |           '[2025-08-19T08:30:00.000 GMT] ERROR Class3 - First old error',
104 |           '[2025-08-19T08:25:00.000 GMT] ERROR Class4 - Second old error',
105 |         ]);
106 | 
107 |       // Mock extractTimestampFromLogEntry for all entries
108 |       mockExtractTimestamp
109 |         .mockReturnValueOnce(new Date('2025-08-19T10:30:00.000Z')) // First new error
110 |         .mockReturnValueOnce(new Date('2025-08-19T10:25:00.000Z')) // Second new error
111 |         .mockReturnValueOnce(new Date('2025-08-19T08:30:00.000Z')) // First old error
112 |         .mockReturnValueOnce(new Date('2025-08-19T08:25:00.000Z')); // Second old error
113 | 
114 |       const result = await logProcessor.processLogFiles(files, 'error' as LogLevel, fileContents);
115 | 
116 |       expect(result).toHaveLength(4);
117 | 
118 |       // Verify order values and timestamps for all entries
119 |       expect(result[0].order).toBe(0);    // 0 * 1000000 + 0 = 0
120 |       expect(result[0].timestamp).toEqual(new Date('2025-08-19T10:30:00.000Z'));
121 |       expect(result[1].order).toBe(1);    // 0 * 1000000 + 1 = 1
122 |       expect(result[1].timestamp).toEqual(new Date('2025-08-19T10:25:00.000Z'));
123 | 
124 |       expect(result[2].order).toBe(1000000); // 1 * 1000000 + 0 = 1000000
125 |       expect(result[2].timestamp).toEqual(new Date('2025-08-19T08:30:00.000Z'));
126 |       expect(result[3].order).toBe(1000001); // 1 * 1000000 + 1 = 1000001
127 |       expect(result[3].timestamp).toEqual(new Date('2025-08-19T08:25:00.000Z'));
128 |     });
129 |   });
130 | 
131 |   describe('sortAndLimitEntries', () => {
132 |     it('should return the most recent entries by timestamp when limit is applied', () => {
133 |       const entries: LogEntry[] = [
134 |         {
135 |           entry: '[file1] Error 1',
136 |           filename: 'file1.log',
137 |           order: 0,
138 |           timestamp: new Date('2025-08-19T08:00:00.000Z'), // oldest
139 |         },
140 |         {
141 |           entry: '[file1] Error 2',
142 |           filename: 'file1.log',
143 |           order: 1,
144 |           timestamp: new Date('2025-08-19T10:00:00.000Z'), // newest
145 |         },
146 |         {
147 |           entry: '[file2] Error 3',
148 |           filename: 'file2.log',
149 |           order: 1000,
150 |           timestamp: new Date('2025-08-19T09:00:00.000Z'), // middle
151 |         },
152 |       ];
153 | 
154 |       const result = logProcessor.sortAndLimitEntries(entries, 2);
155 | 
156 |       // Should take the 2 most recent entries chronologically (newest first)
157 |       expect(result).toHaveLength(2);
158 |       expect(result[0].entry).toBe('[file1] Error 2'); // 10:00 - newest timestamp
159 |       expect(result[1].entry).toBe('[file2] Error 3'); // 09:00 - second newest
160 |     });
161 | 
162 |     it('should handle limit greater than available entries', () => {
163 |       const entries: LogEntry[] = [
164 |         {
165 |           entry: '[file1] Error 1',
166 |           filename: 'file1.log',
167 |           order: 0,
168 |           timestamp: new Date('2025-08-19T08:00:00.000Z'),
169 |         },
170 |         {
171 |           entry: '[file1] Error 2',
172 |           filename: 'file1.log',
173 |           order: 1,
174 |           timestamp: new Date('2025-08-19T09:00:00.000Z'),
175 |         },
176 |       ];
177 | 
178 |       const result = logProcessor.sortAndLimitEntries(entries, 10);
179 | 
180 |       expect(result).toHaveLength(2);
181 |       expect(result[0].entry).toBe('[file1] Error 2'); // newest first
182 |       expect(result[1].entry).toBe('[file1] Error 1');
183 |     });
184 | 
185 |     it('should handle empty entries array', () => {
186 |       const result = logProcessor.sortAndLimitEntries([], 5);
187 |       expect(result).toHaveLength(0);
188 |     });
189 | 
190 |     it('should fallback to order-based sorting when timestamps are missing', () => {
191 |       const entries: LogEntry[] = [
192 |         { entry: '[file1] Error A', filename: 'file1.log', order: 2 }, // no timestamp
193 |         { entry: '[file1] Error B', filename: 'file1.log', order: 0 }, // no timestamp
194 |         { entry: '[file1] Error C', filename: 'file1.log', order: 1 }, // no timestamp
195 |       ];
196 | 
197 |       const result = logProcessor.sortAndLimitEntries(entries, 3);
198 | 
199 |       expect(result).toHaveLength(3);
200 |       // Should sort by order (ascending) when no timestamps
201 |       expect(result[0].entry).toBe('[file1] Error B'); // order: 0
202 |       expect(result[1].entry).toBe('[file1] Error C'); // order: 1
203 |       expect(result[2].entry).toBe('[file1] Error A'); // order: 2
204 |     });
205 | 
206 |     it('should prioritize entries with timestamps over those without', () => {
207 |       const entries: LogEntry[] = [
208 |         { entry: '[file1] Error A', filename: 'file1.log', order: 0 }, // no timestamp
209 |         {
210 |           entry: '[file1] Error B',
211 |           filename: 'file1.log',
212 |           order: 1000,
213 |           timestamp: new Date('2025-08-19T09:00:00.000Z'), // has timestamp
214 |         },
215 |       ];
216 | 
217 |       const result = logProcessor.sortAndLimitEntries(entries, 2);
218 | 
219 |       expect(result).toHaveLength(2);
220 |       // Entry with timestamp should come first
221 |       expect(result[0].entry).toBe('[file1] Error B');
222 |       expect(result[1].entry).toBe('[file1] Error A');
223 |     });
224 |   });
225 | 
226 |   describe('extractFormattedEntries', () => {
227 |     it('should extract entries from sorted log entries', () => {
228 |       const sortedEntries: LogEntry[] = [
229 |         { entry: '[file1] Error 1', filename: 'file1.log', order: 0 },
230 |         { entry: '[file1] Error 2', filename: 'file1.log', order: 1 },
231 |       ];
232 | 
233 |       const result = logProcessor.extractFormattedEntries(sortedEntries);
234 | 
235 |       expect(result).toEqual(['[file1] Error 1', '[file1] Error 2']);
236 |     });
237 | 
238 |     it('should handle empty array', () => {
239 |       const result = logProcessor.extractFormattedEntries([]);
240 |       expect(result).toEqual([]);
241 |     });
242 |   });
243 | });
244 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/activate-code-version.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # ==================================================================================
  2 | # SFCC MCP Server - activate_code_version Tool YAML Tests (Docs-Only Mode)
  3 | # Validates that code version activation tools are NOT available in docs-only mode
  4 | # This tool requires SFCC credentials and should not be available without them
  5 | #
  6 | # Test Coverage:
  7 | # - Tool unavailability in docs-only mode (not listed in tools/list)
  8 | # - Authentication error responses when tool is called directly
  9 | # - Consistent error messaging across different parameter scenarios
 10 | # - Error structure validation (proper MCP response format)
 11 | # - Performance requirements (fast failure under 1000ms)
 12 | # - Mode verification (ensuring we're actually in docs-only mode)
 13 | # - Authentication error precedence over parameter validation
 14 | # 
 15 | # Quick Test Commands:
 16 | # aegis "tests/mcp/yaml/activate-code-version.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose
 17 | # aegis query activate_code_version 'codeVersionId:test-version-001' --config "aegis.config.docs-only.json"
 18 | # ==================================================================================
 19 | 
 20 | description: "activate_code_version tool docs-only mode tests - Tool unavailability validation"
 21 | 
 22 | tests:
 23 |   # ==================================================================================
 24 |   # TOOL UNAVAILABILITY IN DOCS-ONLY MODE
 25 |   # ==================================================================================
 26 |   - it: "should NOT list activate_code_version tool in docs-only mode"
 27 |     request:
 28 |       jsonrpc: "2.0"
 29 |       id: "tool-not-available-docs"
 30 |       method: "tools/list"
 31 |       params: {}
 32 |     expect:
 33 |       response:
 34 |         jsonrpc: "2.0"
 35 |         id: "tool-not-available-docs"
 36 |         result:
 37 |           tools:
 38 |             match:arrayElements:
 39 |               match:partial:
 40 |                 name: "match:type:string"
 41 |                 description: "match:type:string"
 42 |           match:extractField: "tools.*.name"
 43 |           value: "match:not:arrayContains:activate_code_version"
 44 |       stderr: "toBeEmpty"
 45 | 
 46 |   - it: "should exclude activate_code_version from available tools list"
 47 |     request:
 48 |       jsonrpc: "2.0"
 49 |       id: "excluded-from-list"
 50 |       method: "tools/list"
 51 |       params: {}
 52 |     expect:
 53 |       response:
 54 |         jsonrpc: "2.0"
 55 |         id: "excluded-from-list"
 56 |         result:
 57 |           tools: "match:not:arrayContains:name:activate_code_version"
 58 |       stderr: "toBeEmpty"
 59 | 
 60 |   # ==================================================================================
 61 |   # AUTHENTICATION ERROR TESTS (Tool Can Be Called But Returns Error)
 62 |   # ==================================================================================
 63 |   - it: "should return authentication error when calling activate_code_version in docs-only mode"
 64 |     request:
 65 |       jsonrpc: "2.0"
 66 |       id: "auth-error-default"
 67 |       method: "tools/call"
 68 |       params:
 69 |         name: "activate_code_version"
 70 |         arguments:
 71 |           codeVersionId: "test-version-001"
 72 |     expect:
 73 |       response:
 74 |         jsonrpc: "2.0"
 75 |         id: "auth-error-default"
 76 |         result:
 77 |           content:
 78 |             match:arrayElements:
 79 |               type: "text"
 80 |               text: "match:contains:OCAPI client not configured"
 81 |           isError: true
 82 |       stderr: "toBeEmpty"
 83 |     performance:
 84 |       maxResponseTime: "1000ms"
 85 | 
 86 |   - it: "should return consistent error message for authentication failure"
 87 |     request:
 88 |       jsonrpc: "2.0"
 89 |       id: "auth-error-consistent"
 90 |       method: "tools/call"
 91 |       params:
 92 |         name: "activate_code_version"
 93 |         arguments:
 94 |           codeVersionId: "any-version-id"
 95 |     expect:
 96 |       response:
 97 |         jsonrpc: "2.0"
 98 |         id: "auth-error-consistent"
 99 |         result:
100 |           content:
101 |             match:arrayElements:
102 |               type: "text"
103 |               text: "match:regex:Error[\\s\\S]*OCAPI[\\s\\S]*not configured[\\s\\S]*"
104 |           isError: true
105 |       stderr: "toBeEmpty"
106 |     performance:
107 |       maxResponseTime: "1000ms"
108 | 
109 |   - it: "should return authentication error regardless of code version ID validity"
110 |     request:
111 |       jsonrpc: "2.0"
112 |       id: "auth-error-regardless"
113 |       method: "tools/call"
114 |       params:
115 |         name: "activate_code_version"
116 |         arguments:
117 |           codeVersionId: "invalid@#$%^version"
118 |     expect:
119 |       response:
120 |         jsonrpc: "2.0"
121 |         id: "auth-error-regardless"
122 |         result:
123 |           content:
124 |             match:arrayElements:
125 |               type: "text"
126 |               text: "match:contains:OCAPI client not configured"
127 |           isError: true
128 |       stderr: "toBeEmpty"
129 |     performance:
130 |       maxResponseTime: "1000ms"
131 | 
132 |   # ==================================================================================
133 |   # ERROR RESPONSE VALIDATION
134 |   # ==================================================================================
135 |   - it: "should return proper error structure in docs-only mode"
136 |     request:
137 |       jsonrpc: "2.0"
138 |       id: "error-structure"
139 |       method: "tools/call"
140 |       params:
141 |         name: "activate_code_version"
142 |         arguments:
143 |           codeVersionId: "test-version"
144 |     expect:
145 |       response:
146 |         jsonrpc: "2.0"
147 |         id: "error-structure"
148 |         result:
149 |           content:
150 |             match:arrayElements:
151 |               match:partial:
152 |                 type: "text"
153 |                 text: "match:type:string"
154 |           isError: true
155 |       stderr: "toBeEmpty"
156 | 
157 |   - it: "should include helpful guidance in error message"
158 |     request:
159 |       jsonrpc: "2.0"
160 |       id: "helpful-error"
161 |       method: "tools/call"
162 |       params:
163 |         name: "activate_code_version"
164 |         arguments:
165 |           codeVersionId: "test-guidance"
166 |     expect:
167 |       response:
168 |         jsonrpc: "2.0"
169 |         id: "helpful-error"
170 |         result:
171 |           content:
172 |             match:arrayElements:
173 |               type: "text"
174 |               text: "match:regex:Error[\\s\\S]*credentials[\\s\\S]*full mode[\\s\\S]*"
175 |           isError: true
176 |       stderr: "toBeEmpty"
177 | 
178 |   # ==================================================================================
179 |   # PARAMETER VALIDATION IN DOCS-ONLY MODE
180 |   # ==================================================================================
181 |   - it: "should return authentication error even with missing parameters"
182 |     request:
183 |       jsonrpc: "2.0"
184 |       id: "auth-error-missing-params"
185 |       method: "tools/call"
186 |       params:
187 |         name: "activate_code_version"
188 |         arguments: {}
189 |     expect:
190 |       response:
191 |         jsonrpc: "2.0"
192 |         id: "auth-error-missing-params"
193 |         result:
194 |           content:
195 |             match:arrayElements:
196 |               type: "text"
197 |               text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
198 |           isError: true
199 |       stderr: "toBeEmpty"
200 |     performance:
201 |       maxResponseTime: "1000ms"
202 | 
203 |   - it: "should return authentication error even with empty parameters"
204 |     request:
205 |       jsonrpc: "2.0"
206 |       id: "auth-error-empty-params"
207 |       method: "tools/call"
208 |       params:
209 |         name: "activate_code_version"
210 |         arguments:
211 |           codeVersionId: ""
212 |     expect:
213 |       response:
214 |         jsonrpc: "2.0"
215 |         id: "auth-error-empty-params"
216 |         result:
217 |           content:
218 |             match:arrayElements:
219 |               type: "text"
220 |               text: "match:regex:(OCAPI client not configured|required.*codeVersionId|invalid.*empty)"
221 |           isError: true
222 |       stderr: "toBeEmpty"
223 |     performance:
224 |       maxResponseTime: "1000ms"
225 | 
226 |   # ==================================================================================
227 |   # PERFORMANCE TESTS FOR DOCS-ONLY MODE
228 |   # ==================================================================================
229 |   - it: "should fail fast in docs-only mode"
230 |     request:
231 |       jsonrpc: "2.0"
232 |       id: "fail-fast"
233 |       method: "tools/call"
234 |       params:
235 |         name: "activate_code_version"
236 |         arguments:
237 |           codeVersionId: "performance-test"
238 |     expect:
239 |       response:
240 |         jsonrpc: "2.0"
241 |         id: "fail-fast"
242 |         result:
243 |           content:
244 |             match:arrayElements:
245 |               type: "text"
246 |               text: "match:contains:Error"
247 |           isError: true
248 |       stderr: "toBeEmpty"
249 |     performance:
250 |       maxResponseTime: "500ms"
251 | 
252 |   # ==================================================================================
253 |   # CONSISTENCY TESTS ACROSS DIFFERENT INPUTS
254 |   # ==================================================================================
255 |   - it: "should return consistent auth errors for different valid-looking IDs"
256 |     request:
257 |       jsonrpc: "2.0"
258 |       id: "consistent-auth-1"
259 |       method: "tools/call"
260 |       params:
261 |         name: "activate_code_version"
262 |         arguments:
263 |           codeVersionId: "version-001"
264 |     expect:
265 |       response:
266 |         jsonrpc: "2.0"
267 |         id: "consistent-auth-1"
268 |         result:
269 |           content:
270 |             match:arrayElements:
271 |               type: "text"
272 |               text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
273 |           isError: true
274 |       stderr: "toBeEmpty"
275 | 
276 |   - it: "should return consistent auth errors for different format IDs"
277 |     request:
278 |       jsonrpc: "2.0"
279 |       id: "consistent-auth-2"
280 |       method: "tools/call"
281 |       params:
282 |         name: "activate_code_version"
283 |         arguments:
284 |           codeVersionId: "v1.2.3-release"
285 |     expect:
286 |       response:
287 |         jsonrpc: "2.0"
288 |         id: "consistent-auth-2"
289 |         result:
290 |           content:
291 |             match:arrayElements:
292 |               type: "text"
293 |               text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
294 |           isError: true
295 |       stderr: "toBeEmpty"
296 | 
297 |   # ==================================================================================
298 |   # MODE VERIFICATION
299 |   # ==================================================================================
300 |   - it: "should verify that we're actually in docs-only mode by checking available tools"
301 |     request:
302 |       jsonrpc: "2.0"
303 |       id: "verify-docs-mode"
304 |       method: "tools/list"
305 |       params: {}
306 |     expect:
307 |       response:
308 |         jsonrpc: "2.0"
309 |         id: "verify-docs-mode"
310 |         result:
311 |           tools:
312 |             # Docs-only mode should have documentation tools but not OCAPI tools
313 |             match:extractField: "tools.*.name"
314 |             value: "match:arrayContains:get_sfcc_class_info"
315 |           # And should NOT have code version tools
316 |           match:extractField: "tools.*.name"
317 |           value: "match:not:arrayContains:activate_code_version"
318 |       stderr: "toBeEmpty"
```
Page 19/61FirstPrevNextLast