#
tokens: 49479/50000 10/825 files (page 22/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 22 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/get-sfra-categories.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | description: "Comprehensive tests for get_sfra_categories tool in docs-only mode"
  2 | 
  3 | tests:
  4 |   # Basic functionality tests
  5 |   - it: "should return all SFRA category information with proper structure"
  6 |     request:
  7 |       jsonrpc: "2.0" 
  8 |       id: "test-1"
  9 |       method: "tools/call"
 10 |       params:
 11 |         name: "get_sfra_categories"
 12 |         arguments: {}
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "test-1"
 17 |         result:
 18 |           content:
 19 |             - type: "text"
 20 |               text: "match:type:string"
 21 |           isError: false
 22 |       stderr: "toBeEmpty"
 23 | 
 24 |   - it: "should return JSON array of category objects"
 25 |     request:
 26 |       jsonrpc: "2.0"
 27 |       id: "test-2"
 28 |       method: "tools/call"
 29 |       params:
 30 |         name: "get_sfra_categories"
 31 |         arguments: {}
 32 |     expect:
 33 |       response:
 34 |         jsonrpc: "2.0"
 35 |         id: "test-2"
 36 |         result:
 37 |           content:
 38 |             - type: "text"
 39 |               text: "match:regex:^\\[[\\s\\S]*\\]$"
 40 |           isError: false
 41 |       stderr: "toBeEmpty"
 42 | 
 43 |   - it: "should include all expected category fields"
 44 |     request:
 45 |       jsonrpc: "2.0"
 46 |       id: "test-3"
 47 |       method: "tools/call"
 48 |       params:
 49 |         name: "get_sfra_categories"
 50 |         arguments: {}
 51 |     expect:
 52 |       response:
 53 |         jsonrpc: "2.0"
 54 |         id: "test-3"
 55 |         result:
 56 |           content:
 57 |             - type: "text"
 58 |               text: "match:regex:[\\s\\S]*category[\\s\\S]*count[\\s\\S]*description[\\s\\S]*"
 59 |           isError: false
 60 |       stderr: "toBeEmpty"
 61 | 
 62 |   # Specific category validation tests
 63 |   - it: "should include core category with proper information"
 64 |     request:
 65 |       jsonrpc: "2.0"
 66 |       id: "test-4"
 67 |       method: "tools/call"
 68 |       params:
 69 |         name: "get_sfra_categories"
 70 |         arguments: {}
 71 |     expect:
 72 |       response:
 73 |         jsonrpc: "2.0"
 74 |         id: "test-4"
 75 |         result:
 76 |           content:
 77 |             - type: "text"
 78 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"core\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Core SFRA classes[\\s\\S]*"
 79 |           isError: false
 80 |       stderr: "toBeEmpty"
 81 | 
 82 |   - it: "should include customer category with proper information"
 83 |     request:
 84 |       jsonrpc: "2.0"
 85 |       id: "test-5"
 86 |       method: "tools/call"
 87 |       params:
 88 |         name: "get_sfra_categories"
 89 |         arguments: {}
 90 |     expect:
 91 |       response:
 92 |         jsonrpc: "2.0"
 93 |         id: "test-5"
 94 |         result:
 95 |           content:
 96 |             - type: "text"
 97 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"customer\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Customer account[\\s\\S]*"
 98 |           isError: false
 99 |       stderr: "toBeEmpty"
100 | 
101 |   - it: "should include order category with proper information"
102 |     request:
103 |       jsonrpc: "2.0"
104 |       id: "test-6"
105 |       method: "tools/call"
106 |       params:
107 |         name: "get_sfra_categories"
108 |         arguments: {}
109 |     expect:
110 |       response:
111 |         jsonrpc: "2.0"
112 |         id: "test-6"
113 |         result:
114 |           content:
115 |             - type: "text"
116 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"order\"[\\s\\S]*\"count\":[\\s]*6[\\s\\S]*Order, cart, billing[\\s\\S]*"
117 |           isError: false
118 |       stderr: "toBeEmpty"
119 | 
120 |   - it: "should include product category with proper information"
121 |     request:
122 |       jsonrpc: "2.0"
123 |       id: "test-7"
124 |       method: "tools/call"
125 |       params:
126 |         name: "get_sfra_categories"
127 |         arguments: {}
128 |     expect:
129 |       response:
130 |         jsonrpc: "2.0"
131 |         id: "test-7"
132 |         result:
133 |           content:
134 |             - type: "text"
135 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"product\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Product-related[\\s\\S]*"
136 |           isError: false
137 |       stderr: "toBeEmpty"
138 | 
139 |   - it: "should include pricing category with proper information"
140 |     request:
141 |       jsonrpc: "2.0"
142 |       id: "test-8"
143 |       method: "tools/call"
144 |       params:
145 |         name: "get_sfra_categories"
146 |         arguments: {}
147 |     expect:
148 |       response:
149 |         jsonrpc: "2.0"
150 |         id: "test-8"
151 |         result:
152 |           content:
153 |             - type: "text"
154 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"pricing\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Pricing and discount[\\s\\S]*"
155 |           isError: false
156 |       stderr: "toBeEmpty"
157 | 
158 |   - it: "should include store category with proper information"
159 |     request:
160 |       jsonrpc: "2.0"
161 |       id: "test-9"
162 |       method: "tools/call"
163 |       params:
164 |         name: "get_sfra_categories"
165 |         arguments: {}
166 |     expect:
167 |       response:
168 |         jsonrpc: "2.0"
169 |         id: "test-9"
170 |         result:
171 |           content:
172 |             - type: "text"
173 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"store\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Store and location[\\s\\S]*"
174 |           isError: false
175 |       stderr: "toBeEmpty"
176 | 
177 |   - it: "should include other category with proper information"
178 |     request:
179 |       jsonrpc: "2.0"
180 |       id: "test-10"
181 |       method: "tools/call"
182 |       params:
183 |         name: "get_sfra_categories"
184 |         arguments: {}
185 |     expect:
186 |       response:
187 |         jsonrpc: "2.0"
188 |         id: "test-10"
189 |         result:
190 |           content:
191 |             - type: "text"
192 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"other\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Other models and utilities[\\s\\S]*"
193 |           isError: false
194 |       stderr: "toBeEmpty"
195 | 
196 |   # Count validation tests
197 |   - it: "should return exactly 7 categories"
198 |     request:
199 |       jsonrpc: "2.0"
200 |       id: "test-11"
201 |       method: "tools/call"
202 |       params:
203 |         name: "get_sfra_categories"
204 |         arguments: {}
205 |     expect:
206 |       response:
207 |         jsonrpc: "2.0"
208 |         id: "test-11"
209 |         result:
210 |           content:
211 |             - type: "text"
212 |               text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
213 |           isError: false
214 |       stderr: "toBeEmpty"
215 | 
216 |   - it: "should have core with count 5"
217 |     request:
218 |       jsonrpc: "2.0"
219 |       id: "test-12"
220 |       method: "tools/call"
221 |       params:
222 |         name: "get_sfra_categories"
223 |         arguments: {}
224 |     expect:
225 |       response:
226 |         jsonrpc: "2.0"
227 |         id: "test-12"
228 |         result:
229 |           content:
230 |             - type: "text"
231 |               text: "match:contains:\"count\": 5"
232 |           isError: false
233 |       stderr: "toBeEmpty"
234 | 
235 |   - it: "should have customer with count 2"
236 |     request:
237 |       jsonrpc: "2.0"
238 |       id: "test-13"
239 |       method: "tools/call"
240 |       params:
241 |         name: "get_sfra_categories"
242 |         arguments: {}
243 |     expect:
244 |       response:
245 |         jsonrpc: "2.0"
246 |         id: "test-13"
247 |         result:
248 |           content:
249 |             - type: "text"
250 |               text: "match:contains:\"count\": 2"
251 |           isError: false
252 |       stderr: "toBeEmpty"
253 | 
254 |   - it: "should have order with count 6"
255 |     request:
256 |       jsonrpc: "2.0"
257 |       id: "test-14"
258 |       method: "tools/call"
259 |       params:
260 |         name: "get_sfra_categories"
261 |         arguments: {}
262 |     expect:
263 |       response:
264 |         jsonrpc: "2.0"
265 |         id: "test-14"
266 |         result:
267 |           content:
268 |             - type: "text"
269 |               text: "match:contains:\"count\": 6"
270 |           isError: false
271 |       stderr: "toBeEmpty"
272 | 
273 |   # JSON structure validation tests
274 |   - it: "should return valid JSON structure with proper formatting"
275 |     request:
276 |       jsonrpc: "2.0"
277 |       id: "test-15"
278 |       method: "tools/call"
279 |       params:
280 |         name: "get_sfra_categories"
281 |         arguments: {}
282 |     expect:
283 |       response:
284 |         jsonrpc: "2.0"
285 |         id: "test-15"
286 |         result:
287 |           content:
288 |             - type: "text"
289 |               text: "match:regex:^\\[[\\s\\S]*\\{[\\s\\S]*\"category\"[\\s\\S]*\"count\"[\\s\\S]*\"description\"[\\s\\S]*\\}[\\s\\S]*\\]$"
290 |           isError: false
291 |       stderr: "toBeEmpty"
292 | 
293 |   # Error handling and edge case tests
294 |   - it: "should handle empty parameters gracefully"
295 |     request:
296 |       jsonrpc: "2.0"
297 |       id: "test-16"
298 |       method: "tools/call"
299 |       params:
300 |         name: "get_sfra_categories"
301 |         arguments: {}
302 |     expect:
303 |       response:
304 |         jsonrpc: "2.0"
305 |         id: "test-16"
306 |         result:
307 |           content:
308 |             - type: "text"
309 |               text: "match:contains:core"
310 |           isError: false
311 |       stderr: "toBeEmpty"
312 | 
313 |   - it: "should ignore invalid parameters and return normal result"
314 |     request:
315 |       jsonrpc: "2.0"
316 |       id: "test-17"
317 |       method: "tools/call"
318 |       params:
319 |         name: "get_sfra_categories"
320 |         arguments:
321 |           invalid: "param"
322 |           another: "value"
323 |     expect:
324 |       response:
325 |         jsonrpc: "2.0"
326 |         id: "test-17"
327 |         result:
328 |           content:
329 |             - type: "text"
330 |               text: "match:contains:\"category\": \"core\""
331 |           isError: false
332 |       stderr: "toBeEmpty"
333 | 
334 |   # Performance tests
335 |   - it: "should respond quickly for metadata operation"
336 |     request:
337 |       jsonrpc: "2.0"
338 |       id: "test-18"
339 |       method: "tools/call"
340 |       params:
341 |         name: "get_sfra_categories"
342 |         arguments: {}
343 |     expect:
344 |       response:
345 |         jsonrpc: "2.0"
346 |         id: "test-18"
347 |         result:
348 |           content:
349 |             - type: "text"
350 |               text: "match:type:string"
351 |           isError: false
352 |       performance:
353 |         maxResponseTime: "500ms"
354 |       stderr: "toBeEmpty"
355 | 
356 |   # Content validation patterns
357 |   - it: "should have consistent category naming patterns"
358 |     request:
359 |       jsonrpc: "2.0"
360 |       id: "test-19"
361 |       method: "tools/call"
362 |       params:
363 |         name: "get_sfra_categories"
364 |         arguments: {}
365 |     expect:
366 |       response:
367 |         jsonrpc: "2.0"
368 |         id: "test-19"
369 |         result:
370 |           content:
371 |             - type: "text"
372 |               text: "match:regex:\"category\":[\\s]*\"[a-z]+\""
373 |           isError: false
374 |       stderr: "toBeEmpty"
375 | 
376 |   - it: "should have numeric count values"
377 |     request:
378 |       jsonrpc: "2.0"
379 |       id: "test-20"
380 |       method: "tools/call"
381 |       params:
382 |         name: "get_sfra_categories"
383 |         arguments: {}
384 |     expect:
385 |       response:
386 |         jsonrpc: "2.0"
387 |         id: "test-20"
388 |         result:
389 |           content:
390 |             - type: "text"
391 |               text: "match:regex:\"count\":[\\s]*[0-9]+"
392 |           isError: false
393 |       stderr: "toBeEmpty"
394 | 
395 |   - it: "should have descriptive text for all categories"
396 |     request:
397 |       jsonrpc: "2.0"
398 |       id: "test-21"
399 |       method: "tools/call"
400 |       params:
401 |         name: "get_sfra_categories"
402 |         arguments: {}
403 |     expect:
404 |       response:
405 |         jsonrpc: "2.0"
406 |         id: "test-21"
407 |         result:
408 |           content:
409 |             - type: "text"
410 |               text: "match:regex:\"description\":[\\s]*\"[A-Z][\\s\\S]*\""
411 |           isError: false
412 |       stderr: "toBeEmpty"
413 | 
414 |   # Category completeness test - adjusted order to match actual response order
415 |   - it: "should include all expected SFRA category names in order"
416 |     request:
417 |       jsonrpc: "2.0"
418 |       id: "test-22"
419 |       method: "tools/call"
420 |       params:
421 |         name: "get_sfra_categories"
422 |         arguments: {}
423 |     expect:
424 |       response:
425 |         jsonrpc: "2.0"
426 |         id: "test-22"
427 |         result:
428 |           content:
429 |             - type: "text"
430 |               text: "match:regex:[\\s\\S]*core[\\s\\S]*customer[\\s\\S]*order[\\s\\S]*other[\\s\\S]*pricing[\\s\\S]*product[\\s\\S]*store[\\s\\S]*"
431 |           isError: false
432 |       stderr: "toBeEmpty"
433 | 
434 |   # Robustness tests
435 |   - it: "should return consistent results across multiple calls"
436 |     request:
437 |       jsonrpc: "2.0"
438 |       id: "test-23"
439 |       method: "tools/call"
440 |       params:
441 |         name: "get_sfra_categories"
442 |         arguments: {}
443 |     expect:
444 |       response:
445 |         jsonrpc: "2.0"
446 |         id: "test-23"
447 |         result:
448 |           content:
449 |             - type: "text"
450 |               text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
451 |           isError: false
452 |       stderr: "toBeEmpty"
453 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get-sfra-categories.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | description: "Comprehensive tests for get_sfra_categories tool in full-mode mode"
  2 | 
  3 | tests:
  4 |   # Basic functionality tests
  5 |   - it: "should return all SFRA category information with proper structure"
  6 |     request:
  7 |       jsonrpc: "2.0" 
  8 |       id: "test-1"
  9 |       method: "tools/call"
 10 |       params:
 11 |         name: "get_sfra_categories"
 12 |         arguments: {}
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "test-1"
 17 |         result:
 18 |           content:
 19 |             - type: "text"
 20 |               text: "match:type:string"
 21 |           isError: false
 22 |       stderr: "toBeEmpty"
 23 | 
 24 |   - it: "should return JSON array of category objects"
 25 |     request:
 26 |       jsonrpc: "2.0"
 27 |       id: "test-2"
 28 |       method: "tools/call"
 29 |       params:
 30 |         name: "get_sfra_categories"
 31 |         arguments: {}
 32 |     expect:
 33 |       response:
 34 |         jsonrpc: "2.0"
 35 |         id: "test-2"
 36 |         result:
 37 |           content:
 38 |             - type: "text"
 39 |               text: "match:regex:^\\[[\\s\\S]*\\]$"
 40 |           isError: false
 41 |       stderr: "toBeEmpty"
 42 | 
 43 |   - it: "should include all expected category fields"
 44 |     request:
 45 |       jsonrpc: "2.0"
 46 |       id: "test-3"
 47 |       method: "tools/call"
 48 |       params:
 49 |         name: "get_sfra_categories"
 50 |         arguments: {}
 51 |     expect:
 52 |       response:
 53 |         jsonrpc: "2.0"
 54 |         id: "test-3"
 55 |         result:
 56 |           content:
 57 |             - type: "text"
 58 |               text: "match:regex:[\\s\\S]*category[\\s\\S]*count[\\s\\S]*description[\\s\\S]*"
 59 |           isError: false
 60 |       stderr: "toBeEmpty"
 61 | 
 62 |   # Specific category validation tests
 63 |   - it: "should include core category with proper information"
 64 |     request:
 65 |       jsonrpc: "2.0"
 66 |       id: "test-4"
 67 |       method: "tools/call"
 68 |       params:
 69 |         name: "get_sfra_categories"
 70 |         arguments: {}
 71 |     expect:
 72 |       response:
 73 |         jsonrpc: "2.0"
 74 |         id: "test-4"
 75 |         result:
 76 |           content:
 77 |             - type: "text"
 78 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"core\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Core SFRA classes[\\s\\S]*"
 79 |           isError: false
 80 |       stderr: "toBeEmpty"
 81 | 
 82 |   - it: "should include customer category with proper information"
 83 |     request:
 84 |       jsonrpc: "2.0"
 85 |       id: "test-5"
 86 |       method: "tools/call"
 87 |       params:
 88 |         name: "get_sfra_categories"
 89 |         arguments: {}
 90 |     expect:
 91 |       response:
 92 |         jsonrpc: "2.0"
 93 |         id: "test-5"
 94 |         result:
 95 |           content:
 96 |             - type: "text"
 97 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"customer\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Customer account[\\s\\S]*"
 98 |           isError: false
 99 |       stderr: "toBeEmpty"
100 | 
101 |   - it: "should include order category with proper information"
102 |     request:
103 |       jsonrpc: "2.0"
104 |       id: "test-6"
105 |       method: "tools/call"
106 |       params:
107 |         name: "get_sfra_categories"
108 |         arguments: {}
109 |     expect:
110 |       response:
111 |         jsonrpc: "2.0"
112 |         id: "test-6"
113 |         result:
114 |           content:
115 |             - type: "text"
116 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"order\"[\\s\\S]*\"count\":[\\s]*6[\\s\\S]*Order, cart, billing[\\s\\S]*"
117 |           isError: false
118 |       stderr: "toBeEmpty"
119 | 
120 |   - it: "should include product category with proper information"
121 |     request:
122 |       jsonrpc: "2.0"
123 |       id: "test-7"
124 |       method: "tools/call"
125 |       params:
126 |         name: "get_sfra_categories"
127 |         arguments: {}
128 |     expect:
129 |       response:
130 |         jsonrpc: "2.0"
131 |         id: "test-7"
132 |         result:
133 |           content:
134 |             - type: "text"
135 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"product\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Product-related[\\s\\S]*"
136 |           isError: false
137 |       stderr: "toBeEmpty"
138 | 
139 |   - it: "should include pricing category with proper information"
140 |     request:
141 |       jsonrpc: "2.0"
142 |       id: "test-8"
143 |       method: "tools/call"
144 |       params:
145 |         name: "get_sfra_categories"
146 |         arguments: {}
147 |     expect:
148 |       response:
149 |         jsonrpc: "2.0"
150 |         id: "test-8"
151 |         result:
152 |           content:
153 |             - type: "text"
154 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"pricing\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Pricing and discount[\\s\\S]*"
155 |           isError: false
156 |       stderr: "toBeEmpty"
157 | 
158 |   - it: "should include store category with proper information"
159 |     request:
160 |       jsonrpc: "2.0"
161 |       id: "test-9"
162 |       method: "tools/call"
163 |       params:
164 |         name: "get_sfra_categories"
165 |         arguments: {}
166 |     expect:
167 |       response:
168 |         jsonrpc: "2.0"
169 |         id: "test-9"
170 |         result:
171 |           content:
172 |             - type: "text"
173 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"store\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Store and location[\\s\\S]*"
174 |           isError: false
175 |       stderr: "toBeEmpty"
176 | 
177 |   - it: "should include other category with proper information"
178 |     request:
179 |       jsonrpc: "2.0"
180 |       id: "test-10"
181 |       method: "tools/call"
182 |       params:
183 |         name: "get_sfra_categories"
184 |         arguments: {}
185 |     expect:
186 |       response:
187 |         jsonrpc: "2.0"
188 |         id: "test-10"
189 |         result:
190 |           content:
191 |             - type: "text"
192 |               text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"other\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Other models and utilities[\\s\\S]*"
193 |           isError: false
194 |       stderr: "toBeEmpty"
195 | 
196 |   # Count validation tests
197 |   - it: "should return exactly 7 categories"
198 |     request:
199 |       jsonrpc: "2.0"
200 |       id: "test-11"
201 |       method: "tools/call"
202 |       params:
203 |         name: "get_sfra_categories"
204 |         arguments: {}
205 |     expect:
206 |       response:
207 |         jsonrpc: "2.0"
208 |         id: "test-11"
209 |         result:
210 |           content:
211 |             - type: "text"
212 |               text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
213 |           isError: false
214 |       stderr: "toBeEmpty"
215 | 
216 |   - it: "should have core with count 5"
217 |     request:
218 |       jsonrpc: "2.0"
219 |       id: "test-12"
220 |       method: "tools/call"
221 |       params:
222 |         name: "get_sfra_categories"
223 |         arguments: {}
224 |     expect:
225 |       response:
226 |         jsonrpc: "2.0"
227 |         id: "test-12"
228 |         result:
229 |           content:
230 |             - type: "text"
231 |               text: "match:contains:\"count\": 5"
232 |           isError: false
233 |       stderr: "toBeEmpty"
234 | 
235 |   - it: "should have customer with count 2"
236 |     request:
237 |       jsonrpc: "2.0"
238 |       id: "test-13"
239 |       method: "tools/call"
240 |       params:
241 |         name: "get_sfra_categories"
242 |         arguments: {}
243 |     expect:
244 |       response:
245 |         jsonrpc: "2.0"
246 |         id: "test-13"
247 |         result:
248 |           content:
249 |             - type: "text"
250 |               text: "match:contains:\"count\": 2"
251 |           isError: false
252 |       stderr: "toBeEmpty"
253 | 
254 |   - it: "should have order with count 6"
255 |     request:
256 |       jsonrpc: "2.0"
257 |       id: "test-14"
258 |       method: "tools/call"
259 |       params:
260 |         name: "get_sfra_categories"
261 |         arguments: {}
262 |     expect:
263 |       response:
264 |         jsonrpc: "2.0"
265 |         id: "test-14"
266 |         result:
267 |           content:
268 |             - type: "text"
269 |               text: "match:contains:\"count\": 6"
270 |           isError: false
271 |       stderr: "toBeEmpty"
272 | 
273 |   # JSON structure validation tests
274 |   - it: "should return valid JSON structure with proper formatting"
275 |     request:
276 |       jsonrpc: "2.0"
277 |       id: "test-15"
278 |       method: "tools/call"
279 |       params:
280 |         name: "get_sfra_categories"
281 |         arguments: {}
282 |     expect:
283 |       response:
284 |         jsonrpc: "2.0"
285 |         id: "test-15"
286 |         result:
287 |           content:
288 |             - type: "text"
289 |               text: "match:regex:^\\[[\\s\\S]*\\{[\\s\\S]*\"category\"[\\s\\S]*\"count\"[\\s\\S]*\"description\"[\\s\\S]*\\}[\\s\\S]*\\]$"
290 |           isError: false
291 |       stderr: "toBeEmpty"
292 | 
293 |   # Error handling and edge case tests
294 |   - it: "should handle empty parameters gracefully"
295 |     request:
296 |       jsonrpc: "2.0"
297 |       id: "test-16"
298 |       method: "tools/call"
299 |       params:
300 |         name: "get_sfra_categories"
301 |         arguments: {}
302 |     expect:
303 |       response:
304 |         jsonrpc: "2.0"
305 |         id: "test-16"
306 |         result:
307 |           content:
308 |             - type: "text"
309 |               text: "match:contains:core"
310 |           isError: false
311 |       stderr: "toBeEmpty"
312 | 
313 |   - it: "should ignore invalid parameters and return normal result"
314 |     request:
315 |       jsonrpc: "2.0"
316 |       id: "test-17"
317 |       method: "tools/call"
318 |       params:
319 |         name: "get_sfra_categories"
320 |         arguments:
321 |           invalid: "param"
322 |           another: "value"
323 |     expect:
324 |       response:
325 |         jsonrpc: "2.0"
326 |         id: "test-17"
327 |         result:
328 |           content:
329 |             - type: "text"
330 |               text: "match:contains:\"category\": \"core\""
331 |           isError: false
332 |       stderr: "toBeEmpty"
333 | 
334 |   # Performance tests
335 |   - it: "should respond quickly for metadata operation"
336 |     request:
337 |       jsonrpc: "2.0"
338 |       id: "test-18"
339 |       method: "tools/call"
340 |       params:
341 |         name: "get_sfra_categories"
342 |         arguments: {}
343 |     expect:
344 |       response:
345 |         jsonrpc: "2.0"
346 |         id: "test-18"
347 |         result:
348 |           content:
349 |             - type: "text"
350 |               text: "match:type:string"
351 |           isError: false
352 |       performance:
353 |         maxResponseTime: "500ms"
354 |       stderr: "toBeEmpty"
355 | 
356 |   # Content validation patterns
357 |   - it: "should have consistent category naming patterns"
358 |     request:
359 |       jsonrpc: "2.0"
360 |       id: "test-19"
361 |       method: "tools/call"
362 |       params:
363 |         name: "get_sfra_categories"
364 |         arguments: {}
365 |     expect:
366 |       response:
367 |         jsonrpc: "2.0"
368 |         id: "test-19"
369 |         result:
370 |           content:
371 |             - type: "text"
372 |               text: "match:regex:\"category\":[\\s]*\"[a-z]+\""
373 |           isError: false
374 |       stderr: "toBeEmpty"
375 | 
376 |   - it: "should have numeric count values"
377 |     request:
378 |       jsonrpc: "2.0"
379 |       id: "test-20"
380 |       method: "tools/call"
381 |       params:
382 |         name: "get_sfra_categories"
383 |         arguments: {}
384 |     expect:
385 |       response:
386 |         jsonrpc: "2.0"
387 |         id: "test-20"
388 |         result:
389 |           content:
390 |             - type: "text"
391 |               text: "match:regex:\"count\":[\\s]*[0-9]+"
392 |           isError: false
393 |       stderr: "toBeEmpty"
394 | 
395 |   - it: "should have descriptive text for all categories"
396 |     request:
397 |       jsonrpc: "2.0"
398 |       id: "test-21"
399 |       method: "tools/call"
400 |       params:
401 |         name: "get_sfra_categories"
402 |         arguments: {}
403 |     expect:
404 |       response:
405 |         jsonrpc: "2.0"
406 |         id: "test-21"
407 |         result:
408 |           content:
409 |             - type: "text"
410 |               text: "match:regex:\"description\":[\\s]*\"[A-Z][\\s\\S]*\""
411 |           isError: false
412 |       stderr: "toBeEmpty"
413 | 
414 |   # Category completeness test - adjusted order to match actual response order
415 |   - it: "should include all expected SFRA category names in order"
416 |     request:
417 |       jsonrpc: "2.0"
418 |       id: "test-22"
419 |       method: "tools/call"
420 |       params:
421 |         name: "get_sfra_categories"
422 |         arguments: {}
423 |     expect:
424 |       response:
425 |         jsonrpc: "2.0"
426 |         id: "test-22"
427 |         result:
428 |           content:
429 |             - type: "text"
430 |               text: "match:regex:[\\s\\S]*core[\\s\\S]*customer[\\s\\S]*order[\\s\\S]*other[\\s\\S]*pricing[\\s\\S]*product[\\s\\S]*store[\\s\\S]*"
431 |           isError: false
432 |       stderr: "toBeEmpty"
433 | 
434 |   # Robustness tests
435 |   - it: "should return consistent results across multiple calls"
436 |     request:
437 |       jsonrpc: "2.0"
438 |       id: "test-23"
439 |       method: "tools/call"
440 |       params:
441 |         name: "get_sfra_categories"
442 |         arguments: {}
443 |     expect:
444 |       response:
445 |         jsonrpc: "2.0"
446 |         id: "test-23"
447 |         result:
448 |           content:
449 |             - type: "text"
450 |               text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
451 |           isError: false
452 |       stderr: "toBeEmpty"
453 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/tools.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # ==================================================================================
  2 | # SFCC MCP Server - Full Mode Comprehensive Validation Tests
  3 | # Validates full server mode with WebDAV/OCAPI credentials for complete functionality
  4 | # Focuses on tool metadata validation (NOT tool execution due to test credentials)
  5 | # 
  6 | # Quick Test Commands:
  7 | # aegis "tests/mcp/yaml/full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose
  8 | # aegis "tests/mcp/yaml/full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --debug --timing
  9 | # aegis query --config "aegis.config.with-dw.json"  # List all tools
 10 | # aegis query get_latest_error '{"limit": 5}' --config "aegis.config.with-dw.json"
 11 | # ==================================================================================
 12 | description: "SFCC MCP Server - Full Mode comprehensive metadata validation and tool presence verification"
 13 | 
 14 | # ==================================================================================
 15 | # TOOL COUNT & AVAILABILITY VALIDATION
 16 | # ==================================================================================
 17 | tests:
 18 |   - it: "should have exactly 36 tools available in full mode"
 19 |     request:
 20 |       jsonrpc: "2.0"
 21 |       id: "full-tools-count"
 22 |       method: "tools/list"
 23 |       params: {}
 24 |     expect:
 25 |       response:
 26 |         jsonrpc: "2.0"
 27 |         id: "full-tools-count"
 28 |         result:
 29 |           tools: "match:arrayLength:36"
 30 |       stderr: "toBeEmpty"
 31 | 
 32 |   - it: "should have significantly more tools than docs-only mode"
 33 |     request:
 34 |       jsonrpc: "2.0"
 35 |       id: "full-vs-docs-count"
 36 |       method: "tools/list"
 37 |       params: {}
 38 |     expect:
 39 |       response:
 40 |         jsonrpc: "2.0"
 41 |         id: "full-vs-docs-count"
 42 |         result:
 43 |           tools: "match:arrayLength:36"  # More than docs-only 15 tools
 44 |       stderr: "toBeEmpty"
 45 | 
 46 |   - it: "should have proper tool structure with required fields"
 47 |     request:
 48 |       jsonrpc: "2.0"
 49 |       id: "full-tool-structure"
 50 |       method: "tools/list"
 51 |       params: {}
 52 |     expect:
 53 |       response:
 54 |         jsonrpc: "2.0"
 55 |         id: "full-tool-structure"
 56 |         result:
 57 |           tools:
 58 |             match:arrayElements:
 59 |               match:partial:
 60 |                 name: "match:type:string"
 61 |                 description: "match:type:string"
 62 |                 inputSchema: "match:type:object"
 63 |       stderr: "toBeEmpty"
 64 | 
 65 |   # ==================================================================================
 66 |   # TOOL METADATA QUALITY VALIDATION
 67 |   # ==================================================================================
 68 | 
 69 |   - it: "should have tool names following snake_case convention"
 70 |     request:
 71 |       jsonrpc: "2.0"
 72 |       id: "full-naming-convention"
 73 |       method: "tools/list"
 74 |       params: {}
 75 |     expect:
 76 |       response:
 77 |         jsonrpc: "2.0"
 78 |         id: "full-naming-convention"
 79 |         result:
 80 |           tools:
 81 |             match:arrayElements:
 82 |               match:partial:
 83 |                 name: "match:regex:^[a-z][a-z0-9_]*$"
 84 |       stderr: "toBeEmpty"
 85 | 
 86 |   - it: "should have meaningful tool descriptions"
 87 |     request:
 88 |       jsonrpc: "2.0"
 89 |       id: "full-description-quality"
 90 |       method: "tools/list"
 91 |       params: {}
 92 |     expect:
 93 |       response:
 94 |         jsonrpc: "2.0"
 95 |         id: "full-description-quality"
 96 |         result:
 97 |           tools:
 98 |             match:arrayElements:
 99 |               match:partial:
100 |                 description: "match:regex:.{20,}"  # At least 20 characters
101 |       stderr: "toBeEmpty"
102 | 
103 |   - it: "should have non-empty tool descriptions"
104 |     request:
105 |       jsonrpc: "2.0"
106 |       id: "full-description-nonempty"
107 |       method: "tools/list"
108 |       params: {}
109 |     expect:
110 |       response:
111 |         jsonrpc: "2.0"
112 |         id: "full-description-nonempty"
113 |         result:
114 |           tools:
115 |             match:arrayElements:
116 |               match:partial:
117 |                 description: "match:not:regex:^\\s*$"  # Not empty or whitespace-only
118 |       stderr: "toBeEmpty"
119 | 
120 |   - it: "should have proper inputSchema structure"
121 |     request:
122 |       jsonrpc: "2.0"
123 |       id: "full-schema-structure"
124 |       method: "tools/list"
125 |       params: {}
126 |     expect:
127 |       response:
128 |         jsonrpc: "2.0"
129 |         id: "full-schema-structure"
130 |         result:
131 |           tools:
132 |             match:arrayElements:
133 |               match:partial:
134 |                 inputSchema:
135 |                   type: "object"
136 |                   properties: "match:type:object"
137 |       stderr: "toBeEmpty"
138 | 
139 |   # ==================================================================================
140 |   # TOOL CATEGORY VALIDATION - Documentation Tools (from docs-only mode)
141 |   # ==================================================================================
142 | 
143 |   - it: "should have documentation tools available"
144 |     request:
145 |       jsonrpc: "2.0"
146 |       id: "full-tools-list-2"
147 |       method: "tools/list"
148 |       params: {}
149 |     expect:
150 |       response:
151 |         jsonrpc: "2.0"
152 |         id: "full-tools-list-2"
153 |         result:
154 |           match:extractField: "tools.*.name"
155 |           value: "match:arrayContains:get_sfcc_class_info"
156 |       stderr: "toBeEmpty"
157 | 
158 |   - it: "should include all docs-only tools in full mode"
159 |     request:
160 |       jsonrpc: "2.0"
161 |       id: "full-includes-docs"
162 |       method: "tools/list"
163 |       params: {}
164 |     expect:
165 |       response:
166 |         jsonrpc: "2.0"
167 |         id: "full-includes-docs"
168 |         result:
169 |           match:extractField: "tools.*.name"
170 |           value: "match:arrayContains:search_sfra_documentation"
171 |       stderr: "toBeEmpty"
172 | 
173 |   - it: "should have cartridge generation tools available"
174 |     request:
175 |       jsonrpc: "2.0"
176 |       id: "full-tools-cartridge"
177 |       method: "tools/list"
178 |       params: {}
179 |     expect:
180 |       response:
181 |         jsonrpc: "2.0"
182 |         id: "full-tools-cartridge"
183 |         result:
184 |           match:extractField: "tools.*.name"
185 |           value: "match:arrayContains:generate_cartridge_structure"
186 |       stderr: "toBeEmpty"
187 | 
188 |   # ==================================================================================
189 |   # FULL-MODE SPECIFIC TOOLS - WebDAV Dependent (Log Analysis)
190 |   # ==================================================================================
191 | 
192 |   - it: "should have log analysis tools available in full mode"
193 |     request:
194 |       jsonrpc: "2.0"
195 |       id: "full-tools-logs"
196 |       method: "tools/list"
197 |       params: {}
198 |     expect:
199 |       response:
200 |         jsonrpc: "2.0"
201 |         id: "full-tools-logs"
202 |         result:
203 |           match:extractField: "tools.*.name"
204 |           value: "match:arrayContains:get_latest_error"
205 |       stderr: "toBeEmpty"
206 | 
207 |   - it: "should have WebDAV-dependent tools (log analysis)"
208 |     request:
209 |       jsonrpc: "2.0"
210 |       id: "webdav-tools"
211 |       method: "tools/list"
212 |       params: {}
213 |     expect:
214 |       response:
215 |         jsonrpc: "2.0"
216 |         id: "webdav-tools"
217 |         result:
218 |           match:extractField: "tools.*.name"
219 |           value: "match:arrayContains:summarize_logs"
220 |       stderr: "toBeEmpty"
221 | 
222 |   - it: "should have job log tools available in full mode"
223 |     request:
224 |       jsonrpc: "2.0"
225 |       id: "full-tools-jobs"
226 |       method: "tools/list"
227 |       params: {}
228 |     expect:
229 |       response:
230 |         jsonrpc: "2.0"
231 |         id: "full-tools-jobs"
232 |         result:
233 |           match:extractField: "tools.*.name"
234 |           value: "match:arrayContains:get_job_log_entries"
235 |       stderr: "toBeEmpty"
236 | 
237 |   - it: "should have log search and analysis capabilities"
238 |     request:
239 |       jsonrpc: "2.0"
240 |       id: "log-search-tools"
241 |       method: "tools/list"
242 |       params: {}
243 |     expect:
244 |       response:
245 |         jsonrpc: "2.0"
246 |         id: "log-search-tools"
247 |         result:
248 |           match:extractField: "tools.*.name"
249 |           value: "match:arrayContains:search_logs"
250 |       stderr: "toBeEmpty"
251 | 
252 |   # ==================================================================================
253 |   # FULL-MODE SPECIFIC TOOLS - OCAPI Dependent (System Objects & Site Preferences)
254 |   # ==================================================================================
255 | 
256 |   - it: "should have system object tools available in full mode"
257 |     request:
258 |       jsonrpc: "2.0"
259 |       id: "full-tools-system"
260 |       method: "tools/list"
261 |       params: {}
262 |     expect:
263 |       response:
264 |         jsonrpc: "2.0"
265 |         id: "full-tools-system"
266 |         result:
267 |           match:extractField: "tools.*.name"
268 |           value: "match:arrayContains:get_system_object_definitions"
269 |       stderr: "toBeEmpty"
270 | 
271 |   - it: "should have OCAPI-dependent tools (system objects)"
272 |     request:
273 |       jsonrpc: "2.0"
274 |       id: "ocapi-tools"
275 |       method: "tools/list"
276 |       params: {}
277 |     expect:
278 |       response:
279 |         jsonrpc: "2.0"
280 |         id: "ocapi-tools"
281 |         result:
282 |           match:extractField: "tools.*.name"
283 |           value: "match:arrayContains:search_site_preferences"
284 |       stderr: "toBeEmpty"
285 | 
286 |   - it: "should have site preferences search capabilities"
287 |     request:
288 |       jsonrpc: "2.0"
289 |       id: "site-prefs-tools"
290 |       method: "tools/list"
291 |       params: {}
292 |     expect:
293 |       response:
294 |         jsonrpc: "2.0"
295 |         id: "site-prefs-tools"
296 |         result:
297 |           match:extractField: "tools.*.name"
298 |           value: "match:arrayContains:search_system_object_attribute_groups"
299 |       stderr: "toBeEmpty"
300 | 
301 |   # ==================================================================================
302 |   # FULL-MODE SPECIFIC TOOLS - Code Version Management
303 |   # ==================================================================================
304 | 
305 |   - it: "should have code version tools available in full mode"
306 |     request:
307 |       jsonrpc: "2.0"
308 |       id: "full-tools-code"
309 |       method: "tools/list"
310 |       params: {}
311 |     expect:
312 |       response:
313 |         jsonrpc: "2.0"
314 |         id: "full-tools-code"
315 |         result:
316 |           match:extractField: "tools.*.name"
317 |           value: "match:arrayContains:get_code_versions"
318 |       stderr: "toBeEmpty"
319 | 
320 |   - it: "should have code version activation capabilities"
321 |     request:
322 |       jsonrpc: "2.0"
323 |       id: "code-activation-tools"
324 |       method: "tools/list"
325 |       params: {}
326 |     expect:
327 |       response:
328 |         jsonrpc: "2.0"
329 |         id: "code-activation-tools"
330 |         result:
331 |           match:extractField: "tools.*.name"
332 |           value: "match:arrayContains:activate_code_version"
333 |       stderr: "toBeEmpty"
334 | 
335 |   # ==================================================================================
336 |   # COMPREHENSIVE TOOL VALIDATION - Combined Patterns
337 |   # ==================================================================================
338 | 
339 |   - it: "should validate all tools have consistent metadata structure"
340 |     request:
341 |       jsonrpc: "2.0"
342 |       id: "full-comprehensive-validation"
343 |       method: "tools/list"
344 |       params: {}
345 |     expect:
346 |       response:
347 |         jsonrpc: "2.0"
348 |         id: "full-comprehensive-validation"
349 |         result:
350 |           tools:
351 |             match:arrayElements:
352 |               match:partial:
353 |                 name: "match:regex:^[a-z][a-z0-9_]*$"  # snake_case names
354 |                 description: "match:regex:.{10,}"       # min 10 chars
355 |                 inputSchema:
356 |                   type: "object"
357 |                   properties: "match:type:object"
358 |       stderr: "toBeEmpty"
359 | 
360 |   - it: "should have tools categorized by functionality"
361 |     request:
362 |       jsonrpc: "2.0"
363 |       id: "tool-categorization"
364 |       method: "tools/list"
365 |       params: {}
366 |     expect:
367 |       response:
368 |         jsonrpc: "2.0"
369 |         id: "tool-categorization"
370 |         result:
371 |           tools: "match:not:arrayLength:0"  # Not empty
372 |       stderr: "toBeEmpty"
373 | 
374 |   # NOTE: We intentionally do NOT test tool execution in full mode because:
375 |   # 1. The test credentials are not real SFCC instances
376 |   # 2. Tools would fail with authentication/connection errors
377 |   # 3. This test suite is designed to verify tool PRESENCE, not functionality
378 |   # 4. Tool functionality testing should be done against real SFCC development instances
379 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get_latest_error.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | ---
  2 | description: "Test get_latest_error tool in full mode"
  3 | tests:
  4 |   # Basic functionality tests
  5 |   - it: "should retrieve latest error messages with default parameters"
  6 |     request:
  7 |       jsonrpc: "2.0"
  8 |       id: "error-default"
  9 |       method: "tools/call"
 10 |       params:
 11 |         name: "get_latest_error"
 12 |         arguments: {}
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "error-default"
 17 |         result:
 18 |           content:
 19 |             match:arrayElements:
 20 |               type: "text"
 21 |               text: "match:type:string"
 22 |           isError: false
 23 |       stderr: "toBeEmpty"
 24 |     performance:
 25 |       maxResponseTime: "2000ms"
 26 | 
 27 |   - it: "should limit error messages when limit parameter is provided"
 28 |     request:
 29 |       jsonrpc: "2.0"
 30 |       id: "error-limit"
 31 |       method: "tools/call"
 32 |       params:
 33 |         name: "get_latest_error"
 34 |         arguments:
 35 |           limit: 3
 36 |     expect:
 37 |       response:
 38 |         jsonrpc: "2.0"
 39 |         id: "error-limit"
 40 |         result:
 41 |           content:
 42 |             match:arrayElements:
 43 |               type: "text"
 44 |               text: "match:contains:Latest 3 error messages"
 45 |           isError: false
 46 |       stderr: "toBeEmpty"
 47 |     performance:
 48 |       maxResponseTime: "2000ms"
 49 | 
 50 |   - it: "should retrieve the latest 2 error messages"
 51 |     request:
 52 |       jsonrpc: "2.0"
 53 |       id: "error-date"
 54 |       method: "tools/call"
 55 |       params:
 56 |         name: "get_latest_error"
 57 |         arguments:
 58 |           limit: 2
 59 |     expect:
 60 |       response:
 61 |         jsonrpc: "2.0"
 62 |         id: "error-date"
 63 |         result:
 64 |           content:
 65 |             match:arrayElements:
 66 |               type: "text"
 67 |               text: "match:contains:Latest 2 error messages"
 68 |           isError: false
 69 |       stderr: "toBeEmpty"
 70 |     performance:
 71 |       maxResponseTime: "2000ms"
 72 | 
 73 |   # Content validation tests
 74 |   - it: "should include log file name in response"
 75 |     request:
 76 |       jsonrpc: "2.0"
 77 |       id: "error-filename"
 78 |       method: "tools/call"
 79 |       params:
 80 |         name: "get_latest_error"
 81 |         arguments:
 82 |           limit: 1
 83 |     expect:
 84 |       response:
 85 |         jsonrpc: "2.0"
 86 |         id: "error-filename"
 87 |         result:
 88 |           content:
 89 |             match:arrayElements:
 90 |               match:partial:
 91 |                 text: "match:regex:error-blade-[\\d]{8}-[\\d]{6}\\.log"
 92 |           isError: false
 93 |       stderr: "toBeEmpty"
 94 | 
 95 |   - it: "should include ERROR level log entries"
 96 |     request:
 97 |       jsonrpc: "2.0"
 98 |       id: "error-level"
 99 |       method: "tools/call"
100 |       params:
101 |         name: "get_latest_error"
102 |         arguments:
103 |           limit: 5
104 |     expect:
105 |       response:
106 |         jsonrpc: "2.0"
107 |         id: "error-level"
108 |         result:
109 |           content:
110 |             match:arrayElements:
111 |               match:partial:
112 |                 text: "match:contains:ERROR"
113 |           isError: false
114 |       stderr: "toBeEmpty"
115 | 
116 |   - it: "should include timestamps in GMT format"
117 |     request:
118 |       jsonrpc: "2.0"
119 |       id: "error-timestamp"
120 |       method: "tools/call"
121 |       params:
122 |         name: "get_latest_error"
123 |         arguments:
124 |           limit: 2
125 |     expect:
126 |       response:
127 |         jsonrpc: "2.0"
128 |         id: "error-timestamp"
129 |         result:
130 |           content:
131 |             match:arrayElements:
132 |               match:partial:
133 |                 text: "match:regex:[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:[\\d]{2}:[\\d]{2}\\.[\\d]{3} GMT"
134 |           isError: false
135 |       stderr: "toBeEmpty"
136 | 
137 |   - it: "should separate multiple error entries with separators"
138 |     request:
139 |       jsonrpc: "2.0"
140 |       id: "error-separators"
141 |       method: "tools/call"
142 |       params:
143 |         name: "get_latest_error"
144 |         arguments:
145 |           limit: 3
146 |     expect:
147 |       response:
148 |         jsonrpc: "2.0"
149 |         id: "error-separators"
150 |         result:
151 |           content:
152 |             match:arrayElements:
153 |               match:partial:
154 |                 text: "match:contains:---"
155 |           isError: false
156 |       stderr: "toBeEmpty"
157 | 
158 |   # Common SFCC error patterns validation
159 |   - it: "should contain realistic SFCC error scenarios"
160 |     request:
161 |       jsonrpc: "2.0"
162 |       id: "error-patterns"
163 |       method: "tools/call"
164 |       params:
165 |         name: "get_latest_error"
166 |         arguments:
167 |           limit: 5
168 |     expect:
169 |       response:
170 |         jsonrpc: "2.0"
171 |         id: "error-patterns"
172 |         result:
173 |           content:
174 |             match:arrayElements:
175 |               match:partial:
176 |                 text: "match:regex:(?:PipelineCallServlet|SystemJobThread)"
177 |           isError: false
178 |       stderr: "toBeEmpty"
179 | 
180 |   - it: "should include SFCC Sites and thread information"
181 |     request:
182 |       jsonrpc: "2.0"
183 |       id: "error-sites"
184 |       method: "tools/call"
185 |       params:
186 |         name: "get_latest_error"
187 |         arguments:
188 |           limit: 3
189 |     expect:
190 |       response:
191 |         jsonrpc: "2.0"
192 |         id: "error-sites"
193 |         result:
194 |           content:
195 |             match:arrayElements:
196 |               match:partial:
197 |                 text: "match:contains:Sites-"
198 |           isError: false
199 |       stderr: "toBeEmpty"
200 | 
201 |   # Parameter handling tests
202 |   - it: "should handle string limit parameter gracefully"
203 |     request:
204 |       jsonrpc: "2.0"
205 |       id: "error-string-limit"
206 |       method: "tools/call"
207 |       params:
208 |         name: "get_latest_error"
209 |         arguments:
210 |           limit: "5"
211 |     expect:
212 |       response:
213 |         jsonrpc: "2.0"
214 |         id: "error-string-limit"
215 |         result:
216 |           content:
217 |             match:arrayElements:
218 |               type: "text"
219 |               text: "match:contains:Latest 5 error messages"
220 |           isError: false
221 |       stderr: "toBeEmpty"
222 | 
223 |   - it: "should handle large limit values"
224 |     request:
225 |       jsonrpc: "2.0"
226 |       id: "error-large-limit"
227 |       method: "tools/call"
228 |       params:
229 |         name: "get_latest_error"
230 |         arguments:
231 |           limit: 50
232 |     expect:
233 |       response:
234 |         jsonrpc: "2.0"
235 |         id: "error-large-limit"
236 |         result:
237 |           content:
238 |             match:arrayElements:
239 |               type: "text"
240 |               text: "match:contains:Latest 50 error messages"
241 |           isError: false
242 |       stderr: "toBeEmpty"
243 |     performance:
244 |       maxResponseTime: "3000ms"
245 | 
246 |   - it: "should handle zero limit parameter with error response"
247 |     request:
248 |       jsonrpc: "2.0"
249 |       id: "error-zero-limit"
250 |       method: "tools/call"
251 |       params:
252 |         name: "get_latest_error"
253 |         arguments:
254 |           limit: 0
255 |     expect:
256 |       response:
257 |         jsonrpc: "2.0"
258 |         id: "error-zero-limit"
259 |         result:
260 |           content:
261 |             match:arrayElements:
262 |               match:partial:
263 |                 text: "match:contains:Invalid limit '0' for get_latest_error"
264 |           isError: true
265 |       stderr: "toBeEmpty"
266 | 
267 |   # Date format validation
268 |   - it: "should handle valid YYYYMMDD date format"
269 |     request:
270 |       jsonrpc: "2.0"
271 |       id: "error-valid-date"
272 |       method: "tools/call"
273 |       params:
274 |         name: "get_latest_error"
275 |         arguments:
276 |           date: "20240101"
277 |           limit: 1
278 |     expect:
279 |       response:
280 |         jsonrpc: "2.0"
281 |         id: "error-valid-date"
282 |         result:
283 |           content:
284 |             match:arrayElements:
285 |               type: "text"
286 |               text: "match:type:string"
287 |           isError: false
288 |       stderr: "toBeEmpty"
289 | 
290 |   - it: "should handle future dates gracefully"
291 |     request:
292 |       jsonrpc: "2.0"
293 |       id: "error-future-date"
294 |       method: "tools/call"
295 |       params:
296 |         name: "get_latest_error"
297 |         arguments:
298 |           date: "20251231"
299 |           limit: 1
300 |     expect:
301 |       response:
302 |         jsonrpc: "2.0"
303 |         id: "error-future-date"
304 |         result:
305 |           content:
306 |             match:arrayElements:
307 |               type: "text"
308 |               text: "match:type:string"
309 |           isError: false
310 |       stderr: "toBeEmpty"
311 | 
312 |   # Performance tests
313 |   - it: "should respond quickly for small limits"
314 |     request:
315 |       jsonrpc: "2.0"
316 |       id: "error-perf-small"
317 |       method: "tools/call"
318 |       params:
319 |         name: "get_latest_error"
320 |         arguments:
321 |           limit: 1
322 |     expect:
323 |       response:
324 |         jsonrpc: "2.0"
325 |         id: "error-perf-small"
326 |         result:
327 |           content: "match:type:array"
328 |           isError: false
329 |       stderr: "toBeEmpty"
330 |     performance:
331 |       maxResponseTime: "1000ms"
332 | 
333 |   - it: "should maintain performance with default parameters"
334 |     request:
335 |       jsonrpc: "2.0"
336 |       id: "error-perf-default"
337 |       method: "tools/call"
338 |       params:
339 |         name: "get_latest_error"
340 |         arguments: {}
341 |     expect:
342 |       response:
343 |         jsonrpc: "2.0"
344 |         id: "error-perf-default"
345 |         result:
346 |           content: "match:type:array"
347 |           isError: false
348 |       stderr: "toBeEmpty"
349 |     performance:
350 |       maxResponseTime: "1500ms"
351 | 
352 |   # Edge cases and robustness
353 | 
354 |   - it: "should return consistent structure regardless of parameters"
355 |     request:
356 |       jsonrpc: "2.0"
357 |       id: "error-structure"
358 |       method: "tools/call"
359 |       params:
360 |         name: "get_latest_error"
361 |         arguments:
362 |           limit: 1
363 |     expect:
364 |       response:
365 |         jsonrpc: "2.0"
366 |         id: "error-structure"
367 |         result:
368 |           match:partial:
369 |             content: "match:type:array"
370 |             isError: "match:type:boolean"
371 |       stderr: "toBeEmpty"
372 | 
373 |   # Comprehensive content validation
374 |   - it: "should include comprehensive error information with multiple patterns"
375 |     request:
376 |       jsonrpc: "2.0"
377 |       id: "error-comprehensive"
378 |       method: "tools/call"
379 |       params:
380 |         name: "get_latest_error"
381 |         arguments:
382 |           limit: 3
383 |     expect:
384 |       response:
385 |         jsonrpc: "2.0"
386 |         id: "error-comprehensive"
387 |         result:
388 |           content:
389 |             match:arrayElements:
390 |               match:partial:
391 |                 type: "text"
392 |                 text: "match:regex:[\\s\\S]*(?:Custom cartridge error|Product import failed|Customer profile creation failed|Payment authorization failed|AWS S3 Configuration Issue)[\\s\\S]*"
393 |           isError: false
394 |       stderr: "toBeEmpty"
395 | 
396 |   # Additional edge cases
397 |   - it: "should handle negative limit parameter with error response"
398 |     request:
399 |       jsonrpc: "2.0"
400 |       id: "error-negative-limit"
401 |       method: "tools/call"
402 |       params:
403 |         name: "get_latest_error"
404 |         arguments:
405 |           limit: -5
406 |     expect:
407 |       response:
408 |         jsonrpc: "2.0"
409 |         id: "error-negative-limit"
410 |         result:
411 |           content:
412 |             match:arrayElements:
413 |               match:partial:
414 |                 text: "match:contains:Invalid limit"
415 |           isError: true
416 |       stderr: "toBeEmpty"
417 | 
418 |   - it: "should handle extremely large limit parameter gracefully"
419 |     request:
420 |       jsonrpc: "2.0"
421 |       id: "error-huge-limit"
422 |       method: "tools/call"
423 |       params:
424 |         name: "get_latest_error"
425 |         arguments:
426 |           limit: 9999
427 |     expect:
428 |       response:
429 |         jsonrpc: "2.0"
430 |         id: "error-huge-limit"
431 |         result:
432 |           content:
433 |             match:arrayElements:
434 |               match:partial:
435 |                 text: "match:contains:Invalid limit"
436 |           isError: true
437 |       stderr: "toBeEmpty"
438 | 
439 |   - it: "should handle invalid date format gracefully"
440 |     request:
441 |       jsonrpc: "2.0"
442 |       id: "error-invalid-date"
443 |       method: "tools/call"
444 |       params:
445 |         name: "get_latest_error"
446 |         arguments:
447 |           date: "2024-01-01"
448 |           limit: 1
449 |     expect:
450 |       response:
451 |         jsonrpc: "2.0"
452 |         id: "error-invalid-date"
453 |         result:
454 |           content: "match:type:array"
455 |           isError: "match:type:boolean"
456 |       stderr: "toBeEmpty"
457 | 
458 |   - it: "should handle missing arguments object gracefully"
459 |     request:
460 |       jsonrpc: "2.0"
461 |       id: "error-no-args"
462 |       method: "tools/call"
463 |       params:
464 |         name: "get_latest_error"
465 |     expect:
466 |       response:
467 |         jsonrpc: "2.0"
468 |         id: "error-no-args"
469 |         result:
470 |           content: "match:type:array"
471 |           isError: false
472 |       stderr: "toBeEmpty"
473 | 
```

--------------------------------------------------------------------------------
/src/clients/best-practices-client.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * SFCC Best Practices Client
  3 |  *
  4 |  * Provides access to SFCC development best practices documentation including
  5 |  * cartridge creation, ISML templates, job framework, LocalServiceRegistry,
  6 |  * OCAPI hooks, SCAPI hooks, SCAPI custom endpoints, SFRA controllers, and SFRA models.
  7 |  */
  8 | 
  9 | import * as fs from 'fs/promises';
 10 | import * as path from 'path';
 11 | import { PathResolver } from '../utils/path-resolver.js';
 12 | import { CacheManager } from '../utils/cache.js';
 13 | import { Logger } from '../utils/logger.js';
 14 | 
 15 | export interface BestPracticeGuide {
 16 |   title: string;
 17 |   description: string;
 18 |   sections: string[];
 19 |   content: string;
 20 | }
 21 | 
 22 | /**
 23 |  * Client for accessing SFCC best practices documentation
 24 |  */
 25 | export class SFCCBestPracticesClient {
 26 |   private cache: CacheManager;
 27 |   private docsPath: string;
 28 |   private logger: Logger;
 29 | 
 30 |   constructor() {
 31 |     this.cache = new CacheManager();
 32 |     this.docsPath = PathResolver.getBestPracticesPath();
 33 |     this.logger = Logger.getChildLogger('BestPracticesClient');
 34 |   }
 35 | 
 36 |   /**
 37 |    * Get all available best practice guides
 38 |    */
 39 |   async getAvailableGuides(): Promise<Array<{name: string; title: string; description: string}>> {
 40 |     const cacheKey = 'best-practices:available-guides';
 41 |     const cached = this.cache.getSearchResults(cacheKey);
 42 |     if (cached) {return cached;}
 43 | 
 44 |     const guides = [
 45 |       {
 46 |         name: 'cartridge_creation',
 47 |         title: 'Cartridge Creation Best Practices',
 48 |         description: 'Instructions and best practices for creating, configuring, and deploying custom SFRA cartridges',
 49 |       },
 50 |       {
 51 |         name: 'isml_templates',
 52 |         title: 'ISML Templates Best Practices',
 53 |         description: 'Comprehensive best practices for developing ISML templates within the SFRA framework, including security, performance, and maintainability guidelines',
 54 |       },
 55 |       {
 56 |         name: 'job_framework',
 57 |         title: 'Job Framework Best Practices',
 58 |         description: 'Comprehensive guide for developing custom jobs in the SFCC Job Framework, covering both task-oriented and chunk-oriented approaches with performance optimization and debugging strategies',
 59 |       },
 60 |       {
 61 |         name: 'localserviceregistry',
 62 |         title: 'LocalServiceRegistry Best Practices',
 63 |         description: 'Comprehensive guide for creating server-to-server integrations in SFCC using dw.svc.LocalServiceRegistry, including configuration patterns, callback implementation, OAuth flows, and reusable service module patterns',
 64 |       },
 65 |       {
 66 |         name: 'ocapi_hooks',
 67 |         title: 'OCAPI Hooks Best Practices',
 68 |         description: 'Best practices for implementing OCAPI hooks in Salesforce B2C Commerce Cloud',
 69 |       },
 70 |       {
 71 |         name: 'scapi_hooks',
 72 |         title: 'SCAPI Hooks Best Practices',
 73 |         description: 'Essential best practices for implementing SCAPI hooks with AI development assistance',
 74 |       },
 75 |       {
 76 |         name: 'scapi_custom_endpoint',
 77 |         title: 'Custom SCAPI Endpoint Best Practices',
 78 |         description: 'Best practices for creating custom SCAPI endpoints in B2C Commerce Cloud',
 79 |       },
 80 |       {
 81 |         name: 'sfra_controllers',
 82 |         title: 'SFRA Controllers Best Practices',
 83 |         description: 'Best practices and code patterns for developing SFRA controllers',
 84 |       },
 85 |       {
 86 |         name: 'sfra_models',
 87 |         title: 'SFRA Models Best Practices',
 88 |         description: 'Best practices for developing SFRA models in Salesforce B2C Commerce Cloud',
 89 |       },
 90 |       {
 91 |         name: 'sfra_client_side_js',
 92 |         title: 'SFRA Client-Side JavaScript Best Practices',
 93 |         description: 'Comprehensive patterns for architecting, extending, validating, and optimizing client-side JavaScript in SFRA storefronts using jQuery',
 94 |       },
 95 |       {
 96 |         name: 'sfra_scss',
 97 |         title: 'SFRA SCSS Best Practices',
 98 |         description: 'Implementation-focused SCSS override strategies for SFRA storefronts covering cartridge overlays, theming tokens, responsive mixins, and plugin extension guardrails',
 99 |       },
100 |       {
101 |         name: 'performance',
102 |         title: 'Performance and Stability Best Practices',
103 |         description: 'Comprehensive performance optimization strategies, coding standards, and stability guidelines for SFCC development including caching, index-friendly APIs, and job development',
104 |       },
105 |       {
106 |         name: 'security',
107 |         title: 'Security Best Practices',
108 |         description: 'Comprehensive security best practices for SFCC development covering SFRA Controllers, OCAPI/SCAPI Hooks, and Custom SCAPI Endpoints with OWASP compliance guidelines',
109 |       },
110 |     ];
111 | 
112 |     this.cache.setSearchResults(cacheKey, guides);
113 |     return guides;
114 |   }
115 | 
116 |   /**
117 |    * Get a specific best practice guide
118 |    */
119 |   async getBestPracticeGuide(guideName: string): Promise<BestPracticeGuide | null> {
120 |     const cacheKey = `best-practices:guide:${guideName}`;
121 |     const cached = this.cache.getFileContent(cacheKey);
122 |     if (cached) {return JSON.parse(cached);}
123 | 
124 |     try {
125 |       // Enhanced security validation - validate guideName before path construction
126 |       if (!guideName || typeof guideName !== 'string') {
127 |         throw new Error('Invalid guide name: must be a non-empty string');
128 |       }
129 | 
130 |       // Prevent null bytes and dangerous characters in the guide name itself
131 |       if (guideName.includes('\0') || guideName.includes('\x00')) {
132 |         throw new Error('Invalid guide name: contains null bytes');
133 |       }
134 | 
135 |       // Prevent path traversal sequences in the guide name
136 |       if (guideName.includes('..') || guideName.includes('/') || guideName.includes('\\')) {
137 |         throw new Error('Invalid guide name: contains path traversal sequences');
138 |       }
139 | 
140 |       // Only allow alphanumeric characters, underscores, and hyphens
141 |       if (!/^[a-zA-Z0-9_-]+$/.test(guideName)) {
142 |         throw new Error('Invalid guide name: contains invalid characters');
143 |       }
144 | 
145 |       const filePath = path.join(this.docsPath, `${guideName}.md`);
146 | 
147 |       // Additional security validation - ensure the resolved path is within the docs directory
148 |       const resolvedPath = path.resolve(filePath);
149 |       const resolvedDocsPath = path.resolve(this.docsPath);
150 | 
151 |       if (!resolvedPath.startsWith(resolvedDocsPath)) {
152 |         throw new Error('Invalid guide name: path outside allowed directory');
153 |       }
154 | 
155 |       // Ensure the file still ends with .md after path resolution
156 |       if (!resolvedPath.toLowerCase().endsWith('.md')) {
157 |         throw new Error('Invalid guide name: must reference a markdown file');
158 |       }
159 | 
160 |       const content = await fs.readFile(resolvedPath, 'utf-8');
161 | 
162 |       // Basic content validation
163 |       if (!content.trim()) {
164 |         throw new Error(`Empty best practice guide: ${guideName}`);
165 |       }
166 | 
167 |       // Check for binary content
168 |       if (content.includes('\0')) {
169 |         throw new Error(`Invalid content in best practice guide: ${guideName}`);
170 |       }
171 | 
172 |       const lines = content.split('\n');
173 |       const title = lines.find(line => line.startsWith('#'))?.replace('#', '').trim() ?? guideName;
174 | 
175 |       // Extract sections (## headers)
176 |       const sections = lines
177 |         .filter(line => line.startsWith('##'))
178 |         .map(line => line.replace('##', '').trim());
179 | 
180 |       // Extract description (first paragraph after title)
181 |       const descriptionStart = lines.findIndex(line => line.startsWith('#')) + 1;
182 |       const descriptionEnd = lines.findIndex((line, index) =>
183 |         index > descriptionStart && (line.startsWith('#') || line.trim() === ''));
184 |       const description = lines
185 |         .slice(descriptionStart, descriptionEnd > -1 ? descriptionEnd : descriptionStart + 3)
186 |         .join(' ')
187 |         .trim();
188 | 
189 |       const guide: BestPracticeGuide = {
190 |         title,
191 |         description,
192 |         sections,
193 |         content,
194 |       };
195 | 
196 |       this.cache.setFileContent(cacheKey, JSON.stringify(guide));
197 |       return guide;
198 |     } catch (error) {
199 |       this.logger.error(`Error reading best practice guide ${guideName}:`, error);
200 |       return null;
201 |     }
202 |   }
203 | 
204 |   /**
205 |    * Search across all best practices for specific terms
206 |    */
207 |   async searchBestPractices(query: string): Promise<Array<{
208 |     guide: string;
209 |     title: string;
210 |     matches: Array<{section: string; content: string}>;
211 |   }>> {
212 |     const cacheKey = `best-practices:search:${query.toLowerCase()}`;
213 |     const cached = this.cache.getSearchResults(cacheKey);
214 |     if (cached) {return cached;}
215 | 
216 |     const guides = await this.getAvailableGuides();
217 |     const results = [];
218 | 
219 |     for (const guide of guides) {
220 |       const guideContent = await this.getBestPracticeGuide(guide.name);
221 |       if (!guideContent) {continue;}
222 | 
223 |       const matches = [];
224 |       const lines = guideContent.content.split('\n');
225 |       let currentSection = '';
226 | 
227 |       for (let i = 0; i < lines.length; i++) {
228 |         const line = lines[i];
229 | 
230 |         if (line.startsWith('##')) {
231 |           currentSection = line.replace('##', '').trim();
232 |         }
233 | 
234 |         if (line.toLowerCase().includes(query.toLowerCase())) {
235 |           // Get context around the match
236 |           const start = Math.max(0, i - 2);
237 |           const end = Math.min(lines.length, i + 3);
238 |           const context = lines.slice(start, end).join('\n');
239 | 
240 |           matches.push({
241 |             section: currentSection || 'Introduction',
242 |             content: context,
243 |           });
244 |         }
245 |       }
246 | 
247 |       if (matches.length > 0) {
248 |         results.push({
249 |           guide: guide.name,
250 |           title: guide.title,
251 |           matches,
252 |         });
253 |       }
254 |     }
255 | 
256 |     this.cache.setSearchResults(cacheKey, results);
257 |     return results;
258 |   }
259 | 
260 |   /**
261 |    * Get hook reference tables for OCAPI/SCAPI hooks
262 |    */
263 |   async getHookReference(guideName: string): Promise<Array<{
264 |     category: string;
265 |     hooks: Array<{endpoint: string; hookPoints: string[]; signature?: string}>;
266 |   }>> {
267 |     if (!guideName.includes('hooks')) {return [];}
268 | 
269 |     const cacheKey = `best-practices:hook-reference:${guideName}`;
270 |     const cached = this.cache.getSearchResults(cacheKey);
271 |     if (cached) {return cached;}
272 | 
273 |     const guide = await this.getBestPracticeGuide(guideName);
274 |     if (!guide) {return [];}
275 | 
276 |     const reference = [];
277 |     const lines = guide.content.split('\n');
278 |     let currentCategory = '';
279 |     let inTable = false;
280 |     let hooks: Array<{endpoint: string; hookPoints: string[]; signature?: string}> = [];
281 | 
282 |     for (const line of lines) {
283 |       // Look for hook reference sections
284 |       if (line.match(/^###?\s+(Shop API Hooks|Data API Hooks|Shopper.*Hooks|.*API Hooks)/i)) {
285 |         if (currentCategory && hooks.length > 0) {
286 |           reference.push({ category: currentCategory, hooks: [...hooks] });
287 |         }
288 |         currentCategory = line.replace(/^#+\s*/, '');
289 |         hooks = [];
290 |         inTable = false;
291 |       }
292 | 
293 |       // Detect table headers
294 |       if (line.includes('API Endpoint') && line.includes('Hook')) {
295 |         inTable = true;
296 |         continue;
297 |       }
298 | 
299 |       // Skip separator line
300 |       if (line.match(/^\|[\s\-|]+\|$/)) {
301 |         continue;
302 |       }
303 | 
304 |       // Parse table rows
305 |       if (inTable && line.startsWith('|') && !line.includes('**')) {
306 |         const parts = line.split('|').map(p => p.trim()).filter(p => p);
307 |         if (parts.length >= 2) {
308 |           const endpoint = parts[0].replace(/`/g, '');
309 |           const hookPoints = parts[1].split(',').map(h => h.replace(/`/g, '').trim());
310 |           const signature = parts[2] ? parts[2].replace(/`/g, '') : undefined;
311 | 
312 |           if (endpoint && hookPoints.length > 0) {
313 |             hooks.push({ endpoint, hookPoints, signature });
314 |           }
315 |         }
316 |       }
317 | 
318 |       // End table when we hit a new section
319 |       if (inTable && line.startsWith('#')) {
320 |         inTable = false;
321 |       }
322 |     }
323 | 
324 |     // Add last category
325 |     if (currentCategory && hooks.length > 0) {
326 |       reference.push({ category: currentCategory, hooks });
327 |     }
328 | 
329 |     this.cache.setSearchResults(cacheKey, reference);
330 |     return reference;
331 |   }
332 | }
333 | 
```

--------------------------------------------------------------------------------
/tests/mcp/node/search-job-logs-by-name.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('search_job_logs_by_name - Optimized Programmatic Tests', () => {
  6 |   let client;
  7 |   let discoveredJobNames = [];
  8 | 
  9 |   before(async () => {
 10 |     client = await connect('./aegis.config.with-dw.json');
 11 |     
 12 |     // Discover available job names for dynamic testing
 13 |     await discoverJobNames();
 14 |   });
 15 | 
 16 |   after(async () => {
 17 |     if (client?.connected) {
 18 |       await client.disconnect();
 19 |     }
 20 |   });
 21 | 
 22 |   beforeEach(() => {
 23 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 24 |     client.clearAllBuffers();
 25 |   });
 26 | 
 27 |   // Simplified helper functions focused on essential validation
 28 |   function assertValidMCPResponse(result) {
 29 |     assert.ok(result.content, 'Should have content');
 30 |     assert.ok(Array.isArray(result.content), 'Content should be array');
 31 |     assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
 32 |   }
 33 | 
 34 |   function parseResponseText(text) {
 35 |     return text.startsWith('"') && text.endsWith('"') ? JSON.parse(text) : text;
 36 |   }
 37 | 
 38 |   function assertSuccessResponse(result) {
 39 |     assertValidMCPResponse(result);
 40 |     assert.equal(result.isError, false, 'Should not be an error response');
 41 |     assert.equal(result.content[0].type, 'text');
 42 |   }
 43 | 
 44 |   function assertErrorResponse(result, expectedErrorText) {
 45 |     assertValidMCPResponse(result);
 46 |     assert.equal(result.isError, true, 'Should be an error response');
 47 |     if (expectedErrorText) {
 48 |       const text = parseResponseText(result.content[0].text);
 49 |       assert.ok(text.includes(expectedErrorText), 
 50 |         `Expected "${expectedErrorText}" in "${text}"`);
 51 |     }
 52 |   }
 53 | 
 54 |   function assertJobSearchResults(result) {
 55 |     assertSuccessResponse(result);
 56 |     const text = parseResponseText(result.content[0].text);
 57 |     
 58 |     if (text.includes('No job logs found')) {
 59 |       return 0; // Valid empty result
 60 |     }
 61 |     
 62 |     // Extract count and validate basic structure
 63 |     const countMatch = text.match(/Found (\d+) job logs:/);
 64 |     assert.ok(countMatch, 'Should contain job count message');
 65 |     
 66 |     const actualCount = parseInt(countMatch[1]);
 67 |     assert.ok(actualCount > 0, 'Should have positive count when jobs found');
 68 |     
 69 |     // Validate key components are present
 70 |     assert.ok(text.includes('🔧 Job:'), 'Should contain job emoji');
 71 |     assert.ok(text.includes('ID:'), 'Should contain job ID');
 72 |     assert.ok(text.includes('File:'), 'Should contain file name');
 73 |     assert.ok(text.includes('Modified:'), 'Should contain modification time');
 74 |     assert.ok(text.includes('Size:'), 'Should contain file size');
 75 |     
 76 |     return actualCount;
 77 |   }
 78 | 
 79 |   async function discoverJobNames() {
 80 |     try {
 81 |       const result = await client.callTool('search_job_logs_by_name', { 
 82 |         jobName: 'Import', // Use common search term to find jobs
 83 |         limit: 5
 84 |       });
 85 |       
 86 |       if (!result.isError && result.content?.[0]?.text) {
 87 |         const text = parseResponseText(result.content[0].text);
 88 |         if (!text.includes('No job logs found')) {
 89 |           // Extract job names from the response
 90 |           const jobMatches = text.match(/🔧 Job: ([A-Za-z0-9]+)/g) || [];
 91 |           discoveredJobNames = [...new Set(jobMatches.map(match => 
 92 |             match.replace('🔧 Job: ', '')))];
 93 |         }
 94 |       }
 95 |     } catch (error) {
 96 |       console.warn('Could not discover job names:', error.message);
 97 |     }
 98 |   }
 99 | 
100 |   // Core functionality tests
101 |   describe('Core Functionality', () => {
102 |     test('should search for job logs and return structured results', async () => {
103 |       const result = await client.callTool('search_job_logs_by_name', { 
104 |         jobName: 'Import' 
105 |       });
106 |       
107 |       const count = assertJobSearchResults(result);
108 |       
109 |       if (count > 0) {
110 |         const text = parseResponseText(result.content[0].text);
111 |         // Verify partial matching works (job names containing "Import")
112 |         const jobMatches = text.match(/🔧 Job: ([A-Za-z0-9]+)/g) || [];
113 |         const containsImport = jobMatches.some(match => 
114 |           match.toLowerCase().includes('import'));
115 |         assert.ok(containsImport, 'Should find jobs containing "Import"');
116 |       }
117 |     });
118 | 
119 |     test('should respect limit parameter and handle various values', async () => {
120 |       const testCases = [
121 |         { limit: 1, desc: 'single result' },
122 |         { limit: 3, desc: 'multiple results' },
123 |         { limit: 1000, desc: 'large limit' }
124 |       ];
125 |       
126 |       for (const { limit, desc } of testCases) {
127 |         const result = await client.callTool('search_job_logs_by_name', { 
128 |           jobName: 'Import',
129 |           limit 
130 |         });
131 |         
132 |         assertSuccessResponse(result);
133 |         const text = parseResponseText(result.content[0].text);
134 |         
135 |         if (!text.includes('No job logs found')) {
136 |           const countMatch = text.match(/Found (\d+) job logs:/);
137 |           if (countMatch) {
138 |             const actualCount = parseInt(countMatch[1]);
139 |             assert.ok(actualCount <= limit, 
140 |               `${desc}: actual count ${actualCount} should not exceed limit ${limit}`);
141 |           }
142 |         }
143 |       }
144 |     });
145 | 
146 |     test('should return no results for non-existent job name', async () => {
147 |       const result = await client.callTool('search_job_logs_by_name', { 
148 |         jobName: 'NonExistentJobXYZ123'
149 |       });
150 |       
151 |       assertSuccessResponse(result);
152 |       const text = parseResponseText(result.content[0].text);
153 |       assert.ok(text.includes('No job logs found'), 'Should indicate no results found');
154 |     });
155 |   });
156 | 
157 |   // Parameter validation tests
158 |   describe('Parameter Validation', () => {
159 |     test('should reject empty job name', async () => {
160 |       const result = await client.callTool('search_job_logs_by_name', { 
161 |         jobName: '' 
162 |       });
163 |       
164 |       assertErrorResponse(result, 'jobName must be a non-empty string');
165 |     });
166 | 
167 |     test('should reject missing jobName parameter', async () => {
168 |       const result = await client.callTool('search_job_logs_by_name', {});
169 |       
170 |       assertErrorResponse(result, 'jobName must be a non-empty string');
171 |     });
172 | 
173 |     test('should handle invalid limit parameter', async () => {
174 |       const invalidLimits = [-1, 0, 'invalid'];
175 |       
176 |       for (const limit of invalidLimits) {
177 |         const result = await client.callTool('search_job_logs_by_name', { 
178 |           jobName: 'Import',
179 |           limit 
180 |         });
181 |         
182 |         assertErrorResponse(result); // Should be an error, don't need to check exact message
183 |       }
184 |     });
185 |   });
186 | 
187 |   // Dynamic discovery tests
188 |   describe('Dynamic Discovery Tests', () => {
189 |     test('should handle discovered job names effectively', async () => {
190 |       if (discoveredJobNames.length === 0) {
191 |         console.log('⚠️ Skipping dynamic discovery test - no job names found');
192 |         return;
193 |       }
194 | 
195 |       // Test with discovered job names
196 |       const testJobName = discoveredJobNames[0];
197 |       const result = await client.callTool('search_job_logs_by_name', { 
198 |         jobName: testJobName,
199 |         limit: 2
200 |       });
201 |       
202 |       const count = assertJobSearchResults(result);
203 |       assert.ok(count >= 0, 'Should return valid count for discovered job name');
204 |     });
205 | 
206 |     test('should support case-insensitive search with discovered names', async () => {
207 |       if (discoveredJobNames.length === 0) {
208 |         console.log('⚠️ Skipping case-insensitive test - no job names discovered');
209 |         return;
210 |       }
211 |       
212 |       const testJobName = discoveredJobNames[0];
213 |       const variations = [
214 |         testJobName.toLowerCase(),
215 |         testJobName.substring(0, 3).toLowerCase()
216 |       ];
217 |       
218 |       for (const variation of variations) {
219 |         const result = await client.callTool('search_job_logs_by_name', { 
220 |           jobName: variation
221 |         });
222 |         
223 |         assertSuccessResponse(result);
224 |         // Should find results or return no results message
225 |         const text = parseResponseText(result.content[0].text);
226 |         assert.ok(text.includes('Found') || text.includes('No job logs found'),
227 |           `Should return valid response for case variation "${variation}"`);
228 |       }
229 |     });
230 |   });
231 | 
232 |   // Multi-step workflow tests
233 |   describe('Multi-Step Workflows', () => {
234 |     test('should support workflow: discover -> search -> validate', async () => {
235 |       // Step 1: Search with broad term to discover available jobs
236 |       const discoveryResult = await client.callTool('search_job_logs_by_name', { 
237 |         jobName: 'Job',
238 |         limit: 5
239 |       });
240 |       
241 |       assertSuccessResponse(discoveryResult);
242 |       
243 |       const discoveryText = parseResponseText(discoveryResult.content[0].text);
244 |       if (!discoveryText.includes('No job logs found')) {
245 |         // Step 2: Extract a specific job name for targeted search
246 |         const jobMatch = discoveryText.match(/🔧 Job: ([A-Za-z0-9]+)/);
247 |         
248 |         if (jobMatch) {
249 |           const specificJobName = jobMatch[1];
250 |           
251 |           // Step 3: Search for that specific job
252 |           const specificResult = await client.callTool('search_job_logs_by_name', { 
253 |             jobName: specificJobName,
254 |             limit: 1
255 |           });
256 |           
257 |           const specificCount = assertJobSearchResults(specificResult);
258 |           assert.ok(specificCount >= 0, 'Specific search should return valid results');
259 |           
260 |           // Step 4: Validate the targeted search found the job
261 |           if (specificCount > 0) {
262 |             const specificText = parseResponseText(specificResult.content[0].text);
263 |             assert.ok(specificText.includes(specificJobName),
264 |               `Targeted search should include job name "${specificJobName}"`);
265 |           }
266 |         }
267 |       }
268 |     });
269 | 
270 |     test('should handle sequential searches consistently', async () => {
271 |       const searchTerms = ['Import', 'Job', 'Process'];
272 |       const results = [];
273 |       
274 |       // Perform sequential searches
275 |       for (const term of searchTerms) {
276 |         const result = await client.callTool('search_job_logs_by_name', { 
277 |           jobName: term,
278 |           limit: 2
279 |         });
280 |         
281 |         assertSuccessResponse(result);
282 |         results.push({ term, result });
283 |       }
284 |       
285 |       // Validate all searches completed successfully
286 |       assert.equal(results.length, searchTerms.length, 
287 |         'All sequential searches should complete');
288 |       
289 |       // Each result should be valid
290 |       for (const { term, result } of results) {
291 |         const text = parseResponseText(result.content[0].text);
292 |         assert.ok(text.includes('Found') || text.includes('No job logs found'),
293 |           `Search for "${term}" should return valid response format`);
294 |       }
295 |     });
296 |   });
297 | 
298 |   // Edge cases and error handling
299 |   describe('Edge Cases', () => {
300 |     test('should handle various edge case inputs', async () => {
301 |       const edgeCases = [
302 |         { jobName: 'a', desc: 'single character' },
303 |         { jobName: 'Job-With-Hyphens', desc: 'special characters' },
304 |         { jobName: '   Import   ', desc: 'whitespace padding' }
305 |       ];
306 |       
307 |       for (const { jobName, desc } of edgeCases) {
308 |         const result = await client.callTool('search_job_logs_by_name', { 
309 |           jobName,
310 |           limit: 1
311 |         });
312 |         
313 |         // Should not throw errors, should return valid response
314 |         assertValidMCPResponse(result);
315 |         
316 |         if (result.isError) {
317 |           // If it's an error, should be a validation error
318 |           const text = parseResponseText(result.content[0].text);
319 |           assert.ok(text.includes('Error'), 
320 |             `${desc}: Error response should contain "Error"`);
321 |         } else {
322 |           // If successful, should have valid format
323 |           const text = parseResponseText(result.content[0].text);
324 |           assert.ok(text.includes('Found') || text.includes('No job logs found'),
325 |             `${desc}: Should return valid search response`);
326 |         }
327 |       }
328 |     });
329 |   });
330 | });
```

