#
tokens: 47859/50000 15/825 files (page 14/43)
lines: off (toggle) GitHub
raw markdown copy
This is page 14 of 43. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=false&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

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

```typescript
import { LogProcessor } from '../src/clients/logs/log-processor';
import { LogEntry, LogFileMetadata, LogLevel } from '../src/clients/logs/log-types';
import { Logger } from '../src/utils/logger';

// Mock utils functions
jest.mock('../src/utils/utils', () => ({
  parseLogEntries: jest.fn(),
  extractUniqueErrors: jest.fn(),
  normalizeFilePath: jest.fn((path: string) => path),
  extractTimestampFromLogEntry: jest.fn(),
}));

import { parseLogEntries, extractTimestampFromLogEntry } from '../src/utils/utils';
const mockParseLogEntries = parseLogEntries as jest.MockedFunction<typeof parseLogEntries>;
const mockExtractTimestamp = extractTimestampFromLogEntry as jest.MockedFunction<
  typeof extractTimestampFromLogEntry
>;

describe('LogProcessor', () => {
  let logProcessor: LogProcessor;
  let mockLogger: Logger;

  beforeEach(() => {
    mockLogger = {
      methodEntry: jest.fn(),
      methodExit: jest.fn(),
      debug: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      timing: jest.fn(),
      log: jest.fn(),
      info: jest.fn(),
      createChildLogger: jest.fn(),
      setDebugEnabled: jest.fn(),
      getLogDirectory: jest.fn(),
    } as unknown as Logger;

    logProcessor = new LogProcessor(mockLogger);
    jest.clearAllMocks();
  });

  describe('processLogFiles', () => {
    it('should process files in chronological order by modification date (newest first)', async () => {
      const files: LogFileMetadata[] = [
        { filename: 'error-server1-20250819.log', lastmod: '2025-08-19T08:00:00.000Z' }, // oldest
        { filename: 'error-server2-20250819.log', lastmod: '2025-08-19T10:00:00.000Z' }, // newest
        { filename: 'error-server3-20250819.log', lastmod: '2025-08-19T09:00:00.000Z' }, // middle
      ];

      const fileContents = new Map([
        ['error-server1-20250819.log', '[2025-08-19T08:30:00.000 GMT] ERROR Class1 - Old error'],
        ['error-server2-20250819.log', '[2025-08-19T10:30:00.000 GMT] ERROR Class2 - New error'],
        ['error-server3-20250819.log', '[2025-08-19T09:30:00.000 GMT] ERROR Class3 - Middle error'],
      ]);

      // Mock parseLogEntries to return the messages as they appear in content
      mockParseLogEntries
        .mockReturnValueOnce(['[2025-08-19T10:30:00.000 GMT] ERROR Class2 - New error']) // newest file processed first
        .mockReturnValueOnce(['[2025-08-19T09:30:00.000 GMT] ERROR Class3 - Middle error']) // middle file
        .mockReturnValueOnce(['[2025-08-19T08:30:00.000 GMT] ERROR Class1 - Old error']); // oldest file processed last

      // Mock extractTimestampFromLogEntry to return appropriate timestamps
      mockExtractTimestamp
        .mockReturnValueOnce(new Date('2025-08-19T10:30:00.000Z')) // newest error
        .mockReturnValueOnce(new Date('2025-08-19T09:30:00.000Z')) // middle error
        .mockReturnValueOnce(new Date('2025-08-19T08:30:00.000Z')); // oldest error

      const result = await logProcessor.processLogFiles(files, 'error' as LogLevel, fileContents);

      // Verify files were processed in chronological order by modification date (newest first)
      expect(mockParseLogEntries).toHaveBeenNthCalledWith(1, fileContents.get('error-server2-20250819.log'), 'ERROR');
      expect(mockParseLogEntries).toHaveBeenNthCalledWith(2, fileContents.get('error-server3-20250819.log'), 'ERROR');
      expect(mockParseLogEntries).toHaveBeenNthCalledWith(3, fileContents.get('error-server1-20250819.log'), 'ERROR');

      // Verify entries have timestamps extracted and order values assigned
      expect(result).toHaveLength(3);
      expect(result[0].timestamp).toEqual(new Date('2025-08-19T10:30:00.000Z')); // newest timestamp
      expect(result[0].order).toBe(0); // newest file, first entry: 0 * 1000000 + 0 = 0
      expect(result[1].timestamp).toEqual(new Date('2025-08-19T09:30:00.000Z')); // middle timestamp
      expect(result[1].order).toBe(1000000); // middle file, first entry: 1 * 1000000 + 0 = 1000000
      expect(result[2].timestamp).toEqual(new Date('2025-08-19T08:30:00.000Z')); // oldest timestamp
      expect(result[2].order).toBe(2000000); // oldest file, first entry: 2 * 1000000 + 0 = 2000000
    });

    it('should assign correct order values for multiple entries within files', async () => {
      const files: LogFileMetadata[] = [
        { filename: 'error-new-20250819.log', lastmod: '2025-08-19T10:00:00.000Z' }, // newer
        { filename: 'error-old-20250819.log', lastmod: '2025-08-19T08:00:00.000Z' }, // older
      ];

      const fileContents = new Map([
        ['error-new-20250819.log', 'content1'],
        ['error-old-20250819.log', 'content2'],
      ]);

      // Mock parseLogEntries to return multiple entries per file
      mockParseLogEntries
        .mockReturnValueOnce([ // newer file (processed first, order starts at 0)
          '[2025-08-19T10:30:00.000 GMT] ERROR Class1 - First new error',
          '[2025-08-19T10:25:00.000 GMT] ERROR Class2 - Second new error',
        ])
        .mockReturnValueOnce([ // older file (processed second, order starts at 1000)
          '[2025-08-19T08:30:00.000 GMT] ERROR Class3 - First old error',
          '[2025-08-19T08:25:00.000 GMT] ERROR Class4 - Second old error',
        ]);

      // Mock extractTimestampFromLogEntry for all entries
      mockExtractTimestamp
        .mockReturnValueOnce(new Date('2025-08-19T10:30:00.000Z')) // First new error
        .mockReturnValueOnce(new Date('2025-08-19T10:25:00.000Z')) // Second new error
        .mockReturnValueOnce(new Date('2025-08-19T08:30:00.000Z')) // First old error
        .mockReturnValueOnce(new Date('2025-08-19T08:25:00.000Z')); // Second old error

      const result = await logProcessor.processLogFiles(files, 'error' as LogLevel, fileContents);

      expect(result).toHaveLength(4);

      // Verify order values and timestamps for all entries
      expect(result[0].order).toBe(0);    // 0 * 1000000 + 0 = 0
      expect(result[0].timestamp).toEqual(new Date('2025-08-19T10:30:00.000Z'));
      expect(result[1].order).toBe(1);    // 0 * 1000000 + 1 = 1
      expect(result[1].timestamp).toEqual(new Date('2025-08-19T10:25:00.000Z'));

      expect(result[2].order).toBe(1000000); // 1 * 1000000 + 0 = 1000000
      expect(result[2].timestamp).toEqual(new Date('2025-08-19T08:30:00.000Z'));
      expect(result[3].order).toBe(1000001); // 1 * 1000000 + 1 = 1000001
      expect(result[3].timestamp).toEqual(new Date('2025-08-19T08:25:00.000Z'));
    });
  });

  describe('sortAndLimitEntries', () => {
    it('should return the most recent entries by timestamp when limit is applied', () => {
      const entries: LogEntry[] = [
        {
          entry: '[file1] Error 1',
          filename: 'file1.log',
          order: 0,
          timestamp: new Date('2025-08-19T08:00:00.000Z'), // oldest
        },
        {
          entry: '[file1] Error 2',
          filename: 'file1.log',
          order: 1,
          timestamp: new Date('2025-08-19T10:00:00.000Z'), // newest
        },
        {
          entry: '[file2] Error 3',
          filename: 'file2.log',
          order: 1000,
          timestamp: new Date('2025-08-19T09:00:00.000Z'), // middle
        },
      ];

      const result = logProcessor.sortAndLimitEntries(entries, 2);

      // Should take the 2 most recent entries chronologically (newest first)
      expect(result).toHaveLength(2);
      expect(result[0].entry).toBe('[file1] Error 2'); // 10:00 - newest timestamp
      expect(result[1].entry).toBe('[file2] Error 3'); // 09:00 - second newest
    });

    it('should handle limit greater than available entries', () => {
      const entries: LogEntry[] = [
        {
          entry: '[file1] Error 1',
          filename: 'file1.log',
          order: 0,
          timestamp: new Date('2025-08-19T08:00:00.000Z'),
        },
        {
          entry: '[file1] Error 2',
          filename: 'file1.log',
          order: 1,
          timestamp: new Date('2025-08-19T09:00:00.000Z'),
        },
      ];

      const result = logProcessor.sortAndLimitEntries(entries, 10);

      expect(result).toHaveLength(2);
      expect(result[0].entry).toBe('[file1] Error 2'); // newest first
      expect(result[1].entry).toBe('[file1] Error 1');
    });

    it('should handle empty entries array', () => {
      const result = logProcessor.sortAndLimitEntries([], 5);
      expect(result).toHaveLength(0);
    });

    it('should fallback to order-based sorting when timestamps are missing', () => {
      const entries: LogEntry[] = [
        { entry: '[file1] Error A', filename: 'file1.log', order: 2 }, // no timestamp
        { entry: '[file1] Error B', filename: 'file1.log', order: 0 }, // no timestamp
        { entry: '[file1] Error C', filename: 'file1.log', order: 1 }, // no timestamp
      ];

      const result = logProcessor.sortAndLimitEntries(entries, 3);

      expect(result).toHaveLength(3);
      // Should sort by order (ascending) when no timestamps
      expect(result[0].entry).toBe('[file1] Error B'); // order: 0
      expect(result[1].entry).toBe('[file1] Error C'); // order: 1
      expect(result[2].entry).toBe('[file1] Error A'); // order: 2
    });

    it('should prioritize entries with timestamps over those without', () => {
      const entries: LogEntry[] = [
        { entry: '[file1] Error A', filename: 'file1.log', order: 0 }, // no timestamp
        {
          entry: '[file1] Error B',
          filename: 'file1.log',
          order: 1000,
          timestamp: new Date('2025-08-19T09:00:00.000Z'), // has timestamp
        },
      ];

      const result = logProcessor.sortAndLimitEntries(entries, 2);

      expect(result).toHaveLength(2);
      // Entry with timestamp should come first
      expect(result[0].entry).toBe('[file1] Error B');
      expect(result[1].entry).toBe('[file1] Error A');
    });
  });

  describe('extractFormattedEntries', () => {
    it('should extract entries from sorted log entries', () => {
      const sortedEntries: LogEntry[] = [
        { entry: '[file1] Error 1', filename: 'file1.log', order: 0 },
        { entry: '[file1] Error 2', filename: 'file1.log', order: 1 },
      ];

      const result = logProcessor.extractFormattedEntries(sortedEntries);

      expect(result).toEqual(['[file1] Error 1', '[file1] Error 2']);
    });

    it('should handle empty array', () => {
      const result = logProcessor.extractFormattedEntries([]);
      expect(result).toEqual([]);
    });
  });
});

```

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

