#
tokens: 47756/50000 11/825 files (page 20/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 20 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .DS_Store
├── .github
│   ├── dependabot.yml
│   ├── instructions
│   │   ├── mcp-node-tests.instructions.md
│   │   └── mcp-yml-tests.instructions.md
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── feature_request.yml
│   │   └── question.yml
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bug_fix.md
│   │   ├── documentation.md
│   │   └── new_tool.md
│   ├── pull_request_template.md
│   └── workflows
│       ├── ci.yml
│       ├── deploy-pages.yml
│       ├── publish.yml
│       └── update-docs.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── aegis.config.docs-only.json
├── aegis.config.json
├── aegis.config.with-dw.json
├── AGENTS.md
├── ai-instructions
│   ├── claude-desktop
│   │   └── claude_custom_instructions.md
│   ├── cursor
│   │   └── .cursor
│   │       └── rules
│   │           ├── debugging-workflows.mdc
│   │           ├── hooks-development.mdc
│   │           ├── isml-templates.mdc
│   │           ├── job-framework.mdc
│   │           ├── performance-optimization.mdc
│   │           ├── scapi-endpoints.mdc
│   │           ├── security-patterns.mdc
│   │           ├── sfcc-development.mdc
│   │           ├── sfra-controllers.mdc
│   │           ├── sfra-models.mdc
│   │           ├── system-objects.mdc
│   │           └── testing-patterns.mdc
│   └── github-copilot
│       └── copilot-instructions.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│   ├── best-practices
│   │   ├── cartridge_creation.md
│   │   ├── isml_templates.md
│   │   ├── job_framework.md
│   │   ├── localserviceregistry.md
│   │   ├── ocapi_hooks.md
│   │   ├── performance.md
│   │   ├── scapi_custom_endpoint.md
│   │   ├── scapi_hooks.md
│   │   ├── security.md
│   │   ├── sfra_client_side_js.md
│   │   ├── sfra_controllers.md
│   │   ├── sfra_models.md
│   │   └── sfra_scss.md
│   ├── dw_campaign
│   │   ├── ABTest.md
│   │   ├── ABTestMgr.md
│   │   ├── ABTestSegment.md
│   │   ├── AmountDiscount.md
│   │   ├── ApproachingDiscount.md
│   │   ├── BonusChoiceDiscount.md
│   │   ├── BonusDiscount.md
│   │   ├── Campaign.md
│   │   ├── CampaignMgr.md
│   │   ├── CampaignStatusCodes.md
│   │   ├── Coupon.md
│   │   ├── CouponMgr.md
│   │   ├── CouponRedemption.md
│   │   ├── CouponStatusCodes.md
│   │   ├── Discount.md
│   │   ├── DiscountPlan.md
│   │   ├── FixedPriceDiscount.md
│   │   ├── FixedPriceShippingDiscount.md
│   │   ├── FreeDiscount.md
│   │   ├── FreeShippingDiscount.md
│   │   ├── PercentageDiscount.md
│   │   ├── PercentageOptionDiscount.md
│   │   ├── PriceBookPriceDiscount.md
│   │   ├── Promotion.md
│   │   ├── PromotionMgr.md
│   │   ├── PromotionPlan.md
│   │   ├── SlotContent.md
│   │   ├── SourceCodeGroup.md
│   │   ├── SourceCodeInfo.md
│   │   ├── SourceCodeStatusCodes.md
│   │   └── TotalFixedPriceDiscount.md
│   ├── dw_catalog
│   │   ├── Catalog.md
│   │   ├── CatalogMgr.md
│   │   ├── Category.md
│   │   ├── CategoryAssignment.md
│   │   ├── CategoryLink.md
│   │   ├── PriceBook.md
│   │   ├── PriceBookMgr.md
│   │   ├── Product.md
│   │   ├── ProductActiveData.md
│   │   ├── ProductAttributeModel.md
│   │   ├── ProductAvailabilityLevels.md
│   │   ├── ProductAvailabilityModel.md
│   │   ├── ProductInventoryList.md
│   │   ├── ProductInventoryMgr.md
│   │   ├── ProductInventoryRecord.md
│   │   ├── ProductLink.md
│   │   ├── ProductMgr.md
│   │   ├── ProductOption.md
│   │   ├── ProductOptionModel.md
│   │   ├── ProductOptionValue.md
│   │   ├── ProductPriceInfo.md
│   │   ├── ProductPriceModel.md
│   │   ├── ProductPriceTable.md
│   │   ├── ProductSearchHit.md
│   │   ├── ProductSearchModel.md
│   │   ├── ProductSearchRefinementDefinition.md
│   │   ├── ProductSearchRefinements.md
│   │   ├── ProductSearchRefinementValue.md
│   │   ├── ProductVariationAttribute.md
│   │   ├── ProductVariationAttributeValue.md
│   │   ├── ProductVariationModel.md
│   │   ├── Recommendation.md
│   │   ├── SearchModel.md
│   │   ├── SearchRefinementDefinition.md
│   │   ├── SearchRefinements.md
│   │   ├── SearchRefinementValue.md
│   │   ├── SortingOption.md
│   │   ├── SortingRule.md
│   │   ├── Store.md
│   │   ├── StoreGroup.md
│   │   ├── StoreInventoryFilter.md
│   │   ├── StoreInventoryFilterValue.md
│   │   ├── StoreMgr.md
│   │   ├── Variant.md
│   │   └── VariationGroup.md
│   ├── dw_content
│   │   ├── Content.md
│   │   ├── ContentMgr.md
│   │   ├── ContentSearchModel.md
│   │   ├── ContentSearchRefinementDefinition.md
│   │   ├── ContentSearchRefinements.md
│   │   ├── ContentSearchRefinementValue.md
│   │   ├── Folder.md
│   │   ├── Library.md
│   │   ├── MarkupText.md
│   │   └── MediaFile.md
│   ├── dw_crypto
│   │   ├── CertificateRef.md
│   │   ├── CertificateUtils.md
│   │   ├── Cipher.md
│   │   ├── Encoding.md
│   │   ├── JWE.md
│   │   ├── JWEHeader.md
│   │   ├── JWS.md
│   │   ├── JWSHeader.md
│   │   ├── KeyRef.md
│   │   ├── Mac.md
│   │   ├── MessageDigest.md
│   │   ├── SecureRandom.md
│   │   ├── Signature.md
│   │   ├── WeakCipher.md
│   │   ├── WeakMac.md
│   │   ├── WeakMessageDigest.md
│   │   ├── WeakSignature.md
│   │   └── X509Certificate.md
│   ├── dw_customer
│   │   ├── AddressBook.md
│   │   ├── AgentUserMgr.md
│   │   ├── AgentUserStatusCodes.md
│   │   ├── AuthenticationStatus.md
│   │   ├── Credentials.md
│   │   ├── Customer.md
│   │   ├── CustomerActiveData.md
│   │   ├── CustomerAddress.md
│   │   ├── CustomerCDPData.md
│   │   ├── CustomerContextMgr.md
│   │   ├── CustomerGroup.md
│   │   ├── CustomerList.md
│   │   ├── CustomerMgr.md
│   │   ├── CustomerPasswordConstraints.md
│   │   ├── CustomerPaymentInstrument.md
│   │   ├── CustomerStatusCodes.md
│   │   ├── EncryptedObject.md
│   │   ├── ExternalProfile.md
│   │   ├── OrderHistory.md
│   │   ├── ProductList.md
│   │   ├── ProductListItem.md
│   │   ├── ProductListItemPurchase.md
│   │   ├── ProductListMgr.md
│   │   ├── ProductListRegistrant.md
│   │   ├── Profile.md
│   │   └── Wallet.md
│   ├── dw_extensions.applepay
│   │   ├── ApplePayHookResult.md
│   │   └── ApplePayHooks.md
│   ├── dw_extensions.facebook
│   │   ├── FacebookFeedHooks.md
│   │   └── FacebookProduct.md
│   ├── dw_extensions.paymentrequest
│   │   ├── PaymentRequestHookResult.md
│   │   └── PaymentRequestHooks.md
│   ├── dw_extensions.payments
│   │   ├── SalesforceBancontactPaymentDetails.md
│   │   ├── SalesforceCardPaymentDetails.md
│   │   ├── SalesforceEpsPaymentDetails.md
│   │   ├── SalesforceIdealPaymentDetails.md
│   │   ├── SalesforceKlarnaPaymentDetails.md
│   │   ├── SalesforcePaymentDetails.md
│   │   ├── SalesforcePaymentIntent.md
│   │   ├── SalesforcePaymentMethod.md
│   │   ├── SalesforcePaymentRequest.md
│   │   ├── SalesforcePaymentsHooks.md
│   │   ├── SalesforcePaymentsMgr.md
│   │   ├── SalesforcePaymentsSiteConfiguration.md
│   │   ├── SalesforcePayPalOrder.md
│   │   ├── SalesforcePayPalOrderAddress.md
│   │   ├── SalesforcePayPalOrderPayer.md
│   │   ├── SalesforcePayPalPaymentDetails.md
│   │   ├── SalesforceSepaDebitPaymentDetails.md
│   │   └── SalesforceVenmoPaymentDetails.md
│   ├── dw_extensions.pinterest
│   │   ├── PinterestAvailability.md
│   │   ├── PinterestFeedHooks.md
│   │   ├── PinterestOrder.md
│   │   ├── PinterestOrderHooks.md
│   │   └── PinterestProduct.md
│   ├── dw_io
│   │   ├── CSVStreamReader.md
│   │   ├── CSVStreamWriter.md
│   │   ├── File.md
│   │   ├── FileReader.md
│   │   ├── FileWriter.md
│   │   ├── InputStream.md
│   │   ├── OutputStream.md
│   │   ├── PrintWriter.md
│   │   ├── RandomAccessFileReader.md
│   │   ├── Reader.md
│   │   ├── StringWriter.md
│   │   ├── Writer.md
│   │   ├── XMLIndentingStreamWriter.md
│   │   ├── XMLStreamConstants.md
│   │   ├── XMLStreamReader.md
│   │   └── XMLStreamWriter.md
│   ├── dw_job
│   │   ├── JobExecution.md
│   │   └── JobStepExecution.md
│   ├── dw_net
│   │   ├── FTPClient.md
│   │   ├── FTPFileInfo.md
│   │   ├── HTTPClient.md
│   │   ├── HTTPRequestPart.md
│   │   ├── Mail.md
│   │   ├── SFTPClient.md
│   │   ├── SFTPFileInfo.md
│   │   ├── WebDAVClient.md
│   │   └── WebDAVFileInfo.md
│   ├── dw_object
│   │   ├── ActiveData.md
│   │   ├── CustomAttributes.md
│   │   ├── CustomObject.md
│   │   ├── CustomObjectMgr.md
│   │   ├── Extensible.md
│   │   ├── ExtensibleObject.md
│   │   ├── Note.md
│   │   ├── ObjectAttributeDefinition.md
│   │   ├── ObjectAttributeGroup.md
│   │   ├── ObjectAttributeValueDefinition.md
│   │   ├── ObjectTypeDefinition.md
│   │   ├── PersistentObject.md
│   │   ├── SimpleExtensible.md
│   │   └── SystemObjectMgr.md
│   ├── dw_order
│   │   ├── AbstractItem.md
│   │   ├── AbstractItemCtnr.md
│   │   ├── Appeasement.md
│   │   ├── AppeasementItem.md
│   │   ├── Basket.md
│   │   ├── BasketMgr.md
│   │   ├── BonusDiscountLineItem.md
│   │   ├── CouponLineItem.md
│   │   ├── CreateAgentBasketLimitExceededException.md
│   │   ├── CreateBasketFromOrderException.md
│   │   ├── CreateCouponLineItemException.md
│   │   ├── CreateOrderException.md
│   │   ├── CreateTemporaryBasketLimitExceededException.md
│   │   ├── GiftCertificate.md
│   │   ├── GiftCertificateLineItem.md
│   │   ├── GiftCertificateMgr.md
│   │   ├── GiftCertificateStatusCodes.md
│   │   ├── Invoice.md
│   │   ├── InvoiceItem.md
│   │   ├── LineItem.md
│   │   ├── LineItemCtnr.md
│   │   ├── Order.md
│   │   ├── OrderAddress.md
│   │   ├── OrderItem.md
│   │   ├── OrderMgr.md
│   │   ├── OrderPaymentInstrument.md
│   │   ├── OrderProcessStatusCodes.md
│   │   ├── PaymentCard.md
│   │   ├── PaymentInstrument.md
│   │   ├── PaymentMethod.md
│   │   ├── PaymentMgr.md
│   │   ├── PaymentProcessor.md
│   │   ├── PaymentStatusCodes.md
│   │   ├── PaymentTransaction.md
│   │   ├── PriceAdjustment.md
│   │   ├── PriceAdjustmentLimitTypes.md
│   │   ├── ProductLineItem.md
│   │   ├── ProductShippingCost.md
│   │   ├── ProductShippingLineItem.md
│   │   ├── ProductShippingModel.md
│   │   ├── Return.md
│   │   ├── ReturnCase.md
│   │   ├── ReturnCaseItem.md
│   │   ├── ReturnItem.md
│   │   ├── Shipment.md
│   │   ├── ShipmentShippingCost.md
│   │   ├── ShipmentShippingModel.md
│   │   ├── ShippingLineItem.md
│   │   ├── ShippingLocation.md
│   │   ├── ShippingMethod.md
│   │   ├── ShippingMgr.md
│   │   ├── ShippingOrder.md
│   │   ├── ShippingOrderItem.md
│   │   ├── SumItem.md
│   │   ├── TaxGroup.md
│   │   ├── TaxItem.md
│   │   ├── TaxMgr.md
│   │   ├── TrackingInfo.md
│   │   └── TrackingRef.md
│   ├── dw_order.hooks
│   │   ├── CalculateHooks.md
│   │   ├── OrderHooks.md
│   │   ├── PaymentHooks.md
│   │   ├── ReturnHooks.md
│   │   └── ShippingOrderHooks.md
│   ├── dw_rpc
│   │   ├── SOAPUtil.md
│   │   ├── Stub.md
│   │   └── WebReference.md
│   ├── dw_suggest
│   │   ├── BrandSuggestions.md
│   │   ├── CategorySuggestions.md
│   │   ├── ContentSuggestions.md
│   │   ├── CustomSuggestions.md
│   │   ├── ProductSuggestions.md
│   │   ├── SearchPhraseSuggestions.md
│   │   ├── SuggestedCategory.md
│   │   ├── SuggestedContent.md
│   │   ├── SuggestedPhrase.md
│   │   ├── SuggestedProduct.md
│   │   ├── SuggestedTerm.md
│   │   ├── SuggestedTerms.md
│   │   ├── Suggestions.md
│   │   └── SuggestModel.md
│   ├── dw_svc
│   │   ├── FTPService.md
│   │   ├── FTPServiceDefinition.md
│   │   ├── HTTPFormService.md
│   │   ├── HTTPFormServiceDefinition.md
│   │   ├── HTTPService.md
│   │   ├── HTTPServiceDefinition.md
│   │   ├── LocalServiceRegistry.md
│   │   ├── Result.md
│   │   ├── Service.md
│   │   ├── ServiceCallback.md
│   │   ├── ServiceConfig.md
│   │   ├── ServiceCredential.md
│   │   ├── ServiceDefinition.md
│   │   ├── ServiceProfile.md
│   │   ├── ServiceRegistry.md
│   │   ├── SOAPService.md
│   │   └── SOAPServiceDefinition.md
│   ├── dw_system
│   │   ├── AgentUserStatusCodes.md
│   │   ├── Cache.md
│   │   ├── CacheMgr.md
│   │   ├── HookMgr.md
│   │   ├── InternalObject.md
│   │   ├── JobProcessMonitor.md
│   │   ├── Log.md
│   │   ├── Logger.md
│   │   ├── LogNDC.md
│   │   ├── OrganizationPreferences.md
│   │   ├── Pipeline.md
│   │   ├── PipelineDictionary.md
│   │   ├── RemoteInclude.md
│   │   ├── Request.md
│   │   ├── RequestHooks.md
│   │   ├── Response.md
│   │   ├── RESTErrorResponse.md
│   │   ├── RESTResponseMgr.md
│   │   ├── RESTSuccessResponse.md
│   │   ├── SearchStatus.md
│   │   ├── Session.md
│   │   ├── Site.md
│   │   ├── SitePreferences.md
│   │   ├── Status.md
│   │   ├── StatusItem.md
│   │   ├── System.md
│   │   └── Transaction.md
│   ├── dw_util
│   │   ├── ArrayList.md
│   │   ├── Assert.md
│   │   ├── BigInteger.md
│   │   ├── Bytes.md
│   │   ├── Calendar.md
│   │   ├── Collection.md
│   │   ├── Currency.md
│   │   ├── DateUtils.md
│   │   ├── Decimal.md
│   │   ├── FilteringCollection.md
│   │   ├── Geolocation.md
│   │   ├── HashMap.md
│   │   ├── HashSet.md
│   │   ├── Iterator.md
│   │   ├── LinkedHashMap.md
│   │   ├── LinkedHashSet.md
│   │   ├── List.md
│   │   ├── Locale.md
│   │   ├── Map.md
│   │   ├── MapEntry.md
│   │   ├── MappingKey.md
│   │   ├── MappingMgr.md
│   │   ├── PropertyComparator.md
│   │   ├── SecureEncoder.md
│   │   ├── SecureFilter.md
│   │   ├── SeekableIterator.md
│   │   ├── Set.md
│   │   ├── SortedMap.md
│   │   ├── SortedSet.md
│   │   ├── StringUtils.md
│   │   ├── Template.md
│   │   └── UUIDUtils.md
│   ├── dw_value
│   │   ├── EnumValue.md
│   │   ├── MimeEncodedText.md
│   │   ├── Money.md
│   │   └── Quantity.md
│   ├── dw_web
│   │   ├── ClickStream.md
│   │   ├── ClickStreamEntry.md
│   │   ├── Cookie.md
│   │   ├── Cookies.md
│   │   ├── CSRFProtection.md
│   │   ├── Form.md
│   │   ├── FormAction.md
│   │   ├── FormElement.md
│   │   ├── FormElementValidationResult.md
│   │   ├── FormField.md
│   │   ├── FormFieldOption.md
│   │   ├── FormFieldOptions.md
│   │   ├── FormGroup.md
│   │   ├── FormList.md
│   │   ├── FormListItem.md
│   │   ├── Forms.md
│   │   ├── HttpParameter.md
│   │   ├── HttpParameterMap.md
│   │   ├── LoopIterator.md
│   │   ├── PageMetaData.md
│   │   ├── PageMetaTag.md
│   │   ├── PagingModel.md
│   │   ├── Resource.md
│   │   ├── URL.md
│   │   ├── URLAction.md
│   │   ├── URLParameter.md
│   │   ├── URLRedirect.md
│   │   ├── URLRedirectMgr.md
│   │   └── URLUtils.md
│   ├── sfra
│   │   ├── account.md
│   │   ├── address.md
│   │   ├── billing.md
│   │   ├── cart.md
│   │   ├── categories.md
│   │   ├── content.md
│   │   ├── locale.md
│   │   ├── order.md
│   │   ├── payment.md
│   │   ├── price-default.md
│   │   ├── price-range.md
│   │   ├── price-tiered.md
│   │   ├── product-bundle.md
│   │   ├── product-full.md
│   │   ├── product-line-items.md
│   │   ├── product-search.md
│   │   ├── product-tile.md
│   │   ├── querystring.md
│   │   ├── render.md
│   │   ├── request.md
│   │   ├── response.md
│   │   ├── server.md
│   │   ├── shipping.md
│   │   ├── store.md
│   │   ├── stores.md
│   │   └── totals.md
│   └── TopLevel
│       ├── APIException.md
│       ├── arguments.md
│       ├── Array.md
│       ├── ArrayBuffer.md
│       ├── BigInt.md
│       ├── Boolean.md
│       ├── ConversionError.md
│       ├── DataView.md
│       ├── Date.md
│       ├── Error.md
│       ├── ES6Iterator.md
│       ├── EvalError.md
│       ├── Fault.md
│       ├── Float32Array.md
│       ├── Float64Array.md
│       ├── Function.md
│       ├── Generator.md
│       ├── global.md
│       ├── Int16Array.md
│       ├── Int32Array.md
│       ├── Int8Array.md
│       ├── InternalError.md
│       ├── IOError.md
│       ├── Iterable.md
│       ├── Iterator.md
│       ├── JSON.md
│       ├── Map.md
│       ├── Math.md
│       ├── Module.md
│       ├── Namespace.md
│       ├── Number.md
│       ├── Object.md
│       ├── QName.md
│       ├── RangeError.md
│       ├── ReferenceError.md
│       ├── RegExp.md
│       ├── Set.md
│       ├── StopIteration.md
│       ├── String.md
│       ├── Symbol.md
│       ├── SyntaxError.md
│       ├── SystemError.md
│       ├── TypeError.md
│       ├── Uint16Array.md
│       ├── Uint32Array.md
│       ├── Uint8Array.md
│       ├── Uint8ClampedArray.md
│       ├── URIError.md
│       ├── WeakMap.md
│       ├── WeakSet.md
│       ├── XML.md
│       ├── XMLList.md
│       └── XMLStreamError.md
├── docs-site
│   ├── .gitignore
│   ├── App.tsx
│   ├── components
│   │   ├── Badge.tsx
│   │   ├── BreadcrumbSchema.tsx
│   │   ├── CodeBlock.tsx
│   │   ├── Collapsible.tsx
│   │   ├── ConfigBuilder.tsx
│   │   ├── ConfigHero.tsx
│   │   ├── ConfigModeTabs.tsx
│   │   ├── icons.tsx
│   │   ├── Layout.tsx
│   │   ├── LightCodeContainer.tsx
│   │   ├── NewcomerCTA.tsx
│   │   ├── NextStepsStrip.tsx
│   │   ├── OnThisPage.tsx
│   │   ├── Search.tsx
│   │   ├── SEO.tsx
│   │   ├── Sidebar.tsx
│   │   ├── StructuredData.tsx
│   │   ├── ToolCard.tsx
│   │   ├── ToolFilters.tsx
│   │   ├── Typography.tsx
│   │   └── VersionBadge.tsx
│   ├── constants.tsx
│   ├── index.html
│   ├── main.tsx
│   ├── metadata.json
│   ├── package-lock.json
│   ├── package.json
│   ├── pages
│   │   ├── AIInterfacesPage.tsx
│   │   ├── ConfigurationPage.tsx
│   │   ├── DevelopmentPage.tsx
│   │   ├── ExamplesPage.tsx
│   │   ├── FeaturesPage.tsx
│   │   ├── HomePage.tsx
│   │   ├── SecurityPage.tsx
│   │   ├── ToolsPage.tsx
│   │   └── TroubleshootingPage.tsx
│   ├── postcss.config.js
│   ├── public
│   │   ├── .well-known
│   │   │   └── security.txt
│   │   ├── 404.html
│   │   ├── android-chrome-192x192.png
│   │   ├── android-chrome-512x512.png
│   │   ├── apple-touch-icon.png
│   │   ├── explain-product-pricing-methods-no-mcp.png
│   │   ├── explain-product-pricing-methods.png
│   │   ├── favicon-16x16.png
│   │   ├── favicon-32x32.png
│   │   ├── favicon.ico
│   │   ├── llms.txt
│   │   ├── robots.txt
│   │   ├── site.webmanifest
│   │   └── sitemap.xml
│   ├── README.md
│   ├── scripts
│   │   ├── generate-search-index.js
│   │   ├── generate-sitemap.js
│   │   └── search-dev.js
│   ├── src
│   │   └── styles
│   │       ├── input.css
│   │       └── prism-theme.css
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   ├── types.ts
│   ├── utils
│   │   ├── search.ts
│   │   └── toolsData.ts
│   └── vite.config.ts
├── eslint.config.js
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── convert-docs.js
├── SECURITY.md
├── server.json
├── src
│   ├── clients
│   │   ├── base
│   │   │   ├── http-client.ts
│   │   │   ├── oauth-token.ts
│   │   │   └── ocapi-auth-client.ts
│   │   ├── best-practices-client.ts
│   │   ├── cartridge-generation-client.ts
│   │   ├── docs
│   │   │   ├── class-content-parser.ts
│   │   │   ├── class-name-resolver.ts
│   │   │   ├── documentation-scanner.ts
│   │   │   ├── index.ts
│   │   │   └── referenced-types-extractor.ts
│   │   ├── docs-client.ts
│   │   ├── log-client.ts
│   │   ├── logs
│   │   │   ├── index.ts
│   │   │   ├── log-analyzer.ts
│   │   │   ├── log-client.ts
│   │   │   ├── log-constants.ts
│   │   │   ├── log-file-discovery.ts
│   │   │   ├── log-file-reader.ts
│   │   │   ├── log-formatter.ts
│   │   │   ├── log-processor.ts
│   │   │   ├── log-types.ts
│   │   │   └── webdav-client-manager.ts
│   │   ├── ocapi
│   │   │   ├── code-versions-client.ts
│   │   │   ├── site-preferences-client.ts
│   │   │   └── system-objects-client.ts
│   │   ├── ocapi-client.ts
│   │   └── sfra-client.ts
│   ├── config
│   │   ├── configuration-factory.ts
│   │   └── dw-json-loader.ts
│   ├── core
│   │   ├── handlers
│   │   │   ├── abstract-log-tool-handler.ts
│   │   │   ├── base-handler.ts
│   │   │   ├── best-practices-handler.ts
│   │   │   ├── cartridge-handler.ts
│   │   │   ├── client-factory.ts
│   │   │   ├── code-version-handler.ts
│   │   │   ├── docs-handler.ts
│   │   │   ├── job-log-handler.ts
│   │   │   ├── job-log-tool-config.ts
│   │   │   ├── log-handler.ts
│   │   │   ├── log-tool-config.ts
│   │   │   ├── sfra-handler.ts
│   │   │   ├── system-object-handler.ts
│   │   │   └── validation-helpers.ts
│   │   ├── server.ts
│   │   └── tool-definitions.ts
│   ├── index.ts
│   ├── main.ts
│   ├── services
│   │   ├── file-system-service.ts
│   │   ├── index.ts
│   │   └── path-service.ts
│   ├── tool-configs
│   │   ├── best-practices-tool-config.ts
│   │   ├── cartridge-tool-config.ts
│   │   ├── code-version-tool-config.ts
│   │   ├── docs-tool-config.ts
│   │   ├── job-log-tool-config.ts
│   │   ├── log-tool-config.ts
│   │   ├── sfra-tool-config.ts
│   │   └── system-object-tool-config.ts
│   ├── types
│   │   └── types.ts
│   └── utils
│       ├── cache.ts
│       ├── job-log-tool-config.ts
│       ├── job-log-utils.ts
│       ├── log-cache.ts
│       ├── log-tool-config.ts
│       ├── log-tool-constants.ts
│       ├── log-tool-utils.ts
│       ├── logger.ts
│       ├── ocapi-url-builder.ts
│       ├── path-resolver.ts
│       ├── query-builder.ts
│       ├── utils.ts
│       └── validator.ts
├── tests
│   ├── __mocks__
│   │   ├── docs-client.ts
│   │   ├── src
│   │   │   └── clients
│   │   │       └── base
│   │   │           └── http-client.js
│   │   └── webdav.js
│   ├── base-handler.test.ts
│   ├── base-http-client.test.ts
│   ├── best-practices-handler.test.ts
│   ├── cache.test.ts
│   ├── cartridge-handler.test.ts
│   ├── class-content-parser.test.ts
│   ├── class-name-resolver.test.ts
│   ├── client-factory.test.ts
│   ├── code-version-handler.test.ts
│   ├── code-versions-client.test.ts
│   ├── config.test.ts
│   ├── configuration-factory.test.ts
│   ├── docs-handler.test.ts
│   ├── documentation-scanner.test.ts
│   ├── file-system-service.test.ts
│   ├── job-log-handler.test.ts
│   ├── job-log-utils.test.ts
│   ├── log-client.test.ts
│   ├── log-handler.test.ts
│   ├── log-processor.test.ts
│   ├── logger.test.ts
│   ├── mcp
│   │   ├── AGENTS.md
│   │   ├── node
│   │   │   ├── activate-code-version-advanced.full-mode.programmatic.test.js
│   │   │   ├── code-versions.full-mode.programmatic.test.js
│   │   │   ├── generate-cartridge-structure.docs-only.programmatic.test.js
│   │   │   ├── get-available-best-practice-guides.docs-only.programmatic.test.js
│   │   │   ├── get-available-sfra-documents.programmatic.test.js
│   │   │   ├── get-best-practice-guide.docs-only.programmatic.test.js
│   │   │   ├── get-hook-reference.docs-only.programmatic.test.js
│   │   │   ├── get-job-execution-summary.full-mode.programmatic.test.js
│   │   │   ├── get-job-log-entries.full-mode.programmatic.test.js
│   │   │   ├── get-latest-debug.full-mode.programmatic.test.js
│   │   │   ├── get-latest-error.full-mode.programmatic.test.js
│   │   │   ├── get-latest-info.full-mode.programmatic.test.js
│   │   │   ├── get-latest-job-log-files.full-mode.programmatic.test.js
│   │   │   ├── get-latest-warn.full-mode.programmatic.test.js
│   │   │   ├── get-log-file-contents.full-mode.programmatic.test.js
│   │   │   ├── get-sfcc-class-documentation.docs-only.programmatic.test.js
│   │   │   ├── get-sfcc-class-info.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-categories.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-document.programmatic.test.js
│   │   │   ├── get-sfra-documents-by-category.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definition.full-mode.programmatic.test.js
│   │   │   ├── get-system-object-definitions.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definitions.full-mode.programmatic.test.js
│   │   │   ├── list-log-files.full-mode.programmatic.test.js
│   │   │   ├── list-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-best-practices.docs-only.programmatic.test.js
│   │   │   ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs-by-name.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs.full-mode.programmatic.test.js
│   │   │   ├── search-logs.full-mode.programmatic.test.js
│   │   │   ├── search-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-sfcc-methods.docs-only.programmatic.test.js
│   │   │   ├── search-sfra-documentation.docs-only.programmatic.test.js
│   │   │   ├── search-site-preferences.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-groups.full-mode.programmatic.test.js
│   │   │   ├── summarize-logs.full-mode.programmatic.test.js
│   │   │   ├── tools.docs-only.programmatic.test.js
│   │   │   └── tools.full-mode.programmatic.test.js
│   │   ├── README.md
│   │   ├── test-fixtures
│   │   │   └── dw.json
│   │   └── yaml
│   │       ├── activate-code-version.docs-only.test.mcp.yml
│   │       ├── activate-code-version.full-mode.test.mcp.yml
│   │       ├── get_latest_error.test.mcp.yml
│   │       ├── get-available-best-practice-guides.docs-only.test.mcp.yml
│   │       ├── get-available-best-practice-guides.full-mode.test.mcp.yml
│   │       ├── get-available-sfra-documents.docs-only.test.mcp.yml
│   │       ├── get-available-sfra-documents.full-mode.test.mcp.yml
│   │       ├── get-best-practice-guide.docs-only.test.mcp.yml
│   │       ├── get-best-practice-guide.full-mode.test.mcp.yml
│   │       ├── get-code-versions.docs-only.test.mcp.yml
│   │       ├── get-code-versions.full-mode.test.mcp.yml
│   │       ├── get-hook-reference.docs-only.test.mcp.yml
│   │       ├── get-hook-reference.full-mode.test.mcp.yml
│   │       ├── get-job-execution-summary.full-mode.test.mcp.yml
│   │       ├── get-job-log-entries.full-mode.test.mcp.yml
│   │       ├── get-latest-debug.full-mode.test.mcp.yml
│   │       ├── get-latest-error.full-mode.test.mcp.yml
│   │       ├── get-latest-info.full-mode.test.mcp.yml
│   │       ├── get-latest-job-log-files.full-mode.test.mcp.yml
│   │       ├── get-latest-warn.full-mode.test.mcp.yml
│   │       ├── get-log-file-contents.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-info.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-info.full-mode.test.mcp.yml
│   │       ├── get-sfra-categories.docs-only.test.mcp.yml
│   │       ├── get-sfra-categories.full-mode.test.mcp.yml
│   │       ├── get-sfra-document.docs-only.test.mcp.yml
│   │       ├── get-sfra-document.full-mode.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.docs-only.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.full-mode.test.mcp.yml
│   │       ├── get-system-object-definition.docs-only.test.mcp.yml
│   │       ├── get-system-object-definition.full-mode.test.mcp.yml
│   │       ├── get-system-object-definitions.docs-only.test.mcp.yml
│   │       ├── get-system-object-definitions.full-mode.test.mcp.yml
│   │       ├── list-log-files.full-mode.test.mcp.yml
│   │       ├── list-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── list-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-best-practices.docs-only.test.mcp.yml
│   │       ├── search-best-practices.full-mode.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.test.mcp.yml
│   │       ├── search-job-logs-by-name.full-mode.test.mcp.yml
│   │       ├── search-job-logs.full-mode.test.mcp.yml
│   │       ├── search-logs.full-mode.test.mcp.yml
│   │       ├── search-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── search-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-sfcc-methods.docs-only.test.mcp.yml
│   │       ├── search-sfcc-methods.full-mode.test.mcp.yml
│   │       ├── search-sfra-documentation.docs-only.test.mcp.yml
│   │       ├── search-sfra-documentation.full-mode.test.mcp.yml
│   │       ├── search-site-preferences.docs-only.test.mcp.yml
│   │       ├── search-site-preferences.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.full-mode.test.mcp.yml
│   │       ├── summarize-logs.full-mode.test.mcp.yml
│   │       ├── tools.docs-only.test.mcp.yml
│   │       └── tools.full-mode.test.mcp.yml
│   ├── oauth-token.test.ts
│   ├── ocapi-auth-client.test.ts
│   ├── ocapi-client.test.ts
│   ├── path-service.test.ts
│   ├── query-builder.test.ts
│   ├── referenced-types-extractor.test.ts
│   ├── servers
│   │   ├── sfcc-mock-server
│   │   │   ├── mock-data
│   │   │   │   └── ocapi
│   │   │   │       ├── code-versions.json
│   │   │   │       ├── custom-object-attributes-customapi.json
│   │   │   │       ├── custom-object-attributes-globalsettings.json
│   │   │   │       ├── custom-object-attributes-versionhistory.json
│   │   │   │       ├── site-preferences-ccv.json
│   │   │   │       ├── site-preferences-fastforward.json
│   │   │   │       ├── site-preferences-sfra.json
│   │   │   │       ├── site-preferences-storefront.json
│   │   │   │       ├── site-preferences-system.json
│   │   │   │       ├── system-object-attribute-groups-campaign.json
│   │   │   │       ├── system-object-attribute-groups-category.json
│   │   │   │       ├── system-object-attribute-groups-order.json
│   │   │   │       ├── system-object-attribute-groups-product.json
│   │   │   │       ├── system-object-attribute-groups-sitepreferences.json
│   │   │   │       ├── system-object-attributes-customeraddress.json
│   │   │   │       ├── system-object-attributes-product-expanded.json
│   │   │   │       ├── system-object-attributes-product.json
│   │   │   │       ├── system-object-definition-category.json
│   │   │   │       ├── system-object-definition-customer.json
│   │   │   │       ├── system-object-definition-customeraddress.json
│   │   │   │       ├── system-object-definition-order.json
│   │   │   │       ├── system-object-definition-product.json
│   │   │   │       ├── system-object-definitions-old.json
│   │   │   │       └── system-object-definitions.json
│   │   │   ├── package-lock.json
│   │   │   ├── package.json
│   │   │   ├── README.md
│   │   │   ├── scripts
│   │   │   │   └── setup-logs.js
│   │   │   ├── server.js
│   │   │   └── src
│   │   │       ├── app.js
│   │   │       ├── config
│   │   │       │   └── server-config.js
│   │   │       ├── middleware
│   │   │       │   ├── auth.js
│   │   │       │   ├── cors.js
│   │   │       │   └── logging.js
│   │   │       ├── routes
│   │   │       │   ├── ocapi
│   │   │       │   │   ├── code-versions-handler.js
│   │   │       │   │   ├── oauth-handler.js
│   │   │       │   │   ├── ocapi-error-utils.js
│   │   │       │   │   ├── ocapi-utils.js
│   │   │       │   │   ├── site-preferences-handler.js
│   │   │       │   │   └── system-objects-handler.js
│   │   │       │   ├── ocapi.js
│   │   │       │   └── webdav.js
│   │   │       └── utils
│   │   │           ├── mock-data-loader.js
│   │   │           └── webdav-xml.js
│   │   └── sfcc-mock-server-manager.ts
│   ├── sfcc-mock-server.test.ts
│   ├── site-preferences-client.test.ts
│   ├── system-objects-client.test.ts
│   ├── utils.test.ts
│   ├── validation-helpers.test.ts
│   └── validator.test.ts
├── tsconfig.json
└── tsconfig.test.json
```

# Files

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

```yaml
  1 | description: "Tests for search_custom_object_attribute_definitions tool"
  2 | # Run with: npx aegis "tests/mcp/yaml/search-custom-object-attribute-definitions.test.mcp.yml" --config ./aegis.config.with-dw.json
  3 | 
  4 | tests:
  5 |   # Full Mode Tests (primary tests with mock server)
  6 |   - it: "should successfully search custom object attribute definitions with all query parameters"
  7 |     request:
  8 |       jsonrpc: "2.0"
  9 |       method: "tools/call"
 10 |       id: 1
 11 |       params:
 12 |         name: "search_custom_object_attribute_definitions"
 13 |         arguments:
 14 |           objectType: "VersionHistory"
 15 |           searchRequest:
 16 |             query:
 17 |               match_all_query: {}
 18 |             start: 0
 19 |             count: 10
 20 |             select: "(**)"
 21 |     expect:
 22 |       response:
 23 |         jsonrpc: "2.0"
 24 |         id: 1
 25 |         result:
 26 |           content:
 27 |             match:arrayElements:
 28 |               type: "text"
 29 |               text: "match:contains:object_attribute_definition_search_result"
 30 |           isError: false
 31 |       stderr: "toBeEmpty"
 32 |       performance:
 33 |         maxResponseTime: "2000ms"
 34 | 
 35 |   - it: "should return valid JSON structure for custom object attributes"
 36 |     request:
 37 |       jsonrpc: "2.0"
 38 |       method: "tools/call"
 39 |       id: 2
 40 |       params:
 41 |         name: "search_custom_object_attribute_definitions"
 42 |         arguments:
 43 |           objectType: "VersionHistory"
 44 |           searchRequest:
 45 |             query:
 46 |               match_all_query: {}
 47 |     expect:
 48 |       response:
 49 |         jsonrpc: "2.0"
 50 |         id: 2
 51 |         result:
 52 |           content:
 53 |             match:arrayElements:
 54 |               match:partial:
 55 |                 type: "text"
 56 |                 text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*"
 57 |           isError: false
 58 |       stderr: "toBeEmpty"
 59 | 
 60 |   - it: "should validate attribute definition structure in response"
 61 |     request:
 62 |       jsonrpc: "2.0"
 63 |       method: "tools/call"
 64 |       id: 3
 65 |       params:
 66 |         name: "search_custom_object_attribute_definitions"
 67 |         arguments:
 68 |           objectType: "VersionHistory"
 69 |           searchRequest:
 70 |             query:
 71 |               match_all_query: {}
 72 |     expect:
 73 |       response:
 74 |         jsonrpc: "2.0"
 75 |         id: 3
 76 |         result:
 77 |           content:
 78 |             match:arrayElements:
 79 |               match:partial:
 80 |                 type: "text"
 81 |                 text: "match:regex:[\\s\\S]*\"count\"[\\s\\S]*\"hits\"[\\s\\S]*\"total\"[\\s\\S]*"
 82 |           isError: false
 83 |       stderr: "toBeEmpty"
 84 | 
 85 |   - it: "should include required attribute definition fields"
 86 |     request:
 87 |       jsonrpc: "2.0"
 88 |       method: "tools/call"
 89 |       id: 4
 90 |       params:
 91 |         name: "search_custom_object_attribute_definitions"
 92 |         arguments:
 93 |           objectType: "VersionHistory"
 94 |           searchRequest:
 95 |             query:
 96 |               match_all_query: {}
 97 |     expect:
 98 |       response:
 99 |         jsonrpc: "2.0"
100 |         id: 4
101 |         result:
102 |           content:
103 |             match:arrayElements:
104 |               match:partial:
105 |                 type: "text"
106 |                 text: "match:regex:[\\s\\S]*\"id\"[\\s\\S]*\"value_type\"[\\s\\S]*\"mandatory\"[\\s\\S]*"
107 |           isError: false
108 |       stderr: "toBeEmpty"
109 | 
110 |   - it: "should work with minimal search request (missing searchRequest parameter)"
111 |     request:
112 |       jsonrpc: "2.0"
113 |       method: "tools/call"
114 |       id: 5
115 |       params:
116 |         name: "search_custom_object_attribute_definitions"
117 |         arguments:
118 |           objectType: "VersionHistory"
119 |     expect:
120 |       response:
121 |         jsonrpc: "2.0"
122 |         id: 5
123 |         result:
124 |           content:
125 |             match:arrayElements:
126 |               match:partial:
127 |                 type: "text"
128 |                 text: "match:contains:object_attribute_definition_search_result"
129 |           isError: false
130 |       stderr: "toBeEmpty"
131 | 
132 |   - it: "should handle text search query"
133 |     request:
134 |       jsonrpc: "2.0"
135 |       method: "tools/call"
136 |       id: 6
137 |       params:
138 |         name: "search_custom_object_attribute_definitions"
139 |         arguments:
140 |           objectType: "VersionHistory"
141 |           searchRequest:
142 |             query:
143 |               text_query:
144 |                 fields: ["id", "display_name"]
145 |                 search_phrase: "UUID"
146 |             start: 0
147 |             count: 5
148 |     expect:
149 |       response:
150 |         jsonrpc: "2.0"
151 |         id: 6
152 |         result:
153 |           content:
154 |             match:arrayElements:
155 |               match:partial:
156 |                 type: "text"
157 |                 text: "match:contains:object_attribute_definition_search_result"
158 |           isError: false
159 |       stderr: "toBeEmpty"
160 | 
161 |   - it: "should handle pagination parameters"
162 |     request:
163 |       jsonrpc: "2.0"
164 |       method: "tools/call"
165 |       id: 7
166 |       params:
167 |         name: "search_custom_object_attribute_definitions"
168 |         arguments:
169 |           objectType: "VersionHistory"
170 |           searchRequest:
171 |             query:
172 |               match_all_query: {}
173 |             start: 2
174 |             count: 3
175 |     expect:
176 |       response:
177 |         jsonrpc: "2.0"
178 |         id: 7
179 |         result:
180 |           content:
181 |             match:arrayElements:
182 |               match:partial:
183 |                 type: "text"
184 |                 text: "match:regex:[\\s\\S]*\"start\"\\s*:\\s*2[\\s\\S]*"
185 |           isError: false
186 |       stderr: "toBeEmpty"
187 | 
188 |   - it: "should handle sorting parameters"
189 |     request:
190 |       jsonrpc: "2.0"
191 |       method: "tools/call"
192 |       id: 8
193 |       params:
194 |         name: "search_custom_object_attribute_definitions"
195 |         arguments:
196 |           objectType: "VersionHistory"
197 |           searchRequest:
198 |             query:
199 |               match_all_query: {}
200 |             sorts:
201 |               - field: "id"
202 |                 sort_order: "asc"
203 |     expect:
204 |       response:
205 |         jsonrpc: "2.0"
206 |         id: 8
207 |         result:
208 |           content:
209 |             match:arrayElements:
210 |               match:partial:
211 |                 type: "text"
212 |                 text: "match:contains:object_attribute_definition_search_result"
213 |           isError: false
214 |       stderr: "toBeEmpty"
215 | 
216 |   # Error Handling Tests (Full Mode)
217 |   - it: "should reject empty objectType parameter"
218 |     request:
219 |       jsonrpc: "2.0"
220 |       method: "tools/call"
221 |       id: 9
222 |       params:
223 |         name: "search_custom_object_attribute_definitions"
224 |         arguments:
225 |           objectType: ""
226 |           searchRequest:
227 |             query:
228 |               match_all_query: {}
229 |     expect:
230 |       response:
231 |         jsonrpc: "2.0"
232 |         id: 9
233 |         result:
234 |           content:
235 |             match:arrayElements:
236 |               match:partial:
237 |                 type: "text"
238 |                 text: "match:contains:objectType must be a non-empty string"
239 |           isError: true
240 |       stderr: "toBeEmpty"
241 |       performance:
242 |         maxResponseTime: "800ms"
243 | 
244 |   - it: "should handle unknown custom object type"
245 |     request:
246 |       jsonrpc: "2.0"
247 |       method: "tools/call"
248 |       id: 10
249 |       params:
250 |         name: "search_custom_object_attribute_definitions"
251 |         arguments:
252 |           objectType: "UnknownCustomObject"
253 |           searchRequest:
254 |             query:
255 |               match_all_query: {}
256 |     expect:
257 |       response:
258 |         jsonrpc: "2.0"
259 |         id: 10
260 |         result:
261 |           content:
262 |             match:arrayElements:
263 |               match:partial:
264 |                 type: "text"
265 |                 text: "match:regex:[\\s\\S]*ObjectTypeNotFoundException[\\s\\S]*"
266 |           isError: true
267 |       stderr: "toBeEmpty"
268 |       performance:
269 |         maxResponseTime: "800ms"
270 | 
271 |   - it: "should handle invalid search request structure"
272 |     request:
273 |       jsonrpc: "2.0"
274 |       method: "tools/call"
275 |       id: 11
276 |       params:
277 |         name: "search_custom_object_attribute_definitions"
278 |         arguments:
279 |           objectType: "VersionHistory"
280 |           searchRequest:
281 |             invalid: "structure"
282 |     expect:
283 |       response:
284 |         jsonrpc: "2.0"
285 |         id: 11
286 |         result:
287 |           content:
288 |             match:arrayElements:
289 |               match:partial:
290 |                 type: "text"
291 |                 text: "match:regex:[\\s\\S]*PropertyConstraintViolationException[\\s\\S]*"
292 |           isError: true
293 |       stderr: "toBeEmpty"
294 |       performance:
295 |         maxResponseTime: "800ms"
296 | 
297 |   - it: "should reject missing objectType parameter"
298 |     request:
299 |       jsonrpc: "2.0"
300 |       method: "tools/call"
301 |       id: 12
302 |       params:
303 |         name: "search_custom_object_attribute_definitions"
304 |         arguments:
305 |           searchRequest:
306 |             query:
307 |               match_all_query: {}
308 |     expect:
309 |       response:
310 |         jsonrpc: "2.0"
311 |         id: 12
312 |         result:
313 |           content:
314 |             match:arrayElements:
315 |               match:partial:
316 |                 type: "text"
317 |                 text: "match:contains:objectType"
318 |           isError: true
319 |       stderr: "toBeEmpty"
320 | 
321 |   # Performance Tests
322 |   - it: "should respond quickly for basic search operations"
323 |     request:
324 |       jsonrpc: "2.0"
325 |       method: "tools/call"
326 |       id: 13
327 |       params:
328 |         name: "search_custom_object_attribute_definitions"
329 |         arguments:
330 |           objectType: "VersionHistory"
331 |           searchRequest:
332 |             query:
333 |               match_all_query: {}
334 |     expect:
335 |       response:
336 |         jsonrpc: "2.0"
337 |         id: 13
338 |         result:
339 |           content:
340 |             match:arrayElements:
341 |               match:partial:
342 |                 type: "text"
343 |           isError: false
344 |       stderr: "toBeEmpty"
345 |       performance:
346 |         maxResponseTime: "1500ms"
347 | 
348 |   - it: "should handle complex queries within reasonable time"
349 |     request:
350 |       jsonrpc: "2.0"
351 |       method: "tools/call"
352 |       id: 14
353 |       params:
354 |         name: "search_custom_object_attribute_definitions"
355 |         arguments:
356 |           objectType: "VersionHistory"
357 |           searchRequest:
358 |             query:
359 |               bool_query:
360 |                 must:
361 |                   - text_query:
362 |                       fields: ["id"]
363 |                       search_phrase: "component"
364 |                 should:
365 |                   - term_query:
366 |                       fields: ["value_type"]
367 |                       operator: "is"
368 |                       values: ["string"]
369 |             sorts:
370 |               - field: "id"
371 |                 sort_order: "desc"
372 |             start: 0
373 |             count: 20
374 |     expect:
375 |       response:
376 |         jsonrpc: "2.0"
377 |         id: 14
378 |         result:
379 |           content:
380 |             match:arrayElements:
381 |               match:partial:
382 |                 type: "text"
383 |                 text: "match:contains:object_attribute_definition_search_result"
384 |           isError: false
385 |       stderr: "toBeEmpty"
386 |       performance:
387 |         maxResponseTime: "2000ms"
```

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

```markdown
  1 | ## Package: dw.content
  2 | 
  3 | # Class Content
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.content.Content
 11 | 
 12 | ## Description
 13 | 
 14 | Class representing a Content asset in Commerce Cloud Digital.
 15 | 
 16 | ## Properties
 17 | 
 18 | ### classificationFolder
 19 | 
 20 | **Type:** Folder (Read Only)
 21 | 
 22 | The Folder associated with this Content. The folder is
 23 |  used to determine the classification of the content.
 24 | 
 25 | ### description
 26 | 
 27 | **Type:** String (Read Only)
 28 | 
 29 | The description in the current locale or null.
 30 | 
 31 | ### folders
 32 | 
 33 | **Type:** Collection (Read Only)
 34 | 
 35 | All folders to which this content is assigned.
 36 | 
 37 | ### ID
 38 | 
 39 | **Type:** String (Read Only)
 40 | 
 41 | The ID of the content asset.
 42 | 
 43 | ### name
 44 | 
 45 | **Type:** String (Read Only)
 46 | 
 47 | The name of the content asset.
 48 | 
 49 | ### online
 50 | 
 51 | **Type:** boolean (Read Only)
 52 | 
 53 | The online status of the content.
 54 | 
 55 | ### onlineFlag
 56 | 
 57 | **Type:** boolean (Read Only)
 58 | 
 59 | The online status flag of the content.
 60 | 
 61 | ### page
 62 | 
 63 | **Type:** Page (Read Only)
 64 | 
 65 | Returns if the content is a Page or not.
 66 | 
 67 | ### pageDescription
 68 | 
 69 | **Type:** String (Read Only)
 70 | 
 71 | The page description for the content in the current locale
 72 |  or null if there is no page description.
 73 | 
 74 | ### pageKeywords
 75 | 
 76 | **Type:** String (Read Only)
 77 | 
 78 | The page keywords for the content in the current locale
 79 |  or null if there is no page title.
 80 | 
 81 | ### pageMetaTags
 82 | 
 83 | **Type:** Array (Read Only)
 84 | 
 85 | All page meta tags, defined for this instance for which content can be generated.
 86 | 
 87 |  The meta tag content is generated based on the content detail page meta tag context and rules.
 88 |  The rules are obtained from the current content or inherited from the default folder,
 89 |  up to the root folder.
 90 | 
 91 | ### pageTitle
 92 | 
 93 | **Type:** String (Read Only)
 94 | 
 95 | The page title for the content in the current locale
 96 |  or null if there is no page title.
 97 | 
 98 | ### pageURL
 99 | 
100 | **Type:** String (Read Only)
101 | 
102 | The page URL for the content in the current locale
103 |  or null if there is no page URL.
104 | 
105 | ### searchable
106 | 
107 | **Type:** boolean (Read Only)
108 | 
109 | The search status of the content.
110 | 
111 | ### searchableFlag
112 | 
113 | **Type:** boolean (Read Only)
114 | 
115 | The online status flag of the content.
116 | 
117 | ### siteMapChangeFrequency
118 | 
119 | **Type:** String (Read Only)
120 | 
121 | The contents change frequency needed for the sitemap creation.
122 | 
123 | ### siteMapIncluded
124 | 
125 | **Type:** Number (Read Only)
126 | 
127 | The status if the content is included into the sitemap.
128 | 
129 | ### siteMapPriority
130 | 
131 | **Type:** Number (Read Only)
132 | 
133 | The contents priority needed for the sitemap creation.
134 |  If no priority is defined, the method returns 0.0.
135 | 
136 | ### template
137 | 
138 | **Type:** String (Read Only)
139 | 
140 | The value of attribute 'template'.
141 | 
142 | ## Constructor Summary
143 | 
144 | ## Method Summary
145 | 
146 | ### getClassificationFolder
147 | 
148 | **Signature:** `getClassificationFolder() : Folder`
149 | 
150 | Returns the Folder associated with this Content.
151 | 
152 | ### getDescription
153 | 
154 | **Signature:** `getDescription() : String`
155 | 
156 | Returns the description in the current locale or null.
157 | 
158 | ### getFolders
159 | 
160 | **Signature:** `getFolders() : Collection`
161 | 
162 | Returns all folders to which this content is assigned.
163 | 
164 | ### getID
165 | 
166 | **Signature:** `getID() : String`
167 | 
168 | Returns the ID of the content asset.
169 | 
170 | ### getName
171 | 
172 | **Signature:** `getName() : String`
173 | 
174 | Returns the name of the content asset.
175 | 
176 | ### getOnlineFlag
177 | 
178 | **Signature:** `getOnlineFlag() : boolean`
179 | 
180 | Returns the online status flag of the content.
181 | 
182 | ### getPageDescription
183 | 
184 | **Signature:** `getPageDescription() : String`
185 | 
186 | Returns the page description for the content in the current locale or null if there is no page description.
187 | 
188 | ### getPageKeywords
189 | 
190 | **Signature:** `getPageKeywords() : String`
191 | 
192 | Returns the page keywords for the content in the current locale or null if there is no page title.
193 | 
194 | ### getPageMetaTag
195 | 
196 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag`
197 | 
198 | Returns the page meta tag for the specified id.
199 | 
200 | ### getPageMetaTags
201 | 
202 | **Signature:** `getPageMetaTags() : Array`
203 | 
204 | Returns all page meta tags, defined for this instance for which content can be generated.
205 | 
206 | ### getPageTitle
207 | 
208 | **Signature:** `getPageTitle() : String`
209 | 
210 | Returns the page title for the content in the current locale or null if there is no page title.
211 | 
212 | ### getPageURL
213 | 
214 | **Signature:** `getPageURL() : String`
215 | 
216 | Returns the page URL for the content in the current locale or null if there is no page URL.
217 | 
218 | ### getSearchableFlag
219 | 
220 | **Signature:** `getSearchableFlag() : boolean`
221 | 
222 | Returns the online status flag of the content.
223 | 
224 | ### getSiteMapChangeFrequency
225 | 
226 | **Signature:** `getSiteMapChangeFrequency() : String`
227 | 
228 | Returns the contents change frequency needed for the sitemap creation.
229 | 
230 | ### getSiteMapIncluded
231 | 
232 | **Signature:** `getSiteMapIncluded() : Number`
233 | 
234 | Returns the status if the content is included into the sitemap.
235 | 
236 | ### getSiteMapPriority
237 | 
238 | **Signature:** `getSiteMapPriority() : Number`
239 | 
240 | Returns the contents priority needed for the sitemap creation.
241 | 
242 | ### getTemplate
243 | 
244 | **Signature:** `getTemplate() : String`
245 | 
246 | Returns the value of attribute 'template'.
247 | 
248 | ### isOnline
249 | 
250 | **Signature:** `isOnline() : boolean`
251 | 
252 | Returns the online status of the content.
253 | 
254 | ### isPage
255 | 
256 | **Signature:** `isPage() : boolean`
257 | 
258 | Returns if the content is a Page or not.
259 | 
260 | ### isSearchable
261 | 
262 | **Signature:** `isSearchable() : boolean`
263 | 
264 | Returns the search status of the content.
265 | 
266 | ### toPage
267 | 
268 | **Signature:** `toPage() : Page`
269 | 
270 | Converts the content into the Page representation if isPage() yields true.
271 | 
272 | ## Method Detail
273 | 
274 | ## Method Details
275 | 
276 | ### getClassificationFolder
277 | 
278 | **Signature:** `getClassificationFolder() : Folder`
279 | 
280 | **Description:** Returns the Folder associated with this Content. The folder is used to determine the classification of the content.
281 | 
282 | **Returns:**
283 | 
284 | the classification Folder.
285 | 
286 | ---
287 | 
288 | ### getDescription
289 | 
290 | **Signature:** `getDescription() : String`
291 | 
292 | **Description:** Returns the description in the current locale or null.
293 | 
294 | **Returns:**
295 | 
296 | the description in the current locale or null.
297 | 
298 | ---
299 | 
300 | ### getFolders
301 | 
302 | **Signature:** `getFolders() : Collection`
303 | 
304 | **Description:** Returns all folders to which this content is assigned.
305 | 
306 | **Returns:**
307 | 
308 | Collection of Folder objects.
309 | 
310 | ---
311 | 
312 | ### getID
313 | 
314 | **Signature:** `getID() : String`
315 | 
316 | **Description:** Returns the ID of the content asset.
317 | 
318 | **Returns:**
319 | 
320 | the ID of the content asset.
321 | 
322 | ---
323 | 
324 | ### getName
325 | 
326 | **Signature:** `getName() : String`
327 | 
328 | **Description:** Returns the name of the content asset.
329 | 
330 | **Returns:**
331 | 
332 | the name of the content asset.
333 | 
334 | ---
335 | 
336 | ### getOnlineFlag
337 | 
338 | **Signature:** `getOnlineFlag() : boolean`
339 | 
340 | **Description:** Returns the online status flag of the content.
341 | 
342 | **Returns:**
343 | 
344 | true if the content is online, false otherwise.
345 | 
346 | ---
347 | 
348 | ### getPageDescription
349 | 
350 | **Signature:** `getPageDescription() : String`
351 | 
352 | **Description:** Returns the page description for the content in the current locale or null if there is no page description.
353 | 
354 | **Returns:**
355 | 
356 | the page description for the content in the current locale or null if there is no page description.
357 | 
358 | ---
359 | 
360 | ### getPageKeywords
361 | 
362 | **Signature:** `getPageKeywords() : String`
363 | 
364 | **Description:** Returns the page keywords for the content in the current locale or null if there is no page title.
365 | 
366 | **Returns:**
367 | 
368 | the page keywords for the content in the current locale or null if there is no page title.
369 | 
370 | ---
371 | 
372 | ### getPageMetaTag
373 | 
374 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag`
375 | 
376 | **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.
377 | 
378 | **Parameters:**
379 | 
380 | - `id`: the ID to get the page meta tag for
381 | 
382 | **Returns:**
383 | 
384 | page meta tag containing content generated based on rules
385 | 
386 | ---
387 | 
388 | ### getPageMetaTags
389 | 
390 | **Signature:** `getPageMetaTags() : Array`
391 | 
392 | **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.
393 | 
394 | **Returns:**
395 | 
396 | page meta tags defined for this instance, containing content generated based on rules
397 | 
398 | ---
399 | 
400 | ### getPageTitle
401 | 
402 | **Signature:** `getPageTitle() : String`
403 | 
404 | **Description:** Returns the page title for the content in the current locale or null if there is no page title.
405 | 
406 | **Returns:**
407 | 
408 | the page title for the content in the current locale or null if there is no page title.
409 | 
410 | ---
411 | 
412 | ### getPageURL
413 | 
414 | **Signature:** `getPageURL() : String`
415 | 
416 | **Description:** Returns the page URL for the content in the current locale or null if there is no page URL.
417 | 
418 | **Returns:**
419 | 
420 | the page URL for the content in the current locale or null if there is no page URL.
421 | 
422 | ---
423 | 
424 | ### getSearchableFlag
425 | 
426 | **Signature:** `getSearchableFlag() : boolean`
427 | 
428 | **Description:** Returns the online status flag of the content.
429 | 
430 | **Returns:**
431 | 
432 | true if the content is searchable, false otherwise.
433 | 
434 | ---
435 | 
436 | ### getSiteMapChangeFrequency
437 | 
438 | **Signature:** `getSiteMapChangeFrequency() : String`
439 | 
440 | **Description:** Returns the contents change frequency needed for the sitemap creation.
441 | 
442 | **Returns:**
443 | 
444 | The contents sitemap change frequency.
445 | 
446 | ---
447 | 
448 | ### getSiteMapIncluded
449 | 
450 | **Signature:** `getSiteMapIncluded() : Number`
451 | 
452 | **Description:** Returns the status if the content is included into the sitemap.
453 | 
454 | **Returns:**
455 | 
456 | the value of the attribute 'siteMapIncluded'
457 | 
458 | ---
459 | 
460 | ### getSiteMapPriority
461 | 
462 | **Signature:** `getSiteMapPriority() : Number`
463 | 
464 | **Description:** Returns the contents priority needed for the sitemap creation. If no priority is defined, the method returns 0.0.
465 | 
466 | **Returns:**
467 | 
468 | The contents sitemap priority.
469 | 
470 | ---
471 | 
472 | ### getTemplate
473 | 
474 | **Signature:** `getTemplate() : String`
475 | 
476 | **Description:** Returns the value of attribute 'template'.
477 | 
478 | **Returns:**
479 | 
480 | the value of the attribute 'template'
481 | 
482 | ---
483 | 
484 | ### isOnline
485 | 
486 | **Signature:** `isOnline() : boolean`
487 | 
488 | **Description:** Returns the online status of the content.
489 | 
490 | **Returns:**
491 | 
492 | true if the content is online, false otherwise.
493 | 
494 | ---
495 | 
496 | ### isPage
497 | 
498 | **Signature:** `isPage() : boolean`
499 | 
500 | **Description:** Returns if the content is a Page or not.
501 | 
502 | **Returns:**
503 | 
504 | true if the content is a Page, false otherwise.
505 | 
506 | ---
507 | 
508 | ### isSearchable
509 | 
510 | **Signature:** `isSearchable() : boolean`
511 | 
512 | **Description:** Returns the search status of the content.
513 | 
514 | **Returns:**
515 | 
516 | true if the content is searchable, false otherwise.
517 | 
518 | ---
519 | 
520 | ### toPage
521 | 
522 | **Signature:** `toPage() : Page`
523 | 
524 | **Description:** Converts the content into the Page representation if isPage() yields true.
525 | 
526 | **Returns:**
527 | 
528 | the Page representation of the content if it is a page, null otherwise.
529 | 
530 | **See Also:**
531 | 
532 | PageMgr.getPage(String)
533 | 
534 | ---
```

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

```yaml
  1 | description: "Tests for search_sfra_documentation tool in docs-only mode"
  2 | 
  3 | tests:
  4 |   - it: "should return structured search results for valid query 'render'"
  5 |     request:
  6 |       jsonrpc: "2.0"
  7 |       method: "tools/call"
  8 |       id: "test-1"
  9 |       params:
 10 |         name: "search_sfra_documentation"
 11 |         arguments:
 12 |           query: "render"
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "test-1"
 17 |         result:
 18 |           isError: false
 19 |           content:
 20 |             match:arrayElements:
 21 |               type: "text"
 22 |               text: "match:regex:\\[[\\s\\S]*\\]"
 23 |       stderr: "toBeEmpty"
 24 | 
 25 |   - it: "should validate search result structure contains JSON array with documents"
 26 |     request:
 27 |       jsonrpc: "2.0"
 28 |       method: "tools/call"
 29 |       id: "test-2"
 30 |       params:
 31 |         name: "search_sfra_documentation"
 32 |         arguments:
 33 |           query: "server"
 34 |     expect:
 35 |       response:
 36 |         jsonrpc: "2.0"
 37 |         id: "test-2"
 38 |         result:
 39 |           isError: false
 40 |           content:
 41 |             match:arrayElements:
 42 |               type: "text"
 43 |               text: "match:contains:document"
 44 |       stderr: "toBeEmpty"
 45 | 
 46 |   - it: "should include relevance scores and matches in search results"
 47 |     request:
 48 |       jsonrpc: "2.0"
 49 |       method: "tools/call"
 50 |       id: "test-3"
 51 |       params:
 52 |         name: "search_sfra_documentation"
 53 |         arguments:
 54 |           query: "response"
 55 |     expect:
 56 |       response:
 57 |         jsonrpc: "2.0"
 58 |         id: "test-3"
 59 |         result:
 60 |           isError: false
 61 |           content:
 62 |             match:arrayElements:
 63 |               type: "text"
 64 |               text: "match:regex:(?:relevanceScore)[\\s\\S]*(?:matches)"
 65 |       stderr: "toBeEmpty"
 66 | 
 67 |   - it: "should return empty array for query with no matches"
 68 |     request:
 69 |       jsonrpc: "2.0"
 70 |       method: "tools/call"
 71 |       id: "test-4"
 72 |       params:
 73 |         name: "search_sfra_documentation"
 74 |         arguments:
 75 |           query: "zzznothingfound"
 76 |     expect:
 77 |       response:
 78 |         jsonrpc: "2.0"
 79 |         id: "test-4"
 80 |         result:
 81 |           isError: false
 82 |           content:
 83 |             match:arrayElements:
 84 |               type: "text"
 85 |               text: "match:regex:^\\[\\s*\\]$"
 86 |       stderr: "toBeEmpty"
 87 | 
 88 |   - it: "should handle empty query with validation error"
 89 |     request:
 90 |       jsonrpc: "2.0"
 91 |       method: "tools/call"
 92 |       id: "test-5"
 93 |       params:
 94 |         name: "search_sfra_documentation"
 95 |         arguments:
 96 |           query: ""
 97 |     expect:
 98 |       response:
 99 |         jsonrpc: "2.0"
100 |         id: "test-5"
101 |         result:
102 |           isError: true
103 |           content:
104 |             match:arrayElements:
105 |               type: "text"
106 |               text: "match:contains:query must be a non-empty string"
107 |       stderr: "toBeEmpty"
108 | 
109 |   - it: "should reject missing query parameter"
110 |     request:
111 |       jsonrpc: "2.0"
112 |       method: "tools/call"
113 |       id: "test-6"
114 |       params:
115 |         name: "search_sfra_documentation"
116 |         arguments: {}
117 |     expect:
118 |       response:
119 |         jsonrpc: "2.0"
120 |         id: "test-6"
121 |         result:
122 |           isError: true
123 |           content:
124 |             match:arrayElements:
125 |               type: "text"
126 |               text: "match:contains:query must be a non-empty string"
127 |       stderr: "toBeEmpty"
128 | 
129 |   - it: "should return results with document categories for complex queries"
130 |     request:
131 |       jsonrpc: "2.0"
132 |       method: "tools/call"
133 |       id: "test-7"
134 |       params:
135 |         name: "search_sfra_documentation"
136 |         arguments:
137 |           query: "product"
138 |     expect:
139 |       response:
140 |         jsonrpc: "2.0"
141 |         id: "test-7"
142 |         result:
143 |           isError: false
144 |           content:
145 |             match:arrayElements:
146 |               type: "text"
147 |               text: "match:regex:(?:category)[\\s\\S]*(?:core|product|order|customer|pricing|store|other)"
148 |       stderr: "toBeEmpty"
149 | 
150 |   - it: "should include document types in search results"
151 |     request:
152 |       jsonrpc: "2.0"
153 |       method: "tools/call"
154 |       id: "test-8"
155 |       params:
156 |         name: "search_sfra_documentation"
157 |         arguments:
158 |           query: "model"
159 |     expect:
160 |       response:
161 |         jsonrpc: "2.0"
162 |         id: "test-8"
163 |         result:
164 |           isError: false
165 |           content:
166 |             match:arrayElements:
167 |               type: "text"
168 |               text: "match:regex:(?:type)[\\s\\S]*(?:class|module|model)"
169 |       stderr: "toBeEmpty"
170 | 
171 |   - it: "should respond within reasonable time for complex search"
172 |     request:
173 |       jsonrpc: "2.0"
174 |       method: "tools/call"
175 |       id: "test-9"
176 |       params:
177 |         name: "search_sfra_documentation"
178 |         arguments:
179 |           query: "cart billing shipping"
180 |     expect:
181 |       response:
182 |         jsonrpc: "2.0"
183 |         id: "test-9"
184 |         result:
185 |           isError: false
186 |           content:
187 |             match:arrayElements:
188 |               type: "text"
189 |               text: "match:type:string"
190 |       performance:
191 |         maxResponseTime: "2000ms"
192 |       stderr: "toBeEmpty"
193 | 
194 |   - it: "should handle special characters in search query"
195 |     request:
196 |       jsonrpc: "2.0"
197 |       method: "tools/call"
198 |       id: "test-10"
199 |       params:
200 |         name: "search_sfra_documentation"
201 |         arguments:
202 |           query: "dw.util"
203 |     expect:
204 |       response:
205 |         jsonrpc: "2.0"
206 |         id: "test-10"
207 |         result:
208 |           isError: false
209 |           content:
210 |             match:arrayElements:
211 |               type: "text"
212 |               text: "match:type:string"
213 |       stderr: "toBeEmpty"
214 | 
215 |   - it: "should handle very long search queries efficiently"
216 |     request:
217 |       jsonrpc: "2.0"
218 |       method: "tools/call"
219 |       id: "test-11"
220 |       params:
221 |         name: "search_sfra_documentation"
222 |         arguments:
223 |           query: "template rendering isml view data processing controller middleware response request server router"
224 |     expect:
225 |       response:
226 |         jsonrpc: "2.0"
227 |         id: "test-11"
228 |         result:
229 |           isError: false
230 |           content:
231 |             match:arrayElements:
232 |               type: "text"
233 |               text: "match:regex:\\[[\\s\\S]*\\]"
234 |       performance:
235 |         maxResponseTime: "3000ms"
236 |       stderr: "toBeEmpty"
237 | 
238 |   - it: "should return results for single character queries"
239 |     request:
240 |       jsonrpc: "2.0"
241 |       method: "tools/call"
242 |       id: "test-12"
243 |       params:
244 |         name: "search_sfra_documentation"
245 |         arguments:
246 |           query: "a"
247 |     expect:
248 |       response:
249 |         jsonrpc: "2.0"
250 |         id: "test-12"
251 |         result:
252 |           isError: false
253 |           content:
254 |             match:arrayElements:
255 |               type: "text"
256 |               text: "match:regex:\\[[\\s\\S]*\\]"
257 |       stderr: "toBeEmpty"
258 | 
259 |   - it: "should handle numeric search terms"
260 |     request:
261 |       jsonrpc: "2.0"
262 |       method: "tools/call"
263 |       id: "test-13"
264 |       params:
265 |         name: "search_sfra_documentation"
266 |         arguments:
267 |           query: "200"
268 |     expect:
269 |       response:
270 |         jsonrpc: "2.0"
271 |         id: "test-13"
272 |         result:
273 |           isError: false
274 |           content:
275 |             match:arrayElements:
276 |               type: "text"
277 |               text: "match:type:string"
278 |       stderr: "toBeEmpty"
279 | 
280 |   - it: "should return consistent results for same query"
281 |     request:
282 |       jsonrpc: "2.0"
283 |       method: "tools/call"
284 |       id: "test-14"
285 |       params:
286 |         name: "search_sfra_documentation"
287 |         arguments:
288 |           query: "cart"
289 |     expect:
290 |       response:
291 |         jsonrpc: "2.0"
292 |         id: "test-14"
293 |         result:
294 |           isError: false
295 |           content:
296 |             match:arrayElements:
297 |               type: "text"
298 |               text: "match:contains:relevanceScore"
299 |       stderr: "toBeEmpty"
300 | 
301 |   - it: "should handle case-insensitive searches"
302 |     request:
303 |       jsonrpc: "2.0"
304 |       method: "tools/call"
305 |       id: "test-15"
306 |       params:
307 |         name: "search_sfra_documentation"
308 |         arguments:
309 |           query: "PRODUCT"
310 |     expect:
311 |       response:
312 |         jsonrpc: "2.0"
313 |         id: "test-15"
314 |         result:
315 |           isError: false
316 |           content:
317 |             match:arrayElements:
318 |               type: "text"
319 |               text: "match:regex:\\[[\\s\\S]*\\]"
320 |       stderr: "toBeEmpty"
321 | 
322 |   - it: "should find results for core SFRA concepts"
323 |     request:
324 |       jsonrpc: "2.0"
325 |       method: "tools/call"
326 |       id: "test-16"
327 |       params:
328 |         name: "search_sfra_documentation"
329 |         arguments:
330 |           query: "middleware"
331 |     expect:
332 |       response:
333 |         jsonrpc: "2.0"
334 |         id: "test-16"
335 |         result:
336 |           isError: false
337 |           content:
338 |             match:arrayElements:
339 |               type: "text"
340 |               text: "match:regex:(?:matches)[\\s\\S]*(?:section)[\\s\\S]*(?:content)"
341 |       stderr: "toBeEmpty"
342 | 
343 |   - it: "should handle hyphenated search terms"
344 |     request:
345 |       jsonrpc: "2.0"
346 |       method: "tools/call"
347 |       id: "test-17"
348 |       params:
349 |         name: "search_sfra_documentation"
350 |         arguments:
351 |           query: "product-full"
352 |     expect:
353 |       response:
354 |         jsonrpc: "2.0"
355 |         id: "test-17"
356 |         result:
357 |           isError: false
358 |           content:
359 |             match:arrayElements:
360 |               type: "text"
361 |               text: "match:type:string"
362 |       stderr: "toBeEmpty"
363 | 
364 |   - it: "should return structured results for pricing queries"
365 |     request:
366 |       jsonrpc: "2.0"
367 |       method: "tools/call"
368 |       id: "test-18"
369 |       params:
370 |         name: "search_sfra_documentation"
371 |         arguments:
372 |           query: "price"
373 |     expect:
374 |       response:
375 |         jsonrpc: "2.0"
376 |         id: "test-18"
377 |         result:
378 |           isError: false
379 |           content:
380 |             match:arrayElements:
381 |               type: "text"
382 |               text: "match:regex:(?:category)[\\s\\S]*(?:pricing|product|core)"
383 |       stderr: "toBeEmpty"
384 | 
385 |   - it: "should handle underscore in search terms"
386 |     request:
387 |       jsonrpc: "2.0"
388 |       method: "tools/call"
389 |       id: "test-19"
390 |       params:
391 |         name: "search_sfra_documentation"
392 |         arguments:
393 |           query: "view_data"
394 |     expect:
395 |       response:
396 |         jsonrpc: "2.0"
397 |         id: "test-19"
398 |         result:
399 |           isError: false
400 |           content:
401 |             match:arrayElements:
402 |               type: "text"
403 |               text: "match:type:string"
404 |       stderr: "toBeEmpty"
405 | 
406 |   - it: "should respond quickly for common search terms"
407 |     request:
408 |       jsonrpc: "2.0"
409 |       method: "tools/call"
410 |       id: "test-20"
411 |       params:
412 |         name: "search_sfra_documentation"
413 |         arguments:
414 |           query: "model"
415 |     expect:
416 |       response:
417 |         jsonrpc: "2.0"
418 |         id: "test-20"
419 |         result:
420 |           isError: false
421 |           content:
422 |             match:arrayElements:
423 |               type: "text"
424 |               text: "match:contains:document"
425 |       performance:
426 |         maxResponseTime: "800ms"
427 |       stderr: "toBeEmpty"
428 | 
```

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

```yaml
  1 | description: "Tests for search_sfra_documentation tool in full-mode mode"
  2 | 
  3 | tests:
  4 |   - it: "should return structured search results for valid query 'render'"
  5 |     request:
  6 |       jsonrpc: "2.0"
  7 |       method: "tools/call"
  8 |       id: "test-1"
  9 |       params:
 10 |         name: "search_sfra_documentation"
 11 |         arguments:
 12 |           query: "render"
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "test-1"
 17 |         result:
 18 |           isError: false
 19 |           content:
 20 |             match:arrayElements:
 21 |               type: "text"
 22 |               text: "match:regex:\\[[\\s\\S]*\\]"
 23 |       stderr: "toBeEmpty"
 24 | 
 25 |   - it: "should validate search result structure contains JSON array with documents"
 26 |     request:
 27 |       jsonrpc: "2.0"
 28 |       method: "tools/call"
 29 |       id: "test-2"
 30 |       params:
 31 |         name: "search_sfra_documentation"
 32 |         arguments:
 33 |           query: "server"
 34 |     expect:
 35 |       response:
 36 |         jsonrpc: "2.0"
 37 |         id: "test-2"
 38 |         result:
 39 |           isError: false
 40 |           content:
 41 |             match:arrayElements:
 42 |               type: "text"
 43 |               text: "match:contains:document"
 44 |       stderr: "toBeEmpty"
 45 | 
 46 |   - it: "should include relevance scores and matches in search results"
 47 |     request:
 48 |       jsonrpc: "2.0"
 49 |       method: "tools/call"
 50 |       id: "test-3"
 51 |       params:
 52 |         name: "search_sfra_documentation"
 53 |         arguments:
 54 |           query: "response"
 55 |     expect:
 56 |       response:
 57 |         jsonrpc: "2.0"
 58 |         id: "test-3"
 59 |         result:
 60 |           isError: false
 61 |           content:
 62 |             match:arrayElements:
 63 |               type: "text"
 64 |               text: "match:regex:(?:relevanceScore)[\\s\\S]*(?:matches)"
 65 |       stderr: "toBeEmpty"
 66 | 
 67 |   - it: "should return empty array for query with no matches"
 68 |     request:
 69 |       jsonrpc: "2.0"
 70 |       method: "tools/call"
 71 |       id: "test-4"
 72 |       params:
 73 |         name: "search_sfra_documentation"
 74 |         arguments:
 75 |           query: "zzznothingfound"
 76 |     expect:
 77 |       response:
 78 |         jsonrpc: "2.0"
 79 |         id: "test-4"
 80 |         result:
 81 |           isError: false
 82 |           content:
 83 |             match:arrayElements:
 84 |               type: "text"
 85 |               text: "match:regex:^\\[\\s*\\]$"
 86 |       stderr: "toBeEmpty"
 87 | 
 88 |   - it: "should handle empty query with validation error"
 89 |     request:
 90 |       jsonrpc: "2.0"
 91 |       method: "tools/call"
 92 |       id: "test-5"
 93 |       params:
 94 |         name: "search_sfra_documentation"
 95 |         arguments:
 96 |           query: ""
 97 |     expect:
 98 |       response:
 99 |         jsonrpc: "2.0"
100 |         id: "test-5"
101 |         result:
102 |           isError: true
103 |           content:
104 |             match:arrayElements:
105 |               type: "text"
106 |               text: "match:contains:query must be a non-empty string"
107 |       stderr: "toBeEmpty"
108 | 
109 |   - it: "should reject missing query parameter"
110 |     request:
111 |       jsonrpc: "2.0"
112 |       method: "tools/call"
113 |       id: "test-6"
114 |       params:
115 |         name: "search_sfra_documentation"
116 |         arguments: {}
117 |     expect:
118 |       response:
119 |         jsonrpc: "2.0"
120 |         id: "test-6"
121 |         result:
122 |           isError: true
123 |           content:
124 |             match:arrayElements:
125 |               type: "text"
126 |               text: "match:contains:query must be a non-empty string"
127 |       stderr: "toBeEmpty"
128 | 
129 |   - it: "should return results with document categories for complex queries"
130 |     request:
131 |       jsonrpc: "2.0"
132 |       method: "tools/call"
133 |       id: "test-7"
134 |       params:
135 |         name: "search_sfra_documentation"
136 |         arguments:
137 |           query: "product"
138 |     expect:
139 |       response:
140 |         jsonrpc: "2.0"
141 |         id: "test-7"
142 |         result:
143 |           isError: false
144 |           content:
145 |             match:arrayElements:
146 |               type: "text"
147 |               text: "match:regex:(?:category)[\\s\\S]*(?:core|product|order|customer|pricing|store|other)"
148 |       stderr: "toBeEmpty"
149 | 
150 |   - it: "should include document types in search results"
151 |     request:
152 |       jsonrpc: "2.0"
153 |       method: "tools/call"
154 |       id: "test-8"
155 |       params:
156 |         name: "search_sfra_documentation"
157 |         arguments:
158 |           query: "model"
159 |     expect:
160 |       response:
161 |         jsonrpc: "2.0"
162 |         id: "test-8"
163 |         result:
164 |           isError: false
165 |           content:
166 |             match:arrayElements:
167 |               type: "text"
168 |               text: "match:regex:(?:type)[\\s\\S]*(?:class|module|model)"
169 |       stderr: "toBeEmpty"
170 | 
171 |   - it: "should respond within reasonable time for complex search"
172 |     request:
173 |       jsonrpc: "2.0"
174 |       method: "tools/call"
175 |       id: "test-9"
176 |       params:
177 |         name: "search_sfra_documentation"
178 |         arguments:
179 |           query: "cart billing shipping"
180 |     expect:
181 |       response:
182 |         jsonrpc: "2.0"
183 |         id: "test-9"
184 |         result:
185 |           isError: false
186 |           content:
187 |             match:arrayElements:
188 |               type: "text"
189 |               text: "match:type:string"
190 |       performance:
191 |         maxResponseTime: "2000ms"
192 |       stderr: "toBeEmpty"
193 | 
194 |   - it: "should handle special characters in search query"
195 |     request:
196 |       jsonrpc: "2.0"
197 |       method: "tools/call"
198 |       id: "test-10"
199 |       params:
200 |         name: "search_sfra_documentation"
201 |         arguments:
202 |           query: "dw.util"
203 |     expect:
204 |       response:
205 |         jsonrpc: "2.0"
206 |         id: "test-10"
207 |         result:
208 |           isError: false
209 |           content:
210 |             match:arrayElements:
211 |               type: "text"
212 |               text: "match:type:string"
213 |       stderr: "toBeEmpty"
214 | 
215 |   - it: "should handle very long search queries efficiently"
216 |     request:
217 |       jsonrpc: "2.0"
218 |       method: "tools/call"
219 |       id: "test-11"
220 |       params:
221 |         name: "search_sfra_documentation"
222 |         arguments:
223 |           query: "template rendering isml view data processing controller middleware response request server router"
224 |     expect:
225 |       response:
226 |         jsonrpc: "2.0"
227 |         id: "test-11"
228 |         result:
229 |           isError: false
230 |           content:
231 |             match:arrayElements:
232 |               type: "text"
233 |               text: "match:regex:\\[[\\s\\S]*\\]"
234 |       performance:
235 |         maxResponseTime: "3000ms"
236 |       stderr: "toBeEmpty"
237 | 
238 |   - it: "should return results for single character queries"
239 |     request:
240 |       jsonrpc: "2.0"
241 |       method: "tools/call"
242 |       id: "test-12"
243 |       params:
244 |         name: "search_sfra_documentation"
245 |         arguments:
246 |           query: "a"
247 |     expect:
248 |       response:
249 |         jsonrpc: "2.0"
250 |         id: "test-12"
251 |         result:
252 |           isError: false
253 |           content:
254 |             match:arrayElements:
255 |               type: "text"
256 |               text: "match:regex:\\[[\\s\\S]*\\]"
257 |       stderr: "toBeEmpty"
258 | 
259 |   - it: "should handle numeric search terms"
260 |     request:
261 |       jsonrpc: "2.0"
262 |       method: "tools/call"
263 |       id: "test-13"
264 |       params:
265 |         name: "search_sfra_documentation"
266 |         arguments:
267 |           query: "200"
268 |     expect:
269 |       response:
270 |         jsonrpc: "2.0"
271 |         id: "test-13"
272 |         result:
273 |           isError: false
274 |           content:
275 |             match:arrayElements:
276 |               type: "text"
277 |               text: "match:type:string"
278 |       stderr: "toBeEmpty"
279 | 
280 |   - it: "should return consistent results for same query"
281 |     request:
282 |       jsonrpc: "2.0"
283 |       method: "tools/call"
284 |       id: "test-14"
285 |       params:
286 |         name: "search_sfra_documentation"
287 |         arguments:
288 |           query: "cart"
289 |     expect:
290 |       response:
291 |         jsonrpc: "2.0"
292 |         id: "test-14"
293 |         result:
294 |           isError: false
295 |           content:
296 |             match:arrayElements:
297 |               type: "text"
298 |               text: "match:contains:relevanceScore"
299 |       stderr: "toBeEmpty"
300 | 
301 |   - it: "should handle case-insensitive searches"
302 |     request:
303 |       jsonrpc: "2.0"
304 |       method: "tools/call"
305 |       id: "test-15"
306 |       params:
307 |         name: "search_sfra_documentation"
308 |         arguments:
309 |           query: "PRODUCT"
310 |     expect:
311 |       response:
312 |         jsonrpc: "2.0"
313 |         id: "test-15"
314 |         result:
315 |           isError: false
316 |           content:
317 |             match:arrayElements:
318 |               type: "text"
319 |               text: "match:regex:\\[[\\s\\S]*\\]"
320 |       stderr: "toBeEmpty"
321 | 
322 |   - it: "should find results for core SFRA concepts"
323 |     request:
324 |       jsonrpc: "2.0"
325 |       method: "tools/call"
326 |       id: "test-16"
327 |       params:
328 |         name: "search_sfra_documentation"
329 |         arguments:
330 |           query: "middleware"
331 |     expect:
332 |       response:
333 |         jsonrpc: "2.0"
334 |         id: "test-16"
335 |         result:
336 |           isError: false
337 |           content:
338 |             match:arrayElements:
339 |               type: "text"
340 |               text: "match:regex:(?:matches)[\\s\\S]*(?:section)[\\s\\S]*(?:content)"
341 |       stderr: "toBeEmpty"
342 | 
343 |   - it: "should handle hyphenated search terms"
344 |     request:
345 |       jsonrpc: "2.0"
346 |       method: "tools/call"
347 |       id: "test-17"
348 |       params:
349 |         name: "search_sfra_documentation"
350 |         arguments:
351 |           query: "product-full"
352 |     expect:
353 |       response:
354 |         jsonrpc: "2.0"
355 |         id: "test-17"
356 |         result:
357 |           isError: false
358 |           content:
359 |             match:arrayElements:
360 |               type: "text"
361 |               text: "match:type:string"
362 |       stderr: "toBeEmpty"
363 | 
364 |   - it: "should return structured results for pricing queries"
365 |     request:
366 |       jsonrpc: "2.0"
367 |       method: "tools/call"
368 |       id: "test-18"
369 |       params:
370 |         name: "search_sfra_documentation"
371 |         arguments:
372 |           query: "price"
373 |     expect:
374 |       response:
375 |         jsonrpc: "2.0"
376 |         id: "test-18"
377 |         result:
378 |           isError: false
379 |           content:
380 |             match:arrayElements:
381 |               type: "text"
382 |               text: "match:regex:(?:category)[\\s\\S]*(?:pricing|product|core)"
383 |       stderr: "toBeEmpty"
384 | 
385 |   - it: "should handle underscore in search terms"
386 |     request:
387 |       jsonrpc: "2.0"
388 |       method: "tools/call"
389 |       id: "test-19"
390 |       params:
391 |         name: "search_sfra_documentation"
392 |         arguments:
393 |           query: "view_data"
394 |     expect:
395 |       response:
396 |         jsonrpc: "2.0"
397 |         id: "test-19"
398 |         result:
399 |           isError: false
400 |           content:
401 |             match:arrayElements:
402 |               type: "text"
403 |               text: "match:type:string"
404 |       stderr: "toBeEmpty"
405 | 
406 |   - it: "should respond quickly for common search terms"
407 |     request:
408 |       jsonrpc: "2.0"
409 |       method: "tools/call"
410 |       id: "test-20"
411 |       params:
412 |         name: "search_sfra_documentation"
413 |         arguments:
414 |           query: "model"
415 |     expect:
416 |       response:
417 |         jsonrpc: "2.0"
418 |         id: "test-20"
419 |         result:
420 |           isError: false
421 |           content:
422 |             match:arrayElements:
423 |               type: "text"
424 |               text: "match:contains:document"
425 |       performance:
426 |         maxResponseTime: "800ms"
427 |       stderr: "toBeEmpty"
428 | 
```

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

```typescript
  1 | import { Logger } from '../src/utils/logger';
  2 | import { existsSync, readFileSync, rmSync } from 'fs';
  3 | import { join } from 'path';
  4 | import { tmpdir } from 'os';
  5 | 
  6 | describe('Logger', () => {
  7 |   let logger: Logger;
  8 |   let testLogDir: string;
  9 | 
 10 |   beforeEach(() => {
 11 |     // Create a unique test log directory for each test
 12 |     testLogDir = join(tmpdir(), `sfcc-mcp-logs-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
 13 | 
 14 |     // Clean up if directory somehow exists
 15 |     if (existsSync(testLogDir)) {
 16 |       rmSync(testLogDir, { recursive: true, force: true });
 17 |     }
 18 |   });
 19 | 
 20 |   afterEach(() => {
 21 |     // Clean up test log directory
 22 |     if (existsSync(testLogDir)) {
 23 |       rmSync(testLogDir, { recursive: true, force: true });
 24 |     }
 25 |   });
 26 | 
 27 |   describe('constructor', () => {
 28 |     it('should create logger and create log directory', () => {
 29 |       logger = new Logger('TEST', true, false, testLogDir);
 30 | 
 31 |       expect(existsSync(testLogDir)).toBe(true);
 32 |       expect(logger.getLogDirectory()).toBe(testLogDir);
 33 |     });
 34 | 
 35 |     it('should create logger with custom context and write logs correctly', () => {
 36 |       logger = new Logger('CUSTOM-CONTEXT', true, false, testLogDir);
 37 | 
 38 |       logger.log('test message');
 39 | 
 40 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
 41 |       expect(existsSync(logFile)).toBe(true);
 42 | 
 43 |       const logContent = readFileSync(logFile, 'utf8');
 44 |       expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[CUSTOM-CONTEXT\] test message\n$/);
 45 |     });
 46 | 
 47 |     it('should create logger with timestamp disabled', () => {
 48 |       logger = new Logger('TEST', false, false, testLogDir);
 49 | 
 50 |       logger.log('test message');
 51 | 
 52 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
 53 |       expect(existsSync(logFile)).toBe(true);
 54 | 
 55 |       const logContent = readFileSync(logFile, 'utf8');
 56 |       expect(logContent).toBe('[TEST] test message\n');
 57 |     });
 58 | 
 59 |     it('should create logger with debug enabled', () => {
 60 |       logger = new Logger('TEST', true, true, testLogDir);
 61 | 
 62 |       logger.debug('debug message');
 63 | 
 64 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
 65 |       expect(existsSync(logFile)).toBe(true);
 66 | 
 67 |       const logContent = readFileSync(logFile, 'utf8');
 68 |       expect(logContent).toMatch(
 69 |         /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/,
 70 |       );
 71 |     });
 72 |   });
 73 | 
 74 |   describe('logging methods', () => {
 75 |     beforeEach(() => {
 76 |       logger = new Logger('TEST', true, false, testLogDir);
 77 |     });
 78 | 
 79 |     it('should write info messages to info log file', () => {
 80 |       logger.info('info message');
 81 | 
 82 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
 83 |       expect(existsSync(logFile)).toBe(true);
 84 | 
 85 |       const logContent = readFileSync(logFile, 'utf8');
 86 |       expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] info message\n$/);
 87 |     });
 88 | 
 89 |     it('should write log messages to info log file', () => {
 90 |       logger.log('log message');
 91 | 
 92 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
 93 |       expect(existsSync(logFile)).toBe(true);
 94 | 
 95 |       const logContent = readFileSync(logFile, 'utf8');
 96 |       expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] log message\n$/);
 97 |     });
 98 | 
 99 |     it('should write warning messages to warn log file', () => {
100 |       logger.warn('warning message');
101 | 
102 |       const logFile = join(testLogDir, 'sfcc-mcp-warn.log');
103 |       expect(existsSync(logFile)).toBe(true);
104 | 
105 |       const logContent = readFileSync(logFile, 'utf8');
106 |       expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] warning message\n$/);
107 |     });
108 | 
109 |     it('should write error messages to error log file', () => {
110 |       logger.error('error message');
111 | 
112 |       const logFile = join(testLogDir, 'sfcc-mcp-error.log');
113 |       expect(existsSync(logFile)).toBe(true);
114 | 
115 |       const logContent = readFileSync(logFile, 'utf8');
116 |       expect(logContent).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] error message\n$/);
117 |     });
118 | 
119 |     it('should write debug messages to debug log file when debug is enabled', () => {
120 |       logger = new Logger('TEST', true, true, testLogDir);
121 |       logger.debug('debug message');
122 | 
123 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
124 |       expect(existsSync(logFile)).toBe(true);
125 | 
126 |       const logContent = readFileSync(logFile, 'utf8');
127 |       expect(logContent).toMatch(
128 |         /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[TEST\] \[DEBUG\] debug message\n$/,
129 |       );
130 |     });
131 | 
132 |     it('should not write debug messages when debug is disabled', () => {
133 |       logger = new Logger('TEST', true, false, testLogDir);
134 |       logger.debug('debug message');
135 | 
136 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
137 |       expect(existsSync(logFile)).toBe(false);
138 |     });
139 | 
140 |     it('should handle additional arguments', () => {
141 |       const testObject = { key: 'value', number: 42 };
142 |       logger.info('message with args', 'string arg', testObject);
143 | 
144 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
145 |       expect(existsSync(logFile)).toBe(true);
146 | 
147 |       const logContent = readFileSync(logFile, 'utf8');
148 |       expect(logContent).toContain('message with args');
149 |       expect(logContent).toContain('string arg');
150 |       expect(logContent).toContain('"key": "value"');
151 |       expect(logContent).toContain('"number": 42');
152 |     });
153 |   });
154 | 
155 |   describe('debug logging methods', () => {
156 |     beforeEach(() => {
157 |       logger = new Logger('TEST', true, true, testLogDir);
158 |     });
159 | 
160 |     it('should log method entry', () => {
161 |       logger.methodEntry('testMethod', { param1: 'value1' });
162 | 
163 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
164 |       expect(existsSync(logFile)).toBe(true);
165 | 
166 |       const logContent = readFileSync(logFile, 'utf8');
167 |       expect(logContent).toContain('[DEBUG] Entering method: testMethod with params:');
168 |       expect(logContent).toContain('"param1":"value1"');
169 |     });
170 | 
171 |     it('should log method entry without params', () => {
172 |       logger.methodEntry('testMethod');
173 | 
174 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
175 |       expect(existsSync(logFile)).toBe(true);
176 | 
177 |       const logContent = readFileSync(logFile, 'utf8');
178 |       expect(logContent).toContain('[DEBUG] Entering method: testMethod');
179 |       expect(logContent).not.toContain('with params:');
180 |     });
181 | 
182 |     it('should log method exit', () => {
183 |       logger.methodExit('testMethod', { result: 'success' });
184 | 
185 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
186 |       expect(existsSync(logFile)).toBe(true);
187 | 
188 |       const logContent = readFileSync(logFile, 'utf8');
189 |       expect(logContent).toContain('[DEBUG] Exiting method: testMethod with result:');
190 |       expect(logContent).toContain('"result":"success"');
191 |     });
192 | 
193 |     it('should log method exit without result', () => {
194 |       logger.methodExit('testMethod');
195 | 
196 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
197 |       expect(existsSync(logFile)).toBe(true);
198 | 
199 |       const logContent = readFileSync(logFile, 'utf8');
200 |       expect(logContent).toContain('[DEBUG] Exiting method: testMethod');
201 |       expect(logContent).not.toContain('with result:');
202 |     });
203 | 
204 |     it('should log timing information', () => {
205 |       const startTime = Date.now() - 100; // Simulate 100ms operation
206 |       logger.timing('testOperation', startTime);
207 | 
208 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
209 |       expect(existsSync(logFile)).toBe(true);
210 | 
211 |       const logContent = readFileSync(logFile, 'utf8');
212 |       expect(logContent).toMatch(/\[DEBUG\] Performance: testOperation took \d+ms/);
213 |     });
214 |   });
215 | 
216 |   describe('utility methods', () => {
217 |     beforeEach(() => {
218 |       logger = new Logger('TEST', true, false, testLogDir);
219 |     });
220 | 
221 |     it('should create child logger with combined context', () => {
222 |       const childLogger = logger.createChildLogger('CHILD');
223 |       childLogger.info('child message');
224 | 
225 |       const logFile = join(testLogDir, 'sfcc-mcp-info.log');
226 |       expect(existsSync(logFile)).toBe(true);
227 | 
228 |       const logContent = readFileSync(logFile, 'utf8');
229 |       expect(logContent).toContain('[TEST:CHILD] child message');
230 |     });
231 | 
232 |     it('should enable debug logging dynamically', () => {
233 |       logger.setDebugEnabled(true);
234 |       logger.debug('now debug is enabled');
235 | 
236 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
237 |       expect(existsSync(logFile)).toBe(true);
238 | 
239 |       const logContent = readFileSync(logFile, 'utf8');
240 |       expect(logContent).toContain('[DEBUG] now debug is enabled');
241 |     });
242 | 
243 |     it('should disable debug logging dynamically', () => {
244 |       logger = new Logger('TEST', true, true, testLogDir);
245 |       logger.setDebugEnabled(false);
246 |       logger.debug('this should not appear');
247 | 
248 |       const logFile = join(testLogDir, 'sfcc-mcp-debug.log');
249 |       expect(existsSync(logFile)).toBe(false);
250 |     });
251 | 
252 |     it('should return log directory path', () => {
253 |       const logDirectory = logger.getLogDirectory();
254 |       expect(logDirectory).toBe(testLogDir);
255 |       expect(existsSync(logDirectory)).toBe(true);
256 |     });
257 |   });
258 | 
259 |   describe('error handling', () => {
260 |     it('should handle file write errors gracefully', () => {
261 |       // Mock appendFileSync to throw an error
262 |       // eslint-disable-next-line @typescript-eslint/no-require-imports
263 |       const fsMock = jest.spyOn(require('fs'), 'appendFileSync');
264 |       fsMock.mockImplementation(() => {
265 |         throw new Error('File write error');
266 |       });
267 | 
268 |       // Mock stderr.write to capture fallback behavior
269 |       const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
270 | 
271 |       logger = new Logger('TEST', true, false, testLogDir);
272 |       logger.error('test error message');
273 | 
274 |       // Should have attempted to write to stderr as fallback
275 |       expect(stderrSpy).toHaveBeenCalledWith('[LOGGER ERROR] Could not write to log file: Error: File write error\n');
276 |       expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('test error message'));
277 | 
278 |       stderrSpy.mockRestore();
279 |       fsMock.mockRestore();
280 |     });
281 | 
282 |     it('should not fallback to stderr for non-error log levels', () => {
283 |       // Mock appendFileSync to throw an error
284 |       // eslint-disable-next-line @typescript-eslint/no-require-imports
285 |       const fsMock = jest.spyOn(require('fs'), 'appendFileSync');
286 |       fsMock.mockImplementation(() => {
287 |         throw new Error('File write error');
288 |       });
289 | 
290 |       // Mock stderr.write to capture fallback behavior
291 |       const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
292 | 
293 |       logger = new Logger('TEST', true, false, testLogDir);
294 |       logger.info('test info message');
295 | 
296 |       // Should not have written to stderr for non-error levels
297 |       expect(stderrSpy).not.toHaveBeenCalled();
298 | 
299 |       stderrSpy.mockRestore();
300 |       fsMock.mockRestore();
301 |     });
302 |   });
303 | });
304 | 
```

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

```markdown
  1 | # Class Server
  2 | 
  3 | ## Inheritance Hierarchy
  4 | 
  5 | - Object
  6 |   - sfra.models.Server
  7 | 
  8 | ## Description
  9 | 
 10 | 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.
 11 | 
 12 | ## Properties
 13 | 
 14 | ### routes
 15 | 
 16 | **Type:** Object
 17 | 
 18 | Internal registry of all registered routes, indexed by route name.
 19 | 
 20 | ## Constructor Summary
 21 | 
 22 | ### Server
 23 | 
 24 | **Signature:** `Server()`
 25 | 
 26 | Creates a new Server instance with an empty routes registry.
 27 | 
 28 | ## Method Summary
 29 | 
 30 | ### use
 31 | 
 32 | **Signature:** `use(name, ...middleware) : Route`
 33 | 
 34 | Creates a new route with middleware chain.
 35 | 
 36 | ### get
 37 | 
 38 | **Signature:** `get(name, ...middleware) : Route`
 39 | 
 40 | Creates a GET-specific route with automatic HTTP method validation.
 41 | 
 42 | ### post
 43 | 
 44 | **Signature:** `post(name, ...middleware) : Route`
 45 | 
 46 | Creates a POST-specific route with automatic HTTP method validation.
 47 | 
 48 | ### exports
 49 | 
 50 | **Signature:** `exports() : Object`
 51 | 
 52 | Returns an exportable object containing all registered routes.
 53 | 
 54 | ### extend
 55 | 
 56 | **Signature:** `extend(server) : void`
 57 | 
 58 | Extends the current server with routes from another server object.
 59 | 
 60 | ### prepend
 61 | 
 62 | **Signature:** `prepend(name, ...middleware) : void`
 63 | 
 64 | Adds middleware to the beginning of an existing route's chain.
 65 | 
 66 | ### append
 67 | 
 68 | **Signature:** `append(name, ...middleware) : void`
 69 | 
 70 | Adds middleware to the end of an existing route's chain.
 71 | 
 72 | ### replace
 73 | 
 74 | **Signature:** `replace(name, ...middleware) : void`
 75 | 
 76 | Replaces an existing route with a new middleware chain.
 77 | 
 78 | ### getRoute
 79 | 
 80 | **Signature:** `getRoute(name) : Route`
 81 | 
 82 | Retrieves a specific route by name.
 83 | 
 84 | ## Method Detail
 85 | 
 86 | ### use
 87 | 
 88 | **Signature:** `use(name, ...middleware) : Route`
 89 | 
 90 | **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.
 91 | 
 92 | **Parameters:**
 93 | - `name` (String) - Unique name for the route
 94 | - `...middleware` (Function[]) - Variable number of middleware functions to execute
 95 | 
 96 | **Returns:**
 97 | Route object representing the created route.
 98 | 
 99 | **Throws:**
100 | - Error if name is not a string
101 | - Error if middleware contains non-function items
102 | - Error if route name already exists
103 | 
104 | **Route Completion Handling:**
105 | - Sets cache expiration based on `res.cachePeriod` and `res.cachePeriodUnit`
106 | - Applies personalization with `price_promotion` vary-by header when `res.personalized` is true
107 | - Handles redirects with optional status codes
108 | - Applies rendering through the render system
109 | 
110 | ### get
111 | 
112 | **Signature:** `get(name, ...middleware) : Route`
113 | 
114 | **Description:** Convenience method for creating GET-only routes. Automatically prepends HTTP GET validation middleware to the chain.
115 | 
116 | **Parameters:**
117 | - `name` (String) - Unique name for the route
118 | - `...middleware` (Function[]) - Variable number of middleware functions
119 | 
120 | **Returns:**
121 | Route object with GET method validation.
122 | 
123 | ### post
124 | 
125 | **Signature:** `post(name, ...middleware) : Route`
126 | 
127 | **Description:** Convenience method for creating POST-only routes. Automatically prepends HTTP POST validation middleware to the chain.
128 | 
129 | **Parameters:**
130 | - `name` (String) - Unique name for the route
131 | - `...middleware` (Function[]) - Variable number of middleware functions
132 | 
133 | **Returns:**
134 | Route object with POST method validation.
135 | 
136 | ### exports
137 | 
138 | **Signature:** `exports() : Object`
139 | 
140 | **Description:** Creates an exportable object containing all registered routes with their public interfaces. Includes internal routes registry for extension capabilities.
141 | 
142 | **Returns:**
143 | Object with route names as keys and route interfaces as values, plus `__routes` property containing internal routes.
144 | 
145 | **Export Format:**
146 | ```javascript
147 | {
148 |   "routeName": { /* route interface */ },
149 |   "__routes": { /* internal routes registry */ }
150 | }
151 | ```
152 | 
153 | ### extend
154 | 
155 | **Signature:** `extend(server) : void`
156 | 
157 | **Description:** Extends the current server with routes from another server object created by the `exports()` method. Validates the server object structure before extension.
158 | 
159 | **Parameters:**
160 | - `server` (Object) - Server object created by `exports()` method
161 | 
162 | **Throws:**
163 | - Error if server object is invalid (missing `__routes`)
164 | - Error if server has no routes to extend
165 | 
166 | ### prepend
167 | 
168 | **Signature:** `prepend(name, ...middleware) : void`
169 | 
170 | **Description:** Adds middleware functions to the beginning of an existing route's middleware chain, allowing for route enhancement and modification.
171 | 
172 | **Parameters:**
173 | - `name` (String) - Name of existing route to modify
174 | - `...middleware` (Function[]) - Middleware functions to prepend
175 | 
176 | **Throws:**
177 | - Error if name is not a string
178 | - Error if middleware contains non-function items
179 | - Error if route does not exist
180 | 
181 | ### append
182 | 
183 | **Signature:** `append(name, ...middleware) : void`
184 | 
185 | **Description:** Adds middleware functions to the end of an existing route's middleware chain, enabling route extension and additional processing.
186 | 
187 | **Parameters:**
188 | - `name` (String) - Name of existing route to modify
189 | - `...middleware` (Function[]) - Middleware functions to append
190 | 
191 | **Throws:**
192 | - Error if name is not a string
193 | - Error if middleware contains non-function items
194 | - Error if route does not exist
195 | 
196 | ### replace
197 | 
198 | **Signature:** `replace(name, ...middleware) : void`
199 | 
200 | **Description:** Completely replaces an existing route with a new middleware chain, maintaining the same route name but changing implementation.
201 | 
202 | **Parameters:**
203 | - `name` (String) - Name of existing route to replace
204 | - `...middleware` (Function[]) - New middleware functions for the route
205 | 
206 | **Throws:**
207 | - Error if name is not a string
208 | - Error if middleware contains non-function items
209 | - Error if route does not exist
210 | 
211 | ### getRoute
212 | 
213 | **Signature:** `getRoute(name) : Route`
214 | 
215 | **Description:** Retrieves a specific route object by name for inspection or direct manipulation.
216 | 
217 | **Parameters:**
218 | - `name` (String) - Name of the route to retrieve
219 | 
220 | **Returns:**
221 | Route object if found, undefined otherwise.
222 | 
223 | ## Route Lifecycle Management
224 | 
225 | ### Route Creation Process
226 | 
227 | 1. **Parameter Validation**: Validates route name and middleware chain
228 | 2. **Request/Response Creation**: Creates SFRA Request and Response objects from globals
229 | 3. **Route Object Creation**: Instantiates Route with name, middleware, request, and response
230 | 4. **Event Handler Registration**: Sets up `route:Complete` event handler
231 | 5. **Route Registration**: Stores route in internal registry
232 | 6. **Hook Integration**: Calls `app.server.registerRoute` hook if available
233 | 
234 | ### Route Completion Handling
235 | 
236 | **Cache Management:**
237 | - Applies cache expiration when `res.cachePeriod` is set
238 | - Supports minutes or hours as cache period units (defaults to hours)
239 | - Sets HTTP expires header on the response
240 | 
241 | **Personalization:**
242 | - Sets `price_promotion` vary-by header when `res.personalized` is true
243 | - Enables proper caching behavior for personalized content
244 | 
245 | **Redirect Processing:**
246 | - Handles redirect URLs with optional status codes
247 | - Emits `route:Redirect` event before redirecting
248 | - Supports both default and custom redirect status codes
249 | 
250 | **Rendering Pipeline:**
251 | - Applies all accumulated renderings through the render system
252 | - Processes ISML templates, JSON responses, XML output, and Page Designer pages
253 | 
254 | ## Integration Points
255 | 
256 | ### Global Object Integration
257 | 
258 | **Request Object Creation:**
259 | ```javascript
260 | var rq = new Request(
261 |     typeof request !== 'undefined' ? request : {},
262 |     typeof customer !== 'undefined' ? customer : {},
263 |     typeof session !== 'undefined' ? session : {}
264 | );
265 | ```
266 | 
267 | **Response Object Creation:**
268 | ```javascript
269 | var rs = new Response(typeof response !== 'undefined' ? response : {});
270 | ```
271 | 
272 | ### Hook System Integration
273 | 
274 | The server integrates with SFCC's hook system through the `app.server.registerRoute` hook, allowing:
275 | - Custom route event handlers
276 | - Route-specific processing extensions
277 | - Integration with custom middleware systems
278 | 
279 | ### Middleware Architecture
280 | 
281 | **Middleware Function Signature:**
282 | ```javascript
283 | function middleware(req, res, next) {
284 |     // Middleware logic
285 |     next(); // Continue to next middleware
286 | }
287 | ```
288 | 
289 | **Middleware Chain Execution:**
290 | - Sequential execution of middleware functions
291 | - Support for asynchronous operations through next() callbacks
292 | - Automatic error handling and route completion
293 | 
294 | ## Usage Examples
295 | 
296 | ### Basic Route Creation
297 | ```javascript
298 | server.use('HomePage', function(req, res, next) {
299 |     res.render('home/homepage', { title: 'Welcome' });
300 |     next();
301 | });
302 | ```
303 | 
304 | ### HTTP Method-Specific Routes
305 | ```javascript
306 | server.get('ProductShow', middleware.cache, function(req, res, next) {
307 |     // GET-only route with caching
308 |     next();
309 | });
310 | 
311 | server.post('CartAdd', middleware.csrf, function(req, res, next) {
312 |     // POST-only route with CSRF protection
313 |     next();
314 | });
315 | ```
316 | 
317 | ### Route Modification
318 | ```javascript
319 | // Add authentication to existing route
320 | server.prepend('Account-Show', middleware.auth);
321 | 
322 | // Add logging to existing route
323 | server.append('Checkout-Begin', middleware.logging);
324 | 
325 | // Replace route implementation
326 | server.replace('Search-Show', newSearchMiddleware);
327 | ```
328 | 
329 | ### Server Extension
330 | ```javascript
331 | // Export routes from one server
332 | var exportedRoutes = server.exports();
333 | 
334 | // Extend another server
335 | var newServer = new Server();
336 | newServer.extend(exportedRoutes);
337 | ```
338 | 
339 | ## Error Handling
340 | 
341 | ### Parameter Validation Errors
342 | 
343 | - **Invalid Route Name**: Throws error if first parameter is not a string
344 | - **Invalid Middleware**: Throws error if middleware chain contains non-functions
345 | - **Duplicate Route**: Throws error if route name already exists
346 | 
347 | ### Extension Errors
348 | 
349 | - **Invalid Server Object**: Throws error for objects missing `__routes` property
350 | - **Empty Server**: Throws error when extending with server that has no routes
351 | 
352 | ### Route Modification Errors
353 | 
354 | - **Non-Existent Route**: Throws error when trying to modify routes that don't exist
355 | - **Invalid Parameters**: Validates parameters before attempting modifications
356 | 
357 | ## Property Details
358 | 
359 | ### routes
360 | 
361 | **Type:** Object
362 | 
363 | **Description:** Internal registry storing all route definitions indexed by route name. Each entry contains a Route object with the complete middleware chain and configuration.
364 | 
365 | **Structure:**
366 | ```javascript
367 | {
368 |   "routeName": Route, // Route object instance
369 |   "anotherRoute": Route
370 | }
371 | ```
372 | 
373 | This registry enables route lookup, modification, and export functionality while maintaining the complete route configuration and state.
374 | 
375 | ---
376 | 
```

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

```typescript
  1 | /**
  2 |  * Tests for OCAPISystemObjectsClient
  3 |  * Tests system objects operations
  4 |  */
  5 | 
  6 | import { OCAPISystemObjectsClient } from '../src/clients/ocapi/system-objects-client.js';
  7 | import { OCAPIConfig } from '../src/types/types.js';
  8 | import { QueryBuilder } from '../src/utils/query-builder.js';
  9 | import { Validator } from '../src/utils/validator.js';
 10 | 
 11 | // Mock dependencies
 12 | jest.mock('../src/clients/base/ocapi-auth-client.js');
 13 | jest.mock('../src/utils/query-builder.js');
 14 | jest.mock('../src/utils/validator.js');
 15 | 
 16 | describe('OCAPISystemObjectsClient', () => {
 17 |   let client: OCAPISystemObjectsClient;
 18 |   let mockValidateRequired: jest.MockedFunction<typeof Validator.validateRequired>;
 19 |   let mockValidateObjectType: jest.MockedFunction<typeof Validator.validateObjectType>;
 20 |   let mockValidateSearchRequest: jest.MockedFunction<typeof Validator.validateSearchRequest>;
 21 |   let mockQueryBuilderFromObject: jest.MockedFunction<typeof QueryBuilder.fromObject>;
 22 | 
 23 |   const mockConfig: OCAPIConfig = {
 24 |     hostname: 'test-instance.demandware.net',
 25 |     clientId: 'test-client-id',
 26 |     clientSecret: 'test-client-secret',
 27 |     version: 'v21_3',
 28 |   };
 29 | 
 30 |   beforeEach(() => {
 31 |     jest.clearAllMocks();
 32 | 
 33 |     // Mock Validator methods
 34 |     mockValidateRequired = Validator.validateRequired as jest.MockedFunction<typeof Validator.validateRequired>;
 35 |     mockValidateObjectType = Validator.validateObjectType as jest.MockedFunction<typeof Validator.validateObjectType>;
 36 |     mockValidateSearchRequest = Validator.validateSearchRequest as jest.MockedFunction<
 37 |         typeof Validator.validateSearchRequest
 38 |     >;
 39 | 
 40 |     // Reset mock implementations to default behavior
 41 |     mockValidateRequired.mockImplementation(() => {});
 42 |     mockValidateObjectType.mockImplementation(() => {});
 43 |     mockValidateSearchRequest.mockImplementation(() => {});
 44 | 
 45 |     // Mock QueryBuilder
 46 |     mockQueryBuilderFromObject = QueryBuilder.fromObject as jest.MockedFunction<typeof QueryBuilder.fromObject>;
 47 | 
 48 |     client = new OCAPISystemObjectsClient(mockConfig);
 49 | 
 50 |     // Mock the inherited methods by adding them as properties - avoid protected access
 51 |     (client as any).get = jest.fn().mockResolvedValue({ data: 'mocked' });
 52 |     (client as any).post = jest.fn().mockResolvedValue({ data: 'mocked' });
 53 |   });
 54 | 
 55 |   describe('constructor', () => {
 56 |     it('should initialize with correct base URL', () => {
 57 |       expect(client).toBeInstanceOf(OCAPISystemObjectsClient);
 58 |     });
 59 | 
 60 |     it('should use default version when not provided', () => {
 61 |       const configWithoutVersion = {
 62 |         hostname: 'test.demandware.net',
 63 |         clientId: 'client-id',
 64 |         clientSecret: 'client-secret',
 65 |       };
 66 | 
 67 |       const clientWithDefaults = new OCAPISystemObjectsClient(configWithoutVersion);
 68 |       expect(clientWithDefaults).toBeInstanceOf(OCAPISystemObjectsClient);
 69 |     });
 70 |   });
 71 | 
 72 |   describe('getSystemObjectDefinitions', () => {
 73 |     it('should make GET request to system_object_definitions endpoint', async () => {
 74 |       await client.getSystemObjectDefinitions();
 75 | 
 76 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
 77 |     });
 78 | 
 79 |     it('should include query parameters when provided', async () => {
 80 |       const params = { start: 0, count: 10, select: '(**)' };
 81 |       mockQueryBuilderFromObject.mockReturnValue('start=0&count=10&select=%28%2A%2A%29');
 82 | 
 83 |       await client.getSystemObjectDefinitions(params);
 84 | 
 85 |       expect(QueryBuilder.fromObject).toHaveBeenCalledWith(params);
 86 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions?start=0&count=10&select=%28%2A%2A%29');
 87 |     });
 88 | 
 89 |     it('should not include query string when no parameters provided', async () => {
 90 |       mockQueryBuilderFromObject.mockReturnValue('');
 91 | 
 92 |       await client.getSystemObjectDefinitions({});
 93 | 
 94 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
 95 |     });
 96 |   });
 97 | 
 98 |   describe('getSystemObjectDefinition', () => {
 99 |     it('should validate required parameters', async () => {
100 |       const objectType = 'Product';
101 | 
102 |       await client.getSystemObjectDefinition(objectType);
103 | 
104 |       expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
105 |       expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
106 |     });
107 | 
108 |     it('should make GET request with encoded object type', async () => {
109 |       const objectType = 'Custom Object';
110 | 
111 |       await client.getSystemObjectDefinition(objectType);
112 | 
113 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions/Custom%20Object');
114 |     });
115 | 
116 |     it('should handle special characters in object type', async () => {
117 |       const objectType = 'Product/Variant';
118 | 
119 |       await client.getSystemObjectDefinition(objectType);
120 | 
121 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions/Product%2FVariant');
122 |     });
123 |   });
124 | 
125 |   describe('searchSystemObjectDefinitions', () => {
126 |     it('should validate search request', async () => {
127 |       const searchRequest = {
128 |         query: {
129 |           text_query: {
130 |             fields: ['id', 'display_name'],
131 |             search_phrase: 'product',
132 |           },
133 |         },
134 |       };
135 | 
136 |       await client.searchSystemObjectDefinitions(searchRequest);
137 | 
138 |       expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
139 |     });
140 | 
141 |     it('should make POST request to system object definition search endpoint', async () => {
142 |       const searchRequest = {
143 |         query: {
144 |           text_query: {
145 |             fields: ['id', 'display_name'],
146 |             search_phrase: 'product',
147 |           },
148 |         },
149 |       };
150 | 
151 |       await client.searchSystemObjectDefinitions(searchRequest);
152 | 
153 |       expect((client as any).post).toHaveBeenCalledWith('/system_object_definition_search', searchRequest);
154 |     });
155 |   });
156 | 
157 |   describe('searchSystemObjectAttributeDefinitions', () => {
158 |     it('should validate all required parameters', async () => {
159 |       const objectType = 'Product';
160 |       const searchRequest = {
161 |         query: {
162 |           text_query: {
163 |             fields: ['id', 'display_name'],
164 |             search_phrase: 'custom',
165 |           },
166 |         },
167 |       };
168 | 
169 |       await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);
170 | 
171 |       expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
172 |       expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
173 |       expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
174 |     });
175 | 
176 |     it('should make POST request to attribute definition search endpoint', async () => {
177 |       const objectType = 'Product';
178 |       const searchRequest = {
179 |         query: {
180 |           text_query: {
181 |             fields: ['id', 'display_name'],
182 |             search_phrase: 'custom',
183 |           },
184 |         },
185 |       };
186 | 
187 |       await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);
188 | 
189 |       expect((client as any).post).toHaveBeenCalledWith('/system_object_definitions/Product/attribute_definition_search', searchRequest);
190 |     });
191 |   });
192 | 
193 |   describe('searchSystemObjectAttributeGroups', () => {
194 |     it('should validate all required parameters', async () => {
195 |       const objectType = 'SitePreferences';
196 |       const searchRequest = {
197 |         query: { match_all_query: {} },
198 |       };
199 | 
200 |       await client.searchSystemObjectAttributeGroups(objectType, searchRequest);
201 | 
202 |       expect(Validator.validateRequired).toHaveBeenCalledWith({ objectType }, ['objectType']);
203 |       expect(Validator.validateObjectType).toHaveBeenCalledWith(objectType);
204 |       expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
205 |     });
206 | 
207 |     it('should make POST request to attribute group search endpoint', async () => {
208 |       const objectType = 'SitePreferences';
209 |       const searchRequest = {
210 |         query: {
211 |           text_query: {
212 |             fields: ['id', 'display_name'],
213 |             search_phrase: 'general',
214 |           },
215 |         },
216 |         sorts: [{ field: 'position', sort_order: 'asc' as const }],
217 |       };
218 | 
219 |       await client.searchSystemObjectAttributeGroups(objectType, searchRequest);
220 | 
221 |       expect((client as any).post).toHaveBeenCalledWith(
222 |         '/system_object_definitions/SitePreferences/attribute_group_search',
223 |         searchRequest,
224 |       );
225 |     });
226 |   });
227 | 
228 |   describe('error handling', () => {
229 |     it('should propagate validation errors', async () => {
230 |       const validationError = new Error('Validation failed');
231 |       mockValidateRequired.mockImplementation(() => {
232 |         throw validationError;
233 |       });
234 | 
235 |       await expect(client.getSystemObjectDefinition('Product')).rejects.toThrow(validationError);
236 |     });
237 | 
238 |     it('should propagate HTTP errors from base client', async () => {
239 |       const httpError = new Error('HTTP request failed');
240 |       (client as any).get = jest.fn().mockRejectedValue(httpError);
241 | 
242 |       await expect(client.getSystemObjectDefinitions()).rejects.toThrow(httpError);
243 |     });
244 | 
245 |     it('should propagate search validation errors', async () => {
246 |       const searchValidationError = new Error('Invalid search request');
247 |       mockValidateSearchRequest.mockImplementation(() => {
248 |         throw searchValidationError;
249 |       });
250 | 
251 |       const searchRequest = { query: {} };
252 |       await expect(client.searchSystemObjectDefinitions(searchRequest)).rejects.toThrow(searchValidationError);
253 |     });
254 |   });
255 | 
256 |   describe('integration scenarios', () => {
257 |     it('should handle complex search with all options', async () => {
258 |       const objectType = 'Product';
259 |       const searchRequest = {
260 |         query: {
261 |           bool_query: {
262 |             must: [
263 |               {
264 |                 text_query: {
265 |                   fields: ['id'],
266 |                   search_phrase: 'custom',
267 |                 },
268 |               },
269 |             ],
270 |             must_not: [
271 |               {
272 |                 term_query: {
273 |                   fields: ['system'],
274 |                   operator: 'is',
275 |                   values: [true],
276 |                 },
277 |               },
278 |             ],
279 |           },
280 |         },
281 |         sorts: [
282 |           { field: 'display_name', sort_order: 'asc' as const },
283 |           { field: 'id' },
284 |         ],
285 |         start: 10,
286 |         count: 25,
287 |         select: '(**)',
288 |       };
289 | 
290 |       await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);
291 | 
292 |       expect(Validator.validateRequired).toHaveBeenCalled();
293 |       expect(Validator.validateObjectType).toHaveBeenCalled();
294 |       expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest);
295 |       expect((client as any).post).toHaveBeenCalledWith(
296 |         '/system_object_definitions/Product/attribute_definition_search',
297 |         searchRequest,
298 |       );
299 |     });
300 | 
301 |     it('should handle empty query parameters gracefully', async () => {
302 |       const params = {};
303 |       mockQueryBuilderFromObject.mockReturnValue('');
304 | 
305 |       await client.getSystemObjectDefinitions(params);
306 | 
307 |       expect((client as any).get).toHaveBeenCalledWith('/system_object_definitions');
308 |     });
309 |   });
310 | });
311 | 
```

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

```javascript
  1 | import { test, describe, before, after, beforeEach } from 'node:test';
  2 | import { strict as assert } from 'node:assert';
  3 | import { connect } from 'mcp-aegis';
  4 | 
  5 | describe('get_latest_error - Full Mode Programmatic Tests (Optimized)', () => {
  6 |   let client;
  7 | 
  8 |   before(async () => {
  9 |     client = await connect('./aegis.config.with-dw.json');
 10 |   });
 11 | 
 12 |   after(async () => {
 13 |     if (client?.connected) {
 14 |       await client.disconnect();
 15 |     }
 16 |   });
 17 | 
 18 |   beforeEach(() => {
 19 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 20 |     client.clearAllBuffers(); // Recommended - comprehensive protection
 21 |   });
 22 | 
 23 |   // Simplified helper functions for common validations
 24 |   function assertValidMCPResponse(result) {
 25 |     assert.ok(result.content, 'Should have content');
 26 |     assert.ok(Array.isArray(result.content), 'Content should be array');
 27 |     assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
 28 |     assert.equal(result.content[0].type, 'text', 'Content should be text type');
 29 |   }
 30 | 
 31 |   function assertErrorResponse(result, expectedErrorText) {
 32 |     assertValidMCPResponse(result);
 33 |     assert.equal(result.isError, true, 'Should be an error response');
 34 |     assert.ok(result.content[0].text.includes(expectedErrorText),
 35 |       `Expected error text "${expectedErrorText}" in "${result.content[0].text}"`);
 36 |   }
 37 | 
 38 |   function assertSuccessWithLimit(result, expectedLimit) {
 39 |     assertValidMCPResponse(result);
 40 |     assert.equal(result.isError, false, 'Should not be an error response');
 41 |     const text = result.content[0].text;
 42 |     assert.ok(text.includes(`Latest ${expectedLimit} error messages`),
 43 |       `Should mention "${expectedLimit}" error messages`);
 44 |     assert.ok(/error-blade-\d{8}-\d{6}\.log/.test(text), 'Should contain log file pattern');
 45 |     assert.ok(text.includes('ERROR'), 'Should contain ERROR level entries');
 46 |   }
 47 | 
 48 |   // Helper function to get current date in YYYYMMDD format
 49 |   function getCurrentDateString() {
 50 |     const now = new Date();
 51 |     const year = now.getFullYear();
 52 |     const month = String(now.getMonth() + 1).padStart(2, '0');
 53 |     const day = String(now.getDate()).padStart(2, '0');
 54 |     return `${year}${month}${day}`;
 55 |   }
 56 | 
 57 |   // Core functionality tests (essential scenarios)
 58 |   describe('Core Functionality', () => {
 59 |     test('should retrieve error messages with default parameters', async () => {
 60 |       const result = await client.callTool('get_latest_error', {});
 61 |       
 62 |       assertSuccessWithLimit(result, 10); // Default limit is 10
 63 |       
 64 |       // Verify SFCC-specific content is present
 65 |       const text = result.content[0].text;
 66 |       assert.ok(/PipelineCallServlet|SystemJobThread/.test(text), 'Should contain SFCC thread patterns');
 67 |       assert.ok(text.includes('Sites-'), 'Should contain Sites information');
 68 |       assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} GMT/.test(text), 'Should contain GMT timestamps');
 69 |     });
 70 | 
 71 |     test('should respect limit parameter and return ordered results', async () => {
 72 |       const result = await client.callTool('get_latest_error', { limit: 3 });
 73 |       
 74 |       assertSuccessWithLimit(result, 3);
 75 |       
 76 |       const text = result.content[0].text;
 77 |       // Should contain separators for multiple entries
 78 |       const separatorCount = (text.match(/---/g) || []).length;
 79 |       assert.ok(separatorCount >= 1, 'Should have separators between entries');
 80 |       
 81 |       // Verify chronological order (newest first) with known mock data
 82 |       assert.ok(text.includes('CQ - AWS S3 Configuration Issue'), 'Should contain latest error');
 83 |       assert.ok(text.includes('Payment authorization failed'), 'Should contain second latest error');
 84 |     });
 85 | 
 86 |     test('should handle date parameter correctly', async () => {
 87 |       const result = await client.callTool('get_latest_error', { 
 88 |         date: getCurrentDateString(),
 89 |         limit: 2 
 90 |       });
 91 |       
 92 |       assertSuccessWithLimit(result, 2);
 93 |     });
 94 |   });
 95 | 
 96 |   // Parameter validation tests (core error handling)
 97 |   describe('Parameter Validation', () => {
 98 |     test('should reject invalid limit types and values', async () => {
 99 |       // Test string limit
100 |       const stringResult = await client.callTool('get_latest_error', { limit: '5' });
101 |       assertErrorResponse(stringResult, 'Invalid limit \'5\' for get_latest_error. Must be a valid number');
102 |       
103 |       // Test zero limit
104 |       const zeroResult = await client.callTool('get_latest_error', { limit: 0 });
105 |       assertErrorResponse(zeroResult, 'Invalid limit \'0\' for get_latest_error');
106 |       
107 |       // Test negative limit
108 |       const negativeResult = await client.callTool('get_latest_error', { limit: -5 });
109 |       assertErrorResponse(negativeResult, 'Invalid limit');
110 |     });
111 | 
112 |     test('should handle large limits appropriately', async () => {
113 |       const largeResult = await client.callTool('get_latest_error', { limit: 50 });
114 |       assertSuccessWithLimit(largeResult, 50);
115 |       
116 |       // Test extremely large limit (should error)
117 |       const hugeResult = await client.callTool('get_latest_error', { limit: 9999 });
118 |       assertErrorResponse(hugeResult, 'Invalid limit');
119 |     });
120 | 
121 |     test('should handle date parameters gracefully', async () => {
122 |       // Valid YYYYMMDD date
123 |       const validResult = await client.callTool('get_latest_error', { 
124 |         date: '20240101',
125 |         limit: 1 
126 |       });
127 |       assertValidMCPResponse(validResult);
128 |       assert.equal(validResult.isError, false, 'Valid date should not error');
129 |       
130 |       // Invalid date format (should handle gracefully)
131 |       const invalidResult = await client.callTool('get_latest_error', { 
132 |         date: '2024-01-01',
133 |         limit: 1 
134 |       });
135 |       assertValidMCPResponse(invalidResult);
136 |       // Should not crash, may succeed or fail gracefully
137 |     });
138 | 
139 |     test('should handle missing arguments gracefully', async () => {
140 |       const result = await client.callTool('get_latest_error');
141 |       assertSuccessWithLimit(result, 10); // Should use default limit
142 |     });
143 |   });
144 | 
145 |   // Content validation tests (SFCC-specific patterns)
146 |   describe('Content Validation', () => {
147 |     test('should include realistic SFCC error scenarios and structure', async () => {
148 |       const result = await client.callTool('get_latest_error', { limit: 5 });
149 |       
150 |       assertValidMCPResponse(result);
151 |       assert.equal(result.isError, false, 'Should not be an error');
152 |       const text = result.content[0].text;
153 |       
154 |       // Should contain common SFCC error patterns
155 |       const errorPatterns = [
156 |         'Custom cartridge error',
157 |         'Product import failed', 
158 |         'Customer profile creation failed',
159 |         'Payment authorization failed',
160 |         'AWS S3 Configuration Issue'
161 |       ];
162 |       
163 |       const foundPatterns = errorPatterns.filter(pattern => text.includes(pattern));
164 |       assert.ok(foundPatterns.length > 0, 
165 |         `Should contain at least one error pattern. Found: ${foundPatterns.join(', ')}`);
166 |       
167 |       // Validate basic log structure elements
168 |       assert.ok(text.includes('[') && text.includes(']'), 'Should contain log brackets');
169 |       assert.ok(/\|\d+\|/.test(text), 'Should contain thread IDs with pipes');
170 |     });
171 | 
172 |     test('should contain comprehensive SFCC-specific patterns', async () => {
173 |       const result = await client.callTool('get_latest_error', { limit: 3 });
174 |       
175 |       assertValidMCPResponse(result);
176 |       assert.equal(result.isError, false, 'Should not be an error');
177 |       const text = result.content[0].text;
178 |       
179 |       // SFCC-specific validation patterns
180 |       const sfccPatterns = [
181 |         { pattern: /Sites-\w+/, name: 'Sites names' },
182 |         { pattern: /PipelineCallServlet|SystemJobThread/, name: 'Thread types' },
183 |         { pattern: /\|\d+\|/, name: 'Thread IDs' },
184 |         { pattern: /custom \[\]/, name: 'Custom category' }
185 |       ];
186 |       
187 |       const matchedPatterns = sfccPatterns.filter(({ pattern }) => pattern.test(text));
188 |       assert.ok(matchedPatterns.length >= 2,
189 |         `Should match at least 2 SFCC patterns. Matched: ${matchedPatterns.map(p => p.name).join(', ')}`);
190 |     });
191 |   });
192 | 
193 |   // Error recovery and resilience testing
194 |   describe('Error Recovery and Resilience', () => {
195 |     test('should handle error scenarios and recover properly', async () => {
196 |       // Test invalid parameter
197 |       const invalidResult = await client.callTool('get_latest_error', { limit: 0 });
198 |       assertErrorResponse(invalidResult, 'Invalid limit');
199 |       
200 |       // Test recovery with valid parameters
201 |       const validResult = await client.callTool('get_latest_error', { limit: 1 });
202 |       assertValidMCPResponse(validResult);
203 |       assert.equal(validResult.isError, false, 'Should work normally after error');
204 |       assertSuccessWithLimit(validResult, 1);
205 |       
206 |       // Test edge cases without breaking
207 |       const edgeCases = [
208 |         { limit: '1' },      // String limit (should error)
209 |         { limit: 1000 },     // Large limit (should error)  
210 |         { date: '' },        // Empty date (should handle gracefully)
211 |         { invalid: 'param' } // Invalid parameter name (should handle gracefully)
212 |       ];
213 |       
214 |       // Test all edge cases sequentially
215 |       for (const testCase of edgeCases) {
216 |         const result = await client.callTool('get_latest_error', testCase);
217 |         assertValidMCPResponse(result);
218 |         // Some may error, some may succeed - but none should crash
219 |       }
220 |       
221 |       // Verify tool still works after edge cases
222 |       const finalResult = await client.callTool('get_latest_error', { limit: 1 });
223 |       assertValidMCPResponse(finalResult);
224 |       assert.equal(finalResult.isError, false, 'Should work after edge cases');
225 |     });
226 |   });
227 | 
228 |   // Advanced scenarios (simplified multi-step workflow)
229 |   describe('Advanced Scenarios', () => {
230 |     test('should support typical error analysis workflow', async () => {
231 |       // Simulate a typical workflow: recent errors -> specific investigation -> recovery validation
232 |       
233 |       // Step 1: Get recent errors for overview
234 |       const recentErrors = await client.callTool('get_latest_error', { limit: 2 });
235 |       assertValidMCPResponse(recentErrors);
236 |       assert.equal(recentErrors.isError, false, 'Recent errors should succeed');
237 |       
238 |       // Step 2: Get more detailed view for specific analysis
239 |       const detailedErrors = await client.callTool('get_latest_error', { 
240 |         date: getCurrentDateString(),
241 |         limit: 5 
242 |       });
243 |       assertValidMCPResponse(detailedErrors);
244 |       assert.equal(detailedErrors.isError, false, 'Detailed errors should succeed');
245 |       
246 |       // Step 3: Verify both contain expected error content
247 |       [recentErrors, detailedErrors].forEach((result, index) => {
248 |         const text = result.content[0].text;
249 |         assert.ok(text.includes('ERROR'), `Result ${index} should contain ERROR level`);
250 |         assert.ok(text.includes('Latest'), `Result ${index} should contain 'Latest' message`);
251 |         assert.ok(/error-blade-.*\.log/.test(text), `Result ${index} should contain log filename`);
252 |       });
253 |     });
254 |   });
255 | });
256 | 
```

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

```json
  1 | {
  2 |   "_v": "23.2",
  3 |   "_type": "object_attribute_group_search_result",
  4 |   "count": 35,
  5 |   "hits": [
  6 |     {
  7 |       "_type": "object_attribute_group",
  8 |       "_resource_state": "f5c17fdc7ebeaf9136d6fef12d76d68cf238abb98ccaa282e40a657b1a5fcfb6",
  9 |       "id": "ChannelIntegration",
 10 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/ChannelIntegration"
 11 |     },
 12 |     {
 13 |       "_type": "object_attribute_group",
 14 |       "_resource_state": "9787c54106885bf78ff45cd7f3a7f64a073d911485473db6289f4fb0252e345d",
 15 |       "id": "ExternalSearch",
 16 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/ExternalSearch"
 17 |     },
 18 |     {
 19 |       "_type": "object_attribute_group",
 20 |       "_resource_state": "01b32a52ad9a64e426f08a21c0d1c3732fa8f573399db8bcf8d68a49dbdea9b6",
 21 |       "id": "Order",
 22 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Order"
 23 |     },
 24 |     {
 25 |       "_type": "object_attribute_group",
 26 |       "_resource_state": "3ad88093fb2451eb026a2c103b26018e9ab265391da20a39fb8bf3a8c5bb477d",
 27 |       "id": "PXL3_Hoesjes",
 28 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Hoesjes"
 29 |     },
 30 |     {
 31 |       "_type": "object_attribute_group",
 32 |       "_resource_state": "dc899d1d79f89a7a1845bc0fdea1e7b76b826f155f59807be55c0d8ce4763dc9",
 33 |       "id": "PXL3_Oordopjes",
 34 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Oordopjes"
 35 |     },
 36 |     {
 37 |       "_type": "object_attribute_group",
 38 |       "_resource_state": "d01dd2dca011264ffadd379d3c56151a7636dc1d6faf4ada51a081f2c72999eb",
 39 |       "id": "PXL3_Opladers",
 40 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/PXL3_Opladers"
 41 |     },
 42 |     {
 43 |       "_type": "object_attribute_group",
 44 |       "_resource_state": "cc17ba20294c83f5322e26ace9f60843de0c8dd55ff1bbfa4ad7b4e414595b47",
 45 |       "id": "Presentation",
 46 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Presentation"
 47 |     },
 48 |     {
 49 |       "_type": "object_attribute_group",
 50 |       "_resource_state": "e0f82a956514fb4f24c601b1197248b2f21987bb339a7bddf14df5b7a309a1f9",
 51 |       "id": "SearchRanking",
 52 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/SearchRanking"
 53 |     },
 54 |     {
 55 |       "_type": "object_attribute_group",
 56 |       "_resource_state": "3495916efcfef9381c9630e7ac29f740c4ecd2c2e020692db7a8046838ee5a61",
 57 |       "id": "SiteMap",
 58 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/SiteMap"
 59 |     },
 60 |     {
 61 |       "_type": "object_attribute_group",
 62 |       "_resource_state": "e63730013e6fd915009e27898def794a844029e6fc2b96487bf00089b648d03a",
 63 |       "id": "Store",
 64 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/Store"
 65 |     },
 66 |     {
 67 |       "_type": "object_attribute_group",
 68 |       "_resource_state": "89c739821f23ba2e2c1e99d6fd1056228bda5046cc530cd045a2c307ce09d9a1",
 69 |       "id": "WashingInstructions",
 70 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/WashingInstructions"
 71 |     },
 72 |     {
 73 |       "_type": "object_attribute_group",
 74 |       "_resource_state": "0a8bf616f0b756ab850b50b8e6fff58906a4bbe5086a7361d889e01e9e9595b0",
 75 |       "id": "backInStock",
 76 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/backInStock"
 77 |     },
 78 |     {
 79 |       "_type": "object_attribute_group",
 80 |       "_resource_state": "750ad1eb7d39a27b5af165b0d09d37616867c379a5b737b2ff5831581a0c1937",
 81 |       "id": "clothingAttributes",
 82 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/clothingAttributes"
 83 |     },
 84 |     {
 85 |       "_type": "object_attribute_group",
 86 |       "_resource_state": "15add012dad5d62be8278d2962b188f59635c55b236538ed5a8f354e71b3ee73",
 87 |       "id": "custom",
 88 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/custom"
 89 |     },
 90 |     {
 91 |       "_type": "object_attribute_group",
 92 |       "_resource_state": "65e5cd4c2fd95724f96d8c1f838f9254d0f3a01c072e441dd73e518474458f2b",
 93 |       "id": "electronicsDigitalCameraAttributes",
 94 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDigitalCameraAttributes"
 95 |     },
 96 |     {
 97 |       "_type": "object_attribute_group",
 98 |       "_resource_state": "37f492a3eb597f59395358cfb40eeb3a6294178dce09f8c377170e69daa30837",
 99 |       "id": "electronicsDigitalMediaPlayerAttributes",
100 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDigitalMediaPlayerAttributes"
101 |     },
102 |     {
103 |       "_type": "object_attribute_group",
104 |       "_resource_state": "fe9a646c9f866142f2d28011c26f65f90372ff7fc48067a77c57d54eb58c96a7",
105 |       "id": "electronicsDimensionsAndWeight",
106 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsDimensionsAndWeight"
107 |     },
108 |     {
109 |       "_type": "object_attribute_group",
110 |       "_resource_state": "dc186ea27a80c3369c603011027f46d2d34f7a43b60d44cf0bf1208346c40151",
111 |       "id": "electronicsGameRatings",
112 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsGameRatings"
113 |     },
114 |     {
115 |       "_type": "object_attribute_group",
116 |       "_resource_state": "8a3b2e949bdc9929ba41876929676f26e9b0527a5f30001273d09e9f2a53f89c",
117 |       "id": "electronicsGpsAttributes",
118 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/electronicsGpsAttributes"
119 |     },
120 |     {
121 |       "_type": "object_attribute_group",
122 |       "_resource_state": "0c9a9c20a3d1d3a3c5b6378c14551b2ce0f8352bad88de81ac357de2bb1d9145",
123 |       "id": "inStorePickup",
124 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/inStorePickup"
125 |     },
126 |     {
127 |       "_type": "object_attribute_group",
128 |       "_resource_state": "756e58068a70de9dd92c15babbb87e8c7b0856b6701f22a974cbf6f084bfaa3b",
129 |       "id": "mainAttributes",
130 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
131 |     },
132 |     {
133 |       "_type": "object_attribute_group",
134 |       "_resource_state": "87361af2c433c890fe2afcd4bfb24056b763e8ddaa31e6e568a53336a7af78a7",
135 |       "id": "mainAttributes",
136 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
137 |     },
138 |     {
139 |       "_type": "object_attribute_group",
140 |       "_resource_state": "eb2824b3e077e3bbc7c6626ebf4bcba9ca21b96f9804c4128e26120d898a0829",
141 |       "id": "mainAttributes",
142 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
143 |     },
144 |     {
145 |       "_type": "object_attribute_group",
146 |       "_resource_state": "76beb3fafcfbddb4830c18b3382544456122a12c25d2fe5012ebc945a30b1b5d",
147 |       "id": "mainAttributes",
148 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
149 |     },
150 |     {
151 |       "_type": "object_attribute_group",
152 |       "_resource_state": "37e37519eed074a1d4ca5f2be6914b8f32f19bea20475d7b63c6e684f63a2e37",
153 |       "id": "mainAttributes",
154 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mainAttributes"
155 |     },
156 |     {
157 |       "_type": "object_attribute_group",
158 |       "_resource_state": "e3f0676a3f0a5a212fbf171dd9f2d57b73c918ed27135f48a5e0a3d0b32dfb8a",
159 |       "id": "material",
160 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/material"
161 |     },
162 |     {
163 |       "_type": "object_attribute_group",
164 |       "_resource_state": "1810005767e31409c70ae256c98bd2570bf0a9664c82a464a9c097056251b949",
165 |       "id": "materialWashingInstructions",
166 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/materialWashingInstructions"
167 |     },
168 |     {
169 |       "_type": "object_attribute_group",
170 |       "_resource_state": "39d412aa0c6d48528b5efa0a5f639c9b20791744f69c3a18a3932e587071831d",
171 |       "id": "mensAccessoriesAttributes",
172 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mensAccessoriesAttributes"
173 |     },
174 |     {
175 |       "_type": "object_attribute_group",
176 |       "_resource_state": "f696e3513a04d3e4ea494165e9e69e6a9f4d3a0d22a0565921d5860c735c73f9",
177 |       "id": "mensClothingAttributes",
178 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/mensClothingAttributes"
179 |     },
180 |     {
181 |       "_type": "object_attribute_group",
182 |       "_resource_state": "747af7428d1f04dcfe79c2e22a34fe562277ad0f8b13dc4322708121233da7d8",
183 |       "id": "searchRefinements",
184 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/searchRefinements"
185 |     },
186 |     {
187 |       "_type": "object_attribute_group",
188 |       "_resource_state": "59811b6d815972e1403936b0fa5b6de7ca8101ebb27cb1dd83302eb1d9c8b0ff",
189 |       "id": "storefrontAttributes",
190 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
191 |     },
192 |     {
193 |       "_type": "object_attribute_group",
194 |       "_resource_state": "b29d3cb7452c7a6749ca009a024fe1b865d7642ebbf2a7392d5472e7641c5bcf",
195 |       "id": "storefrontAttributes",
196 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
197 |     },
198 |     {
199 |       "_type": "object_attribute_group",
200 |       "_resource_state": "0706cc287e8e9dda03acfc351d70b20e3eb4f9e26ba1f66929f7c68cc4b443d8",
201 |       "id": "storefrontAttributes",
202 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/storefrontAttributes"
203 |     },
204 |     {
205 |       "_type": "object_attribute_group",
206 |       "_resource_state": "7c529b5a59acfe0958ff98e98ce9a53c5045b1638edcce0abdf1ad7608a2ae8b",
207 |       "id": "womensAccessoriesAttributes",
208 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/womensAccessoriesAttributes"
209 |     },
210 |     {
211 |       "_type": "object_attribute_group",
212 |       "_resource_state": "436a1fc1e34643a96e42672f536b3f7802af3f512ec69ccdda05079af88f0277",
213 |       "id": "womensClothingAttributes",
214 |       "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_groups/womensClothingAttributes"
215 |     }
216 |   ],
217 |   "query": {
218 |     "match_all_query": {
219 |       "_type": "match_all_query"
220 |     }
221 |   },
222 |   "start": 0,
223 |   "total": 35
224 | }
225 | 
```

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

```markdown
  1 | ## Package: dw.order
  2 | 
  3 | # Class ReturnCase
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.Extensible
  9 |   - dw.order.AbstractItemCtnr
 10 |     - dw.order.ReturnCase
 11 | 
 12 | ## Description
 13 | 
 14 | 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.
 15 | 
 16 | ## Constants
 17 | 
 18 | ### ORDERBY_ITEMID
 19 | 
 20 | **Type:** Object
 21 | 
 22 | Sorting by item id. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 23 | 
 24 | ### ORDERBY_ITEMPOSITION
 25 | 
 26 | **Type:** Object
 27 | 
 28 | Sorting by the position of the related oder item. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 29 | 
 30 | ### ORDERBY_UNSORTED
 31 | 
 32 | **Type:** Object
 33 | 
 34 | Unsorted , as it is. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 35 | 
 36 | ### QUALIFIER_PRODUCTITEMS
 37 | 
 38 | **Type:** Object
 39 | 
 40 | Selects the product items. Use with method getItems() as an argument to method FilteringCollection.select(Object).
 41 | 
 42 | ### QUALIFIER_SERVICEITEMS
 43 | 
 44 | **Type:** Object
 45 | 
 46 | Selects for the service items. Use with method getItems() as an argument to method FilteringCollection.select(Object).
 47 | 
 48 | ### STATUS_CANCELLED
 49 | 
 50 | **Type:** String = "CANCELLED"
 51 | 
 52 | constant for ReturnCase Status CANCELLED
 53 | 
 54 | ### STATUS_CONFIRMED
 55 | 
 56 | **Type:** String = "CONFIRMED"
 57 | 
 58 | constant for ReturnCase Status CONFIRMED
 59 | 
 60 | ### STATUS_NEW
 61 | 
 62 | **Type:** String = "NEW"
 63 | 
 64 | constant for ReturnCase Status NEW
 65 | 
 66 | ### STATUS_PARTIAL_RETURNED
 67 | 
 68 | **Type:** String = "PARTIAL_RETURNED"
 69 | 
 70 | constant for ReturnCase Status PARTIAL RETURNED
 71 | 
 72 | ### STATUS_RETURNED
 73 | 
 74 | **Type:** String = "RETURNED"
 75 | 
 76 | constant for ReturnCase Status RETURNED
 77 | 
 78 | ## Properties
 79 | 
 80 | ### invoice
 81 | 
 82 | **Type:** Invoice (Read Only)
 83 | 
 84 | Returns null or the previously created Invoice.
 85 | 
 86 | ### invoiceNumber
 87 | 
 88 | **Type:** String (Read Only)
 89 | 
 90 | Returns null or the invoice-number.
 91 | 
 92 | ### items
 93 | 
 94 | **Type:** FilteringCollection (Read Only)
 95 | 
 96 | Access the collection of ReturnCaseItems.
 97 |  
 98 |  This FilteringCollection can be sorted / filtered using:
 99 |  
100 |  FilteringCollection.sort(Object) with ORDERBY_ITEMID
101 |  FilteringCollection.sort(Object) with
102 |  ORDERBY_ITEMPOSITION
103 |  FilteringCollection.sort(Object) with ORDERBY_UNSORTED
104 |  FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS
105 |  FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS
106 | 
107 | ### returnCaseNumber
108 | 
109 | **Type:** String (Read Only)
110 | 
111 | The mandatory return case number identifying this document.
112 | 
113 | ### returns
114 | 
115 | **Type:** Collection (Read Only)
116 | 
117 | Return the collection of Returns associated with this ReturnCase.
118 | 
119 | ### RMA
120 | 
121 | **Type:** Order.createReturnCase(String, Boolean) (Read Only)
122 | 
123 | Return whether this is an RMA. This is specified when calling Order.createReturnCase(String, Boolean).
124 | 
125 | ### status
126 | 
127 | **Type:** EnumValue (Read Only)
128 | 
129 | Gets the return case item status. The status of a ReturnCase is read-only and calculated from the status of
130 |  the associated ReturnCaseItems.
131 |  
132 |  The possible values are STATUS_NEW,STATUS_CONFIRMED,
133 |  STATUS_PARTIAL_RETURNED, STATUS_RETURNED,
134 |  STATUS_CANCELLED.
135 | 
136 | ## Constructor Summary
137 | 
138 | ## Method Summary
139 | 
140 | ### confirm
141 | 
142 | **Signature:** `confirm() : void`
143 | 
144 | Attempt to confirm the ReturnCase.
145 | 
146 | ### createInvoice
147 | 
148 | **Signature:** `createInvoice() : Invoice`
149 | 
150 | Creates a new Invoice based on this ReturnCase.
151 | 
152 | ### createInvoice
153 | 
154 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice`
155 | 
156 | Creates a new Invoice based on this ReturnCase.
157 | 
158 | ### createItem
159 | 
160 | **Signature:** `createItem(orderItemID : String) : ReturnCaseItem`
161 | 
162 | Creates a new item for a given order item.
163 | 
164 | ### createReturn
165 | 
166 | **Signature:** `createReturn(returnNumber : String) : Return`
167 | 
168 | Creates a new Return with the given number and associates it with this ReturnCase.
169 | 
170 | ### createReturn
171 | 
172 | **Signature:** `createReturn() : Return`
173 | 
174 | Creates a new Return with a generated number and associates it with this ReturnCase.
175 | 
176 | ### getInvoice
177 | 
178 | **Signature:** `getInvoice() : Invoice`
179 | 
180 | Returns null or the previously created Invoice.
181 | 
182 | ### getInvoiceNumber
183 | 
184 | **Signature:** `getInvoiceNumber() : String`
185 | 
186 | Returns null or the invoice-number.
187 | 
188 | ### getItems
189 | 
190 | **Signature:** `getItems() : FilteringCollection`
191 | 
192 | Access the collection of ReturnCaseItems.
193 | 
194 | ### getReturnCaseNumber
195 | 
196 | **Signature:** `getReturnCaseNumber() : String`
197 | 
198 | Returns the mandatory return case number identifying this document.
199 | 
200 | ### getReturns
201 | 
202 | **Signature:** `getReturns() : Collection`
203 | 
204 | Return the collection of Returns associated with this ReturnCase.
205 | 
206 | ### getStatus
207 | 
208 | **Signature:** `getStatus() : EnumValue`
209 | 
210 | Gets the return case item status.
211 | 
212 | ### isRMA
213 | 
214 | **Signature:** `isRMA() : boolean`
215 | 
216 | Return whether this is an RMA.
217 | 
218 | ## Method Detail
219 | 
220 | ## Method Details
221 | 
222 | ### confirm
223 | 
224 | **Signature:** `confirm() : void`
225 | 
226 | **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.
227 | 
228 | **Throws:**
229 | 
230 | IllegalStateException - thrown if Status is not STATUS_NEW
231 | 
232 | ---
233 | 
234 | ### createInvoice
235 | 
236 | **Signature:** `createInvoice() : Invoice`
237 | 
238 | **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.
239 | 
240 | **Returns:**
241 | 
242 | new invoice
243 | 
244 | ---
245 | 
246 | ### createInvoice
247 | 
248 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice`
249 | 
250 | **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.
251 | 
252 | **Parameters:**
253 | 
254 | - `invoiceNumber`: the invoice-number to be used for the invoice creation
255 | 
256 | **Returns:**
257 | 
258 | new invoice
259 | 
260 | ---
261 | 
262 | ### createItem
263 | 
264 | **Signature:** `createItem(orderItemID : String) : ReturnCaseItem`
265 | 
266 | **Description:** Creates a new item for a given order item. Note: a ReturnCase may have only one item per order item.
267 | 
268 | **Parameters:**
269 | 
270 | - `orderItemID`: order item id
271 | 
272 | **Returns:**
273 | 
274 | null or item for given order item
275 | 
276 | **Throws:**
277 | 
278 | IllegalArgumentException - thrown if getItem(orderItem) returns non null
279 | 
280 | ---
281 | 
282 | ### createReturn
283 | 
284 | **Signature:** `createReturn(returnNumber : String) : Return`
285 | 
286 | **Description:** Creates a new Return with the given number and associates it with this ReturnCase.
287 | 
288 | **Parameters:**
289 | 
290 | - `returnNumber`: return number to assign
291 | 
292 | **Returns:**
293 | 
294 | new Return instance
295 | 
296 | ---
297 | 
298 | ### createReturn
299 | 
300 | **Signature:** `createReturn() : Return`
301 | 
302 | **Description:** Creates a new Return with a generated number and associates it with this ReturnCase.
303 | 
304 | **Returns:**
305 | 
306 | new Return instance
307 | 
308 | ---
309 | 
310 | ### getInvoice
311 | 
312 | **Signature:** `getInvoice() : Invoice`
313 | 
314 | **Description:** Returns null or the previously created Invoice.
315 | 
316 | **Returns:**
317 | 
318 | null or the previously created invoice.
319 | 
320 | **See Also:**
321 | 
322 | createInvoice(String)
323 | 
324 | ---
325 | 
326 | ### getInvoiceNumber
327 | 
328 | **Signature:** `getInvoiceNumber() : String`
329 | 
330 | **Description:** Returns null or the invoice-number.
331 | 
332 | **Returns:**
333 | 
334 | null or the previously created invoice.
335 | 
336 | **See Also:**
337 | 
338 | createInvoice(String)
339 | 
340 | ---
341 | 
342 | ### getItems
343 | 
344 | **Signature:** `getItems() : FilteringCollection`
345 | 
346 | **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
347 | 
348 | **Returns:**
349 | 
350 | the items
351 | 
352 | ---
353 | 
354 | ### getReturnCaseNumber
355 | 
356 | **Signature:** `getReturnCaseNumber() : String`
357 | 
358 | **Description:** Returns the mandatory return case number identifying this document.
359 | 
360 | **Returns:**
361 | 
362 | the return case number
363 | 
364 | ---
365 | 
366 | ### getReturns
367 | 
368 | **Signature:** `getReturns() : Collection`
369 | 
370 | **Description:** Return the collection of Returns associated with this ReturnCase.
371 | 
372 | **Returns:**
373 | 
374 | the collection of Returns.
375 | 
376 | ---
377 | 
378 | ### getStatus
379 | 
380 | **Signature:** `getStatus() : EnumValue`
381 | 
382 | **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.
383 | 
384 | **Returns:**
385 | 
386 | the status
387 | 
388 | ---
389 | 
390 | ### isRMA
391 | 
392 | **Signature:** `isRMA() : boolean`
393 | 
394 | **Description:** Return whether this is an RMA. This is specified when calling Order.createReturnCase(String, Boolean).
395 | 
396 | **Returns:**
397 | 
398 | whether this is an RMA.
399 | 
400 | ---
```

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

```typescript
  1 | /**
  2 |  * Tests for the refactored OCAPIClient
  3 |  * Tests the facade pattern that orchestrates specialized client modules
  4 |  */
  5 | 
  6 | import { OCAPIClient } from '../src/clients/ocapi-client.js';
  7 | import { TokenManager } from '../src/clients/base/oauth-token.js';
  8 | import { OCAPIConfig } from '../src/types/types.js';
  9 | 
 10 | // Mock fetch globally
 11 | global.fetch = jest.fn();
 12 | 
 13 | // Mock TokenManager
 14 | jest.mock('../src/clients/base/oauth-token.js');
 15 | 
 16 | // Mock the specialized clients
 17 | jest.mock('../src/clients/ocapi/system-objects-client.js');
 18 | jest.mock('../src/clients/ocapi/site-preferences-client.js');
 19 | jest.mock('../src/clients/ocapi/code-versions-client.js');
 20 | jest.mock('../src/clients/base/ocapi-auth-client.js');
 21 | 
 22 | describe('OCAPIClient', () => {
 23 |   let client: OCAPIClient;
 24 |   let mockTokenManager: jest.Mocked<TokenManager>;
 25 |   const mockConfig: OCAPIConfig = {
 26 |     hostname: 'test-instance.demandware.net',
 27 |     clientId: 'test-client-id',
 28 |     clientSecret: 'test-client-secret',
 29 |     version: 'v21_3',
 30 |   };
 31 | 
 32 |   beforeEach(() => {
 33 |     jest.clearAllMocks();
 34 | 
 35 |     // Setup TokenManager mock
 36 |     mockTokenManager = {
 37 |       getValidToken: jest.fn(),
 38 |       storeToken: jest.fn(),
 39 |       clearToken: jest.fn(),
 40 |       getTokenExpiration: jest.fn(),
 41 |       isTokenValid: jest.fn(),
 42 |       clearAllTokens: jest.fn(),
 43 |     } as any;
 44 | 
 45 |     (TokenManager.getInstance as jest.Mock).mockReturnValue(mockTokenManager);
 46 |     (fetch as jest.Mock).mockClear();
 47 | 
 48 |     client = new OCAPIClient(mockConfig);
 49 |   });
 50 | 
 51 |   describe('constructor', () => {
 52 |     it('should initialize with provided config', () => {
 53 |       expect(client).toBeInstanceOf(OCAPIClient);
 54 |       // Note: TokenManager.getInstance is called by the auth client, not directly by OCAPIClient
 55 |       expect(client.systemObjects).toBeDefined();
 56 |       expect(client.sitePreferences).toBeDefined();
 57 |     });
 58 | 
 59 |     it('should use default version when not provided', () => {
 60 |       const configWithoutVersion = {
 61 |         hostname: 'test.demandware.net',
 62 |         clientId: 'client-id',
 63 |         clientSecret: 'client-secret',
 64 |       };
 65 | 
 66 |       const clientWithDefaults = new OCAPIClient(configWithoutVersion);
 67 |       expect(clientWithDefaults).toBeInstanceOf(OCAPIClient);
 68 |     });
 69 | 
 70 |     it('should initialize all specialized client modules', () => {
 71 |       expect(client.systemObjects).toBeDefined();
 72 |       expect(client.sitePreferences).toBeDefined();
 73 |     });
 74 |   });
 75 | 
 76 |   describe('System Objects API delegation', () => {
 77 |     it('should delegate getSystemObjectDefinitions to SystemObjectsClient', async () => {
 78 |       const mockResponse = { data: 'system-objects' };
 79 |       jest.spyOn(client.systemObjects, 'getSystemObjectDefinitions').mockResolvedValue(mockResponse);
 80 | 
 81 |       const result = await client.getSystemObjectDefinitions();
 82 | 
 83 |       expect(client.systemObjects.getSystemObjectDefinitions).toHaveBeenCalledWith(undefined);
 84 |       expect(result).toBe(mockResponse);
 85 |     });
 86 | 
 87 |     it('should delegate getSystemObjectDefinition with objectType to SystemObjectsClient', async () => {
 88 |       const mockResponse = { data: 'product-definition' };
 89 |       const objectType = 'Product';
 90 |       jest.spyOn(client.systemObjects, 'getSystemObjectDefinition').mockResolvedValue(mockResponse);
 91 | 
 92 |       const result = await client.getSystemObjectDefinition(objectType);
 93 | 
 94 |       expect(client.systemObjects.getSystemObjectDefinition).toHaveBeenCalledWith(objectType);
 95 |       expect(result).toBe(mockResponse);
 96 |     });
 97 | 
 98 |     it('should delegate searchSystemObjectDefinitions to SystemObjectsClient', async () => {
 99 |       const mockResponse = { data: 'search-results' };
100 |       const searchRequest = { query: { match_all_query: {} } };
101 |       jest.spyOn(client.systemObjects, 'searchSystemObjectDefinitions').mockResolvedValue(mockResponse);
102 | 
103 |       const result = await client.searchSystemObjectDefinitions(searchRequest);
104 | 
105 |       expect(client.systemObjects.searchSystemObjectDefinitions).toHaveBeenCalledWith(searchRequest);
106 |       expect(result).toBe(mockResponse);
107 |     });
108 | 
109 |     it('should delegate searchSystemObjectAttributeDefinitions to SystemObjectsClient', async () => {
110 |       const mockResponse = { data: 'attribute-search-results' };
111 |       const objectType = 'Product';
112 |       const searchRequest = { query: { text_query: { fields: ['id'], search_phrase: 'custom' } } };
113 |       jest.spyOn(client.systemObjects, 'searchSystemObjectAttributeDefinitions').mockResolvedValue(mockResponse);
114 | 
115 |       const result = await client.searchSystemObjectAttributeDefinitions(objectType, searchRequest);
116 | 
117 |       expect(client.systemObjects.searchSystemObjectAttributeDefinitions)
118 |         .toHaveBeenCalledWith(objectType, searchRequest);
119 |       expect(result).toBe(mockResponse);
120 |     });
121 | 
122 |     it('should delegate searchSystemObjectAttributeGroups to SystemObjectsClient', async () => {
123 |       const mockResponse = { data: 'attribute-groups' };
124 |       const objectType = 'SitePreferences';
125 |       const searchRequest = { query: { match_all_query: {} } };
126 |       jest.spyOn(client.systemObjects, 'searchSystemObjectAttributeGroups').mockResolvedValue(mockResponse);
127 | 
128 |       const result = await client.searchSystemObjectAttributeGroups(objectType, searchRequest);
129 | 
130 |       expect(client.systemObjects.searchSystemObjectAttributeGroups).toHaveBeenCalledWith(objectType, searchRequest);
131 |       expect(result).toBe(mockResponse);
132 |     });
133 |   });
134 | 
135 |   describe('Site Preferences API delegation', () => {
136 |     it('should delegate searchSitePreferences to SitePreferencesClient', async () => {
137 |       const mockResponse = { data: 'site-preferences' };
138 |       const groupId = 'SiteGeneral';
139 |       const instanceType = 'sandbox';
140 |       const searchRequest = { query: { match_all_query: {} } };
141 |       const options = { maskPasswords: true };
142 |       jest.spyOn(client.sitePreferences, 'searchSitePreferences').mockResolvedValue(mockResponse);
143 | 
144 |       const result = await client.searchSitePreferences(groupId, instanceType, searchRequest, options);
145 | 
146 |       expect(client.sitePreferences.searchSitePreferences)
147 |         .toHaveBeenCalledWith(groupId, instanceType, searchRequest, options);
148 |       expect(result).toBe(mockResponse);
149 |     });
150 |   });
151 | 
152 |   describe('Authentication & Token Management delegation', () => {
153 |     it('should delegate getTokenExpiration to AuthClient', () => {
154 |       const mockExpiration = new Date();
155 | 
156 |       // Mock the authClient's getTokenExpiration method
157 |       const mockAuthClient = {
158 |         getTokenExpiration: jest.fn().mockReturnValue(mockExpiration),
159 |       };
160 | 
161 |       // Access the private authClient property and mock it
162 |       (client as any).authClient = mockAuthClient;
163 | 
164 |       const result = client.getTokenExpiration();
165 | 
166 |       expect(mockAuthClient.getTokenExpiration).toHaveBeenCalled();
167 |       expect(result).toBe(mockExpiration);
168 |     });
169 | 
170 |     it('should delegate refreshToken to AuthClient', async () => {
171 |       // Mock the authClient's refreshToken method
172 |       const mockAuthClient = {
173 |         refreshToken: jest.fn().mockResolvedValue(undefined),
174 |       };
175 | 
176 |       // Access the private authClient property and mock it
177 |       (client as any).authClient = mockAuthClient;
178 | 
179 |       await client.refreshToken();
180 | 
181 |       expect(mockAuthClient.refreshToken).toHaveBeenCalled();
182 |     });
183 | 
184 |     it('should delegate getCodeVersions to CodeVersionsClient', async () => {
185 |       // Mock the codeVersions client's getCodeVersions method
186 |       const mockCodeVersions = {
187 |         _v: '23.2',
188 |         _type: 'code_version_result',
189 |         count: 1,
190 |         data: [
191 |           {
192 |             _type: 'code_version',
193 |             id: 'version1',
194 |             active: true,
195 |             cartridges: 'app_storefront_base',
196 |             compatibility_mode: '23.2',
197 |             activation_time: '2024-01-01T00:00:00Z',
198 |             total_size: '1024 KB',
199 |           },
200 |         ],
201 |         total: 1,
202 |       };
203 | 
204 |       const mockCodeVersionsClient = {
205 |         getCodeVersions: jest.fn().mockResolvedValue(mockCodeVersions),
206 |       };
207 | 
208 |       // Access the private codeVersions property and mock it
209 |       (client as any).codeVersions = mockCodeVersionsClient;
210 | 
211 |       const result = await client.getCodeVersions();
212 | 
213 |       expect(mockCodeVersionsClient.getCodeVersions).toHaveBeenCalled();
214 |       expect(result).toBe(mockCodeVersions);
215 |     });
216 | 
217 |     it('should delegate activateCodeVersion to CodeVersionsClient', async () => {
218 |       // Mock the codeVersions client's activateCodeVersion method
219 |       const mockActivatedVersion = {
220 |         _v: '23.2',
221 |         _type: 'code_version',
222 |         _resource_state: 'new-resource-state-12345',
223 |         id: 'version2',
224 |         active: true,
225 |         cartridges: 'app_storefront_base',
226 |         compatibility_mode: '23.2',
227 |         activation_time: '2024-01-15T10:30:00Z',
228 |         total_size: '1024 KB',
229 |       };
230 | 
231 |       const mockCodeVersionsClient = {
232 |         activateCodeVersion: jest.fn().mockResolvedValue(mockActivatedVersion),
233 |       };
234 | 
235 |       // Access the private codeVersions property and mock it
236 |       (client as any).codeVersions = mockCodeVersionsClient;
237 | 
238 |       const codeVersionId = 'version2';
239 |       const result = await client.activateCodeVersion(codeVersionId);
240 | 
241 |       expect(mockCodeVersionsClient.activateCodeVersion).toHaveBeenCalledWith(codeVersionId);
242 |       expect(result).toBe(mockActivatedVersion);
243 |     });
244 |   });
245 | 
246 |   describe('Configuration handling', () => {
247 |     it('should merge config with defaults', () => {
248 |       const configWithoutVersion = {
249 |         hostname: 'test.demandware.net',
250 |         clientId: 'client-id',
251 |         clientSecret: 'client-secret',
252 |       };
253 | 
254 |       const clientWithDefaults = new OCAPIClient(configWithoutVersion);
255 | 
256 |       // Verify that the client was created successfully (which means defaults were applied)
257 |       expect(clientWithDefaults).toBeInstanceOf(OCAPIClient);
258 |       expect(clientWithDefaults.systemObjects).toBeDefined();
259 |       expect(clientWithDefaults.sitePreferences).toBeDefined();
260 |     });
261 | 
262 |     it('should preserve provided config values', () => {
263 |       const customConfig = {
264 |         hostname: 'custom.demandware.net',
265 |         clientId: 'custom-client-id',
266 |         clientSecret: 'custom-client-secret',
267 |         version: 'v22_1',
268 |       };
269 | 
270 |       const customClient = new OCAPIClient(customConfig);
271 | 
272 |       // Verify that the client was created successfully with custom config
273 |       expect(customClient).toBeInstanceOf(OCAPIClient);
274 |       expect(customClient.systemObjects).toBeDefined();
275 |       expect(customClient.sitePreferences).toBeDefined();
276 |     });
277 |   });
278 | 
279 |   describe('Error handling', () => {
280 |     it('should propagate errors from specialized clients', async () => {
281 |       const error = new Error('System objects error');
282 |       jest.spyOn(client.systemObjects, 'getSystemObjectDefinitions').mockRejectedValue(error);
283 | 
284 |       await expect(client.getSystemObjectDefinitions()).rejects.toThrow('System objects error');
285 |     });
286 | 
287 |     it('should propagate errors from site preferences client', async () => {
288 |       const error = new Error('Site preferences error');
289 |       jest.spyOn(client.sitePreferences, 'searchSitePreferences').mockRejectedValue(error);
290 | 
291 |       const searchRequest = { query: { match_all_query: {} } };
292 |       await expect(client.searchSitePreferences('groupId', 'sandbox', searchRequest)).rejects.toThrow('Site preferences error');
293 |     });
294 |   });
295 | });
296 | 
```
Page 20/61FirstPrevNextLast