--------------------------------------------------------------------------------
/docs/dw_net/Mail.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.net
  2 | 
  3 | # Class Mail
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.net.Mail
  9 | 
 10 | ## Description
 11 | 
 12 | This class is used to send an email with either plain text or MimeEncodedText content. Recipient data (from, to, cc, bcc) and subject are specified using setter methods. When the send() method is invoked, the email is put into an internal queue and sent asynchronously. Note: when this class is used with sensitive data, be careful in persisting sensitive information to disk. The following example script sends an email with MimeEncodedText content: function sendMail() { var template: Template = new dw.util.Template("myTemplate.isml"); var o: Map = new dw.util.HashMap(); o.put("customer","customer"); o.put("product","product"); var content: MimeEncodedText = template.render(o); var mail: Mail = new dw.net.Mail(); mail.addTo("[email protected]"); mail.setFrom("[email protected]"); mail.setSubject("Example Email"); mail.setContent(content); mail.send();//returns either Status.ERROR or Status.OK, mail might not be sent yet, when this method returns } See Sending email via scripts or hooks in the documentation for additional examples.
 13 | 
 14 | ## Properties
 15 | 
 16 | ### bcc
 17 | 
 18 | **Type:** List
 19 | 
 20 | Gets the bcc address List.
 21 | 
 22 | ### cc
 23 | 
 24 | **Type:** List
 25 | 
 26 | Gets the cc address List.
 27 | 
 28 | ### from
 29 | 
 30 | **Type:** String
 31 | 
 32 | Gets the email address to use as the from address for the
 33 |  email.
 34 | 
 35 | ### replyTo
 36 | 
 37 | **Type:** List (Read Only)
 38 | 
 39 | Gets the replyTo address List.
 40 | 
 41 | ### subject
 42 | 
 43 | **Type:** String
 44 | 
 45 | Gets the subject of the email.
 46 | 
 47 | ### to
 48 | 
 49 | **Type:** List
 50 | 
 51 | Gets the to address List where the email is sent.
 52 | 
 53 | ## Constructor Summary
 54 | 
 55 | Mail()
 56 | 
 57 | ## Method Summary
 58 | 
 59 | ### addAttachment
 60 | 
 61 | **Signature:** `addAttachment(file : File) : Mail`
 62 | 
 63 | Adds a file attachment to the email.
 64 | 
 65 | ### addBcc
 66 | 
 67 | **Signature:** `addBcc(bcc : String) : Mail`
 68 | 
 69 | Adds an address to the bcc List.
 70 | 
 71 | ### addCc
 72 | 
 73 | **Signature:** `addCc(cc : String) : Mail`
 74 | 
 75 | Adds an address to the cc List.
 76 | 
 77 | ### addReplyTo
 78 | 
 79 | **Signature:** `addReplyTo(replyTo : String) : Mail`
 80 | 
 81 | Adds an address to the replyTo List.
 82 | 
 83 | ### addTo
 84 | 
 85 | **Signature:** `addTo(to : String) : Mail`
 86 | 
 87 | Adds an address to the to address List.
 88 | 
 89 | ### getBcc
 90 | 
 91 | **Signature:** `getBcc() : List`
 92 | 
 93 | Gets the bcc address List.
 94 | 
 95 | ### getCc
 96 | 
 97 | **Signature:** `getCc() : List`
 98 | 
 99 | Gets the cc address List.
100 | 
101 | ### getFrom
102 | 
103 | **Signature:** `getFrom() : String`
104 | 
105 | Gets the email address to use as the from address for the email.
106 | 
107 | ### getReplyTo
108 | 
109 | **Signature:** `getReplyTo() : List`
110 | 
111 | Gets the replyTo address List.
112 | 
113 | ### getSubject
114 | 
115 | **Signature:** `getSubject() : String`
116 | 
117 | Gets the subject of the email.
118 | 
119 | ### getTo
120 | 
121 | **Signature:** `getTo() : List`
122 | 
123 | Gets the to address List where the email is sent.
124 | 
125 | ### send
126 | 
127 | **Signature:** `send() : Status`
128 | 
129 | prepares an email that is queued to the internal mail system for delivery.
130 | 
131 | ### setBcc
132 | 
133 | **Signature:** `setBcc(bcc : List) : Mail`
134 | 
135 | Sets the bcc address List.
136 | 
137 | ### setCc
138 | 
139 | **Signature:** `setCc(cc : List) : Mail`
140 | 
141 | Sets the cc address List where the email is sent.
142 | 
143 | ### setContent
144 | 
145 | **Signature:** `setContent(content : String) : Mail`
146 | 
147 | Mandatory Sets the email content.
148 | 
149 | ### setContent
150 | 
151 | **Signature:** `setContent(content : String, mimeType : String, encoding : String) : Mail`
152 | 
153 | Mandatory Sets the email content, MIME type, and encoding.
154 | 
155 | ### setContent
156 | 
157 | **Signature:** `setContent(mimeEncodedText : MimeEncodedText) : Mail`
158 | 
159 | Mandatory Uses MimeEncodedText to set the content, MIME type and encoding.
160 | 
161 | ### setFrom
162 | 
163 | **Signature:** `setFrom(from : String) : Mail`
164 | 
165 | Mandatory Sets the sender address for this email.
166 | 
167 | ### setListUnsubscribe
168 | 
169 | **Signature:** `setListUnsubscribe(listUnsubscribe : String) : Mail`
170 | 
171 | Sets the List-Unsubscribe header value to work with List-Unsubscribe-Post to allow integration with an externally-managed mailing list.
172 | 
173 | ### setListUnsubscribePost
174 | 
175 | **Signature:** `setListUnsubscribePost(listUnsubscribePost : String) : Mail`
176 | 
177 | Sets the List-Unsubscribe-Post header value.
178 | 
179 | ### setSubject
180 | 
181 | **Signature:** `setSubject(subject : String) : Mail`
182 | 
183 | Mandatory sets the subject for the email.
184 | 
185 | ### setTo
186 | 
187 | **Signature:** `setTo(to : List) : Mail`
188 | 
189 | Sets the to address List where the email is sent.
190 | 
191 | ### validateAddress
192 | 
193 | **Signature:** `static validateAddress(address : String) : boolean`
194 | 
195 | Validates the address that is sent as parameter.
196 | 
197 | ## Constructor Detail
198 | 
199 | ## Method Detail
200 | 
201 | ## Method Details
202 | 
203 | ### addAttachment
204 | 
205 | **Signature:** `addAttachment(file : File) : Mail`
206 | 
207 | **Description:** Adds a file attachment to the email. This method is restricted to Job context only.
208 | 
209 | **Parameters:**
210 | 
211 | - `file`: The file to be attached to the email. Must not be null and must exist.
212 | 
213 | **Returns:**
214 | 
215 | this Mail object
216 | 
217 | **Throws:**
218 | 
219 | IllegalArgumentException - if the file is null, doesn't exist, or is not a file
220 | 
221 | ---
222 | 
223 | ### addBcc
224 | 
225 | **Signature:** `addBcc(bcc : String) : Mail`
226 | 
227 | **Description:** Adds an address to the bcc List. Address must conform to the RFC822 standard.
228 | 
229 | **Parameters:**
230 | 
231 | - `bcc`: new bcc address to add to bcc address List.
232 | 
233 | **Returns:**
234 | 
235 | this Mail object.
236 | 
237 | ---
238 | 
239 | ### addCc
240 | 
241 | **Signature:** `addCc(cc : String) : Mail`
242 | 
243 | **Description:** Adds an address to the cc List. The address must conform to RFC822 standard.
244 | 
245 | **Parameters:**
246 | 
247 | - `cc`: new cc address to be added to cc address List.
248 | 
249 | **Returns:**
250 | 
251 | this Mail object.
252 | 
253 | ---
254 | 
255 | ### addReplyTo
256 | 
257 | **Signature:** `addReplyTo(replyTo : String) : Mail`
258 | 
259 | **Description:** Adds an address to the replyTo List. Address must conform to the RFC822 standard.
260 | 
261 | **Parameters:**
262 | 
263 | - `replyTo`: new replyTo address to add to replyTo address List.
264 | 
265 | **Returns:**
266 | 
267 | this Mail object.
268 | 
269 | **Throws:**
270 | 
271 | IllegalArgumentException - if the email address is invalid
272 | 
273 | ---
274 | 
275 | ### addTo
276 | 
277 | **Signature:** `addTo(to : String) : Mail`
278 | 
279 | **Description:** Adds an address to the to address List. The address must conform to the RFC822 standard.
280 | 
281 | **Parameters:**
282 | 
283 | - `to`: email address to add to the to address List.
284 | 
285 | **Returns:**
286 | 
287 | this Mail object.
288 | 
289 | ---
290 | 
291 | ### getBcc
292 | 
293 | **Signature:** `getBcc() : List`
294 | 
295 | **Description:** Gets the bcc address List.
296 | 
297 | **Returns:**
298 | 
299 | bcc address List or empty List if no bcc addresses are set.
300 | 
301 | ---
302 | 
303 | ### getCc
304 | 
305 | **Signature:** `getCc() : List`
306 | 
307 | **Description:** Gets the cc address List.
308 | 
309 | **Returns:**
310 | 
311 | cc address List or empty List if no cc addresses are set.
312 | 
313 | ---
314 | 
315 | ### getFrom
316 | 
317 | **Signature:** `getFrom() : String`
318 | 
319 | **Description:** Gets the email address to use as the from address for the email.
320 | 
321 | **Returns:**
322 | 
323 | the from address for this mail or null if no from address is set yet.
324 | 
325 | ---
326 | 
327 | ### getReplyTo
328 | 
329 | **Signature:** `getReplyTo() : List`
330 | 
331 | **Description:** Gets the replyTo address List.
332 | 
333 | **Returns:**
334 | 
335 | replyTo address List or empty List if no replyTo addresses are set.
336 | 
337 | ---
338 | 
339 | ### getSubject
340 | 
341 | **Signature:** `getSubject() : String`
342 | 
343 | **Description:** Gets the subject of the email.
344 | 
345 | **Returns:**
346 | 
347 | subject or null if no subject is set yet.
348 | 
349 | ---
350 | 
351 | ### getTo
352 | 
353 | **Signature:** `getTo() : List`
354 | 
355 | **Description:** Gets the to address List where the email is sent.
356 | 
357 | **Returns:**
358 | 
359 | to address List or empty List if no to addresses are set.
360 | 
361 | ---
362 | 
363 | ### send
364 | 
365 | **Signature:** `send() : Status`
366 | 
367 | **Description:** prepares an email that is queued to the internal mail system for delivery.
368 | 
369 | **Returns:**
370 | 
371 | Status which tells if the mail could be successfully queued ( Status.OK) or not ( Status.ERROR). If an error is raised, more information about the reason for the failure can be found within the log files. If the mandatory fields from, content, and subject are empty an IllegalArgumentException is raised. An IllegalArgumentException is raised if neither to, cc nor bcc are set.
372 | 
373 | ---
374 | 
375 | ### setBcc
376 | 
377 | **Signature:** `setBcc(bcc : List) : Mail`
378 | 
379 | **Description:** Sets the bcc address List. If there are already bcc addresses they are overwritten.
380 | 
381 | **Parameters:**
382 | 
383 | - `bcc`: list of Strings representing RFC822 compliant email addresses. List replaces any previously set list of addresses. Throws an exception if the given list is null.
384 | 
385 | **Returns:**
386 | 
387 | this Mail object.
388 | 
389 | ---
390 | 
391 | ### setCc
392 | 
393 | **Signature:** `setCc(cc : List) : Mail`
394 | 
395 | **Description:** Sets the cc address List where the email is sent. If there are already cc addresses set, they are overwritten. The address(es) must conform to the RFC822 standard.
396 | 
397 | **Parameters:**
398 | 
399 | - `cc`: List of Strings representing RFC822 compliant email addresses. This List replaces any previously set List of addresses. Throws an exception if the given List is null.
400 | 
401 | **Returns:**
402 | 
403 | this Mail object
404 | 
405 | ---
406 | 
407 | ### setContent
408 | 
409 | **Signature:** `setContent(content : String) : Mail`
410 | 
411 | **Description:** Mandatory Sets the email content. The MIME type is set to "text/plain;charset=UTF-8" and encoding set to "UTF-8".
412 | 
413 | **Parameters:**
414 | 
415 | - `content`: String containing the content of the email.
416 | 
417 | **Returns:**
418 | 
419 | this Mail object.
420 | 
421 | ---
422 | 
423 | ### setContent
424 | 
425 | **Signature:** `setContent(content : String, mimeType : String, encoding : String) : Mail`
426 | 
427 | **Description:** Mandatory Sets the email content, MIME type, and encoding. No validation of MIME type and encoding is done. It is the responsibility of the caller to specify a valid MIME type and encoding.
428 | 
429 | **Parameters:**
430 | 
431 | - `content`: String containing the content of the mail
432 | - `mimeType`: mime type of the content. For example "text/plain;charset=UTF-8" or "text/html"
433 | - `encoding`: character encoding of the email content. For example UTF-8-8
434 | 
435 | **Returns:**
436 | 
437 | this Mail object.
438 | 
439 | ---
440 | 
441 | ### setContent
442 | 
443 | **Signature:** `setContent(mimeEncodedText : MimeEncodedText) : Mail`
444 | 
445 | **Description:** Mandatory Uses MimeEncodedText to set the content, MIME type and encoding.
446 | 
447 | **Parameters:**
448 | 
449 | - `mimeEncodedText`: MimeEncodedText from which the content, MIME type, and encoding information is extracted.
450 | 
451 | **Returns:**
452 | 
453 | this Mail object.
454 | 
455 | ---
456 | 
457 | ### setFrom
458 | 
459 | **Signature:** `setFrom(from : String) : Mail`
460 | 
461 | **Description:** Mandatory Sets the sender address for this email. The address must conform to the RFC822 standard.
462 | 
463 | **Parameters:**
464 | 
465 | - `from`: String containing a RFC822 compliant email address
466 | 
467 | **Returns:**
468 | 
469 | this Mail object.
470 | 
471 | ---
472 | 
473 | ### setListUnsubscribe
474 | 
475 | **Signature:** `setListUnsubscribe(listUnsubscribe : String) : Mail`
476 | 
477 | **Description:** Sets the List-Unsubscribe header value to work with List-Unsubscribe-Post to allow integration with an externally-managed mailing list.
478 | 
479 | **Parameters:**
480 | 
481 | - `listUnsubscribe`: The List-Unsubscribe header value, e.g., "<https://example.com/unsubscribe>"
482 | 
483 | **Returns:**
484 | 
485 | this Mail object
486 | 
487 | ---
488 | 
489 | ### setListUnsubscribePost
490 | 
491 | **Signature:** `setListUnsubscribePost(listUnsubscribePost : String) : Mail`
492 | 
493 | **Description:** Sets the List-Unsubscribe-Post header value. This header supports one-click unsubscribe functionality.
494 | 
495 | **Parameters:**
496 | 
497 | - `listUnsubscribePost`: The List-Unsubscribe-Post header value, typically "List-Unsubscribe=One-Click"
498 | 
499 | **Returns:**
500 | 
501 | this Mail object
502 | 
503 | ---
504 | 
505 | ### setSubject
506 | 
507 | **Signature:** `setSubject(subject : String) : Mail`
508 | 
509 | **Description:** Mandatory sets the subject for the email. If the subject is not set or set to null at the time send() is invoked and IllegalArgumentException is thrown.
510 | 
511 | **Parameters:**
512 | 
513 | - `subject`: subject of the mail to send.
514 | 
515 | **Returns:**
516 | 
517 | this Mail object.
518 | 
519 | ---
520 | 
521 | ### setTo
522 | 
523 | **Signature:** `setTo(to : List) : Mail`
524 | 
525 | **Description:** Sets the to address List where the email is sent. If there are already to addresses, they are overwritten.
526 | 
527 | **Parameters:**
528 | 
529 | - `to`: list of Strings representing RFC822 compliant email addresses. List replaces any previously set List of addresses. Throws an exception if the given List is null.
530 | 
531 | **Returns:**
532 | 
533 | this Mail object
534 | 
535 | ---
536 | 
537 | ### validateAddress
538 | 
539 | **Signature:** `static validateAddress(address : String) : boolean`
540 | 
541 | **Description:** Validates the address that is sent as parameter. This validation includes: The format must match RFC822 The address must be 7-bit ASCII The top-level domain must be IANA-registered Sample domains such as example.com are not allowed
542 | 
543 | **Parameters:**
544 | 
545 | - `address`: Email address to be validated
546 | 
547 | **Returns:**
548 | 
549 | true if valid, false otherwise
550 | 
551 | ---
```

--------------------------------------------------------------------------------
/docs/dw_order/Invoice.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.order
  2 | 
  3 | # Class Invoice
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.Extensible
  9 |   - dw.order.AbstractItemCtnr
 10 |     - dw.order.Invoice
 11 | 
 12 | ## Description
 13 | 
 14 | The Invoice can be a debit or credit invoice, and is created from custom scripts using one of the methods ShippingOrder.createInvoice(String), Appeasement.createInvoice(String), ReturnCase.createInvoice(String) or Return.createInvoice(String). 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_CREATION_DATE
 19 | 
 20 | **Type:** Object
 21 | 
 22 | Sorting by creation date. Use with method getPaymentTransactions() as an argument to method FilteringCollection.sort(Object).
 23 | 
 24 | ### ORDERBY_ITEMID
 25 | 
 26 | **Type:** Object
 27 | 
 28 | Sorting by item id. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 29 | 
 30 | ### ORDERBY_ITEMPOSITION
 31 | 
 32 | **Type:** Object
 33 | 
 34 | Sorting by the position of the related oder item. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 35 | 
 36 | ### ORDERBY_REVERSE
 37 | 
 38 | **Type:** Object
 39 | 
 40 | Reverse orders. Use as an argument to method FilteringCollection.sort(Object).
 41 | 
 42 | ### ORDERBY_UNSORTED
 43 | 
 44 | **Type:** Object
 45 | 
 46 | Unsorted , as it is. Use with method getItems() as an argument to method FilteringCollection.sort(Object).
 47 | 
 48 | ### QUALIFIER_CAPTURE
 49 | 
 50 | **Type:** Object
 51 | 
 52 | Selects the capture transactions. Use with method getPaymentTransactions() as an argument to method FilteringCollection.select(Object).
 53 | 
 54 | ### QUALIFIER_PRODUCTITEMS
 55 | 
 56 | **Type:** Object
 57 | 
 58 | Selects the product items. Use with method getItems() as an argument to method FilteringCollection.select(Object).
 59 | 
 60 | ### QUALIFIER_REFUND
 61 | 
 62 | **Type:** Object
 63 | 
 64 | Selects the refund transactions. Use with method getPaymentTransactions() as an argument to method FilteringCollection.select(Object).
 65 | 
 66 | ### QUALIFIER_SERVICEITEMS
 67 | 
 68 | **Type:** Object
 69 | 
 70 | Selects for the service items. Use with method getItems() as an argument to method FilteringCollection.select(Object).
 71 | 
 72 | ### STATUS_FAILED
 73 | 
 74 | **Type:** String = "FAILED"
 75 | 
 76 | Constant for Invoice Status Failed. The invoice handling failed.
 77 | 
 78 | ### STATUS_MANUAL
 79 | 
 80 | **Type:** String = "MANUAL"
 81 | 
 82 | Constant for Invoice Status Manual. The invoice is not paid but will not be handled automatically. A manual invoice handling (capture or refund) is necessary.
 83 | 
 84 | ### STATUS_NOT_PAID
 85 | 
 86 | **Type:** String = "NOT_PAID"
 87 | 
 88 | Constant for Invoice Status Not Paid. The invoice is not paid and will be handled automatically.
 89 | 
 90 | ### STATUS_PAID
 91 | 
 92 | **Type:** String = "PAID"
 93 | 
 94 | Constant for Invoice Status Paid. The invoice was successfully paid.
 95 | 
 96 | ### TYPE_APPEASEMENT
 97 | 
 98 | **Type:** String = "APPEASEMENT"
 99 | 
100 | Constant for Invoice Type Appeasement. The invoice was created for an appeasement. The invoice amount needs to be refunded.
101 | 
102 | ### TYPE_RETURN
103 | 
104 | **Type:** String = "RETURN"
105 | 
106 | Constant for Invoice Type Return. The invoice was created for a return. The invoice amount needs to be refunded.
107 | 
108 | ### TYPE_RETURN_CASE
109 | 
110 | **Type:** String = "RETURN_CASE"
111 | 
112 | Constant for Invoice Type Return Case. The invoice was created for a return case. The invoice amount needs to be refunded.
113 | 
114 | ### TYPE_SHIPPING
115 | 
116 | **Type:** String = "SHIPPING"
117 | 
118 | Constant for Invoice Type Shipping. The invoice was created for a shipping order. The invoice amount needs to be captured.
119 | 
120 | ## Properties
121 | 
122 | ### capturedAmount
123 | 
124 | **Type:** Money (Read Only)
125 | 
126 | The sum of the captured amounts. The captured amounts are
127 |  calculated on the fly.
128 |  Associate a payment capture for a OrderPaymentInstrument
129 |  with an Invoice using
130 |  addCaptureTransaction(OrderPaymentInstrument, Money).
131 | 
132 | ### invoiceNumber
133 | 
134 | **Type:** String (Read Only)
135 | 
136 | The invoice number.
137 | 
138 | ### items
139 | 
140 | **Type:** FilteringCollection (Read Only)
141 | 
142 | Access the collection of InvoiceItems.
143 |  
144 |  This FilteringCollection can be sorted / filtered using:
145 |  
146 |  FilteringCollection.sort(Object) with
147 |  ORDERBY_ITEMID
148 |  FilteringCollection.sort(Object) with
149 |  ORDERBY_ITEMPOSITION
150 |  FilteringCollection.sort(Object) with
151 |  ORDERBY_UNSORTED
152 |  FilteringCollection.select(Object) with
153 |  QUALIFIER_PRODUCTITEMS
154 |  FilteringCollection.select(Object) with
155 |  QUALIFIER_SERVICEITEMS
156 | 
157 | ### paymentTransactions
158 | 
159 | **Type:** FilteringCollection (Read Only)
160 | 
161 | The payment transactions belonging to this Invoice.
162 |  
163 |  This FilteringCollection can be sorted / filtered using:
164 |  
165 |  FilteringCollection.sort(Object) with
166 |  ORDERBY_CREATION_DATE
167 |  FilteringCollection.sort(Object) with
168 |  ORDERBY_UNSORTED
169 |  FilteringCollection.select(Object) with
170 |  QUALIFIER_CAPTURE
171 |  FilteringCollection.select(Object) with
172 |  QUALIFIER_REFUND
173 | 
174 | ### refundedAmount
175 | 
176 | **Type:** Money (Read Only)
177 | 
178 | The sum of the refunded amounts. The refunded amounts are
179 |  calculated on the fly.
180 |  Associate a payment capture for a OrderPaymentInstrument
181 |  with an Invoice using
182 |  addRefundTransaction(OrderPaymentInstrument, Money).
183 | 
184 | ### status
185 | 
186 | **Type:** EnumValue
187 | 
188 | The invoice status.
189 |  The possible values are STATUS_NOT_PAID, STATUS_MANUAL,
190 |  STATUS_PAID, STATUS_FAILED.
191 | 
192 | ### type
193 | 
194 | **Type:** EnumValue (Read Only)
195 | 
196 | The invoice type.
197 |  The possible values are TYPE_SHIPPING, TYPE_RETURN,
198 |  TYPE_RETURN_CASE, TYPE_APPEASEMENT.
199 | 
200 | ## Constructor Summary
201 | 
202 | ## Method Summary
203 | 
204 | ### account
205 | 
206 | **Signature:** `account() : boolean`
207 | 
208 | The invoice will be accounted.
209 | 
210 | ### addCaptureTransaction
211 | 
212 | **Signature:** `addCaptureTransaction(instrument : OrderPaymentInstrument, capturedAmount : Money) : PaymentTransaction`
213 | 
214 | Calling this method registers an amount captured for a given order payment instrument.
215 | 
216 | ### addRefundTransaction
217 | 
218 | **Signature:** `addRefundTransaction(instrument : OrderPaymentInstrument, refundedAmount : Money) : PaymentTransaction`
219 | 
220 | Calling this method registers an amount refunded for a given order payment instrument.
221 | 
222 | ### getCapturedAmount
223 | 
224 | **Signature:** `getCapturedAmount() : Money`
225 | 
226 | Returns the sum of the captured amounts.
227 | 
228 | ### getInvoiceNumber
229 | 
230 | **Signature:** `getInvoiceNumber() : String`
231 | 
232 | Returns the invoice number.
233 | 
234 | ### getItems
235 | 
236 | **Signature:** `getItems() : FilteringCollection`
237 | 
238 | Access the collection of InvoiceItems.
239 | 
240 | ### getPaymentTransactions
241 | 
242 | **Signature:** `getPaymentTransactions() : FilteringCollection`
243 | 
244 | Returns the payment transactions belonging to this Invoice.
245 | 
246 | ### getRefundedAmount
247 | 
248 | **Signature:** `getRefundedAmount() : Money`
249 | 
250 | Returns the sum of the refunded amounts.
251 | 
252 | ### getStatus
253 | 
254 | **Signature:** `getStatus() : EnumValue`
255 | 
256 | Returns the invoice status. The possible values are STATUS_NOT_PAID, STATUS_MANUAL, STATUS_PAID, STATUS_FAILED.
257 | 
258 | ### getType
259 | 
260 | **Signature:** `getType() : EnumValue`
261 | 
262 | Returns the invoice type. The possible values are TYPE_SHIPPING, TYPE_RETURN, TYPE_RETURN_CASE, TYPE_APPEASEMENT.
263 | 
264 | ### setStatus
265 | 
266 | **Signature:** `setStatus(status : String) : void`
267 | 
268 | Sets the invoice status. The possible values are STATUS_NOT_PAID, STATUS_MANUAL, STATUS_PAID, STATUS_FAILED.
269 | 
270 | ## Method Detail
271 | 
272 | ## Method Details
273 | 
274 | ### account
275 | 
276 | **Signature:** `account() : boolean`
277 | 
278 | **Description:** The invoice will be accounted. It will be captured in case of a shipping invoice and it will be refunded in case of an appeasement, return case or return invoice. The accounting will be handled in the payment hooks PaymentHooks.capture(Invoice) or PaymentHooks.refund(Invoice). The implementing script could add payment transactions to the invoice. The accompanying business logic will set the status to PAID or FAILED. The accounting will fail when the invoice state is different to STATUS_NOT_PAID or STATUS_FAILED. The method implements its own transaction handling. The method must not be called inside a transaction.
279 | 
280 | **Returns:**
281 | 
282 | true when the accounting was successful, otherwise false.
283 | 
284 | ---
285 | 
286 | ### addCaptureTransaction
287 | 
288 | **Signature:** `addCaptureTransaction(instrument : OrderPaymentInstrument, capturedAmount : Money) : PaymentTransaction`
289 | 
290 | **Description:** Calling this method registers an amount captured for a given order payment instrument. The authorization for the capture is associated with the payment transaction belonging to the instrument. Calling this method allows the Invoice, the OrderPaymentInstrument and the Order to return their captured amount as a sum calculated on the fly. The method may be called multiple times for the same instrument (multiple capture for one authorization) or for different instruments (invoice settlement using multiple payments).
291 | 
292 | **Parameters:**
293 | 
294 | - `instrument`: the order payment instrument
295 | - `capturedAmount`: amount to register as captured
296 | 
297 | **Returns:**
298 | 
299 | the created capture transaction
300 | 
301 | ---
302 | 
303 | ### addRefundTransaction
304 | 
305 | **Signature:** `addRefundTransaction(instrument : OrderPaymentInstrument, refundedAmount : Money) : PaymentTransaction`
306 | 
307 | **Description:** Calling this method registers an amount refunded for a given order payment instrument. Calling this method allows the Invoice, the OrderPaymentInstrument and the Order to return their refunded amount as a sum calculated on the fly. The method may be called multiple times for the same instrument (multiple refunds of one payment) or for different instruments (invoice settlement using multiple payments).
308 | 
309 | **Parameters:**
310 | 
311 | - `instrument`: the order payment instrument
312 | - `refundedAmount`: amount to register as refunded
313 | 
314 | **Returns:**
315 | 
316 | the created refund transaction
317 | 
318 | ---
319 | 
320 | ### getCapturedAmount
321 | 
322 | **Signature:** `getCapturedAmount() : Money`
323 | 
324 | **Description:** Returns the sum of the captured amounts. The captured amounts are calculated on the fly. Associate a payment capture for a OrderPaymentInstrument with an Invoice using addCaptureTransaction(OrderPaymentInstrument, Money).
325 | 
326 | **Returns:**
327 | 
328 | sum of captured amounts
329 | 
330 | ---
331 | 
332 | ### getInvoiceNumber
333 | 
334 | **Signature:** `getInvoiceNumber() : String`
335 | 
336 | **Description:** Returns the invoice number.
337 | 
338 | **Returns:**
339 | 
340 | the invoice number
341 | 
342 | ---
343 | 
344 | ### getItems
345 | 
346 | **Signature:** `getItems() : FilteringCollection`
347 | 
348 | **Description:** Access the collection of InvoiceItems. 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
349 | 
350 | **Returns:**
351 | 
352 | the invoice items
353 | 
354 | ---
355 | 
356 | ### getPaymentTransactions
357 | 
358 | **Signature:** `getPaymentTransactions() : FilteringCollection`
359 | 
360 | **Description:** Returns the payment transactions belonging to this Invoice. This FilteringCollection can be sorted / filtered using: FilteringCollection.sort(Object) with ORDERBY_CREATION_DATE FilteringCollection.sort(Object) with ORDERBY_UNSORTED FilteringCollection.select(Object) with QUALIFIER_CAPTURE FilteringCollection.select(Object) with QUALIFIER_REFUND
361 | 
362 | **Returns:**
363 | 
364 | the payment transactions.
365 | 
366 | **See Also:**
367 | 
368 | PaymentTransaction
369 | 
370 | ---
371 | 
372 | ### getRefundedAmount
373 | 
374 | **Signature:** `getRefundedAmount() : Money`
375 | 
376 | **Description:** Returns the sum of the refunded amounts. The refunded amounts are calculated on the fly. Associate a payment capture for a OrderPaymentInstrument with an Invoice using addRefundTransaction(OrderPaymentInstrument, Money).
377 | 
378 | **Returns:**
379 | 
380 | sum of refunded amounts
381 | 
382 | ---
383 | 
384 | ### getStatus
385 | 
386 | **Signature:** `getStatus() : EnumValue`
387 | 
388 | **Description:** Returns the invoice status. The possible values are STATUS_NOT_PAID, STATUS_MANUAL, STATUS_PAID, STATUS_FAILED.
389 | 
390 | **Returns:**
391 | 
392 | the invoice status
393 | 
394 | ---
395 | 
396 | ### getType
397 | 
398 | **Signature:** `getType() : EnumValue`
399 | 
400 | **Description:** Returns the invoice type. The possible values are TYPE_SHIPPING, TYPE_RETURN, TYPE_RETURN_CASE, TYPE_APPEASEMENT.
401 | 
402 | **Returns:**
403 | 
404 | the invoice type
405 | 
406 | ---
407 | 
408 | ### setStatus
409 | 
410 | **Signature:** `setStatus(status : String) : void`
411 | 
412 | **Description:** Sets the invoice status. The possible values are STATUS_NOT_PAID, STATUS_MANUAL, STATUS_PAID, STATUS_FAILED.
413 | 
414 | **Parameters:**
415 | 
416 | - `status`: the invoice status to set
417 | 
418 | ---
```

--------------------------------------------------------------------------------
/src/clients/logs/log-processor.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Log parsing, entry processing, and data manipulation
  3 |  */
  4 | 
  5 | import { parseLogEntries, extractUniqueErrors, normalizeFilePath, extractTimestampFromLogEntry } from '../../utils/utils.js';
  6 | import { Logger } from '../../utils/logger.js';
  7 | import { LOG_CONSTANTS } from './log-constants.js';
  8 | import type { LogEntry, LogLevel, LogFileMetadata, ProcessedLogEntry, JobLogInfo } from './log-types.js';
  9 | 
 10 | export class LogProcessor {
 11 |   private logger: Logger;
 12 | 
 13 |   constructor(logger: Logger) {
 14 |     this.logger = logger;
 15 |   }
 16 | 
 17 |   /**
 18 |    * Process log files and extract entries with priority ordering
 19 |    */
 20 |   async processLogFiles(
 21 |     files: LogFileMetadata[],
 22 |     level: LogLevel,
 23 |     fileContents: Map<string, string>,
 24 |   ): Promise<LogEntry[]> {
 25 |     const sortedFiles = this.sortFilesByPriority(files);
 26 |     const allLogEntries: LogEntry[] = [];
 27 | 
 28 |     for (let i = 0; i < sortedFiles.length; i++) {
 29 |       const file = sortedFiles[i];
 30 |       const content = fileContents.get(file.filename);
 31 | 
 32 |       if (!content) {
 33 |         this.logger.warn(`No content found for file: ${file.filename}`);
 34 |         continue;
 35 |       }
 36 | 
 37 |       this.logger.debug(`Processing file: ${file.filename} (priority: ${i})`);
 38 | 
 39 |       try {
 40 |         const logEntries = parseLogEntries(content, level.toUpperCase());
 41 | 
 42 |         // Add entries with extracted timestamps for chronological sorting
 43 |         logEntries.forEach((entry, entryIndex) => {
 44 |           const timestamp = extractTimestampFromLogEntry(entry);
 45 |           allLogEntries.push({
 46 |             entry: `[${normalizeFilePath(file.filename)}] ${entry}`,
 47 |             filename: normalizeFilePath(file.filename),
 48 |             order: i * LOG_CONSTANTS.FILE_ORDER_MULTIPLIER + entryIndex, // Keep for fallback sorting
 49 |             timestamp: timestamp ?? undefined, // Convert null to undefined
 50 |           });
 51 |         });
 52 |       } catch (error) {
 53 |         this.logger.error(`Error processing file ${file.filename}:`, error);
 54 |         // Continue processing other files even if one fails
 55 |       }
 56 |     }
 57 | 
 58 |     return allLogEntries;
 59 |   }
 60 | 
 61 |   /**
 62 |    * Sort files by modification date (newest first) for processing priority
 63 |    */
 64 |   private sortFilesByPriority(files: LogFileMetadata[]): LogFileMetadata[] {
 65 |     return files
 66 |       .sort((a, b) => new Date(b.lastmod).getTime() - new Date(a.lastmod).getTime())
 67 |       .map(file => ({ ...file, filename: file.filename }));
 68 |   }
 69 | 
 70 |   /**
 71 |    * Sort and limit log entries by chronological order (actual timestamps)
 72 |    */
 73 |   sortAndLimitEntries(entries: LogEntry[], limit: number): LogEntry[] {
 74 |     return entries
 75 |       .sort((a, b) => {
 76 |         // Primary sort: by timestamp (newest first)
 77 |         if (a.timestamp && b.timestamp) {
 78 |           return b.timestamp.getTime() - a.timestamp.getTime();
 79 |         }
 80 |         // Fallback for entries without timestamps: use file order (newest files first)
 81 |         if (a.timestamp && !b.timestamp) {
 82 |           return -1;
 83 |         }
 84 |         if (!a.timestamp && b.timestamp) {
 85 |           return 1;
 86 |         }
 87 |         return a.order - b.order; // Original order-based sorting as fallback
 88 |       })
 89 |       .slice(0, limit); // Take the first N entries (most recent chronologically)
 90 |   }
 91 | 
 92 |   /**
 93 |    * Extract formatted log entries from sorted entries
 94 |    */
 95 |   extractFormattedEntries(sortedEntries: LogEntry[]): string[] {
 96 |     return sortedEntries.map(item => item.entry);
 97 |   }
 98 | 
 99 |   /**
100 |    * Process search results from file contents
101 |    */
102 |   processSearchResults(
103 |     files: LogFileMetadata[],
104 |     fileContents: Map<string, string>,
105 |     pattern: string,
106 |     limit: number,
107 |   ): string[] {
108 |     const matchingEntries: string[] = [];
109 | 
110 |     for (const file of files) {
111 |       const content = fileContents.get(file.filename);
112 |       if (!content) {
113 |         continue;
114 |       }
115 | 
116 |       const lines = content.split('\n');
117 | 
118 |       for (const line of lines) {
119 |         if (line.toLowerCase().includes(pattern.toLowerCase()) && matchingEntries.length < limit) {
120 |           matchingEntries.push(`[${normalizeFilePath(file.filename)}] ${line.trim()}`);
121 |         }
122 |       }
123 |     }
124 | 
125 |     return matchingEntries;
126 |   }
127 | 
128 |   /**
129 |    * Count log levels in content
130 |    */
131 |   countLogLevels(content: string): {
132 |     errorCount: number;
133 |     warningCount: number;
134 |     infoCount: number;
135 |     debugCount: number;
136 |   } {
137 |     const lines = content.split('\n');
138 |     const counts = {
139 |       errorCount: 0,
140 |       warningCount: 0,
141 |       infoCount: 0,
142 |       debugCount: 0,
143 |     };
144 | 
145 |     for (const line of lines) {
146 |       if (line.includes(' ERROR ')) { counts.errorCount++; }
147 |       if (line.includes(' WARN ')) { counts.warningCount++; }
148 |       if (line.includes(' INFO ')) { counts.infoCount++; }
149 |       if (line.includes(' DEBUG ')) { counts.debugCount++; }
150 |     }
151 | 
152 |     return counts;
153 |   }
154 | 
155 |   /**
156 |    * Extract key issues from error content
157 |    */
158 |   extractKeyIssues(content: string): string[] {
159 |     const errors = parseLogEntries(content, 'ERROR');
160 |     return extractUniqueErrors(errors);
161 |   }
162 | 
163 |   /**
164 |    * Parse individual log entry for structured data
165 |    */
166 |   parseLogEntry(entry: string): ProcessedLogEntry {
167 |     // Basic parsing - can be enhanced based on log format
168 |     const timestampMatch = entry.match(/\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/);
169 |     const levelMatch = entry.match(/\s(ERROR|WARN|INFO|DEBUG)\s/);
170 | 
171 |     return {
172 |       content: entry,
173 |       timestamp: timestampMatch ? timestampMatch[0] : undefined,
174 |       level: levelMatch ? levelMatch[1].toLowerCase() : undefined,
175 |       source: this.extractSourceFromEntry(entry),
176 |     };
177 |   }
178 | 
179 |   /**
180 |    * Extract source information from log entry
181 |    */
182 |   private extractSourceFromEntry(entry: string): string | undefined {
183 |     // Look for common source patterns like [ClassName] or package.ClassName
184 |     const sourceMatch = entry.match(/\[([^\]]+)\]/) ?? entry.match(/([a-zA-Z]+\.[a-zA-Z]+)/);
185 |     return sourceMatch ? sourceMatch[1] : undefined;
186 |   }
187 | 
188 |   /**
189 |    * Group entries by source/category
190 |    */
191 |   groupEntriesBySource(entries: ProcessedLogEntry[]): Map<string, ProcessedLogEntry[]> {
192 |     const groups = new Map<string, ProcessedLogEntry[]>();
193 | 
194 |     for (const entry of entries) {
195 |       const source = entry.source ?? 'unknown';
196 |       if (!groups.has(source)) {
197 |         groups.set(source, []);
198 |       }
199 |       groups.get(source)!.push(entry);
200 |     }
201 | 
202 |     return groups;
203 |   }
204 | 
205 |   /**
206 |    * Filter entries by time range
207 |    */
208 |   filterEntriesByTimeRange(
209 |     entries: ProcessedLogEntry[],
210 |     startTime?: Date,
211 |     endTime?: Date,
212 |   ): ProcessedLogEntry[] {
213 |     return entries.filter(entry => {
214 |       if (!entry.timestamp) {
215 |         return true; // Include entries without timestamps
216 |       }
217 | 
218 |       const entryTime = new Date(entry.timestamp);
219 | 
220 |       if (startTime && entryTime < startTime) {
221 |         return false;
222 |       }
223 |       if (endTime && entryTime > endTime) {
224 |         return false;
225 |       }
226 | 
227 |       return true;
228 |     });
229 |   }
230 | 
231 |   /**
232 |    * Process job log files - handles all log levels in one file
233 |    */
234 |   async processJobLogFiles(
235 |     jobLogs: JobLogInfo[],
236 |     level: LogLevel | 'all',
237 |     fileContents: Map<string, string>,
238 |   ): Promise<LogEntry[]> {
239 |     const allLogEntries: LogEntry[] = [];
240 | 
241 |     for (let i = 0; i < jobLogs.length; i++) {
242 |       const jobLog = jobLogs[i];
243 |       const content = fileContents.get(jobLog.logFile);
244 | 
245 |       if (!content) {
246 |         this.logger.warn(`No content found for job log file: ${jobLog.logFile}`);
247 |         continue;
248 |       }
249 | 
250 |       this.logger.debug(`Processing job log file: ${jobLog.logFile} (job: ${jobLog.jobName})`);
251 | 
252 |       try {
253 |         // For job logs, we need to parse entries based on the level or all levels
254 |         const logEntries = level === 'all'
255 |           ? this.parseAllLogLevelsFromContent(content)
256 |           : parseLogEntries(content, level.toUpperCase());
257 | 
258 |         // Add entries with job context and priority
259 |         logEntries.forEach((entry, entryIndex) => {
260 |           allLogEntries.push({
261 |             entry: `[${jobLog.jobName}] ${entry}`,
262 |             filename: `Job: ${jobLog.jobName} (${jobLog.jobId})`,
263 |             order: i * LOG_CONSTANTS.FILE_ORDER_MULTIPLIER + entryIndex,
264 |           });
265 |         });
266 |       } catch (error) {
267 |         this.logger.error(`Error processing job log file ${jobLog.logFile}:`, error);
268 |       }
269 |     }
270 | 
271 |     return allLogEntries;
272 |   }
273 | 
274 |   /**
275 |    * Parse all log levels from job log content
276 |    */
277 |   private parseAllLogLevelsFromContent(content: string): string[] {
278 |     const lines = content.split('\n');
279 |     const logEntries: string[] = [];
280 | 
281 |     for (const line of lines) {
282 |       if (line.trim() && this.isLogEntry(line)) {
283 |         logEntries.push(line.trim());
284 |       }
285 |     }
286 | 
287 |     return logEntries;
288 |   }
289 | 
290 |   /**
291 |    * Check if a line is a log entry (contains timestamp and level)
292 |    */
293 |   private isLogEntry(line: string): boolean {
294 |     // Look for timestamp pattern and log level in the line
295 |     const timestampPattern = /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/;
296 |     const levelPattern = /\s(ERROR|WARN|INFO|DEBUG)\s/;
297 | 
298 |     return timestampPattern.test(line) && levelPattern.test(line);
299 |   }
300 | 
301 |   /**
302 |    * Filter job log entries by specific log level
303 |    */
304 |   filterJobLogEntriesByLevel(entries: string[], level: LogLevel): string[] {
305 |     const levelUpper = level.toUpperCase();
306 |     return entries.filter(entry => entry.includes(` ${levelUpper} `));
307 |   }
308 | 
309 |   /**
310 |    * Extract job execution summary from job log content
311 |    */
312 |   extractJobExecutionSummary(content: string): {
313 |     startTime?: string;
314 |     endTime?: string;
315 |     status?: string;
316 |     duration?: string;
317 |     errorCount: number;
318 |     warningCount: number;
319 |     steps: string[];
320 |   } {
321 |     const lines = content.split('\n');
322 |     const summary = {
323 |       startTime: undefined as string | undefined,
324 |       endTime: undefined as string | undefined,
325 |       status: undefined as string | undefined,
326 |       duration: undefined as string | undefined,
327 |       errorCount: 0,
328 |       warningCount: 0,
329 |       steps: [] as string[],
330 |     };
331 | 
332 |     const stepPattern = /Step\s+\d+:/i;
333 | 
334 |     for (const line of lines) {
335 |       // Extract start time (first timestamp)
336 |       if (!summary.startTime) {
337 |         const timestampMatch = line.match(/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/);
338 |         if (timestampMatch) {
339 |           summary.startTime = timestampMatch[1];
340 |         }
341 |       }
342 | 
343 |       // Extract end time (last timestamp with completion indicators)
344 |       if (line.includes('completed') || line.includes('finished') || line.includes('ended')) {
345 |         const timestampMatch = line.match(/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/);
346 |         if (timestampMatch) {
347 |           summary.endTime = timestampMatch[1];
348 |         }
349 |       }
350 | 
351 |       // Extract status
352 |       if (line.includes('Job completed') || line.includes('Job finished')) {
353 |         summary.status = line.includes('successfully') ? 'SUCCESS' : 'COMPLETED';
354 |       } else if (line.includes('Job failed') || line.includes('ERROR')) {
355 |         summary.status = 'FAILED';
356 |       }
357 | 
358 |       // Count errors and warnings
359 |       if (line.includes(' ERROR ')) {
360 |         summary.errorCount++;
361 |       }
362 |       if (line.includes(' WARN ')) {
363 |         summary.warningCount++;
364 |       }
365 | 
366 |       // Extract step information
367 |       if (stepPattern.test(line)) {
368 |         const stepInfo = line.trim();
369 |         if (!summary.steps.includes(stepInfo)) {
370 |           summary.steps.push(stepInfo);
371 |         }
372 |       }
373 |     }
374 | 
375 |     // Calculate duration if we have start and end times
376 |     if (summary.startTime && summary.endTime) {
377 |       const start = new Date(summary.startTime);
378 |       const end = new Date(summary.endTime);
379 |       const durationMs = end.getTime() - start.getTime();
380 |       summary.duration = this.formatDuration(durationMs);
381 |     }
382 | 
383 |     return summary;
384 |   }
385 | 
386 |   /**
387 |    * Format duration in milliseconds to human readable string
388 |    */
389 |   private formatDuration(ms: number): string {
390 |     const seconds = Math.floor(ms / 1000);
391 |     const minutes = Math.floor(seconds / 60);
392 |     const hours = Math.floor(minutes / 60);
393 | 
394 |     if (hours > 0) {
395 |       return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
396 |     } else if (minutes > 0) {
397 |       return `${minutes}m ${seconds % 60}s`;
398 |     } else {
399 |       return `${seconds}s`;
400 |     }
401 |   }
402 | }
403 | 
```

--------------------------------------------------------------------------------
/docs/dw_order/OrderAddress.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.order
  2 | 
  3 | # Class OrderAddress
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.order.OrderAddress
 11 | 
 12 | ## Description
 13 | 
 14 | The Address class represents a customer's address. Note: this class allows access to sensitive personal and private information. Pay attention to appropriate legal and regulatory requirements.
 15 | 
 16 | ## Properties
 17 | 
 18 | ### address1
 19 | 
 20 | **Type:** String
 21 | 
 22 | The customer's first address.
 23 | 
 24 | ### address2
 25 | 
 26 | **Type:** String
 27 | 
 28 | The customer's second address.
 29 | 
 30 | ### city
 31 | 
 32 | **Type:** String
 33 | 
 34 | The Customer's City.
 35 | 
 36 | ### companyName
 37 | 
 38 | **Type:** String
 39 | 
 40 | The Customer's company name.
 41 | 
 42 | ### countryCode
 43 | 
 44 | **Type:** EnumValue
 45 | 
 46 | The customer's country code.
 47 | 
 48 | ### firstName
 49 | 
 50 | **Type:** String
 51 | 
 52 | The Customer's first name.
 53 | 
 54 | ### fullName
 55 | 
 56 | **Type:** String (Read Only)
 57 | 
 58 | A concatenation of the Customer's first, middle,
 59 |  and last names and it' suffix.
 60 | 
 61 | ### jobTitle
 62 | 
 63 | **Type:** String
 64 | 
 65 | The customer's job title.
 66 | 
 67 | ### lastName
 68 | 
 69 | **Type:** String
 70 | 
 71 | The customer's last name.
 72 | 
 73 | ### phone
 74 | 
 75 | **Type:** String
 76 | 
 77 | The customer's phone number.
 78 | 
 79 | ### postalCode
 80 | 
 81 | **Type:** String
 82 | 
 83 | The customer's postal code.
 84 | 
 85 | ### postBox
 86 | 
 87 | **Type:** String
 88 | 
 89 | The customer's post box.
 90 | 
 91 | ### salutation
 92 | 
 93 | **Type:** String
 94 | 
 95 | The customer's salutation.
 96 | 
 97 | ### secondName
 98 | 
 99 | **Type:** String
100 | 
101 | The customer's second name.
102 | 
103 | ### stateCode
104 | 
105 | **Type:** String
106 | 
107 | The customer's state.
108 | 
109 | ### suffix
110 | 
111 | **Type:** String
112 | 
113 | The customer's suffix.
114 | 
115 | ### suite
116 | 
117 | **Type:** String
118 | 
119 | The customer's suite.
120 | 
121 | ### title
122 | 
123 | **Type:** String
124 | 
125 | The customer's title.
126 | 
127 | ## Constructor Summary
128 | 
129 | ## Method Summary
130 | 
131 | ### getAddress1
132 | 
133 | **Signature:** `getAddress1() : String`
134 | 
135 | Returns the customer's first address.
136 | 
137 | ### getAddress2
138 | 
139 | **Signature:** `getAddress2() : String`
140 | 
141 | Returns the customer's second address.
142 | 
143 | ### getCity
144 | 
145 | **Signature:** `getCity() : String`
146 | 
147 | Returns the Customer's City.
148 | 
149 | ### getCompanyName
150 | 
151 | **Signature:** `getCompanyName() : String`
152 | 
153 | Returns the Customer's company name.
154 | 
155 | ### getCountryCode
156 | 
157 | **Signature:** `getCountryCode() : EnumValue`
158 | 
159 | Returns the customer's country code.
160 | 
161 | ### getFirstName
162 | 
163 | **Signature:** `getFirstName() : String`
164 | 
165 | Returns the Customer's first name.
166 | 
167 | ### getFullName
168 | 
169 | **Signature:** `getFullName() : String`
170 | 
171 | Returns a concatenation of the Customer's first, middle, and last names and it' suffix.
172 | 
173 | ### getJobTitle
174 | 
175 | **Signature:** `getJobTitle() : String`
176 | 
177 | Returns the customer's job title.
178 | 
179 | ### getLastName
180 | 
181 | **Signature:** `getLastName() : String`
182 | 
183 | Returns the customer's last name.
184 | 
185 | ### getPhone
186 | 
187 | **Signature:** `getPhone() : String`
188 | 
189 | Returns the customer's phone number.
190 | 
191 | ### getPostalCode
192 | 
193 | **Signature:** `getPostalCode() : String`
194 | 
195 | Returns the customer's postal code.
196 | 
197 | ### getPostBox
198 | 
199 | **Signature:** `getPostBox() : String`
200 | 
201 | Returns the customer's post box.
202 | 
203 | ### getSalutation
204 | 
205 | **Signature:** `getSalutation() : String`
206 | 
207 | Returns the customer's salutation.
208 | 
209 | ### getSecondName
210 | 
211 | **Signature:** `getSecondName() : String`
212 | 
213 | Returns the customer's second name.
214 | 
215 | ### getStateCode
216 | 
217 | **Signature:** `getStateCode() : String`
218 | 
219 | Returns the customer's state.
220 | 
221 | ### getSuffix
222 | 
223 | **Signature:** `getSuffix() : String`
224 | 
225 | Returns the customer's suffix.
226 | 
227 | ### getSuite
228 | 
229 | **Signature:** `getSuite() : String`
230 | 
231 | Returns the customer's suite.
232 | 
233 | ### getTitle
234 | 
235 | **Signature:** `getTitle() : String`
236 | 
237 | Returns the customer's title.
238 | 
239 | ### isEquivalentAddress
240 | 
241 | **Signature:** `isEquivalentAddress(address : Object) : boolean`
242 | 
243 | Returns true if the specified address is equivalent to this address.
244 | 
245 | ### setAddress1
246 | 
247 | **Signature:** `setAddress1(value : String) : void`
248 | 
249 | Sets the customer's first address.
250 | 
251 | ### setAddress2
252 | 
253 | **Signature:** `setAddress2(value : String) : void`
254 | 
255 | Sets the customer's second address.
256 | 
257 | ### setCity
258 | 
259 | **Signature:** `setCity(city : String) : void`
260 | 
261 | Sets the Customer's City.
262 | 
263 | ### setCompanyName
264 | 
265 | **Signature:** `setCompanyName(companyName : String) : void`
266 | 
267 | Sets the Customer's company name.
268 | 
269 | ### setCountryCode
270 | 
271 | **Signature:** `setCountryCode(countryCode : String) : void`
272 | 
273 | Sets the Customer's country code.
274 | 
275 | ### setFirstName
276 | 
277 | **Signature:** `setFirstName(firstName : String) : void`
278 | 
279 | Sets the Customer's first name.
280 | 
281 | ### setJobTitle
282 | 
283 | **Signature:** `setJobTitle(jobTitle : String) : void`
284 | 
285 | Sets the customer's job title.
286 | 
287 | ### setLastName
288 | 
289 | **Signature:** `setLastName(lastName : String) : void`
290 | 
291 | Sets the customer's last name.
292 | 
293 | ### setPhone
294 | 
295 | **Signature:** `setPhone(phoneNumber : String) : void`
296 | 
297 | Sets the customer's phone number.
298 | 
299 | ### setPostalCode
300 | 
301 | **Signature:** `setPostalCode(postalCode : String) : void`
302 | 
303 | Sets the customer's postal code.
304 | 
305 | ### setPostBox
306 | 
307 | **Signature:** `setPostBox(postBox : String) : void`
308 | 
309 | Sets the customer's post box.
310 | 
311 | ### setSaluation
312 | 
313 | **Signature:** `setSaluation(value : String) : void`
314 | 
315 | Sets the customer's salutation.
316 | 
317 | ### setSalutation
318 | 
319 | **Signature:** `setSalutation(value : String) : void`
320 | 
321 | Sets the customer's salutation.
322 | 
323 | ### setSecondName
324 | 
325 | **Signature:** `setSecondName(secondName : String) : void`
326 | 
327 | Sets the customer's second name.
328 | 
329 | ### setStateCode
330 | 
331 | **Signature:** `setStateCode(state : String) : void`
332 | 
333 | Sets the customer's state.
334 | 
335 | ### setSuffix
336 | 
337 | **Signature:** `setSuffix(suffix : String) : void`
338 | 
339 | Sets the customer's suffix.
340 | 
341 | ### setSuite
342 | 
343 | **Signature:** `setSuite(value : String) : void`
344 | 
345 | Sets the customer's suite.
346 | 
347 | ### setTitle
348 | 
349 | **Signature:** `setTitle(title : String) : void`
350 | 
351 | Sets the customer's title.
352 | 
353 | ## Method Detail
354 | 
355 | ## Method Details
356 | 
357 | ### getAddress1
358 | 
359 | **Signature:** `getAddress1() : String`
360 | 
361 | **Description:** Returns the customer's first address.
362 | 
363 | **Returns:**
364 | 
365 | the first address value.
366 | 
367 | ---
368 | 
369 | ### getAddress2
370 | 
371 | **Signature:** `getAddress2() : String`
372 | 
373 | **Description:** Returns the customer's second address.
374 | 
375 | **Returns:**
376 | 
377 | the second address value.
378 | 
379 | ---
380 | 
381 | ### getCity
382 | 
383 | **Signature:** `getCity() : String`
384 | 
385 | **Description:** Returns the Customer's City.
386 | 
387 | **Returns:**
388 | 
389 | the Customer's city.
390 | 
391 | ---
392 | 
393 | ### getCompanyName
394 | 
395 | **Signature:** `getCompanyName() : String`
396 | 
397 | **Description:** Returns the Customer's company name.
398 | 
399 | **Returns:**
400 | 
401 | the company name.
402 | 
403 | ---
404 | 
405 | ### getCountryCode
406 | 
407 | **Signature:** `getCountryCode() : EnumValue`
408 | 
409 | **Description:** Returns the customer's country code.
410 | 
411 | **Returns:**
412 | 
413 | the country code.
414 | 
415 | ---
416 | 
417 | ### getFirstName
418 | 
419 | **Signature:** `getFirstName() : String`
420 | 
421 | **Description:** Returns the Customer's first name.
422 | 
423 | **Returns:**
424 | 
425 | the Customer first name.
426 | 
427 | ---
428 | 
429 | ### getFullName
430 | 
431 | **Signature:** `getFullName() : String`
432 | 
433 | **Description:** Returns a concatenation of the Customer's first, middle, and last names and it' suffix.
434 | 
435 | **Returns:**
436 | 
437 | a concatenation of the Customer's first, middle, and last names and it' suffix.
438 | 
439 | ---
440 | 
441 | ### getJobTitle
442 | 
443 | **Signature:** `getJobTitle() : String`
444 | 
445 | **Description:** Returns the customer's job title.
446 | 
447 | **Returns:**
448 | 
449 | the job title.
450 | 
451 | ---
452 | 
453 | ### getLastName
454 | 
455 | **Signature:** `getLastName() : String`
456 | 
457 | **Description:** Returns the customer's last name.
458 | 
459 | **Returns:**
460 | 
461 | the last name.
462 | 
463 | ---
464 | 
465 | ### getPhone
466 | 
467 | **Signature:** `getPhone() : String`
468 | 
469 | **Description:** Returns the customer's phone number.
470 | 
471 | **Returns:**
472 | 
473 | the phone number.
474 | 
475 | ---
476 | 
477 | ### getPostalCode
478 | 
479 | **Signature:** `getPostalCode() : String`
480 | 
481 | **Description:** Returns the customer's postal code.
482 | 
483 | **Returns:**
484 | 
485 | the postal code.
486 | 
487 | ---
488 | 
489 | ### getPostBox
490 | 
491 | **Signature:** `getPostBox() : String`
492 | 
493 | **Description:** Returns the customer's post box.
494 | 
495 | **Returns:**
496 | 
497 | the postBox.
498 | 
499 | ---
500 | 
501 | ### getSalutation
502 | 
503 | **Signature:** `getSalutation() : String`
504 | 
505 | **Description:** Returns the customer's salutation.
506 | 
507 | **Returns:**
508 | 
509 | the customer's salutation.
510 | 
511 | ---
512 | 
513 | ### getSecondName
514 | 
515 | **Signature:** `getSecondName() : String`
516 | 
517 | **Description:** Returns the customer's second name.
518 | 
519 | **Returns:**
520 | 
521 | the second name.
522 | 
523 | ---
524 | 
525 | ### getStateCode
526 | 
527 | **Signature:** `getStateCode() : String`
528 | 
529 | **Description:** Returns the customer's state.
530 | 
531 | **Returns:**
532 | 
533 | the state.
534 | 
535 | ---
536 | 
537 | ### getSuffix
538 | 
539 | **Signature:** `getSuffix() : String`
540 | 
541 | **Description:** Returns the customer's suffix.
542 | 
543 | **Returns:**
544 | 
545 | the suffix.
546 | 
547 | ---
548 | 
549 | ### getSuite
550 | 
551 | **Signature:** `getSuite() : String`
552 | 
553 | **Description:** Returns the customer's suite.
554 | 
555 | **Returns:**
556 | 
557 | the customer's suite.
558 | 
559 | ---
560 | 
561 | ### getTitle
562 | 
563 | **Signature:** `getTitle() : String`
564 | 
565 | **Description:** Returns the customer's title.
566 | 
567 | **Returns:**
568 | 
569 | the title.
570 | 
571 | ---
572 | 
573 | ### isEquivalentAddress
574 | 
575 | **Signature:** `isEquivalentAddress(address : Object) : boolean`
576 | 
577 | **Description:** Returns true if the specified address is equivalent to this address. An equivalent address is an address whose core attributes contain the same values. The core attributes are: address1 address2 city companyName countryCode firstName lastName postalCode postBox stateCode
578 | 
579 | **Parameters:**
580 | 
581 | - `address`: the address to test.
582 | 
583 | **Returns:**
584 | 
585 | true if the specified address is equivalent to this address, false otherwise.
586 | 
587 | ---
588 | 
589 | ### setAddress1
590 | 
591 | **Signature:** `setAddress1(value : String) : void`
592 | 
593 | **Description:** Sets the customer's first address.
594 | 
595 | **Parameters:**
596 | 
597 | - `value`: The value to set.
598 | 
599 | ---
600 | 
601 | ### setAddress2
602 | 
603 | **Signature:** `setAddress2(value : String) : void`
604 | 
605 | **Description:** Sets the customer's second address.
606 | 
607 | **Parameters:**
608 | 
609 | - `value`: The value to set.
610 | 
611 | ---
612 | 
613 | ### setCity
614 | 
615 | **Signature:** `setCity(city : String) : void`
616 | 
617 | **Description:** Sets the Customer's City.
618 | 
619 | **Parameters:**
620 | 
621 | - `city`: the Customer's city to set.
622 | 
623 | ---
624 | 
625 | ### setCompanyName
626 | 
627 | **Signature:** `setCompanyName(companyName : String) : void`
628 | 
629 | **Description:** Sets the Customer's company name.
630 | 
631 | **Parameters:**
632 | 
633 | - `companyName`: the name of the company.
634 | 
635 | ---
636 | 
637 | ### setCountryCode
638 | 
639 | **Signature:** `setCountryCode(countryCode : String) : void`
640 | 
641 | **Description:** Sets the Customer's country code.
642 | 
643 | **Parameters:**
644 | 
645 | - `countryCode`: the country code.
646 | 
647 | ---
648 | 
649 | ### setFirstName
650 | 
651 | **Signature:** `setFirstName(firstName : String) : void`
652 | 
653 | **Description:** Sets the Customer's first name.
654 | 
655 | **Parameters:**
656 | 
657 | - `firstName`: the customer's first name to set.
658 | 
659 | ---
660 | 
661 | ### setJobTitle
662 | 
663 | **Signature:** `setJobTitle(jobTitle : String) : void`
664 | 
665 | **Description:** Sets the customer's job title.
666 | 
667 | **Parameters:**
668 | 
669 | - `jobTitle`: The job title to set.
670 | 
671 | ---
672 | 
673 | ### setLastName
674 | 
675 | **Signature:** `setLastName(lastName : String) : void`
676 | 
677 | **Description:** Sets the customer's last name.
678 | 
679 | **Parameters:**
680 | 
681 | - `lastName`: The last name to set.
682 | 
683 | ---
684 | 
685 | ### setPhone
686 | 
687 | **Signature:** `setPhone(phoneNumber : String) : void`
688 | 
689 | **Description:** Sets the customer's phone number. The length is restricted to 256 characters.
690 | 
691 | **Parameters:**
692 | 
693 | - `phoneNumber`: The phone number to set.
694 | 
695 | ---
696 | 
697 | ### setPostalCode
698 | 
699 | **Signature:** `setPostalCode(postalCode : String) : void`
700 | 
701 | **Description:** Sets the customer's postal code.
702 | 
703 | **Parameters:**
704 | 
705 | - `postalCode`: The postal code to set.
706 | 
707 | ---
708 | 
709 | ### setPostBox
710 | 
711 | **Signature:** `setPostBox(postBox : String) : void`
712 | 
713 | **Description:** Sets the customer's post box.
714 | 
715 | **Parameters:**
716 | 
717 | - `postBox`: The post box to set.
718 | 
719 | ---
720 | 
721 | ### setSaluation
722 | 
723 | **Signature:** `setSaluation(value : String) : void`
724 | 
725 | **Description:** Sets the customer's salutation.
726 | 
727 | **Deprecated:**
728 | 
729 | Use setSalutation(String)
730 | 
731 | **Parameters:**
732 | 
733 | - `value`: the customer's salutation.
734 | 
735 | ---
736 | 
737 | ### setSalutation
738 | 
739 | **Signature:** `setSalutation(value : String) : void`
740 | 
741 | **Description:** Sets the customer's salutation.
742 | 
743 | **Parameters:**
744 | 
745 | - `value`: the customer's salutation.
746 | 
747 | ---
748 | 
749 | ### setSecondName
750 | 
751 | **Signature:** `setSecondName(secondName : String) : void`
752 | 
753 | **Description:** Sets the customer's second name.
754 | 
755 | **Parameters:**
756 | 
757 | - `secondName`: The second name to set.
758 | 
759 | ---
760 | 
761 | ### setStateCode
762 | 
763 | **Signature:** `setStateCode(state : String) : void`
764 | 
765 | **Description:** Sets the customer's state.
766 | 
767 | **Parameters:**
768 | 
769 | - `state`: The state to set.
770 | 
771 | ---
772 | 
773 | ### setSuffix
774 | 
775 | **Signature:** `setSuffix(suffix : String) : void`
776 | 
777 | **Description:** Sets the customer's suffix.
778 | 
779 | **Parameters:**
780 | 
781 | - `suffix`: The suffix to set.
782 | 
783 | ---
784 | 
785 | ### setSuite
786 | 
787 | **Signature:** `setSuite(value : String) : void`
788 | 
789 | **Description:** Sets the customer's suite. The length is restricted to 256 characters.
790 | 
791 | **Parameters:**
792 | 
793 | - `value`: the customer's suite.
794 | 
795 | ---
796 | 
797 | ### setTitle
798 | 
799 | **Signature:** `setTitle(title : String) : void`
800 | 
801 | **Description:** Sets the customer's title.
802 | 
803 | **Parameters:**
804 | 
805 | - `title`: The title to set.
806 | 
807 | ---
```
Page 22/61FirstPrevNextLast