```yaml
# ==================================================================================
# SFCC MCP Server - activate_code_version Tool YAML Tests (Docs-Only Mode)
# Validates that code version activation tools are NOT available in docs-only mode
# This tool requires SFCC credentials and should not be available without them
#
# Test Coverage:
# - Tool unavailability in docs-only mode (not listed in tools/list)
# - Authentication error responses when tool is called directly
# - Consistent error messaging across different parameter scenarios
# - Error structure validation (proper MCP response format)
# - Performance requirements (fast failure under 1000ms)
# - Mode verification (ensuring we're actually in docs-only mode)
# - Authentication error precedence over parameter validation
# 
# Quick Test Commands:
# aegis "tests/mcp/yaml/activate-code-version.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose
# aegis query activate_code_version 'codeVersionId:test-version-001' --config "aegis.config.docs-only.json"
# ==================================================================================

description: "activate_code_version tool docs-only mode tests - Tool unavailability validation"

tests:
  # ==================================================================================
  # TOOL UNAVAILABILITY IN DOCS-ONLY MODE
  # ==================================================================================
  - it: "should NOT list activate_code_version tool in docs-only mode"
    request:
      jsonrpc: "2.0"
      id: "tool-not-available-docs"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "tool-not-available-docs"
        result:
          tools:
            match:arrayElements:
              match:partial:
                name: "match:type:string"
                description: "match:type:string"
          match:extractField: "tools.*.name"
          value: "match:not:arrayContains:activate_code_version"
      stderr: "toBeEmpty"

  - it: "should exclude activate_code_version from available tools list"
    request:
      jsonrpc: "2.0"
      id: "excluded-from-list"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "excluded-from-list"
        result:
          tools: "match:not:arrayContains:name:activate_code_version"
      stderr: "toBeEmpty"

  # ==================================================================================
  # AUTHENTICATION ERROR TESTS (Tool Can Be Called But Returns Error)
  # ==================================================================================
  - it: "should return authentication error when calling activate_code_version in docs-only mode"
    request:
      jsonrpc: "2.0"
      id: "auth-error-default"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "test-version-001"
    expect:
      response:
        jsonrpc: "2.0"
        id: "auth-error-default"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:OCAPI client not configured"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  - it: "should return consistent error message for authentication failure"
    request:
      jsonrpc: "2.0"
      id: "auth-error-consistent"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "any-version-id"
    expect:
      response:
        jsonrpc: "2.0"
        id: "auth-error-consistent"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:Error[\\s\\S]*OCAPI[\\s\\S]*not configured[\\s\\S]*"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  - it: "should return authentication error regardless of code version ID validity"
    request:
      jsonrpc: "2.0"
      id: "auth-error-regardless"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "invalid@#$%^version"
    expect:
      response:
        jsonrpc: "2.0"
        id: "auth-error-regardless"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:OCAPI client not configured"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  # ==================================================================================
  # ERROR RESPONSE VALIDATION
  # ==================================================================================
  - it: "should return proper error structure in docs-only mode"
    request:
      jsonrpc: "2.0"
      id: "error-structure"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "test-version"
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-structure"
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:type:string"
          isError: true
      stderr: "toBeEmpty"

  - it: "should include helpful guidance in error message"
    request:
      jsonrpc: "2.0"
      id: "helpful-error"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "test-guidance"
    expect:
      response:
        jsonrpc: "2.0"
        id: "helpful-error"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:Error[\\s\\S]*credentials[\\s\\S]*full mode[\\s\\S]*"
          isError: true
      stderr: "toBeEmpty"

  # ==================================================================================
  # PARAMETER VALIDATION IN DOCS-ONLY MODE
  # ==================================================================================
  - it: "should return authentication error even with missing parameters"
    request:
      jsonrpc: "2.0"
      id: "auth-error-missing-params"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "auth-error-missing-params"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  - it: "should return authentication error even with empty parameters"
    request:
      jsonrpc: "2.0"
      id: "auth-error-empty-params"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: ""
    expect:
      response:
        jsonrpc: "2.0"
        id: "auth-error-empty-params"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(OCAPI client not configured|required.*codeVersionId|invalid.*empty)"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  # ==================================================================================
  # PERFORMANCE TESTS FOR DOCS-ONLY MODE
  # ==================================================================================
  - it: "should fail fast in docs-only mode"
    request:
      jsonrpc: "2.0"
      id: "fail-fast"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "performance-test"
    expect:
      response:
        jsonrpc: "2.0"
        id: "fail-fast"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:Error"
          isError: true
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "500ms"

  # ==================================================================================
  # CONSISTENCY TESTS ACROSS DIFFERENT INPUTS
  # ==================================================================================
  - it: "should return consistent auth errors for different valid-looking IDs"
    request:
      jsonrpc: "2.0"
      id: "consistent-auth-1"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "version-001"
    expect:
      response:
        jsonrpc: "2.0"
        id: "consistent-auth-1"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
          isError: true
      stderr: "toBeEmpty"

  - it: "should return consistent auth errors for different format IDs"
    request:
      jsonrpc: "2.0"
      id: "consistent-auth-2"
      method: "tools/call"
      params:
        name: "activate_code_version"
        arguments:
          codeVersionId: "v1.2.3-release"
    expect:
      response:
        jsonrpc: "2.0"
        id: "consistent-auth-2"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(OCAPI client not configured|required.*codeVersionId)"
          isError: true
      stderr: "toBeEmpty"

  # ==================================================================================
  # MODE VERIFICATION
  # ==================================================================================
  - it: "should verify that we're actually in docs-only mode by checking available tools"
    request:
      jsonrpc: "2.0"
      id: "verify-docs-mode"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "verify-docs-mode"
        result:
          tools:
            # Docs-only mode should have documentation tools but not OCAPI tools
            match:extractField: "tools.*.name"
            value: "match:arrayContains:get_sfcc_class_info"
          # And should NOT have code version tools
          match:extractField: "tools.*.name"
          value: "match:not:arrayContains:activate_code_version"
      stderr: "toBeEmpty"
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-custom-object-attribute-definitions.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
description: "Tests for search_custom_object_attribute_definitions tool"
# Run with: npx aegis "tests/mcp/yaml/search-custom-object-attribute-definitions.test.mcp.yml" --config ./aegis.config.with-dw.json

tests:
  # Full Mode Tests (primary tests with mock server)
  - it: "should successfully search custom object attribute definitions with all query parameters"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 1
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
            start: 0
            count: 10
            select: "(**)"
    expect:
      response:
        jsonrpc: "2.0"
        id: 1
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:object_attribute_definition_search_result"
          isError: false
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "2000ms"

  - it: "should return valid JSON structure for custom object attributes"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 2
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 2
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should validate attribute definition structure in response"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 3
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 3
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*\"count\"[\\s\\S]*\"hits\"[\\s\\S]*\"total\"[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include required attribute definition fields"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 4
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 4
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*\"id\"[\\s\\S]*\"value_type\"[\\s\\S]*\"mandatory\"[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should work with minimal search request (missing searchRequest parameter)"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 5
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
    expect:
      response:
        jsonrpc: "2.0"
        id: 5
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:object_attribute_definition_search_result"
          isError: false
      stderr: "toBeEmpty"

  - it: "should handle text search query"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 6
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              text_query:
                fields: ["id", "display_name"]
                search_phrase: "UUID"
            start: 0
            count: 5
    expect:
      response:
        jsonrpc: "2.0"
        id: 6
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:object_attribute_definition_search_result"
          isError: false
      stderr: "toBeEmpty"

  - it: "should handle pagination parameters"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 7
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
            start: 2
            count: 3
    expect:
      response:
        jsonrpc: "2.0"
        id: 7
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*\"start\"\\s*:\\s*2[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should handle sorting parameters"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 8
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
            sorts:
              - field: "id"
                sort_order: "asc"
    expect:
      response:
        jsonrpc: "2.0"
        id: 8
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:object_attribute_definition_search_result"
          isError: false
      stderr: "toBeEmpty"

  # Error Handling Tests (Full Mode)
  - it: "should reject empty objectType parameter"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 9
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: ""
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 9
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:objectType must be a non-empty string"
          isError: true
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "800ms"

  - it: "should handle unknown custom object type"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 10
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "UnknownCustomObject"
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 10
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*ObjectTypeNotFoundException[\\s\\S]*"
          isError: true
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "800ms"

  - it: "should handle invalid search request structure"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 11
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            invalid: "structure"
    expect:
      response:
        jsonrpc: "2.0"
        id: 11
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*PropertyConstraintViolationException[\\s\\S]*"
          isError: true
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "800ms"

  - it: "should reject missing objectType parameter"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 12
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 12
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:objectType"
          isError: true
      stderr: "toBeEmpty"

  # Performance Tests
  - it: "should respond quickly for basic search operations"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 13
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              match_all_query: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: 13
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
          isError: false
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "1500ms"

  - it: "should handle complex queries within reasonable time"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: 14
      params:
        name: "search_custom_object_attribute_definitions"
        arguments:
          objectType: "VersionHistory"
          searchRequest:
            query:
              bool_query:
                must:
                  - text_query:
                      fields: ["id"]
                      search_phrase: "component"
                should:
                  - term_query:
                      fields: ["value_type"]
                      operator: "is"
                      values: ["string"]
            sorts:
              - field: "id"
                sort_order: "desc"
            start: 0
            count: 20
    expect:
      response:
        jsonrpc: "2.0"
        id: 14
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:contains:object_attribute_definition_search_result"
          isError: false
      stderr: "toBeEmpty"
      performance:
        maxResponseTime: "2000ms"
```

--------------------------------------------------------------------------------
/docs/dw_content/Content.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.content

# Class Content

## Inheritance Hierarchy

- Object
  - dw.object.PersistentObject
  - dw.object.ExtensibleObject
    - dw.content.Content

## Description

Class representing a Content asset in Commerce Cloud Digital.

## Properties

### classificationFolder

**Type:** Folder (Read Only)

The Folder associated with this Content. The folder is
 used to determine the classification of the content.

### description

**Type:** String (Read Only)

The description in the current locale or null.

### folders

**Type:** Collection (Read Only)

All folders to which this content is assigned.

### ID

**Type:** String (Read Only)

The ID of the content asset.

### name

**Type:** String (Read Only)

The name of the content asset.

### online

**Type:** boolean (Read Only)

The online status of the content.

### onlineFlag

**Type:** boolean (Read Only)

The online status flag of the content.

### page

**Type:** Page (Read Only)

Returns if the content is a Page or not.

### pageDescription

**Type:** String (Read Only)

The page description for the content in the current locale
 or null if there is no page description.

### pageKeywords

**Type:** String (Read Only)

The page keywords for the content in the current locale
 or null if there is no page title.

### pageMetaTags

**Type:** Array (Read Only)

All page meta tags, defined for this instance for which content can be generated.

 The meta tag content is generated based on the content detail page meta tag context and rules.
 The rules are obtained from the current content or inherited from the default folder,
 up to the root folder.

### pageTitle

**Type:** String (Read Only)

The page title for the content in the current locale
 or null if there is no page title.

### pageURL

**Type:** String (Read Only)

The page URL for the content in the current locale
 or null if there is no page URL.

### searchable

**Type:** boolean (Read Only)

The search status of the content.

### searchableFlag

**Type:** boolean (Read Only)

The online status flag of the content.

### siteMapChangeFrequency

**Type:** String (Read Only)

The contents change frequency needed for the sitemap creation.

### siteMapIncluded

**Type:** Number (Read Only)

The status if the content is included into the sitemap.

### siteMapPriority

**Type:** Number (Read Only)

The contents priority needed for the sitemap creation.
 If no priority is defined, the method returns 0.0.

### template

**Type:** String (Read Only)

The value of attribute 'template'.

## Constructor Summary

## Method Summary

### getClassificationFolder

**Signature:** `getClassificationFolder() : Folder`

Returns the Folder associated with this Content.

### getDescription

**Signature:** `getDescription() : String`

Returns the description in the current locale or null.

### getFolders

**Signature:** `getFolders() : Collection`

Returns all folders to which this content is assigned.

### getID

**Signature:** `getID() : String`

Returns the ID of the content asset.

### getName

**Signature:** `getName() : String`

Returns the name of the content asset.

### getOnlineFlag

**Signature:** `getOnlineFlag() : boolean`

Returns the online status flag of the content.

### getPageDescription

**Signature:** `getPageDescription() : String`

Returns the page description for the content in the current locale or null if there is no page description.

### getPageKeywords

**Signature:** `getPageKeywords() : String`

Returns the page keywords for the content in the current locale or null if there is no page title.

### getPageMetaTag

**Signature:** `getPageMetaTag(id : String) : PageMetaTag`

Returns the page meta tag for the specified id.

### getPageMetaTags

**Signature:** `getPageMetaTags() : Array`

Returns all page meta tags, defined for this instance for which content can be generated.

### getPageTitle

**Signature:** `getPageTitle() : String`

Returns the page title for the content in the current locale or null if there is no page title.

### getPageURL

**Signature:** `getPageURL() : String`

Returns the page URL for the content in the current locale or null if there is no page URL.

### getSearchableFlag

**Signature:** `getSearchableFlag() : boolean`

Returns the online status flag of the content.

### getSiteMapChangeFrequency

**Signature:** `getSiteMapChangeFrequency() : String`

Returns the contents change frequency needed for the sitemap creation.

### getSiteMapIncluded

**Signature:** `getSiteMapIncluded() : Number`

Returns the status if the content is included into the sitemap.

### getSiteMapPriority

**Signature:** `getSiteMapPriority() : Number`

Returns the contents priority needed for the sitemap creation.

### getTemplate

**Signature:** `getTemplate() : String`

Returns the value of attribute 'template'.

### isOnline

**Signature:** `isOnline() : boolean`

Returns the online status of the content.

### isPage

**Signature:** `isPage() : boolean`

Returns if the content is a Page or not.

### isSearchable

**Signature:** `isSearchable() : boolean`

Returns the search status of the content.

### toPage

**Signature:** `toPage() : Page`

Converts the content into the Page representation if isPage() yields true.

## Method Detail

## Method Details

### getClassificationFolder

**Signature:** `getClassificationFolder() : Folder`

**Description:** Returns the Folder associated with this Content. The folder is used to determine the classification of the content.

**Returns:**

the classification Folder.

---

### getDescription

**Signature:** `getDescription() : String`

**Description:** Returns the description in the current locale or null.

**Returns:**

the description in the current locale or null.

---

### getFolders

**Signature:** `getFolders() : Collection`

**Description:** Returns all folders to which this content is assigned.

**Returns:**

Collection of Folder objects.

---

### getID

**Signature:** `getID() : String`

**Description:** Returns the ID of the content asset.

**Returns:**

the ID of the content asset.

---

### getName

**Signature:** `getName() : String`

**Description:** Returns the name of the content asset.

**Returns:**

the name of the content asset.

---

### getOnlineFlag

**Signature:** `getOnlineFlag() : boolean`

**Description:** Returns the online status flag of the content.

**Returns:**

true if the content is online, false otherwise.

---

### getPageDescription

**Signature:** `getPageDescription() : String`

**Description:** Returns the page description for the content in the current locale or null if there is no page description.

**Returns:**

the page description for the content in the current locale or null if there is no page description.

---

### getPageKeywords

**Signature:** `getPageKeywords() : String`

**Description:** Returns the page keywords for the content in the current locale or null if there is no page title.

**Returns:**

the page keywords for the content in the current locale or null if there is no page title.

---

### getPageMetaTag

**Signature:** `getPageMetaTag(id : String) : PageMetaTag`

**Description:** Returns the page meta tag for the specified id. The meta tag content is generated based on the content detail page meta tag context and rule. The rule is obtained from the current content or inherited from the default folder, up to the root folder. Null will be returned if the meta tag is undefined on the current instance, or if no rule can be found for the current context, or if the rule resolves to an empty string.

**Parameters:**

- `id`: the ID to get the page meta tag for

**Returns:**

page meta tag containing content generated based on rules

---

### getPageMetaTags

**Signature:** `getPageMetaTags() : Array`

**Description:** Returns all page meta tags, defined for this instance for which content can be generated. The meta tag content is generated based on the content detail page meta tag context and rules. The rules are obtained from the current content or inherited from the default folder, up to the root folder.

**Returns:**

page meta tags defined for this instance, containing content generated based on rules

---

### getPageTitle

**Signature:** `getPageTitle() : String`

**Description:** Returns the page title for the content in the current locale or null if there is no page title.

**Returns:**

the page title for the content in the current locale or null if there is no page title.

---

### getPageURL

**Signature:** `getPageURL() : String`

**Description:** Returns the page URL for the content in the current locale or null if there is no page URL.

**Returns:**

the page URL for the content in the current locale or null if there is no page URL.

---

### getSearchableFlag

**Signature:** `getSearchableFlag() : boolean`

**Description:** Returns the online status flag of the content.

**Returns:**

true if the content is searchable, false otherwise.

---

### getSiteMapChangeFrequency

**Signature:** `getSiteMapChangeFrequency() : String`

**Description:** Returns the contents change frequency needed for the sitemap creation.

**Returns:**

The contents sitemap change frequency.

---

### getSiteMapIncluded

**Signature:** `getSiteMapIncluded() : Number`

**Description:** Returns the status if the content is included into the sitemap.

**Returns:**

the value of the attribute 'siteMapIncluded'

---

### getSiteMapPriority

**Signature:** `getSiteMapPriority() : Number`

**Description:** Returns the contents priority needed for the sitemap creation. If no priority is defined, the method returns 0.0.

**Returns:**

The contents sitemap priority.

---

### getTemplate

**Signature:** `getTemplate() : String`

**Description:** Returns the value of attribute 'template'.

**Returns:**

the value of the attribute 'template'

---

### isOnline

**Signature:** `isOnline() : boolean`

**Description:** Returns the online status of the content.

**Returns:**

true if the content is online, false otherwise.

---

### isPage

**Signature:** `isPage() : boolean`

**Description:** Returns if the content is a Page or not.

**Returns:**

true if the content is a Page, false otherwise.

---

### isSearchable

**Signature:** `isSearchable() : boolean`

**Description:** Returns the search status of the content.

**Returns:**

true if the content is searchable, false otherwise.

---

### toPage

**Signature:** `toPage() : Page`

**Description:** Converts the content into the Page representation if isPage() yields true.

**Returns:**

the Page representation of the content if it is a page, null otherwise.

**See Also:**

PageMgr.getPage(String)

---
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-sfra-documentation.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
description: "Tests for search_sfra_documentation tool in docs-only mode"

tests:
  - it: "should return structured search results for valid query 'render'"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-1"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "render"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-1"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should validate search result structure contains JSON array with documents"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-2"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "server"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-2"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:document"
      stderr: "toBeEmpty"

  - it: "should include relevance scores and matches in search results"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-3"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "response"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-3"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:relevanceScore)[\\s\\S]*(?:matches)"
      stderr: "toBeEmpty"

  - it: "should return empty array for query with no matches"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-4"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "zzznothingfound"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-4"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:^\\[\\s*\\]$"
      stderr: "toBeEmpty"

  - it: "should handle empty query with validation error"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-5"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: ""
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-5"
        result:
          isError: true
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:query must be a non-empty string"
      stderr: "toBeEmpty"

  - it: "should reject missing query parameter"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-6"
      params:
        name: "search_sfra_documentation"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-6"
        result:
          isError: true
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:query must be a non-empty string"
      stderr: "toBeEmpty"

  - it: "should return results with document categories for complex queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-7"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "product"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-7"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:category)[\\s\\S]*(?:core|product|order|customer|pricing|store|other)"
      stderr: "toBeEmpty"

  - it: "should include document types in search results"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-8"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "model"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-8"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:type)[\\s\\S]*(?:class|module|model)"
      stderr: "toBeEmpty"

  - it: "should respond within reasonable time for complex search"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-9"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "cart billing shipping"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-9"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      performance:
        maxResponseTime: "2000ms"
      stderr: "toBeEmpty"

  - it: "should handle special characters in search query"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-10"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "dw.util"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-10"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should handle very long search queries efficiently"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-11"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "template rendering isml view data processing controller middleware response request server router"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-11"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      performance:
        maxResponseTime: "3000ms"
      stderr: "toBeEmpty"

  - it: "should return results for single character queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-12"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "a"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-12"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should handle numeric search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-13"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "200"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-13"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should return consistent results for same query"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-14"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "cart"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-14"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:relevanceScore"
      stderr: "toBeEmpty"

  - it: "should handle case-insensitive searches"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-15"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "PRODUCT"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-15"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should find results for core SFRA concepts"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-16"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "middleware"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-16"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:matches)[\\s\\S]*(?:section)[\\s\\S]*(?:content)"
      stderr: "toBeEmpty"

  - it: "should handle hyphenated search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-17"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "product-full"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-17"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should return structured results for pricing queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-18"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "price"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-18"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:category)[\\s\\S]*(?:pricing|product|core)"
      stderr: "toBeEmpty"

  - it: "should handle underscore in search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-19"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "view_data"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-19"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should respond quickly for common search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-20"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "model"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-20"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:document"
      performance:
        maxResponseTime: "800ms"
      stderr: "toBeEmpty"

```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-sfra-documentation.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
description: "Tests for search_sfra_documentation tool in full-mode mode"

tests:
  - it: "should return structured search results for valid query 'render'"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-1"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "render"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-1"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should validate search result structure contains JSON array with documents"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-2"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "server"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-2"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:document"
      stderr: "toBeEmpty"

  - it: "should include relevance scores and matches in search results"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-3"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "response"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-3"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:relevanceScore)[\\s\\S]*(?:matches)"
      stderr: "toBeEmpty"

  - it: "should return empty array for query with no matches"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-4"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "zzznothingfound"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-4"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:^\\[\\s*\\]$"
      stderr: "toBeEmpty"

  - it: "should handle empty query with validation error"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-5"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: ""
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-5"
        result:
          isError: true
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:query must be a non-empty string"
      stderr: "toBeEmpty"

  - it: "should reject missing query parameter"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-6"
      params:
        name: "search_sfra_documentation"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-6"
        result:
          isError: true
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:query must be a non-empty string"
      stderr: "toBeEmpty"

  - it: "should return results with document categories for complex queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-7"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "product"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-7"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:category)[\\s\\S]*(?:core|product|order|customer|pricing|store|other)"
      stderr: "toBeEmpty"

  - it: "should include document types in search results"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-8"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "model"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-8"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:type)[\\s\\S]*(?:class|module|model)"
      stderr: "toBeEmpty"

  - it: "should respond within reasonable time for complex search"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-9"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "cart billing shipping"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-9"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      performance:
        maxResponseTime: "2000ms"
      stderr: "toBeEmpty"

  - it: "should handle special characters in search query"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-10"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "dw.util"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-10"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should handle very long search queries efficiently"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-11"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "template rendering isml view data processing controller middleware response request server router"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-11"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      performance:
        maxResponseTime: "3000ms"
      stderr: "toBeEmpty"

  - it: "should return results for single character queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-12"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "a"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-12"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should handle numeric search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-13"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "200"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-13"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should return consistent results for same query"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-14"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "cart"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-14"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:relevanceScore"
      stderr: "toBeEmpty"

  - it: "should handle case-insensitive searches"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-15"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "PRODUCT"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-15"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:\\[[\\s\\S]*\\]"
      stderr: "toBeEmpty"

  - it: "should find results for core SFRA concepts"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-16"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "middleware"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-16"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:matches)[\\s\\S]*(?:section)[\\s\\S]*(?:content)"
      stderr: "toBeEmpty"

  - it: "should handle hyphenated search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-17"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "product-full"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-17"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should return structured results for pricing queries"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-18"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "price"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-18"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:regex:(?:category)[\\s\\S]*(?:pricing|product|core)"
      stderr: "toBeEmpty"

  - it: "should handle underscore in search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-19"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "view_data"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-19"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
      stderr: "toBeEmpty"

  - it: "should respond quickly for common search terms"
    request:
      jsonrpc: "2.0"
      method: "tools/call"
      id: "test-20"
      params:
        name: "search_sfra_documentation"
        arguments:
          query: "model"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-20"
        result:
          isError: false
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:document"
      performance:
        maxResponseTime: "800ms"
      stderr: "toBeEmpty"

```

--------------------------------------------------------------------------------
/tests/logger.test.ts:
--------------------------------------------------------------------------------

```typescript
import { Logger } from '../src/utils/logger';
import { existsSync, readFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';

describe('Logger', () => {
  let logger: Logger;
  let testLogDir: string;

  beforeEach(() => {
    // Create a unique test log directory for each test
    testLogDir = join(tmpdir(), `sfcc-mcp-logs-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);

    // Clean up if directory somehow exists
    if (existsSync(testLogDir)) {
      rmSync(testLogDir, { recursive: true, force: true });
    }
  });

  afterEach(() => {
    // Clean up test log directory
    if (existsSync(testLogDir)) {
      rmSync(testLogDir, { recursive: true, force: true });
    }
  });

  describe('constructor', () => {
    it('should create logger and create log directory', () => {
      logger = new Logger('TEST', true, false, testLogDir);

      expect(existsSync(testLogDir)).toBe(true);
      expect(logger.getLogDirectory()).toBe(testLogDir);
    });

    it('should create logger with custom context and write logs correctly', () => {
      logger = new Logger('CUSTOM-CONTEXT', true, false, testLogDir);

      logger.log('test message');

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[CUSTOM-CONTEXT\] test message\n$/);
    });

    it('should create logger with timestamp disabled', () => {
      logger = new Logger('TEST', false, false, testLogDir);

      logger.log('test message');

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toBe('[TEST] test message\n');
    });

    it('should create logger with debug enabled', () => {
      logger = new Logger('TEST', true, true, testLogDir);

      logger.debug('debug message');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(
        /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/,
      );
    });
  });

  describe('logging methods', () => {
    beforeEach(() => {
      logger = new Logger('TEST', true, false, testLogDir);
    });

    it('should write info messages to info log file', () => {
      logger.info('info message');

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] info message\n$/);
    });

    it('should write log messages to info log file', () => {
      logger.log('log message');

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] log message\n$/);
    });

    it('should write warning messages to warn log file', () => {
      logger.warn('warning message');

      const logFile = join(testLogDir, 'sfcc-mcp-warn.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] warning message\n$/);
    });

    it('should write error messages to error log file', () => {
      logger.error('error message');

      const logFile = join(testLogDir, 'sfcc-mcp-error.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] error message\n$/);
    });

    it('should write debug messages to debug log file when debug is enabled', () => {
      logger = new Logger('TEST', true, true, testLogDir);
      logger.debug('debug message');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(
        /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/,
      );
    });

    it('should not write debug messages when debug is disabled', () => {
      logger = new Logger('TEST', true, false, testLogDir);
      logger.debug('debug message');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(false);
    });

    it('should handle additional arguments', () => {
      const testObject = { key: 'value', number: 42 };
      logger.info('message with args', 'string arg', testObject);

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('message with args');
      expect(logContent).toContain('string arg');
      expect(logContent).toContain('"key": "value"');
      expect(logContent).toContain('"number": 42');
    });
  });

  describe('debug logging methods', () => {
    beforeEach(() => {
      logger = new Logger('TEST', true, true, testLogDir);
    });

    it('should log method entry', () => {
      logger.methodEntry('testMethod', { param1: 'value1' });

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[DEBUG] Entering method: testMethod with params:');
      expect(logContent).toContain('"param1":"value1"');
    });

    it('should log method entry without params', () => {
      logger.methodEntry('testMethod');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[DEBUG] Entering method: testMethod');
      expect(logContent).not.toContain('with params:');
    });

    it('should log method exit', () => {
      logger.methodExit('testMethod', { result: 'success' });

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[DEBUG] Exiting method: testMethod with result:');
      expect(logContent).toContain('"result":"success"');
    });

    it('should log method exit without result', () => {
      logger.methodExit('testMethod');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[DEBUG] Exiting method: testMethod');
      expect(logContent).not.toContain('with result:');
    });

    it('should log timing information', () => {
      const startTime = Date.now() - 100; // Simulate 100ms operation
      logger.timing('testOperation', startTime);

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toMatch(/\[DEBUG\] Performance: testOperation took \d+ms/);
    });
  });

  describe('utility methods', () => {
    beforeEach(() => {
      logger = new Logger('TEST', true, false, testLogDir);
    });

    it('should create child logger with combined context', () => {
      const childLogger = logger.createChildLogger('CHILD');
      childLogger.info('child message');

      const logFile = join(testLogDir, 'sfcc-mcp-info.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[TEST:CHILD] child message');
    });

    it('should enable debug logging dynamically', () => {
      logger.setDebugEnabled(true);
      logger.debug('now debug is enabled');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(true);

      const logContent = readFileSync(logFile, 'utf8');
      expect(logContent).toContain('[DEBUG] now debug is enabled');
    });

    it('should disable debug logging dynamically', () => {
      logger = new Logger('TEST', true, true, testLogDir);
      logger.setDebugEnabled(false);
      logger.debug('this should not appear');

      const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
      expect(existsSync(logFile)).toBe(false);
    });

    it('should return log directory path', () => {
      const logDirectory = logger.getLogDirectory();
      expect(logDirectory).toBe(testLogDir);
      expect(existsSync(logDirectory)).toBe(true);
    });
  });

  describe('error handling', () => {
    it('should handle file write errors gracefully', () => {
      // Mock appendFileSync to throw an error
      // eslint-disable-next-line @typescript-eslint/no-require-imports
      const fsMock = jest.spyOn(require('fs'), 'appendFileSync');
      fsMock.mockImplementation(() => {
        throw new Error('File write error');
      });

      // Mock stderr.write to capture fallback behavior
      const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);

      logger = new Logger('TEST', true, false, testLogDir);
      logger.error('test error message');

      // Should have attempted to write to stderr as fallback
      expect(stderrSpy).toHaveBeenCalledWith('[LOGGER ERROR] Could not write to log file: Error: File write error\n');
      expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('test error message'));

      stderrSpy.mockRestore();
      fsMock.mockRestore();
    });

    it('should not fallback to stderr for non-error log levels', () => {
      // Mock appendFileSync to throw an error
      // eslint-disable-next-line @typescript-eslint/no-require-imports
      const fsMock = jest.spyOn(require('fs'), 'appendFileSync');
      fsMock.mockImplementation(() => {
        throw new Error('File write error');
      });

      // Mock stderr.write to capture fallback behavior
      const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);

      logger = new Logger('TEST', true, false, testLogDir);
      logger.info('test info message');

      // Should not have written to stderr for non-error levels
      expect(stderrSpy).not.toHaveBeenCalled();

      stderrSpy.mockRestore();
      fsMock.mockRestore();
    });
  });
});

```

--------------------------------------------------------------------------------
/docs/sfra/server.md:
--------------------------------------------------------------------------------

```markdown
# Class Server

## Inheritance Hierarchy

- Object
  - sfra.models.Server

## Description

The SFRA Server class is the core routing solution for Salesforce Commerce Cloud's Storefront Reference Architecture (SFRA). This class provides a comprehensive middleware-based routing system that handles HTTP requests, manages route registration, and coordinates the request-response lifecycle. The Server class supports route definition with middleware chains, HTTP method shortcuts (GET/POST), route modification capabilities (prepend/append/replace), and integration with SFCC's hook system for extensibility. It serves as the foundation for all SFRA controller functionality, providing a structured approach to handling web requests with support for caching, personalization, redirects, and various rendering strategies.

## Properties

### routes

**Type:** Object

Internal registry of all registered routes, indexed by route name.

## Constructor Summary

### Server

**Signature:** `Server()`

Creates a new Server instance with an empty routes registry.

## Method Summary

### use

**Signature:** `use(name, ...middleware) : Route`

Creates a new route with middleware chain.

### get

**Signature:** `get(name, ...middleware) : Route`

Creates a GET-specific route with automatic HTTP method validation.

### post

**Signature:** `post(name, ...middleware) : Route`

Creates a POST-specific route with automatic HTTP method validation.

### exports

**Signature:** `exports() : Object`

Returns an exportable object containing all registered routes.

### extend

**Signature:** `extend(server) : void`

Extends the current server with routes from another server object.

### prepend

**Signature:** `prepend(name, ...middleware) : void`

Adds middleware to the beginning of an existing route's chain.

### append

**Signature:** `append(name, ...middleware) : void`

Adds middleware to the end of an existing route's chain.

### replace

**Signature:** `replace(name, ...middleware) : void`

Replaces an existing route with a new middleware chain.

### getRoute

**Signature:** `getRoute(name) : Route`

Retrieves a specific route by name.

## Method Detail

### use

**Signature:** `use(name, ...middleware) : Route`

**Description:** Creates a new route with the specified name and middleware chain. Automatically creates Request and Response objects from global SFCC objects and sets up route completion handling including caching, personalization, and rendering.

**Parameters:**
- `name` (String) - Unique name for the route
- `...middleware` (Function[]) - Variable number of middleware functions to execute

**Returns:**
Route object representing the created route.

**Throws:**
- Error if name is not a string
- Error if middleware contains non-function items
- Error if route name already exists

**Route Completion Handling:**
- Sets cache expiration based on `res.cachePeriod` and `res.cachePeriodUnit`
- Applies personalization with `price_promotion` vary-by header when `res.personalized` is true
- Handles redirects with optional status codes
- Applies rendering through the render system

### get

**Signature:** `get(name, ...middleware) : Route`

**Description:** Convenience method for creating GET-only routes. Automatically prepends HTTP GET validation middleware to the chain.

**Parameters:**
- `name` (String) - Unique name for the route
- `...middleware` (Function[]) - Variable number of middleware functions

**Returns:**
Route object with GET method validation.

### post

**Signature:** `post(name, ...middleware) : Route`

**Description:** Convenience method for creating POST-only routes. Automatically prepends HTTP POST validation middleware to the chain.

**Parameters:**
- `name` (String) - Unique name for the route
- `...middleware` (Function[]) - Variable number of middleware functions

**Returns:**
Route object with POST method validation.

### exports

**Signature:** `exports() : Object`

**Description:** Creates an exportable object containing all registered routes with their public interfaces. Includes internal routes registry for extension capabilities.

**Returns:**
Object with route names as keys and route interfaces as values, plus `__routes` property containing internal routes.

**Export Format:**
```javascript
{
  "routeName": { /* route interface */ },
  "__routes": { /* internal routes registry */ }
}
```

### extend

**Signature:** `extend(server) : void`

**Description:** Extends the current server with routes from another server object created by the `exports()` method. Validates the server object structure before extension.

**Parameters:**
- `server` (Object) - Server object created by `exports()` method

**Throws:**
- Error if server object is invalid (missing `__routes`)
- Error if server has no routes to extend

### prepend

**Signature:** `prepend(name, ...middleware) : void`

**Description:** Adds middleware functions to the beginning of an existing route's middleware chain, allowing for route enhancement and modification.

**Parameters:**
- `name` (String) - Name of existing route to modify
- `...middleware` (Function[]) - Middleware functions to prepend

**Throws:**
- Error if name is not a string
- Error if middleware contains non-function items
- Error if route does not exist

### append

**Signature:** `append(name, ...middleware) : void`

**Description:** Adds middleware functions to the end of an existing route's middleware chain, enabling route extension and additional processing.

**Parameters:**
- `name` (String) - Name of existing route to modify
- `...middleware` (Function[]) - Middleware functions to append

**Throws:**
- Error if name is not a string
- Error if middleware contains non-function items
- Error if route does not exist

### replace

**Signature:** `replace(name, ...middleware) : void`

**Description:** Completely replaces an existing route with a new middleware chain, maintaining the same route name but changing implementation.

**Parameters:**
- `name` (String) - Name of existing route to replace
- `...middleware` (Function[]) - New middleware functions for the route

**Throws:**
- Error if name is not a string
- Error if middleware contains non-function items
- Error if route does not exist

### getRoute

**Signature:** `getRoute(name) : Route`

**Description:** Retrieves a specific route object by name for inspection or direct manipulation.

**Parameters:**
- `name` (String) - Name of the route to retrieve

**Returns:**
Route object if found, undefined otherwise.

## Route Lifecycle Management

### Route Creation Process

1. **Parameter Validation**: Validates route name and middleware chain
2. **Request/Response Creation**: Creates SFRA Request and Response objects from globals
3. **Route Object Creation**: Instantiates Route with name, middleware, request, and response
4. **Event Handler Registration**: Sets up `route:Complete` event handler
5. **Route Registration**: Stores route in internal registry
6. **Hook Integration**: Calls `app.server.registerRoute` hook if available

### Route Completion Handling

**Cache Management:**
- Applies cache expiration when `res.cachePeriod` is set
- Supports minutes or hours as cache period units (defaults to hours)
- Sets HTTP expires header on the response

**Personalization:**
- Sets `price_promotion` vary-by header when `res.personalized` is true
- Enables proper caching behavior for personalized content

**Redirect Processing:**
- Handles redirect URLs with optional status codes
- Emits `route:Redirect` event before redirecting
- Supports both default and custom redirect status codes

**Rendering Pipeline:**
- Applies all accumulated renderings through the render system
- Processes ISML templates, JSON responses, XML output, and Page Designer pages

## Integration Points

### Global Object Integration

**Request Object Creation:**
```javascript
var rq = new Request(
    typeof request !== 'undefined' ? request : {},
    typeof customer !== 'undefined' ? customer : {},
    typeof session !== 'undefined' ? session : {}
);
```

**Response Object Creation:**
```javascript
var rs = new Response(typeof response !== 'undefined' ? response : {});
```

### Hook System Integration

The server integrates with SFCC's hook system through the `app.server.registerRoute` hook, allowing:
- Custom route event handlers
- Route-specific processing extensions
- Integration with custom middleware systems

### Middleware Architecture

**Middleware Function Signature:**
```javascript
function middleware(req, res, next) {
    // Middleware logic
    next(); // Continue to next middleware
}
```

**Middleware Chain Execution:**
- Sequential execution of middleware functions
- Support for asynchronous operations through next() callbacks
- Automatic error handling and route completion

## Usage Examples

### Basic Route Creation
```javascript
server.use('HomePage', function(req, res, next) {
    res.render('home/homepage', { title: 'Welcome' });
    next();
});
```

### HTTP Method-Specific Routes
```javascript
server.get('ProductShow', middleware.cache, function(req, res, next) {
    // GET-only route with caching
    next();
});

server.post('CartAdd', middleware.csrf, function(req, res, next) {
    // POST-only route with CSRF protection
    next();
});
```

### Route Modification
```javascript
// Add authentication to existing route
server.prepend('Account-Show', middleware.auth);

// Add logging to existing route
server.append('Checkout-Begin', middleware.logging);

// Replace route implementation
server.replace('Search-Show', newSearchMiddleware);
```

### Server Extension
```javascript
// Export routes from one server
var exportedRoutes = server.exports();

// Extend another server
var newServer = new Server();
newServer.extend(exportedRoutes);
```

## Error Handling

### Parameter Validation Errors

- **Invalid Route Name**: Throws error if first parameter is not a string
- **Invalid Middleware**: Throws error if middleware chain contains non-functions
- **Duplicate Route**: Throws error if route name already exists

### Extension Errors

- **Invalid Server Object**: Throws error for objects missing `__routes` property
- **Empty Server**: Throws error when extending with server that has no routes

### Route Modification Errors

- **Non-Existent Route**: Throws error when trying to modify routes that don't exist
- **Invalid Parameters**: Validates parameters before attempting modifications

## Property Details

### routes

**Type:** Object

**Description:** Internal registry storing all route definitions indexed by route name. Each entry contains a Route object with the complete middleware chain and configuration.

**Structure:**
```javascript
{
  "routeName": Route, // Route object instance
  "anotherRoute": Route
}
```

This registry enables route lookup, modification, and export functionality while maintaining the complete route configuration and state.

---

```

--------------------------------------------------------------------------------
/tests/system-objects-client.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for OCAPISystemObjectsClient
 * Tests system objects operations
 */

import { OCAPISystemObjectsClient } from '../src/clients/ocapi/system-objects-client.js';
import { OCAPIConfig } from '../src/types/types.js';
import { QueryBuilder } from '../src/utils/query-builder.js';
import { Validator } from '../src/utils/validator.js';

// Mock dependencies
jest.mock('../src/clients/base/ocapi-auth-client.js');
jest.mock('../src/utils/query-builder.js');
jest.mock('../src/utils/validator.js');

describe('OCAPISystemObjectsClient', () => {
  let client: OCAPISystemObjectsClient;
  let mockValidateRequired: jest.MockedFunction<typeof Validator.validateRequired>;
  let mockValidateObjectType: jest.MockedFunction<typeof Validator.validateObjectType>;
  let mockValidateSearchRequest: jest.MockedFunction<typeof Validator.validateSearchRequest>;
  let mockQueryBuilderFromObject: jest.MockedFunction<typeof QueryBuilder.fromObject>;

  const mockConfig: OCAPIConfig = {
    hostname: 'test-instance.demandware.net',
    clientId: 'test-client-id',
    clientSecret: 'test-client-secret',
    version: 'v21_3',
  };

  beforeEach(() => {
    jest.clearAllMocks();

    // Mock Validator methods
    mockValidateRequired = Validator.validateRequired as jest.MockedFunction<typeof Validator.validateRequired>;
    mockValidateObjectType = Validator.validateObjectType as jest.MockedFunction<typeof Validator.validateObjectType>;
    mockValidateSearchRequest = Validator.validateSearchRequest as jest.MockedFunction<
        typeof Validator.validateSearchRequest
    >;

    // Reset mock implementations to default behavior
    mockValidateRequired.mockImplementation(() => {});
    mockValidateObjectType.mockImplementation(() => {});
    mockValidateSearchRequest.mockImplementation(() => {});

    // Mock QueryBuilder
    mockQueryBuilderFromObject = QueryBuilder.fromObject as jest.MockedFunction<typeof QueryBuilder.fromObject>;

    client = new OCAPISystemObjectsClient(mockConfig);

    // Mock the inherited methods by adding them as properties - avoid protected access
    (client as any).get = jest.fn().mockResolvedValue({ data: 'mocked' });
    (client as any).post = jest.fn().mockResolvedValue({ data: 'mocked' });
  });

  describe('constructor', () => {
    it('should initialize with correct base URL', () => {
      expect(client).toBeInstanceOf(OCAPISystemObjectsClient);
    });

    it('should use default version when not provided', () => {
      const configWithoutVersion = {
        hostname: 'test.demandware.net',
        clientId: 'client-id',
        clientSecret: 'client-secret',
      };

      const clientWithDefaults = new OCAPISystemObjectsClient(configWithoutVersion);
      expect(clientWithDefaults).toBeInstanceOf(OCAPISystemObjectsClient);
    });
  });

  describe('getSystemObjectDefinitions', () => {
    it('should make GET request to system_object_definitions endpoint', async () => {
      await client.getSystemObjectDefinitions();

      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
    });

    it('should include query parameters when provided', async () => {
      const params = { start: 0, count: 10, select: '(**)' };
      mockQueryBuilderFromObject.mockReturnValue('start=0&count=10&select=%28%2A%2A%29');

      await client.getSystemObjectDefinitions(params);

      expect(QueryBuilder.fromObject).toHaveBeenCalledWith(params);
      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions?start=0&count=10&select=%28%2A%2A%29');
    });

    it('should not include query string when no parameters provided', async () => {
      mockQueryBuilderFromObject.mockReturnValue('');

      await client.getSystemObjectDefinitions({});

      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
    });
  });

  describe('getSystemObjectDefinition', () => {
    it('should validate required parameters', async () => {
      const objectType = 'Product';

      await client.getSystemObjectDefinition(objectType);

      expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
      expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
    });

    it('should make GET request with encoded object type', async () => {
      const objectType = 'Custom Object';

      await client.getSystemObjectDefinition(objectType);

      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions/Custom%20Object');
    });

    it('should handle special characters in object type', async () => {
      const objectType = 'Product/Variant';

      await client.getSystemObjectDefinition(objectType);

      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions/Product%2FVariant');
    });
  });

  describe('searchSystemObjectDefinitions', () => {
    it('should validate search request', async () => {
      const searchRequest = {
        query: {
          text_query: {
            fields: ['id', 'display_name'],
            search_phrase: 'product',
          },
        },
      };

      await client.searchSystemObjectDefinitions(searchRequest);

      expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
    });

    it('should make POST request to system object definition search endpoint', async () => {
      const searchRequest = {
        query: {
          text_query: {
            fields: ['id', 'display_name'],
            search_phrase: 'product',
          },
        },
      };

      await client.searchSystemObjectDefinitions(searchRequest);

      expect((client as any).post).toHaveBeenCalledWith('/system_object_definition_search', searchRequest);
    });
  });

  describe('searchSystemObjectAttributeDefinitions', () => {
    it('should validate all required parameters', async () => {
      const objectType = 'Product';
      const searchRequest = {
        query: {
          text_query: {
            fields: ['id', 'display_name'],
            search_phrase: 'custom',
          },
        },
      };

      await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);

      expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
      expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
      expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
    });

    it('should make POST request to attribute definition search endpoint', async () => {
      const objectType = 'Product';
      const searchRequest = {
        query: {
          text_query: {
            fields: ['id', 'display_name'],
            search_phrase: 'custom',
          },
        },
      };

      await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);

      expect((client as any).post).toHaveBeenCalledWith('/system_object_definitions/Product/attribute_definition_search', searchRequest);
    });
  });

  describe('searchSystemObjectAttributeGroups', () => {
    it('should validate all required parameters', async () => {
      const objectType = 'SitePreferences';
      const searchRequest = {
        query: { match_all_query: {} },
      };

      await client.searchSystemObjectAttributeGroups(objectType, searchRequest);

      expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
      expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
      expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
    });

    it('should make POST request to attribute group search endpoint', async () => {
      const objectType = 'SitePreferences';
      const searchRequest = {
        query: {
          text_query: {
            fields: ['id', 'display_name'],
            search_phrase: 'general',
          },
        },
        sorts: [{ field: 'position', sort_order: 'asc' as const }],
      };

      await client.searchSystemObjectAttributeGroups(objectType, searchRequest);

      expect((client as any).post).toHaveBeenCalledWith(
        '/system_object_definitions/SitePreferences/attribute_group_search',
        searchRequest,
      );
    });
  });

  describe('error handling', () => {
    it('should propagate validation errors', async () => {
      const validationError = new Error('Validation failed');
      mockValidateRequired.mockImplementation(() => {
        throw validationError;
      });

      await expect(client.getSystemObjectDefinition('Product')).rejects.toThrow(validationError);
    });

    it('should propagate HTTP errors from base client', async () => {
      const httpError = new Error('HTTP request failed');
      (client as any).get = jest.fn().mockRejectedValue(httpError);

      await expect(client.getSystemObjectDefinitions()).rejects.toThrow(httpError);
    });

    it('should propagate search validation errors', async () => {
      const searchValidationError = new Error('Invalid search request');
      mockValidateSearchRequest.mockImplementation(() => {
        throw searchValidationError;
      });

      const searchRequest = { query: {} };
      await expect(client.searchSystemObjectDefinitions(searchRequest)).rejects.toThrow(searchValidationError);
    });
  });

  describe('integration scenarios', () => {
    it('should handle complex search with all options', async () => {
      const objectType = 'Product';
      const searchRequest = {
        query: {
          bool_query: {
            must: [
              {
                text_query: {
                  fields: ['id'],
                  search_phrase: 'custom',
                },
              },
            ],
            must_not: [
              {
                term_query: {
                  fields: ['system'],
                  operator: 'is',
                  values: [true],
                },
              },
            ],
          },
        },
        sorts: [
          { field: 'display_name', sort_order: 'asc' as const },
          { field: 'id' },
        ],
        start: 10,
        count: 25,
        select: '(**)',
      };

      await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);

      expect(Validator.validateRequired).toHaveBeenCalled();
      expect(Validator.validateObjectType).toHaveBeenCalled();
      expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
      expect((client as any).post).toHaveBeenCalledWith(
        '/system_object_definitions/Product/attribute_definition_search',
        searchRequest,
      );
    });

    it('should handle empty query parameters gracefully', async () => {
      const params = {};
      mockQueryBuilderFromObject.mockReturnValue('');

      await client.getSystemObjectDefinitions(params);

      expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/mcp/node/get-latest-error.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
import { test, describe, before, after, beforeEach } from 'node:test';
import { strict as assert } from 'node:assert';
import { connect } from 'mcp-aegis';

describe('get_latest_error - Full Mode Programmatic Tests (Optimized)', () => {
  let client;

  before(async () => {
    client = await connect('./aegis.config.with-dw.json');
  });

  after(async () => {
    if (client?.connected) {
      await client.disconnect();
    }
  });

  beforeEach(() => {
    // CRITICAL: Clear all buffers to prevent leaking into next tests
    client.clearAllBuffers(); // Recommended - comprehensive protection
  });

  // Simplified helper functions for common validations
  function assertValidMCPResponse(result) {
    assert.ok(result.content, 'Should have content');
    assert.ok(Array.isArray(result.content), 'Content should be array');
    assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
    assert.equal(result.content[0].type, 'text', 'Content should be text type');
  }

  function assertErrorResponse(result, expectedErrorText) {
    assertValidMCPResponse(result);
    assert.equal(result.isError, true, 'Should be an error response');
    assert.ok(result.content[0].text.includes(expectedErrorText),
      `Expected error text "${expectedErrorText}" in "${result.content[0].text}"`);
  }

  function assertSuccessWithLimit(result, expectedLimit) {
    assertValidMCPResponse(result);
    assert.equal(result.isError, false, 'Should not be an error response');
    const text = result.content[0].text;
    assert.ok(text.includes(`Latest ${expectedLimit} error messages`),
      `Should mention "${expectedLimit}" error messages`);
    assert.ok(/error-blade-\d{8}-\d{6}\.log/.test(text), 'Should contain log file pattern');
    assert.ok(text.includes('ERROR'), 'Should contain ERROR level entries');
  }

  // Helper function to get current date in YYYYMMDD format
  function getCurrentDateString() {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    return `${year}${month}${day}`;
  }

  // Core functionality tests (essential scenarios)
  describe('Core Functionality', () => {
    test('should retrieve error messages with default parameters', async () => {
      const result = await client.callTool('get_latest_error', {});
      
      assertSuccessWithLimit(result, 10); // Default limit is 10
      
      // Verify SFCC-specific content is present
      const text = result.content[0].text;
      assert.ok(/PipelineCallServlet|SystemJobThread/.test(text), 'Should contain SFCC thread patterns');
      assert.ok(text.includes('Sites-'), 'Should contain Sites information');
      assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} GMT/.test(text), 'Should contain GMT timestamps');
    });

    test('should respect limit parameter and return ordered results', async () => {
      const result = await client.callTool('get_latest_error', { limit: 3 });
      
      assertSuccessWithLimit(result, 3);
      
      const text = result.content[0].text;
      // Should contain separators for multiple entries
      const separatorCount = (text.match(/---/g) || []).length;
      assert.ok(separatorCount >= 1, 'Should have separators between entries');
      
      // Verify chronological order (newest first) with known mock data
      assert.ok(text.includes('CQ - AWS S3 Configuration Issue'), 'Should contain latest error');
      assert.ok(text.includes('Payment authorization failed'), 'Should contain second latest error');
    });

    test('should handle date parameter correctly', async () => {
      const result = await client.callTool('get_latest_error', { 
        date: getCurrentDateString(),
        limit: 2 
      });
      
      assertSuccessWithLimit(result, 2);
    });
  });

  // Parameter validation tests (core error handling)
  describe('Parameter Validation', () => {
    test('should reject invalid limit types and values', async () => {
      // Test string limit
      const stringResult = await client.callTool('get_latest_error', { limit: '5' });
      assertErrorResponse(stringResult, 'Invalid limit \'5\' for get_latest_error. Must be a valid number');
      
      // Test zero limit
      const zeroResult = await client.callTool('get_latest_error', { limit: 0 });
      assertErrorResponse(zeroResult, 'Invalid limit \'0\' for get_latest_error');
      
      // Test negative limit
      const negativeResult = await client.callTool('get_latest_error', { limit: -5 });
      assertErrorResponse(negativeResult, 'Invalid limit');
    });

    test('should handle large limits appropriately', async () => {
      const largeResult = await client.callTool('get_latest_error', { limit: 50 });
      assertSuccessWithLimit(largeResult, 50);
      
      // Test extremely large limit (should error)
      const hugeResult = await client.callTool('get_latest_error', { limit: 9999 });
      assertErrorResponse(hugeResult, 'Invalid limit');
    });

    test('should handle date parameters gracefully', async () => {
      // Valid YYYYMMDD date
      const validResult = await client.callTool('get_latest_error', { 
        date: '20240101',
        limit: 1 
      });
      assertValidMCPResponse(validResult);
      assert.equal(validResult.isError, false, 'Valid date should not error');
      
      // Invalid date format (should handle gracefully)
      const invalidResult = await client.callTool('get_latest_error', { 
        date: '2024-01-01',
        limit: 1 
      });
      assertValidMCPResponse(invalidResult);
      // Should not crash, may succeed or fail gracefully
    });

    test('should handle missing arguments gracefully', async () => {
      const result = await client.callTool('get_latest_error');
      assertSuccessWithLimit(result, 10); // Should use default limit
    });
  });

  // Content validation tests (SFCC-specific patterns)
  describe('Content Validation', () => {
    test('should include realistic SFCC error scenarios and structure', async () => {
      const result = await client.callTool('get_latest_error', { limit: 5 });
      
      assertValidMCPResponse(result);
      assert.equal(result.isError, false, 'Should not be an error');
      const text = result.content[0].text;
      
      // Should contain common SFCC error patterns
      const errorPatterns = [
        'Custom cartridge error',
        'Product import failed', 
        'Customer profile creation failed',
        'Payment authorization failed',
        'AWS S3 Configuration Issue'
      ];
      
      const foundPatterns = errorPatterns.filter(pattern => text.includes(pattern));
      assert.ok(foundPatterns.length > 0, 
        `Should contain at least one error pattern. Found: ${foundPatterns.join(', ')}`);
      
      // Validate basic log structure elements
      assert.ok(text.includes('[') && text.includes(']'), 'Should contain log brackets');
      assert.ok(/\|\d+\|/.test(text), 'Should contain thread IDs with pipes');
    });

    test('should contain comprehensive SFCC-specific patterns', async () => {
      const result = await client.callTool('get_latest_error', { limit: 3 });
      
      assertValidMCPResponse(result);
      assert.equal(result.isError, false, 'Should not be an error');
      const text = result.content[0].text;
      
      // SFCC-specific validation patterns
      const sfccPatterns = [
        { pattern: /Sites-\w+/, name: 'Sites names' },
        { pattern: /PipelineCallServlet|SystemJobThread/, name: 'Thread types' },
        { pattern: /\|\d+\|/, name: 'Thread IDs' },
        { pattern: /custom \[\]/, name: 'Custom category' }
      ];
      
      const matchedPatterns = sfccPatterns.filter(({ pattern }) => pattern.test(text));
      assert.ok(matchedPatterns.length >= 2,
        `Should match at least 2 SFCC patterns. Matched: ${matchedPatterns.map(p => p.name).join(', ')}`);
    });
  });

  // Error recovery and resilience testing
  describe('Error Recovery and Resilience', () => {
    test('should handle error scenarios and recover properly', async () => {
      // Test invalid parameter
      const invalidResult = await client.callTool('get_latest_error', { limit: 0 });
      assertErrorResponse(invalidResult, 'Invalid limit');
      
      // Test recovery with valid parameters
      const validResult = await client.callTool('get_latest_error', { limit: 1 });
      assertValidMCPResponse(validResult);
      assert.equal(validResult.isError, false, 'Should work normally after error');
      assertSuccessWithLimit(validResult, 1);
      
      // Test edge cases without breaking
      const edgeCases = [
        { limit: '1' },      // String limit (should error)
        { limit: 1000 },     // Large limit (should error)  
        { date: '' },        // Empty date (should handle gracefully)
        { invalid: 'param' } // Invalid parameter name (should handle gracefully)
      ];
      
      // Test all edge cases sequentially
      for (const testCase of edgeCases) {
        const result = await client.callTool('get_latest_error', testCase);
        assertValidMCPResponse(result);
        // Some may error, some may succeed - but none should crash
      }
      
      // Verify tool still works after edge cases
      const finalResult = await client.callTool('get_latest_error', { limit: 1 });
      assertValidMCPResponse(finalResult);
      assert.equal(finalResult.isError, false, 'Should work after edge cases');
    });
  });

  // Advanced scenarios (simplified multi-step workflow)
  describe('Advanced Scenarios', () => {
    test('should support typical error analysis workflow', async () => {
      // Simulate a typical workflow: recent errors -> specific investigation -> recovery validation
      
      // Step 1: Get recent errors for overview
      const recentErrors = await client.callTool('get_latest_error', { limit: 2 });
      assertValidMCPResponse(recentErrors);
      assert.equal(recentErrors.isError, false, 'Recent errors should succeed');
      
      // Step 2: Get more detailed view for specific analysis
      const detailedErrors = await client.callTool('get_latest_error', { 
        date: getCurrentDateString(),
        limit: 5 
      });
      assertValidMCPResponse(detailedErrors);
      assert.equal(detailedErrors.isError, false, 'Detailed errors should succeed');
      
      // Step 3: Verify both contain expected error content
      [recentErrors, detailedErrors].forEach((result, index) => {
        const text = result.content[0].text;
        assert.ok(text.includes('ERROR'), `Result ${index} should contain ERROR level`);
        assert.ok(text.includes('Latest'), `Result ${index} should contain 'Latest' message`);
        assert.ok(/error-blade-.*\.log/.test(text), `Result ${index} should contain log filename`);
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/mock-data/ocapi/system-object-attribute-groups-product.json:
--------------------------------------------------------------------------------

```json
{
  "_v": "23.2",
  "_type": "object_attribute_group_search_result",
  "count": 35,
  "hits": [
    {
      "_type": "object_attribute_group",
      "_resource_state": "f5c17fdc7ebeaf9136d6fef12d76d68cf238abb98ccaa282e40a657b1a5fcfb6",
      "id": "ChannelIntegration",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/ChannelIntegration"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "9787c54106885bf78ff45cd7f3a7f64a073d911485473db6289f4fb0252e345d",
      "id": "ExternalSearch",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/ExternalSearch"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "01b32a52ad9a64e426f08a21c0d1c3732fa8f573399db8bcf8d68a49dbdea9b6",
      "id": "Order",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Order"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "3ad88093fb2451eb026a2c103b26018e9ab265391da20a39fb8bf3a8c5bb477d",
      "id": "PXL3_Hoesjes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Hoesjes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "dc899d1d79f89a7a1845bc0fdea1e7b76b826f155f59807be55c0d8ce4763dc9",
      "id": "PXL3_Oordopjes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Oordopjes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "d01dd2dca011264ffadd379d3c56151a7636dc1d6faf4ada51a081f2c72999eb",
      "id": "PXL3_Opladers",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Opladers"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "cc17ba20294c83f5322e26ace9f60843de0c8dd55ff1bbfa4ad7b4e414595b47",
      "id": "Presentation",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Presentation"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "e0f82a956514fb4f24c601b1197248b2f21987bb339a7bddf14df5b7a309a1f9",
      "id": "SearchRanking",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/SearchRanking"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "3495916efcfef9381c9630e7ac29f740c4ecd2c2e020692db7a8046838ee5a61",
      "id": "SiteMap",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/SiteMap"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "e63730013e6fd915009e27898def794a844029e6fc2b96487bf00089b648d03a",
      "id": "Store",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Store"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "89c739821f23ba2e2c1e99d6fd1056228bda5046cc530cd045a2c307ce09d9a1",
      "id": "WashingInstructions",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/WashingInstructions"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "0a8bf616f0b756ab850b50b8e6fff58906a4bbe5086a7361d889e01e9e9595b0",
      "id": "backInStock",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/backInStock"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "750ad1eb7d39a27b5af165b0d09d37616867c379a5b737b2ff5831581a0c1937",
      "id": "clothingAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/clothingAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "15add012dad5d62be8278d2962b188f59635c55b236538ed5a8f354e71b3ee73",
      "id": "custom",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/custom"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "65e5cd4c2fd95724f96d8c1f838f9254d0f3a01c072e441dd73e518474458f2b",
      "id": "electronicsDigitalCameraAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDigitalCameraAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "37f492a3eb597f59395358cfb40eeb3a6294178dce09f8c377170e69daa30837",
      "id": "electronicsDigitalMediaPlayerAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDigitalMediaPlayerAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "fe9a646c9f866142f2d28011c26f65f90372ff7fc48067a77c57d54eb58c96a7",
      "id": "electronicsDimensionsAndWeight",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDimensionsAndWeight"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "dc186ea27a80c3369c603011027f46d2d34f7a43b60d44cf0bf1208346c40151",
      "id": "electronicsGameRatings",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsGameRatings"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "8a3b2e949bdc9929ba41876929676f26e9b0527a5f30001273d09e9f2a53f89c",
      "id": "electronicsGpsAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsGpsAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "0c9a9c20a3d1d3a3c5b6378c14551b2ce0f8352bad88de81ac357de2bb1d9145",
      "id": "inStorePickup",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/inStorePickup"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "756e58068a70de9dd92c15babbb87e8c7b0856b6701f22a974cbf6f084bfaa3b",
      "id": "mainAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "87361af2c433c890fe2afcd4bfb24056b763e8ddaa31e6e568a53336a7af78a7",
      "id": "mainAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "eb2824b3e077e3bbc7c6626ebf4bcba9ca21b96f9804c4128e26120d898a0829",
      "id": "mainAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "76beb3fafcfbddb4830c18b3382544456122a12c25d2fe5012ebc945a30b1b5d",
      "id": "mainAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "37e37519eed074a1d4ca5f2be6914b8f32f19bea20475d7b63c6e684f63a2e37",
      "id": "mainAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "e3f0676a3f0a5a212fbf171dd9f2d57b73c918ed27135f48a5e0a3d0b32dfb8a",
      "id": "material",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/material"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "1810005767e31409c70ae256c98bd2570bf0a9664c82a464a9c097056251b949",
      "id": "materialWashingInstructions",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/materialWashingInstructions"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "39d412aa0c6d48528b5efa0a5f639c9b20791744f69c3a18a3932e587071831d",
      "id": "mensAccessoriesAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mensAccessoriesAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "f696e3513a04d3e4ea494165e9e69e6a9f4d3a0d22a0565921d5860c735c73f9",
      "id": "mensClothingAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mensClothingAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "747af7428d1f04dcfe79c2e22a34fe562277ad0f8b13dc4322708121233da7d8",
      "id": "searchRefinements",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/searchRefinements"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "59811b6d815972e1403936b0fa5b6de7ca8101ebb27cb1dd83302eb1d9c8b0ff",
      "id": "storefrontAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "b29d3cb7452c7a6749ca009a024fe1b865d7642ebbf2a7392d5472e7641c5bcf",
      "id": "storefrontAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "0706cc287e8e9dda03acfc351d70b20e3eb4f9e26ba1f66929f7c68cc4b443d8",
      "id": "storefrontAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "7c529b5a59acfe0958ff98e98ce9a53c5045b1638edcce0abdf1ad7608a2ae8b",
      "id": "womensAccessoriesAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/womensAccessoriesAttributes"
    },
    {
      "_type": "object_attribute_group",
      "_resource_state": "436a1fc1e34643a96e42672f536b3f7802af3f512ec69ccdda05079af88f0277",
      "id": "womensClothingAttributes",
      "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/womensClothingAttributes"
    }
  ],
  "query": {
    "match_all_query": {
      "_type": "match_all_query"
    }
  },
  "start": 0,
  "total": 35
}

```

--------------------------------------------------------------------------------
/docs/dw_order/ReturnCase.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.order

# Class ReturnCase

## Inheritance Hierarchy

- Object
  - dw.object.Extensible
  - dw.order.AbstractItemCtnr
    - dw.order.ReturnCase

## Description

All returns exist in the context of a ReturnCase, each Order can have any number of ReturnCases. The ReturnCase has ReturnCaseItems, each of which is associated with an OrderItem (an extension to either a ProductLineItem or a ShippingLineItem). Each ReturnCaseItem defines ReturnCaseItem.getAuthorizedQuantity() representing the maximum quantity expected to be returned. The ReturnCaseItem may be associated with 0..n ReturnItems - ReturnItems are added to the ReturnCaseItem when Returns are created. Either - a ReturnCase may be used as an RMA, in which case they are created when a customer first shows a wish to return item(s). The customer then includes the RMA number with the returned item(s). The Return created as a result is then associated with the existing ReturnCase. Or - a ReturnCase is automatically created as part of the return creation, i.e. the customer returns some item(s) leading to a creation of both a Return and an associated ReturnCase. The scripting api allows access to the ReturnCases, whether the ReturnCase is an RMA or not, and the ReturnCase status. Both the ReturnCaseItems and any Returns associated with the ReturnCase can be accessed. A ReturnCase has one of these status values: NEW - the ReturnCase has been created and can be edited previous to its authorization CONFIRMED - the ReturnCase is CONFIRMED, can no longer be edited, no Returns have been associated with it. Only a NEW- ReturnCase can be CONFIRMED PARTIAL_RETURNED - the ReturnCase has been associated with at least one Return, but is not yet complete. Only a CONFIRMED- ReturnCase can be set to PARTIAL_RETURNED RETURNED - the ReturnCase has been associated with Returns which match the expected authorized quantity. Only an CONFIRMED- or PARTIAL_RETURNED- return-case can be set to RETURNED CANCELLED - the ReturnCase has been cancelled (only a NEW- or CONFIRMED- ReturnCase can be cancelled) Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice.

## Constants

### ORDERBY_ITEMID

**Type:** Object

Sorting by item id. Use with method getItems() as an argument to method FilteringCollection.sort(Object).

### ORDERBY_ITEMPOSITION

**Type:** Object

Sorting by the position of the related oder item. Use with method getItems() as an argument to method FilteringCollection.sort(Object).

### ORDERBY_UNSORTED

**Type:** Object

Unsorted , as it is. Use with method getItems() as an argument to method FilteringCollection.sort(Object).

### QUALIFIER_PRODUCTITEMS

**Type:** Object

Selects the product items. Use with method getItems() as an argument to method FilteringCollection.select(Object).

### QUALIFIER_SERVICEITEMS

**Type:** Object

Selects for the service items. Use with method getItems() as an argument to method FilteringCollection.select(Object).

### STATUS_CANCELLED

**Type:** String = "CANCELLED"

constant for ReturnCase Status CANCELLED

### STATUS_CONFIRMED

**Type:** String = "CONFIRMED"

constant for ReturnCase Status CONFIRMED

### STATUS_NEW

**Type:** String = "NEW"

constant for ReturnCase Status NEW

### STATUS_PARTIAL_RETURNED

**Type:** String = "PARTIAL_RETURNED"

constant for ReturnCase Status PARTIAL RETURNED

### STATUS_RETURNED

**Type:** String = "RETURNED"

constant for ReturnCase Status RETURNED

## Properties

### invoice

**Type:** Invoice (Read Only)

Returns null or the previously created Invoice.

### invoiceNumber

**Type:** String (Read Only)

Returns null or the invoice-number.

### items

**Type:** FilteringCollection (Read Only)

Access the collection of ReturnCaseItems.
 
 This FilteringCollection can be sorted / filtered using:
 
 FilteringCollection.sort(Object) with ORDERBY_ITEMID
 FilteringCollection.sort(Object) with
 ORDERBY_ITEMPOSITION
 FilteringCollection.sort(Object) with ORDERBY_UNSORTED
 FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS
 FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS

### returnCaseNumber

**Type:** String (Read Only)

The mandatory return case number identifying this document.

### returns

**Type:** Collection (Read Only)

Return the collection of Returns associated with this ReturnCase.

### RMA

**Type:** Order.createReturnCase(String, Boolean) (Read Only)

Return whether this is an RMA. This is specified when calling Order.createReturnCase(String, Boolean).

### status

**Type:** EnumValue (Read Only)

Gets the return case item status. The status of a ReturnCase is read-only and calculated from the status of
 the associated ReturnCaseItems.
 
 The possible values are STATUS_NEW,STATUS_CONFIRMED,
 STATUS_PARTIAL_RETURNED, STATUS_RETURNED,
 STATUS_CANCELLED.

## Constructor Summary

## Method Summary

### confirm

**Signature:** `confirm() : void`

Attempt to confirm the ReturnCase.

### createInvoice

**Signature:** `createInvoice() : Invoice`

Creates a new Invoice based on this ReturnCase.

### createInvoice

**Signature:** `createInvoice(invoiceNumber : String) : Invoice`

Creates a new Invoice based on this ReturnCase.

### createItem

**Signature:** `createItem(orderItemID : String) : ReturnCaseItem`

Creates a new item for a given order item.

### createReturn

**Signature:** `createReturn(returnNumber : String) : Return`

Creates a new Return with the given number and associates it with this ReturnCase.

### createReturn

**Signature:** `createReturn() : Return`

Creates a new Return with a generated number and associates it with this ReturnCase.

### getInvoice

**Signature:** `getInvoice() : Invoice`

Returns null or the previously created Invoice.

### getInvoiceNumber

**Signature:** `getInvoiceNumber() : String`

Returns null or the invoice-number.

### getItems

**Signature:** `getItems() : FilteringCollection`

Access the collection of ReturnCaseItems.

### getReturnCaseNumber

**Signature:** `getReturnCaseNumber() : String`

Returns the mandatory return case number identifying this document.

### getReturns

**Signature:** `getReturns() : Collection`

Return the collection of Returns associated with this ReturnCase.

### getStatus

**Signature:** `getStatus() : EnumValue`

Gets the return case item status.

### isRMA

**Signature:** `isRMA() : boolean`

Return whether this is an RMA.

## Method Detail

## Method Details

### confirm

**Signature:** `confirm() : void`

**Description:** Attempt to confirm the ReturnCase. Without items the return case will be canceled When confirmed, only the the custom attributes of its return case items can be changed.

**Throws:**

IllegalStateException - thrown if Status is not STATUS_NEW

---

### createInvoice

**Signature:** `createInvoice() : Invoice`

**Description:** Creates a new Invoice based on this ReturnCase. The return-case-number will be used as the invoice-number. The Invoice can then be accessed using getInvoice() or its number using getInvoiceNumber(). The method must not be called more than once for a ReturnCase, nor may 2 Invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and will be passed to the refund payment-hook in a separate database transaction for processing.

**Returns:**

new invoice

---

### createInvoice

**Signature:** `createInvoice(invoiceNumber : String) : Invoice`

**Description:** Creates a new Invoice based on this ReturnCase. The invoice-number must be specified as an argument. The Invoice can then be accessed using getInvoice() or its number using getInvoiceNumber(). The method must not be called more than once for a ReturnCase, nor may 2 Invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and will be passed to the refund payment-hook in a separate database transaction for processing.

**Parameters:**

- `invoiceNumber`: the invoice-number to be used for the invoice creation

**Returns:**

new invoice

---

### createItem

**Signature:** `createItem(orderItemID : String) : ReturnCaseItem`

**Description:** Creates a new item for a given order item. Note: a ReturnCase may have only one item per order item.

**Parameters:**

- `orderItemID`: order item id

**Returns:**

null or item for given order item

**Throws:**

IllegalArgumentException - thrown if getItem(orderItem) returns non null

---

### createReturn

**Signature:** `createReturn(returnNumber : String) : Return`

**Description:** Creates a new Return with the given number and associates it with this ReturnCase.

**Parameters:**

- `returnNumber`: return number to assign

**Returns:**

new Return instance

---

### createReturn

**Signature:** `createReturn() : Return`

**Description:** Creates a new Return with a generated number and associates it with this ReturnCase.

**Returns:**

new Return instance

---

### getInvoice

**Signature:** `getInvoice() : Invoice`

**Description:** Returns null or the previously created Invoice.

**Returns:**

null or the previously created invoice.

**See Also:**

createInvoice(String)

---

### getInvoiceNumber

**Signature:** `getInvoiceNumber() : String`

**Description:** Returns null or the invoice-number.

**Returns:**

null or the previously created invoice.

**See Also:**

createInvoice(String)

---

### getItems

**Signature:** `getItems() : FilteringCollection`

**Description:** Access the collection of ReturnCaseItems. This FilteringCollection can be sorted / filtered using: FilteringCollection.sort(Object) with ORDERBY_ITEMID FilteringCollection.sort(Object) with ORDERBY_ITEMPOSITION FilteringCollection.sort(Object) with ORDERBY_UNSORTED FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS

**Returns:**

the items

---

### getReturnCaseNumber

**Signature:** `getReturnCaseNumber() : String`

**Description:** Returns the mandatory return case number identifying this document.

**Returns:**

the return case number

---

### getReturns

**Signature:** `getReturns() : Collection`

**Description:** Return the collection of Returns associated with this ReturnCase.

**Returns:**

the collection of Returns.

---

### getStatus

**Signature:** `getStatus() : EnumValue`

**Description:** Gets the return case item status. The status of a ReturnCase is read-only and calculated from the status of the associated ReturnCaseItems. The possible values are STATUS_NEW,STATUS_CONFIRMED, STATUS_PARTIAL_RETURNED, STATUS_RETURNED, STATUS_CANCELLED.

**Returns:**

the status

---

### isRMA

**Signature:** `isRMA() : boolean`

**Description:** Return whether this is an RMA. This is specified when calling Order.createReturnCase(String, Boolean).

**Returns:**

whether this is an RMA.

---
```

--------------------------------------------------------------------------------
/tests/ocapi-client.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for the refactored OCAPIClient
 * Tests the facade pattern that orchestrates specialized client modules
 */

import { OCAPIClient } from '../src/clients/ocapi-client.js';
import { TokenManager } from '../src/clients/base/oauth-token.js';
import { OCAPIConfig } from '../src/types/types.js';

// Mock fetch globally
global.fetch = jest.fn();

// Mock TokenManager
jest.mock('../src/clients/base/oauth-token.js');

// Mock the specialized clients
jest.mock('../src/clients/ocapi/system-objects-client.js');
jest.mock('../src/clients/ocapi/site-preferences-client.js');
jest.mock('../src/clients/ocapi/code-versions-client.js');
jest.mock('../src/clients/base/ocapi-auth-client.js');

describe('OCAPIClient', () => {
  let client: OCAPIClient;
  let mockTokenManager: jest.Mocked<TokenManager>;
  const mockConfig: OCAPIConfig = {
    hostname: 'test-instance.demandware.net',
    clientId: 'test-client-id',
    clientSecret: 'test-client-secret',
    version: 'v21_3',
  };

  beforeEach(() => {
    jest.clearAllMocks();

    // Setup TokenManager mock
    mockTokenManager = {
      getValidToken: jest.fn(),
      storeToken: jest.fn(),
      clearToken: jest.fn(),
      getTokenExpiration: jest.fn(),
      isTokenValid: jest.fn(),
      clearAllTokens: jest.fn(),
    } as any;

    (TokenManager.getInstance as jest.Mock).mockReturnValue(mockTokenManager);
    (fetch as jest.Mock).mockClear();

    client = new OCAPIClient(mockConfig);
  });

  describe('constructor', () => {
    it('should initialize with provided config', () => {
      expect(client).toBeInstanceOf(OCAPIClient);
      // Note: TokenManager.getInstance is called by the auth client, not directly by OCAPIClient
      expect(client.systemObjects).toBeDefined();
      expect(client.sitePreferences).toBeDefined();
    });

    it('should use default version when not provided', () => {
      const configWithoutVersion = {
        hostname: 'test.demandware.net',
        clientId: 'client-id',
        clientSecret: 'client-secret',
      };

      const clientWithDefaults = new OCAPIClient(configWithoutVersion);
      expect(clientWithDefaults).toBeInstanceOf(OCAPIClient);
    });

    it('should initialize all specialized client modules', () => {
      expect(client.systemObjects).toBeDefined();
      expect(client.sitePreferences).toBeDefined();
    });
  });

  describe('System Objects API delegation', () => {
    it('should delegate getSystemObjectDefinitions to SystemObjectsClient', async () => {
      const mockResponse = { data: 'system-objects' };
      jest.spyOn(client.systemObjects, 'getSystemObjectDefinitions').mockResolvedValue(mockResponse);

      const result = await client.getSystemObjectDefinitions();

      expect(client.systemObjects.getSystemObjectDefinitions).toHaveBeenCalledWith(undefined);
      expect(result).toBe(mockResponse);
    });

    it('should delegate getSystemObjectDefinition with objectType to SystemObjectsClient', async () => {
      const mockResponse = { data: 'product-definition' };
      const objectType = 'Product';
      jest.spyOn(client.systemObjects, 'getSystemObjectDefinition').mockResolvedValue(mockResponse);

      const result = await client.getSystemObjectDefinition(objectType);

      expect(client.systemObjects.getSystemObjectDefinition).toHaveBeenCalledWith(objectType);
      expect(result).toBe(mockResponse);
    });

    it('should delegate searchSystemObjectDefinitions to SystemObjectsClient', async () => {
      const mockResponse = { data: 'search-results' };
      const searchRequest = { query: { match_all_query: {} } };
      jest.spyOn(client.systemObjects, 'searchSystemObjectDefinitions').mockResolvedValue(mockResponse);

      const result = await client.searchSystemObjectDefinitions(searchRequest);

      expect(client.systemObjects.searchSystemObjectDefinitions).toHaveBeenCalledWith(searchRequest);
      expect(result).toBe(mockResponse);
    });

    it('should delegate searchSystemObjectAttributeDefinitions to SystemObjectsClient', async () => {
      const mockResponse = { data: 'attribute-search-results' };
      const objectType = 'Product';
      const searchRequest = { query: { text_query: { fields: ['id'], search_phrase: 'custom' } } };
      jest.spyOn(client.systemObjects, 'searchSystemObjectAttributeDefinitions').mockResolvedValue(mockResponse);

      const result = await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);

      expect(client.systemObjects.searchSystemObjectAttributeDefinitions)
        .toHaveBeenCalledWith(objectType, searchRequest);
      expect(result).toBe(mockResponse);
    });

    it('should delegate searchSystemObjectAttributeGroups to SystemObjectsClient', async () => {
      const mockResponse = { data: 'attribute-groups' };
      const objectType = 'SitePreferences';
      const searchRequest = { query: { match_all_query: {} } };
      jest.spyOn(client.systemObjects, 'searchSystemObjectAttributeGroups').mockResolvedValue(mockResponse);

      const result = await client.searchSystemObjectAttributeGroups(objectType, searchRequest);

      expect(client.systemObjects.searchSystemObjectAttributeGroups).toHaveBeenCalledWith(objectType, searchRequest);
      expect(result).toBe(mockResponse);
    });
  });

  describe('Site Preferences API delegation', () => {
    it('should delegate searchSitePreferences to SitePreferencesClient', async () => {
      const mockResponse = { data: 'site-preferences' };
      const groupId = 'SiteGeneral';
      const instanceType = 'sandbox';
      const searchRequest = { query: { match_all_query: {} } };
      const options = { maskPasswords: true };
      jest.spyOn(client.sitePreferences, 'searchSitePreferences').mockResolvedValue(mockResponse);

      const result = await client.searchSitePreferences(groupId, instanceType, searchRequest, options);

      expect(client.sitePreferences.searchSitePreferences)
        .toHaveBeenCalledWith(groupId, instanceType, searchRequest, options);
      expect(result).toBe(mockResponse);
    });
  });

  describe('Authentication & Token Management delegation', () => {
    it('should delegate getTokenExpiration to AuthClient', () => {
      const mockExpiration = new Date();

      // Mock the authClient's getTokenExpiration method
      const mockAuthClient = {
        getTokenExpiration: jest.fn().mockReturnValue(mockExpiration),
      };

      // Access the private authClient property and mock it
      (client as any).authClient = mockAuthClient;

      const result = client.getTokenExpiration();

      expect(mockAuthClient.getTokenExpiration).toHaveBeenCalled();
      expect(result).toBe(mockExpiration);
    });

    it('should delegate refreshToken to AuthClient', async () => {
      // Mock the authClient's refreshToken method
      const mockAuthClient = {
        refreshToken: jest.fn().mockResolvedValue(undefined),
      };

      // Access the private authClient property and mock it
      (client as any).authClient = mockAuthClient;

      await client.refreshToken();

      expect(mockAuthClient.refreshToken).toHaveBeenCalled();
    });

    it('should delegate getCodeVersions to CodeVersionsClient', async () => {
      // Mock the codeVersions client's getCodeVersions method
      const mockCodeVersions = {
        _v: '23.2',
        _type: 'code_version_result',
        count: 1,
        data: [
          {
            _type: 'code_version',
            id: 'version1',
            active: true,
            cartridges: 'app_storefront_base',
            compatibility_mode: '23.2',
            activation_time: '2024-01-01T00:00:00Z',
            total_size: '1024 KB',
          },
        ],
        total: 1,
      };

      const mockCodeVersionsClient = {
        getCodeVersions: jest.fn().mockResolvedValue(mockCodeVersions),
      };

      // Access the private codeVersions property and mock it
      (client as any).codeVersions = mockCodeVersionsClient;

      const result = await client.getCodeVersions();

      expect(mockCodeVersionsClient.getCodeVersions).toHaveBeenCalled();
      expect(result).toBe(mockCodeVersions);
    });

    it('should delegate activateCodeVersion to CodeVersionsClient', async () => {
      // Mock the codeVersions client's activateCodeVersion method
      const mockActivatedVersion = {
        _v: '23.2',
        _type: 'code_version',
        _resource_state: 'new-resource-state-12345',
        id: 'version2',
        active: true,
        cartridges: 'app_storefront_base',
        compatibility_mode: '23.2',
        activation_time: '2024-01-15T10:30:00Z',
        total_size: '1024 KB',
      };

      const mockCodeVersionsClient = {
        activateCodeVersion: jest.fn().mockResolvedValue(mockActivatedVersion),
      };

      // Access the private codeVersions property and mock it
      (client as any).codeVersions = mockCodeVersionsClient;

      const codeVersionId = 'version2';
      const result = await client.activateCodeVersion(codeVersionId);

      expect(mockCodeVersionsClient.activateCodeVersion).toHaveBeenCalledWith(codeVersionId);
      expect(result).toBe(mockActivatedVersion);
    });
  });

  describe('Configuration handling', () => {
    it('should merge config with defaults', () => {
      const configWithoutVersion = {
        hostname: 'test.demandware.net',
        clientId: 'client-id',
        clientSecret: 'client-secret',
      };

      const clientWithDefaults = new OCAPIClient(configWithoutVersion);

      // Verify that the client was created successfully (which means defaults were applied)
      expect(clientWithDefaults).toBeInstanceOf(OCAPIClient);
      expect(clientWithDefaults.systemObjects).toBeDefined();
      expect(clientWithDefaults.sitePreferences).toBeDefined();
    });

    it('should preserve provided config values', () => {
      const customConfig = {
        hostname: 'custom.demandware.net',
        clientId: 'custom-client-id',
        clientSecret: 'custom-client-secret',
        version: 'v22_1',
      };

      const customClient = new OCAPIClient(customConfig);

      // Verify that the client was created successfully with custom config
      expect(customClient).toBeInstanceOf(OCAPIClient);
      expect(customClient.systemObjects).toBeDefined();
      expect(customClient.sitePreferences).toBeDefined();
    });
  });

  describe('Error handling', () => {
    it('should propagate errors from specialized clients', async () => {
      const error = new Error('System objects error');
      jest.spyOn(client.systemObjects, 'getSystemObjectDefinitions').mockRejectedValue(error);

      await expect(client.getSystemObjectDefinitions()).rejects.toThrow('System objects error');
    });

    it('should propagate errors from site preferences client', async () => {
      const error = new Error('Site preferences error');
      jest.spyOn(client.sitePreferences, 'searchSitePreferences').mockRejectedValue(error);

      const searchRequest = { query: { match_all_query: {} } };
      await expect(client.searchSitePreferences('groupId', 'sandbox', searchRequest)).rejects.toThrow('Site preferences error');
    });
  });
});

```

--------------------------------------------------------------------------------
/docs/dw_order.hooks/ReturnHooks.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.order.hooks

# Class ReturnHooks

## Inheritance Hierarchy

- dw.order.hooks.ReturnHooks

## Description

This interface represents all script hooks that can be registered to customizing the order center return resource. It contains the extension points (hook names), and the functions that are called by each extension point. A function must be defined inside a JavaScript source and must be exported. The script with the exported hook function must be located inside a site cartridge. Inside the site cartridge a 'package.json' file with a 'hooks' entry must exist. "hooks": "./hooks.json" The hooks entry links to a json file, relative to the 'package.json' file. This file lists all registered hooks inside the hooks property: "hooks": [ {"name": "dw.order.return.createReturn", "script": "./returns.ds"}, {"name": "dw.order.return.addReturnItem", "script": "./returns.ds"}, {"name": "dw.order.return.changeStatus", "script": "./returns.ds"}, ] A hook entry has a 'name' and a 'script' property. The 'name' contains the extension point, the hook name. The 'script' contains the script relative to the hooks file, with the exported hook function. Overview Return Functionality Business objects ReturnCase All returns exist in the context of a ReturnCase, each Order can have any number of ReturnCases. A ReturnCase has ReturnCaseItems, each of which is associated with an OrderItem (an extension to either a ProductLineItem or a ShippingLineItem). Each ReturnCaseItem defines an ReturnCaseItem.getAuthorizedQuantity() representing the maximum quantity expected to be returned. A ReturnCaseItem may be associated with 0..n ReturnItems - ReturnItems are added to the ReturnCaseItem when Returns are created. Either - a ReturnCase may be used as an RMA, in which case they are created when a customer first shows a wish to return item(s). The customer then includes the RMA number with the returned item(s). The Return created as a result is then associated with the existing ReturnCase. Or - a ReturnCase is automatically created as part of the return creation, i.e. the customer returns some item(s) leading to a creation of both a Return and an associated ReturnCase. The scripting api allows access to the ReturnCases, whether the ReturnCase is an RMA or not, and the ReturnCase status. Both the ReturnCaseItems and any Returns associated with the ReturnCase can be accessed. A ReturnCase has one of these status values: New - the ReturnCase has been created and can be edited previous to its authorization CONFIRMED - the ReturnCase is CONFIRMED, can no longer be edited, no Returns have been associated with it. Only an New- ReturnCase can be CONFIRMED PARTIAL_RETURNED - the ReturnCase has been associated with at least one Return, but is not yet complete. Only an CONFIRMED- ReturnCase can be set to PARTIAL_RETURNED RETURNED - the ReturnCase has been associated with Returns which match the expected authorized quantity. Only an CONFIRMED- or PARTIAL_RETURNED- return-case can be set to RETURNED Cancelled - the ReturnCase has been cancelled (only a New- or CONFIRMED- ReturnCase can be cancelled) Return A Return represents a physical customer return, and contains 1..n ReturnItems. A Return is associated with one ReturnCase, and each ReturnItem is associated with one ReturnCaseItem and (via the ReturnCaseItem) a single OrderItem usually representing an Order ProductLineItem. A ReturnItem records the quantity returned. A Return can have one of these status values: NEW - the Return is new, i.e. needs to undergo a check before it can be marked as COMPLETED COMPLETED - the return is complete, this is a precondition for refunding the customer for a return. Credit Invoice As a result of making a Return, the customer may be refunded. The refund amount is held in a credit Invoice which may be associated either with one Return or with one ReturnCase. The Invoice is passed to the refund payment hook allowing custom code to handle the payment refund. Process overview Create ReturnCase The creation of ReturnCases is supported using the data-api. The api supports, within the context of an Order, the specification of an (optional) RMA-number and addition of ReturnCaseItems for a given order-item and quantity. Authorize ReturnCase Following its creation, a ReturnCase needs to be CONFIRMED - an CONFIRMED ReturnCase cannot be modified. Cancel ReturnCase Following its creation or authorization, a ReturnCase may be cancelled. Create Return Returns may be imported or created via the data-api. These apis specify an (optional) RMA allowing a Return to be associated with a ReturnCase, and ReturnItems with a quantity and a key allowing them to be associated with an order-item. The process is delegated to custom scripts which control the creation of the Return and the addition of the ReturnItems: Hook extensionPointCreateReturn The creation of the new Return is delegated to the custom script when this hook is called, passing the order, and details of the Return to be created to the script. Typically the script accesses the ReturnCase from the order and creates the return with the provided return-number. It may also update the Order, ReturnCase or Return using custom values passed in the Return details. exports.createReturn = function (order:Order, returnDetails) { var returnNumber=returnDetails.returnNumber; var returnCase = order.getReturnCase(returnDetails.returnCaseNumber); var newReturn = returnCase.createReturn(returnNumber); return newReturn; } Hook extensionPointAddReturnItem This call delegates the creation of individual ReturnItems to a custom script, passing the Order, returnNumber, returnCaseItemId and return-item-details. Typically the script will access the ReturnCaseItem from the order and create a new ReturnItem for it. exports.addReturnItem = function (retrn:Return, returnItemDetails) { var returnCaseItem = order.getReturnCaseItem(returnCaseItemId); var item = returnCaseItem.createReturnItem(returnNr); Hook extensionPointChangeStatus This call delegates the update of the return-status to a custom script, passing the Order, returnNumber and new status. The custom script is responsible for setting the status and taking any other actions necessary, including the possibility of creating a credit invoice: changeStatus = function (retrn:Return, status) { retrn.status=status; Hook extensionPointAfterStatusChange This call delegates the update of the return-status to a custom script, passing the Order, returnNumber and new status. The custom script is responsible for setting the status and taking any other actions necessary, including the possibility of creating a credit invoice: changeStatus = function (retrn:Return, status) { retrn.status=status; Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice.

## Constants

## Properties

## Constructor Summary

## Method Summary

### addReturnItem

**Signature:** `addReturnItem(retrn : Return, inputData : ReturnItem) : Status`

The hook provides customization in the process of assigning the returned amount, quantity etc.

### afterStatusChange

**Signature:** `afterStatusChange(retrn : Return) : Status`

Called after method changeStatus(Return, ReturnWO) returns Status.OK.

### changeStatus

**Signature:** `changeStatus(retrn : Return, inputData : Return) : Status`

Responsible to change the status of a Return: the custom script is responsible for setting the new status using Return.setStatus(String).

### createReturn

**Signature:** `createReturn(inputData : Return) : Return`

This hook is responsible for creating a new Return, based on a ReturnCase.

### notifyStatusChange

**Signature:** `notifyStatusChange(retrn : Return) : Status`

Called after method changeStatus(Return, ReturnWO) returns Status.OK (and after method afterStatusChange(Return)) to inform of a successful status change.

## Method Detail

## Method Details

### addReturnItem

**Signature:** `addReturnItem(retrn : Return, inputData : ReturnItem) : Status`

**Description:** The hook provides customization in the process of assigning the returned amount, quantity etc. Here it is possible to refund differently based on the return reason code for example. Also one could correct the inventory based on the return information. Utilize ReturnCaseItem.createReturnItem(String) to create a new ReturnItem.

**Parameters:**

- `retrn`: the return for which an return item should be created
- `inputData`: the return item

**Returns:**

Status.OK return item is successfully added Status.ERROR return item addition failed.

---

### afterStatusChange

**Signature:** `afterStatusChange(retrn : Return) : Status`

**Description:** Called after method changeStatus(Return, ReturnWO) returns Status.OK. The call is made in a separate database transaction allowing the script implementation to make an independent remote call if desired.

**Parameters:**

- `retrn`: the return

**Returns:**

Status.OK status successful Status.ERROR on failure

---

### changeStatus

**Signature:** `changeStatus(retrn : Return, inputData : Return) : Status`

**Description:** Responsible to change the status of a Return: the custom script is responsible for setting the new status using Return.setStatus(String). The invoice handling should be implemented here using Return.createInvoice(String) or ReturnCase.createInvoice(String). For example create an Invoice for a Return moving to status Return.STATUS_COMPLETED.

**Parameters:**

- `retrn`: the return which status should change
- `inputData`: the data in which the new status is included

**Returns:**

Status.OK status successfully changed Status.ERROR status not changed.

---

### createReturn

**Signature:** `createReturn(inputData : Return) : Return`

**Description:** This hook is responsible for creating a new Return, based on a ReturnCase. 2 basic workflows are supported: On-the-fly return: create the parent ReturnCase using Order.createReturnCase(String, Boolean). Return-merchandise-authorization (RMA) workflow: resolve an existing ReturnCase using Order.getReturnCase(String). In both cases use this method to create the Return based on the inputData. Additional functionality like creating history entry, handling the return fees or the shipping cost credit can be implemented in the hook after the Return is created.

**Parameters:**

- `inputData`: the return

**Returns:**

the created return

---

### notifyStatusChange

**Signature:** `notifyStatusChange(retrn : Return) : Status`

**Description:** Called after method changeStatus(Return, ReturnWO) returns Status.OK (and after method afterStatusChange(Return)) to inform of a successful status change. The call is made outside any database transaction. This is the best hook in which to send customer notifications as the status change has already been successfully written to the database

**Parameters:**

- `retrn`: the return

**Returns:**

Status.OK status successful Status.ERROR on failure

---
```

--------------------------------------------------------------------------------
/docs/dw_customer/ProductListMgr.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.customer

# Class ProductListMgr

## Inheritance Hierarchy

- Object
  - dw.customer.ProductListMgr

## Description

ProductListMgr provides methods for retrieving, creating, searching for, and removing product lists.

## Constructor Summary

## Method Summary

### createProductList

**Signature:** `static createProductList(customer : Customer, type : Number) : ProductList`

Creates a new instance of a product list, of the specified type.

### getProductList

**Signature:** `static getProductList(ID : String) : ProductList`

Gets the product list by its ID.

### getProductList

**Signature:** `static getProductList(profile : Profile, type : Number) : ProductList`

Returns the first product list belonging to the customer with the specified profile.

### getProductLists

**Signature:** `static getProductLists(customer : Customer, type : Number) : Collection`

Retrieve all product lists of the specified type owned by the specified customer.

### getProductLists

**Signature:** `static getProductLists(customer : Customer, type : Number, eventType : String) : Collection`

Retrieve all the product lists of the specified type and event type belonging to the specified customer.

### getProductLists

**Signature:** `static getProductLists(customerAddress : CustomerAddress) : Collection`

Returns the collection of product lists that have the specified address as the shipping address.

### queryProductLists

**Signature:** `static queryProductLists(queryAttributes : Map, sortString : String) : SeekableIterator`

Searches for product list instances.

### queryProductLists

**Signature:** `static queryProductLists(queryString : String, sortString : String, args : Object...) : SeekableIterator`

Searches for product list instances.

### removeProductList

**Signature:** `static removeProductList(productList : ProductList) : void`

Removes the specified product list from the system.

## Method Detail

## Method Details

### createProductList

**Signature:** `static createProductList(customer : Customer, type : Number) : ProductList`

**Description:** Creates a new instance of a product list, of the specified type.

**Parameters:**

- `customer`: The customer owning the product list, must not be null.
- `type`: The type of list (e.g. wish list, gift registry). The types are defined as constants within ProductList.

**Returns:**

the new list instance.

---

### getProductList

**Signature:** `static getProductList(ID : String) : ProductList`

**Description:** Gets the product list by its ID.

**Parameters:**

- `ID`: The product list ID.

**Returns:**

the ProductList instance, or null if a list with the specified UUID doesn't exist.

---

### getProductList

**Signature:** `static getProductList(profile : Profile, type : Number) : ProductList`

**Description:** Returns the first product list belonging to the customer with the specified profile.

**Deprecated:**

Use getProductLists(Customer, Number) or getProductLists(Customer, Number, String) instead.

**Parameters:**

- `profile`: The profile of the customer whose product list is to be retrieved.
- `type`: The type of list (e.g. wish list, gift registry). The types are defined as constants within ProductList.

**Returns:**

the product list, or null if none exists.

---

### getProductLists

**Signature:** `static getProductLists(customer : Customer, type : Number) : Collection`

**Description:** Retrieve all product lists of the specified type owned by the specified customer.

**Parameters:**

- `customer`: The customer used for the query, must not be null.
- `type`: The type of list used for the query. The types are defined as constants within ProductList.

**Returns:**

the unsorted collection of ProductList instances of the specified type belonging to the specified customer.

---

### getProductLists

**Signature:** `static getProductLists(customer : Customer, type : Number, eventType : String) : Collection`

**Description:** Retrieve all the product lists of the specified type and event type belonging to the specified customer.

**Parameters:**

- `customer`: The customer used for the query, must not be null.
- `type`: The type of list used for the query. The types are defined as constants within ProductList.
- `eventType`: The event type used for the query, must not be null.

**Returns:**

the unsorted collection of ProductList instances of the specified type and event type belonging to the specified customer.

---

### getProductLists

**Signature:** `static getProductLists(customerAddress : CustomerAddress) : Collection`

**Description:** Returns the collection of product lists that have the specified address as the shipping address.

**Parameters:**

- `customerAddress`: the address to test, must not be null.

**Returns:**

the unsorted collection of ProductList instances using this address.

---

### queryProductLists

**Signature:** `static queryProductLists(queryAttributes : Map, sortString : String) : SeekableIterator`

**Description:** Searches for product list instances. The search can be configured with a map, which key-value pairs are converted into a query expression. The key-value pairs are turned into a sequence of '=' or 'like' conditions, which are combined with AND statements. Example: A map with the key/value pairs: 'name'/'tom*', 'age'/66 will be converted as follows: "name like 'tom*' and age = 66" The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources.

**Parameters:**

- `queryAttributes`: key-value pairs, which define the query.
- `sortString`: an optional sorting or null if no sorting is necessary.

**Returns:**

SeekableIterator containing the result set of the query.

**See Also:**

SeekableIterator.close()

---

### queryProductLists

**Signature:** `static queryProductLists(queryString : String, sortString : String, args : Object...) : SeekableIterator`

**Description:** Searches for product list instances. The search can be configured using a simple query language, which provides most common filter and operator functionality. The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The following operators are supported in a condition: = Equals - All types; supports NULL value (thumbnail = NULL) != Not equals - All types; supports NULL value (thumbnail != NULL) < Less than - Integer, Number and Date types only > Greater than - Integer, Number and Date types only <= Less or equals than - Integer, Number and Date types only >= Greater or equals than - Integer, Number and Date types only LIKE Like - String types and Email only; use if leading or trailing wildcards will be used to support substring search(custom.country LIKE 'US*') ILIKE Caseindependent Like - String types and Email only, use to support case insensitive query (custom.country ILIKE 'usa'), does also support wildcards for substring matching Conditions can be combined using logical expressions 'AND', 'OR' and 'NOT' and nested using parenthesis e.g. gender = {1} AND (age >= {2} OR (NOT profession LIKE {3})). The query language provides a placeholder syntax to pass objects as additional search parameters. Each passed object is related to a placeholder in the query string. The placeholder must be an Integer that is surrounded by braces. The first Integer value must be '0', the second '1' and so on, e.g. querySystemObjects("sample", "age = {0} or creationDate >= {1}", 18, date) The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. Sometimes it is desired to get all instances with a special sorting condition. This can be easily done by providing the 'sortString' in combination with an empty 'queryString', e.g. querySystemObjects("sample", "", "ID asc") It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources.

**Parameters:**

- `queryString`: the actual query.
- `sortString`: an optional sorting or null if no sorting is necessary.
- `args`: optional parameters for the queryString.

**Returns:**

SeekableIterator containing the result set of the query.

**See Also:**

SeekableIterator.close()

---

### removeProductList

**Signature:** `static removeProductList(productList : ProductList) : void`

**Description:** Removes the specified product list from the system.

**Parameters:**

- `productList`: The list to remove, must not be null.

---
```
Page 14/43FirstPrevNextLast