#
tokens: 45600/50000 8/825 files (page 26/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 26 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/log-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { LogToolHandler } from '../src/core/handlers/log-handler.js';
  2 | import { HandlerContext } from '../src/core/handlers/base-handler.js';
  3 | import { SFCCLogClient } from '../src/clients/log-client.js';
  4 | import { Logger } from '../src/utils/logger.js';
  5 | 
  6 | // Mock the SFCCLogClient
  7 | jest.mock('../src/clients/log-client.js');
  8 | 
  9 | describe('LogToolHandler', () => {
 10 |   let mockLogger: jest.Mocked<Logger>;
 11 |   let mockLogClient: jest.Mocked<SFCCLogClient>;
 12 |   let context: HandlerContext;
 13 |   let handler: LogToolHandler;
 14 | 
 15 |   beforeEach(() => {
 16 |     mockLogger = {
 17 |       debug: jest.fn(),
 18 |       log: jest.fn(),
 19 |       error: jest.fn(),
 20 |       timing: jest.fn(),
 21 |       methodEntry: jest.fn(),
 22 |       methodExit: jest.fn(),
 23 |     } as any;
 24 | 
 25 |     mockLogClient = {
 26 |       getLatestLogs: jest.fn(),
 27 |       summarizeLogs: jest.fn(),
 28 |       searchLogs: jest.fn(),
 29 |       listLogFiles: jest.fn(),
 30 |       getLogFileContents: jest.fn(),
 31 |     } as any;
 32 | 
 33 |     (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockImplementation(() => mockLogClient);
 34 | 
 35 |     jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger);
 36 | 
 37 |     context = {
 38 |       logger: mockLogger,
 39 |       config: {
 40 |         hostname: 'test.demandware.net',
 41 |         username: 'test',
 42 |         password: 'test',
 43 |         clientId: 'test',
 44 |         clientSecret: 'test',
 45 |       },
 46 |       capabilities: { canAccessLogs: true, canAccessOCAPI: true },
 47 |     };
 48 | 
 49 |     handler = new LogToolHandler(context, 'Log');
 50 |   });
 51 | 
 52 |   afterEach(() => {
 53 |     jest.restoreAllMocks();
 54 |   });
 55 | 
 56 |   describe('canHandle', () => {
 57 |     it('should handle log-related tools', () => {
 58 |       expect(handler.canHandle('get_latest_error')).toBe(true);
 59 |       expect(handler.canHandle('get_latest_warn')).toBe(true);
 60 |       expect(handler.canHandle('get_latest_info')).toBe(true);
 61 |       expect(handler.canHandle('get_latest_debug')).toBe(true);
 62 |       expect(handler.canHandle('summarize_logs')).toBe(true);
 63 |       expect(handler.canHandle('search_logs')).toBe(true);
 64 |       expect(handler.canHandle('list_log_files')).toBe(true);
 65 |       expect(handler.canHandle('get_log_file_contents')).toBe(true);
 66 |     });
 67 | 
 68 |     it('should not handle non-log tools', () => {
 69 |       expect(handler.canHandle('get_sfcc_class_info')).toBe(false);
 70 |       expect(handler.canHandle('unknown_tool')).toBe(false);
 71 |     });
 72 |   });
 73 | 
 74 |   // Helper function to initialize handler for tests that need it
 75 |   const initializeHandler = async () => {
 76 |     await (handler as any).initialize();
 77 |   };
 78 | 
 79 |   describe('initialization', () => {
 80 |     it('should initialize log client when capabilities allow', async () => {
 81 |       // Manually trigger initialization
 82 |       await (handler as any).initialize();
 83 | 
 84 |       expect(SFCCLogClient).toHaveBeenCalledWith(context.config);
 85 |       expect(mockLogger.debug).toHaveBeenCalledWith('Log client initialized');
 86 |     });
 87 | 
 88 |     it('should not initialize log client when capabilities do not allow', async () => {
 89 |       const contextWithoutLogs = {
 90 |         ...context,
 91 |         capabilities: { canAccessLogs: false, canAccessOCAPI: false },
 92 |       };
 93 |       const handlerWithoutLogs = new LogToolHandler(contextWithoutLogs, 'Log');
 94 | 
 95 |       // Manually trigger initialization to test the capabilities check
 96 |       await (handlerWithoutLogs as any).initialize();
 97 | 
 98 |       // Reset the mock counter for this test
 99 |       (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockClear();
100 | 
101 |       const result = await handlerWithoutLogs.handle('get_latest_error', {}, Date.now());
102 |       expect(result.isError).toBe(true);
103 |       expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.');
104 | 
105 |       expect(SFCCLogClient).not.toHaveBeenCalled();
106 |     });
107 | 
108 |     it('should not initialize without config', async () => {
109 |       const contextWithoutConfig = {
110 |         ...context,
111 |         config: null as any,
112 |       };
113 |       const handlerWithoutConfig = new LogToolHandler(contextWithoutConfig, 'Log');
114 | 
115 |       const result = await handlerWithoutConfig.handle('get_latest_error', {}, Date.now());
116 |       expect(result.isError).toBe(true);
117 |       expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.');
118 |     });
119 |   });
120 | 
121 |   describe('disposal', () => {
122 |     it('should dispose log client properly', async () => {
123 |       // Initialize first
124 |       await initializeHandler();
125 | 
126 |       // Dispose
127 |       await (handler as any).dispose();
128 | 
129 |       expect(mockLogger.debug).toHaveBeenCalledWith('Log client disposed');
130 |     });
131 |   });
132 | 
133 |   describe('get_latest_* tools', () => {
134 |     beforeEach(async () => {
135 |       await initializeHandler();
136 |       mockLogClient.getLatestLogs.mockResolvedValue('Test log entry\n2023-01-01T00:00:00Z');
137 |     });
138 | 
139 |     it('should handle get_latest_error', async () => {
140 |       const result = await handler.handle('get_latest_error', { limit: 5, date: '20230101' }, Date.now());
141 | 
142 |       expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('error', 5, '20230101');
143 |       expect(result.content[0].text).toContain('Test log entry');
144 |       expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=20230101');
145 |     });
146 | 
147 |     it('should handle get_latest_warn with default parameters', async () => {
148 |       await handler.handle('get_latest_warn', {}, Date.now());
149 | 
150 |       expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('warn', 10, undefined);
151 |       expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest warn logs limit=10 date=today');
152 |     });
153 | 
154 |     it('should handle get_latest_info', async () => {
155 |       await handler.handle('get_latest_info', { limit: 15 }, Date.now());
156 | 
157 |       expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('info', 15, undefined);
158 |     });
159 | 
160 |     it('should handle get_latest_debug', async () => {
161 |       await handler.handle('get_latest_debug', { date: '20230101' }, Date.now());
162 | 
163 |       expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('debug', 10, '20230101');
164 |     });
165 |   });
166 | 
167 |   describe('summarize_logs tool', () => {
168 |     beforeEach(async () => {
169 |       await initializeHandler();
170 |     });
171 |     it('should handle summarize_logs', async () => {
172 |       const mockSummary = JSON.stringify({
173 |         date: '20230101',
174 |         totalLogs: 100,
175 |         errorCount: 5,
176 |         warnCount: 10,
177 |         infoCount: 85,
178 |       });
179 |       mockLogClient.summarizeLogs.mockResolvedValue(mockSummary);
180 | 
181 |       await handler.handle('summarize_logs', { date: '20230101' }, Date.now());
182 | 
183 |       expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith('20230101');
184 |       expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date 20230101');
185 |     });
186 | 
187 |     it('should handle summarize_logs with default date', async () => {
188 |       const mockSummary = JSON.stringify({ date: 'today', totalLogs: 50 });
189 |       mockLogClient.summarizeLogs.mockResolvedValue(mockSummary);
190 | 
191 |       await handler.handle('summarize_logs', {}, Date.now());
192 | 
193 |       expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith(undefined);
194 |       expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date today');
195 |     });
196 |   });
197 | 
198 |   describe('search_logs tool', () => {
199 |     beforeEach(async () => {
200 |       await initializeHandler();
201 |     });
202 |     it('should handle search_logs with required pattern', async () => {
203 |       const mockResults = JSON.stringify({
204 |         results: [{ message: 'Error occurred', timestamp: '2023-01-01T00:00:00Z' }],
205 |         total: 1,
206 |       });
207 |       mockLogClient.searchLogs.mockResolvedValue(mockResults);
208 | 
209 |       const args = { pattern: 'error', logLevel: 'error', limit: 25, date: '20230101' };
210 |       const result = await handler.handle('search_logs', args, Date.now());
211 | 
212 |       expect(mockLogClient.searchLogs).toHaveBeenCalledWith('error', 'error', 25, '20230101');
213 |       expect(result.content[0].text).toContain('Error occurred');
214 |       expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="error" level=error limit=25');
215 |     });
216 | 
217 |     it('should handle search_logs with default parameters', async () => {
218 |       const mockResults = JSON.stringify({ results: [], total: 0 });
219 |       mockLogClient.searchLogs.mockResolvedValue(mockResults);
220 | 
221 |       await handler.handle('search_logs', { pattern: 'test' }, Date.now());
222 | 
223 |       expect(mockLogClient.searchLogs).toHaveBeenCalledWith('test', undefined, 20, undefined);
224 |       expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="test" level=all limit=20');
225 |     });
226 | 
227 |     it('should throw error when pattern is missing', async () => {
228 |       const result = await handler.handle('search_logs', {}, Date.now());
229 |       expect(result.isError).toBe(true);
230 |       expect(result.content[0].text).toContain('pattern must be a non-empty string');
231 |     });
232 | 
233 |     it('should throw error when pattern is empty', async () => {
234 |       const result = await handler.handle('search_logs', { pattern: '' }, Date.now());
235 |       expect(result.isError).toBe(true);
236 |       expect(result.content[0].text).toContain('pattern must be a non-empty string');
237 |     });
238 |   });
239 | 
240 |   describe('get_log_file_contents tool', () => {
241 |     beforeEach(async () => {
242 |       await initializeHandler();
243 |     });
244 | 
245 |     it('should handle get_log_file_contents with filename', async () => {
246 |       const mockFileContents = 'Log file contents with some test data\nMore log entries...';
247 |       mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents);
248 | 
249 |       const result = await handler.handle('get_log_file_contents', { filename: 'error-2023-01-01.log' }, Date.now());
250 | 
251 |       expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('error-2023-01-01.log', undefined, undefined);
252 |       expect(result.content[0].text).toContain('Log file contents with some test data');
253 |       expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: error-2023-01-01.log (maxBytes=default, tailOnly=false)');
254 |     });
255 | 
256 |     it('should handle get_log_file_contents with maxBytes and tailOnly options', async () => {
257 |       const mockFileContents = 'Tail content of log file...';
258 |       mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents);
259 | 
260 |       const result = await handler.handle('get_log_file_contents', {
261 |         filename: 'large-log.log',
262 |         maxBytes: 1024,
263 |         tailOnly: true,
264 |       }, Date.now());
265 | 
266 |       expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 1024, true);
267 |       expect(result.content[0].text).toContain('Tail content of log file');
268 |       expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=1024, tailOnly=true)');
269 |     });
270 | 
271 |     it('should handle get_log_file_contents with maxBytes and tailOnly=false (full file with size limit)', async () => {
272 |       const mockFileContents = 'Full file content with size limit applied...';
273 |       mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents);
274 | 
275 |       const result = await handler.handle('get_log_file_contents', {
276 |         filename: 'large-log.log',
277 |         maxBytes: 512,
278 |         tailOnly: false,
279 |       }, Date.now());
280 | 
281 |       expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 512, false);
282 |       expect(result.content[0].text).toContain('Full file content with size limit');
283 |       expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=512, tailOnly=false)');
284 |     });
285 | 
286 |     it('should require filename parameter', async () => {
287 |       const result = await handler.handle('get_log_file_contents', {}, Date.now());
288 | 
289 |       expect(result.isError).toBe(true);
290 |       expect(result.content[0].text).toContain('filename must be a non-empty string');
291 |     });
292 | 
293 |     it('should handle empty filename', async () => {
294 |       const result = await handler.handle('get_log_file_contents', { filename: '' }, Date.now());
295 | 
296 |       expect(result.isError).toBe(true);
297 |       expect(result.content[0].text).toContain('filename must be a non-empty string');
298 |     });
299 |   });
300 | 
301 |   describe('list_log_files tool', () => {
302 |     beforeEach(async () => {
303 |       await initializeHandler();
304 |     });
305 |     it('should handle list_log_files', async () => {
306 |       const mockFiles = JSON.stringify([
307 |         { name: 'error-2023-01-01.log', size: 1024, modified: '2023-01-01T00:00:00Z' },
308 |         { name: 'info-2023-01-01.log', size: 2048, modified: '2023-01-01T00:00:00Z' },
309 |       ]);
310 |       mockLogClient.listLogFiles.mockResolvedValue(mockFiles);
311 | 
312 |       const result = await handler.handle('list_log_files', {}, Date.now());
313 | 
314 |       expect(mockLogClient.listLogFiles).toHaveBeenCalled();
315 |       expect(result.content[0].text).toContain('error-2023-01-01.log');
316 |       expect(result.content[0].text).toContain('info-2023-01-01.log');
317 |       expect(mockLogger.debug).toHaveBeenCalledWith('Listing log files');
318 |     });
319 |   });
320 | 
321 |   describe('error handling', () => {
322 |     beforeEach(async () => {
323 |       await initializeHandler();
324 |     });
325 | 
326 |     it('should handle client errors gracefully', async () => {
327 |       mockLogClient.getLatestLogs.mockRejectedValue(new Error('Client connection failed'));
328 | 
329 |       const result = await handler.handle('get_latest_error', {}, Date.now());
330 |       expect(result.isError).toBe(true);
331 |       expect(result.content[0].text).toContain('Client connection failed');
332 |     });
333 | 
334 |     it('should throw error for unsupported tools', async () => {
335 |       await expect(handler.handle('unsupported_tool', {}, Date.now()))
336 |         .rejects.toThrow('Unsupported tool');
337 |     });
338 |   });
339 | 
340 |   describe('timing and logging', () => {
341 |     beforeEach(async () => {
342 |       await initializeHandler();
343 |     });
344 |     it('should log timing information', async () => {
345 |       mockLogClient.getLatestLogs.mockResolvedValue('empty logs');
346 | 
347 |       const startTime = Date.now();
348 |       await handler.handle('get_latest_error', {}, startTime);
349 | 
350 |       expect(mockLogger.timing).toHaveBeenCalledWith('get_latest_error', startTime);
351 |     });
352 | 
353 |     it('should log execution details', async () => {
354 |       mockLogClient.getLatestLogs.mockResolvedValue('test logs');
355 | 
356 |       await handler.handle('get_latest_error', { limit: 5 }, Date.now());
357 | 
358 |       expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=today');
359 |       expect(mockLogger.debug).toHaveBeenCalledWith(
360 |         'get_latest_error completed successfully',
361 |         expect.any(Object),
362 |       );
363 |     });
364 |   });
365 | });
366 | 
```

--------------------------------------------------------------------------------
/docs/TopLevel/Object.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: TopLevel
  2 | 
  3 | # Class Object
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 | 
  9 | ## Description
 10 | 
 11 | The Object object is the foundation of all native JavaScript objects. Also, the Object object can be used to generate items in your scripts with behaviors that are defined by custom properties and/or methods. You generally start by creating a blank object with the constructor function and then assign values to new properties of that object.
 12 | 
 13 | ## Constructor Summary
 14 | 
 15 | Object() Object constructor.
 16 | 
 17 | ## Method Summary
 18 | 
 19 | ### assign
 20 | 
 21 | **Signature:** `static assign(target : Object, sources : Object...) : Object`
 22 | 
 23 | Copies the values of all of the enumerable own properties from one or more source objects to a target object.
 24 | 
 25 | ### create
 26 | 
 27 | **Signature:** `static create(prototype : Object) : Object`
 28 | 
 29 | Creates a new object based on a prototype object.
 30 | 
 31 | ### create
 32 | 
 33 | **Signature:** `static create(prototype : Object, properties : Object) : Object`
 34 | 
 35 | Creates a new object based on a prototype object and additional property definitions.
 36 | 
 37 | ### defineProperties
 38 | 
 39 | **Signature:** `static defineProperties(object : Object, properties : Object) : Object`
 40 | 
 41 | Defines or modifies properties of the passed object.
 42 | 
 43 | ### defineProperty
 44 | 
 45 | **Signature:** `static defineProperty(object : Object, propertyKey : Object, descriptor : Object) : Object`
 46 | 
 47 | Defines or modifies a single property of the passed object.
 48 | 
 49 | ### entries
 50 | 
 51 | **Signature:** `static entries(object : Object) : Array`
 52 | 
 53 | Returns the enumerable property names and their values of the passed object.
 54 | 
 55 | ### freeze
 56 | 
 57 | **Signature:** `static freeze(object : Object) : Object`
 58 | 
 59 | Freezes the passed object.
 60 | 
 61 | ### fromEntries
 62 | 
 63 | **Signature:** `static fromEntries(properties : Iterable) : Object`
 64 | 
 65 | Creates a new object with defined properties.
 66 | 
 67 | ### getOwnPropertyDescriptor
 68 | 
 69 | **Signature:** `static getOwnPropertyDescriptor(object : Object, propertyKey : Object) : Object`
 70 | 
 71 | Returns the descriptor for a single property of the passed object.
 72 | 
 73 | ### getOwnPropertyNames
 74 | 
 75 | **Signature:** `static getOwnPropertyNames(object : Object) : Array`
 76 | 
 77 | Returns an arrays containing the names of all enumerable and non-enumerable properties owned by the passed object.
 78 | 
 79 | ### getOwnPropertySymbols
 80 | 
 81 | **Signature:** `static getOwnPropertySymbols(object : Object) : Array`
 82 | 
 83 | Returns an array containing the symbol of all symbol properties owned by the passed object.
 84 | 
 85 | ### getPrototypeOf
 86 | 
 87 | **Signature:** `static getPrototypeOf(object : Object) : Object`
 88 | 
 89 | Returns the prototype of the passed object.
 90 | 
 91 | ### hasOwnProperty
 92 | 
 93 | **Signature:** `hasOwnProperty(propName : String) : boolean`
 94 | 
 95 | Returns Boolean true if at the time the current object's instance was created its constructor (or literal assignment) contained a property with a name that matches the parameter value.
 96 | 
 97 | ### is
 98 | 
 99 | **Signature:** `static is(value1 : Object, value2 : Object) : boolean`
100 | 
101 | Checks if the two values are equal in terms of being the same value.
102 | 
103 | ### isExtensible
104 | 
105 | **Signature:** `static isExtensible(object : Object) : boolean`
106 | 
107 | Returns if new properties can be added to an object.
108 | 
109 | ### isFrozen
110 | 
111 | **Signature:** `static isFrozen(object : Object) : boolean`
112 | 
113 | Returns if the object is frozen.
114 | 
115 | ### isPrototypeOf
116 | 
117 | **Signature:** `isPrototypeOf(prototype : Object) : boolean`
118 | 
119 | Returns true if the current object and the object passed as a prameter conincide at some point along each object's prototype inheritance chain.
120 | 
121 | ### isSealed
122 | 
123 | **Signature:** `static isSealed(object : Object) : boolean`
124 | 
125 | Returns if the object is sealed.
126 | 
127 | ### keys
128 | 
129 | **Signature:** `static keys(object : Object) : Array`
130 | 
131 | Returns the enumerable property names of the passed object.
132 | 
133 | ### preventExtensions
134 | 
135 | **Signature:** `static preventExtensions(object : Object) : Object`
136 | 
137 | Makes the passed object non-extensible.
138 | 
139 | ### propertyIsEnumerable
140 | 
141 | **Signature:** `propertyIsEnumerable(propName : String) : boolean`
142 | 
143 | Return true if the specified property exposes itself to for/in property inspection through the object.
144 | 
145 | ### seal
146 | 
147 | **Signature:** `static seal(object : Object) : Object`
148 | 
149 | Seals the passed object.
150 | 
151 | ### setPrototypeOf
152 | 
153 | **Signature:** `static setPrototypeOf(object : Object, prototype : Object) : Object`
154 | 
155 | Changes the prototype of the passed object.
156 | 
157 | ### toLocaleString
158 | 
159 | **Signature:** `toLocaleString() : String`
160 | 
161 | Converts the object to a localized String.
162 | 
163 | ### toString
164 | 
165 | **Signature:** `toString() : String`
166 | 
167 | Converts the object to a String.
168 | 
169 | ### valueOf
170 | 
171 | **Signature:** `valueOf() : Object`
172 | 
173 | Returns the object's value.
174 | 
175 | ### values
176 | 
177 | **Signature:** `static values(object : Object) : Array`
178 | 
179 | Returns the enumerable property values of the passed object.
180 | 
181 | ## Constructor Detail
182 | 
183 | ## Method Detail
184 | 
185 | ## Method Details
186 | 
187 | ### assign
188 | 
189 | **Signature:** `static assign(target : Object, sources : Object...) : Object`
190 | 
191 | **Description:** Copies the values of all of the enumerable own properties from one or more source objects to a target object.
192 | 
193 | **API Versioned:**
194 | 
195 | From version 21.2.
196 | 
197 | **Parameters:**
198 | 
199 | - `target`: The target object.
200 | - `sources`: The source objects.
201 | 
202 | **Returns:**
203 | 
204 | The target object.
205 | 
206 | ---
207 | 
208 | ### create
209 | 
210 | **Signature:** `static create(prototype : Object) : Object`
211 | 
212 | **Description:** Creates a new object based on a prototype object.
213 | 
214 | **Parameters:**
215 | 
216 | - `prototype`: The prototype for the new object.
217 | 
218 | **Returns:**
219 | 
220 | The newly created object.
221 | 
222 | ---
223 | 
224 | ### create
225 | 
226 | **Signature:** `static create(prototype : Object, properties : Object) : Object`
227 | 
228 | **Description:** Creates a new object based on a prototype object and additional property definitions. The properties are given in the same format as described for defineProperties(Object, Object).
229 | 
230 | **Parameters:**
231 | 
232 | - `prototype`: The prototype for the new object.
233 | - `properties`: The property definitions.
234 | 
235 | **Returns:**
236 | 
237 | The newly created object.
238 | 
239 | ---
240 | 
241 | ### defineProperties
242 | 
243 | **Signature:** `static defineProperties(object : Object, properties : Object) : Object`
244 | 
245 | **Description:** Defines or modifies properties of the passed object. A descriptor for a property supports these properties: configurable, enumerable, value, writable, set and get.
246 | 
247 | **Parameters:**
248 | 
249 | - `object`: The object to change.
250 | - `properties`: The new property definitions.
251 | 
252 | **Returns:**
253 | 
254 | The modified object.
255 | 
256 | ---
257 | 
258 | ### defineProperty
259 | 
260 | **Signature:** `static defineProperty(object : Object, propertyKey : Object, descriptor : Object) : Object`
261 | 
262 | **Description:** Defines or modifies a single property of the passed object. A descriptor for a property supports these properties: configurable, enumerable, value, writable, set and get.
263 | 
264 | **Parameters:**
265 | 
266 | - `object`: The object to change.
267 | - `propertyKey`: The property name.
268 | - `descriptor`: The property descriptor object.
269 | 
270 | **Returns:**
271 | 
272 | The modified object.
273 | 
274 | ---
275 | 
276 | ### entries
277 | 
278 | **Signature:** `static entries(object : Object) : Array`
279 | 
280 | **Description:** Returns the enumerable property names and their values of the passed object.
281 | 
282 | **API Versioned:**
283 | 
284 | From version 22.7.
285 | 
286 | **Parameters:**
287 | 
288 | - `object`: The object to get the enumerable property names from.
289 | 
290 | **Returns:**
291 | 
292 | An array of key/value pairs ( as two element arrays ) that holds all the enumerable properties of the given object.
293 | 
294 | ---
295 | 
296 | ### freeze
297 | 
298 | **Signature:** `static freeze(object : Object) : Object`
299 | 
300 | **Description:** Freezes the passed object. Properties can't be added or removed from the frozen object. Also, definitions of existing object properties can't be changed. Although property values are immutable, setters and getters can be called.
301 | 
302 | **Parameters:**
303 | 
304 | - `object`: The object to be frozen.
305 | 
306 | **Returns:**
307 | 
308 | The frozen object.
309 | 
310 | ---
311 | 
312 | ### fromEntries
313 | 
314 | **Signature:** `static fromEntries(properties : Iterable) : Object`
315 | 
316 | **Description:** Creates a new object with defined properties. The properties are defined by an iterable that produces two element array like objects, which are the key-value pairs. Iterables are e.g. Array, Map or any other Iterable.
317 | 
318 | **API Versioned:**
319 | 
320 | From version 22.7.
321 | 
322 | **Parameters:**
323 | 
324 | - `properties`: The properties.
325 | 
326 | **Returns:**
327 | 
328 | The newly created object.
329 | 
330 | ---
331 | 
332 | ### getOwnPropertyDescriptor
333 | 
334 | **Signature:** `static getOwnPropertyDescriptor(object : Object, propertyKey : Object) : Object`
335 | 
336 | **Description:** Returns the descriptor for a single property of the passed object.
337 | 
338 | **Parameters:**
339 | 
340 | - `object`: The property owning object.
341 | - `propertyKey`: The property to look for.
342 | 
343 | **Returns:**
344 | 
345 | The descriptor object for the property or undefined if the property does not exist.
346 | 
347 | ---
348 | 
349 | ### getOwnPropertyNames
350 | 
351 | **Signature:** `static getOwnPropertyNames(object : Object) : Array`
352 | 
353 | **Description:** Returns an arrays containing the names of all enumerable and non-enumerable properties owned by the passed object.
354 | 
355 | **Parameters:**
356 | 
357 | - `object`: The object owning properties.
358 | 
359 | **Returns:**
360 | 
361 | An array of strings that are the properties found directly in the passed object.
362 | 
363 | ---
364 | 
365 | ### getOwnPropertySymbols
366 | 
367 | **Signature:** `static getOwnPropertySymbols(object : Object) : Array`
368 | 
369 | **Description:** Returns an array containing the symbol of all symbol properties owned by the passed object.
370 | 
371 | **API Versioned:**
372 | 
373 | From version 21.2.
374 | 
375 | **Parameters:**
376 | 
377 | - `object`: The object owning properties.
378 | 
379 | **Returns:**
380 | 
381 | An array of symbol properties found directly in the passed object.
382 | 
383 | ---
384 | 
385 | ### getPrototypeOf
386 | 
387 | **Signature:** `static getPrototypeOf(object : Object) : Object`
388 | 
389 | **Description:** Returns the prototype of the passed object.
390 | 
391 | **Parameters:**
392 | 
393 | - `object`: The object to get the prototype from.
394 | 
395 | **Returns:**
396 | 
397 | The prototype object or null if there is none.
398 | 
399 | ---
400 | 
401 | ### hasOwnProperty
402 | 
403 | **Signature:** `hasOwnProperty(propName : String) : boolean`
404 | 
405 | **Description:** Returns Boolean true if at the time the current object's instance was created its constructor (or literal assignment) contained a property with a name that matches the parameter value.
406 | 
407 | **Parameters:**
408 | 
409 | - `propName`: the property name of the object's property.
410 | 
411 | **Returns:**
412 | 
413 | true if at the object contains a property that matches the parameter, false otherwise.
414 | 
415 | ---
416 | 
417 | ### is
418 | 
419 | **Signature:** `static is(value1 : Object, value2 : Object) : boolean`
420 | 
421 | **Description:** Checks if the two values are equal in terms of being the same value. No coercion is performed, thus -0 and +0 is not equal and NaN is equal to NaN.
422 | 
423 | **API Versioned:**
424 | 
425 | From version 21.2.
426 | 
427 | **Parameters:**
428 | 
429 | - `value1`: The first value.
430 | - `value2`: The second value.
431 | 
432 | **Returns:**
433 | 
434 | true if both values are the same value else false.
435 | 
436 | ---
437 | 
438 | ### isExtensible
439 | 
440 | **Signature:** `static isExtensible(object : Object) : boolean`
441 | 
442 | **Description:** Returns if new properties can be added to an object. By default new objects are extensible. The methods freeze(Object), seal(Object) and preventExtensions(Object) make objects non-extensible.
443 | 
444 | **Parameters:**
445 | 
446 | - `object`: The object to check.
447 | 
448 | **Returns:**
449 | 
450 | true if new properties can be added else false.
451 | 
452 | ---
453 | 
454 | ### isFrozen
455 | 
456 | **Signature:** `static isFrozen(object : Object) : boolean`
457 | 
458 | **Description:** Returns if the object is frozen.
459 | 
460 | **Parameters:**
461 | 
462 | - `object`: The object to check.
463 | 
464 | **Returns:**
465 | 
466 | true if the object is frozen else false.
467 | 
468 | ---
469 | 
470 | ### isPrototypeOf
471 | 
472 | **Signature:** `isPrototypeOf(prototype : Object) : boolean`
473 | 
474 | **Description:** Returns true if the current object and the object passed as a prameter conincide at some point along each object's prototype inheritance chain.
475 | 
476 | **Parameters:**
477 | 
478 | - `prototype`: the object to test.
479 | 
480 | **Returns:**
481 | 
482 | true if the current object and the object passed as a prameter conincide at some point, false otherwise.
483 | 
484 | ---
485 | 
486 | ### isSealed
487 | 
488 | **Signature:** `static isSealed(object : Object) : boolean`
489 | 
490 | **Description:** Returns if the object is sealed.
491 | 
492 | **Parameters:**
493 | 
494 | - `object`: The object to check.
495 | 
496 | **Returns:**
497 | 
498 | true if the object is sealed else false.
499 | 
500 | ---
501 | 
502 | ### keys
503 | 
504 | **Signature:** `static keys(object : Object) : Array`
505 | 
506 | **Description:** Returns the enumerable property names of the passed object.
507 | 
508 | **Parameters:**
509 | 
510 | - `object`: The object to get the enumerable property names from.
511 | 
512 | **Returns:**
513 | 
514 | An array of strings that holds all the enumerable properties of the given object.
515 | 
516 | ---
517 | 
518 | ### preventExtensions
519 | 
520 | **Signature:** `static preventExtensions(object : Object) : Object`
521 | 
522 | **Description:** Makes the passed object non-extensible. This means that no new properties can be added to this object.
523 | 
524 | **Parameters:**
525 | 
526 | - `object`: The object to make non-extensible.
527 | 
528 | **Returns:**
529 | 
530 | The passed object.
531 | 
532 | ---
533 | 
534 | ### propertyIsEnumerable
535 | 
536 | **Signature:** `propertyIsEnumerable(propName : String) : boolean`
537 | 
538 | **Description:** Return true if the specified property exposes itself to for/in property inspection through the object.
539 | 
540 | **Parameters:**
541 | 
542 | - `propName`: the property to test.
543 | 
544 | **Returns:**
545 | 
546 | true if the specified property exposes itself to for/in property inspection through the object, false otherwise.
547 | 
548 | ---
549 | 
550 | ### seal
551 | 
552 | **Signature:** `static seal(object : Object) : Object`
553 | 
554 | **Description:** Seals the passed object. This means properties can't be added or removed. Also, property definitions of existing properties can't be changed.
555 | 
556 | **Parameters:**
557 | 
558 | - `object`: The object to be frozen.
559 | 
560 | **Returns:**
561 | 
562 | The sealed object.
563 | 
564 | ---
565 | 
566 | ### setPrototypeOf
567 | 
568 | **Signature:** `static setPrototypeOf(object : Object, prototype : Object) : Object`
569 | 
570 | **Description:** Changes the prototype of the passed object.
571 | 
572 | **API Versioned:**
573 | 
574 | From version 21.2.
575 | 
576 | **Parameters:**
577 | 
578 | - `object`: The object whose prototype should change.
579 | - `prototype`: The object to set as the new prototype.
580 | 
581 | **Returns:**
582 | 
583 | The object with the changed prototype.
584 | 
585 | ---
586 | 
587 | ### toLocaleString
588 | 
589 | **Signature:** `toLocaleString() : String`
590 | 
591 | **Description:** Converts the object to a localized String.
592 | 
593 | **Returns:**
594 | 
595 | a localized version of the object.
596 | 
597 | ---
598 | 
599 | ### toString
600 | 
601 | **Signature:** `toString() : String`
602 | 
603 | **Description:** Converts the object to a String.
604 | 
605 | **Returns:**
606 | 
607 | the String representation of the object.
608 | 
609 | ---
610 | 
611 | ### valueOf
612 | 
613 | **Signature:** `valueOf() : Object`
614 | 
615 | **Description:** Returns the object's value.
616 | 
617 | **Returns:**
618 | 
619 | the object's value.
620 | 
621 | ---
622 | 
623 | ### values
624 | 
625 | **Signature:** `static values(object : Object) : Array`
626 | 
627 | **Description:** Returns the enumerable property values of the passed object.
628 | 
629 | **API Versioned:**
630 | 
631 | From version 22.7.
632 | 
633 | **Parameters:**
634 | 
635 | - `object`: The object to get the enumerable property values from.
636 | 
637 | **Returns:**
638 | 
639 | An array of values that holds all the enumerable properties of the given object.
640 | 
641 | ---
```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/mock-data/ocapi/site-preferences-storefront.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "_v": "23.2",
  3 |   "_type": "preference_value_search_result",
  4 |   "count": 8,
  5 |   "hits": [
  6 |     {
  7 |       "_type": "preference_value",
  8 |       "attribute_definition": {
  9 |         "_type": "object_attribute_definition",
 10 |         "_resource_state": "storefront1-resource-state",
 11 |         "creation_date": "2024-01-01T00:00:00.000Z",
 12 |         "description": {
 13 |           "default": "Enable or disable the shopping cart functionality"
 14 |         },
 15 |         "display_name": {
 16 |           "default": "Shopping Cart Enabled"
 17 |         },
 18 |         "effective_id": "c_shoppingCartEnabled",
 19 |         "externally_defined": false,
 20 |         "externally_managed": false,
 21 |         "id": "shoppingCartEnabled",
 22 |         "key": false,
 23 |         "last_modified": "2024-01-01T00:00:00.000Z",
 24 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/shoppingCartEnabled",
 25 |         "localizable": false,
 26 |         "mandatory": false,
 27 |         "multi_value_type": false,
 28 |         "order_required": false,
 29 |         "queryable": false,
 30 |         "read_only": false,
 31 |         "requires_encoding": false,
 32 |         "searchable": false,
 33 |         "set_value_type": false,
 34 |         "site_specific": true,
 35 |         "system": false,
 36 |         "value_type": "boolean",
 37 |         "visible": true
 38 |       },
 39 |       "description": {
 40 |         "default": "Enable or disable the shopping cart functionality"
 41 |       },
 42 |       "display_name": {
 43 |         "default": "Shopping Cart Enabled"
 44 |       },
 45 |       "id": "shoppingCartEnabled",
 46 |       "site_values": {
 47 |         "RefArch": true,
 48 |         "RefArchGlobal": true,
 49 |         "pxl_1": false,
 50 |         "pxl_2": true,
 51 |         "pxl_3": null,
 52 |         "pxl_4": null,
 53 |         "pxl_5": null,
 54 |         "pxl_6": null
 55 |       },
 56 |       "value_type": "boolean"
 57 |     },
 58 |     {
 59 |       "_type": "preference_value",
 60 |       "attribute_definition": {
 61 |         "_type": "object_attribute_definition",
 62 |         "_resource_state": "storefront2-resource-state",
 63 |         "creation_date": "2024-01-01T00:00:00.000Z",
 64 |         "description": {
 65 |           "default": "Maximum number of items allowed in shopping cart"
 66 |         },
 67 |         "display_name": {
 68 |           "default": "Max Cart Items"
 69 |         },
 70 |         "effective_id": "c_maxCartItems",
 71 |         "externally_defined": false,
 72 |         "externally_managed": false,
 73 |         "id": "maxCartItems",
 74 |         "key": false,
 75 |         "last_modified": "2024-01-01T00:00:00.000Z",
 76 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/maxCartItems",
 77 |         "localizable": false,
 78 |         "mandatory": true,
 79 |         "multi_value_type": false,
 80 |         "order_required": false,
 81 |         "queryable": false,
 82 |         "read_only": false,
 83 |         "requires_encoding": false,
 84 |         "searchable": false,
 85 |         "set_value_type": false,
 86 |         "site_specific": true,
 87 |         "system": false,
 88 |         "value_type": "int",
 89 |         "visible": true
 90 |       },
 91 |       "description": {
 92 |         "default": "Maximum number of items allowed in shopping cart"
 93 |       },
 94 |       "display_name": {
 95 |         "default": "Max Cart Items"
 96 |       },
 97 |       "id": "maxCartItems",
 98 |       "site_values": {
 99 |         "RefArch": 50,
100 |         "RefArchGlobal": 100,
101 |         "pxl_1": 25,
102 |         "pxl_2": 75,
103 |         "pxl_3": null,
104 |         "pxl_4": null,
105 |         "pxl_5": null,
106 |         "pxl_6": null
107 |       },
108 |       "value_type": "int"
109 |     },
110 |     {
111 |       "_type": "preference_value",
112 |       "attribute_definition": {
113 |         "_type": "object_attribute_definition",
114 |         "_resource_state": "storefront3-resource-state",
115 |         "creation_date": "2024-01-01T00:00:00.000Z",
116 |         "description": {
117 |           "default": "Welcome message displayed on homepage"
118 |         },
119 |         "display_name": {
120 |           "default": "Homepage Welcome Message"
121 |         },
122 |         "effective_id": "c_homepageWelcomeMessage",
123 |         "externally_defined": false,
124 |         "externally_managed": false,
125 |         "field_height": 5,
126 |         "id": "homepageWelcomeMessage",
127 |         "key": false,
128 |         "last_modified": "2024-01-01T00:00:00.000Z",
129 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/homepageWelcomeMessage",
130 |         "localizable": true,
131 |         "mandatory": false,
132 |         "multi_value_type": false,
133 |         "order_required": false,
134 |         "queryable": false,
135 |         "read_only": false,
136 |         "requires_encoding": true,
137 |         "searchable": true,
138 |         "set_value_type": false,
139 |         "site_specific": true,
140 |         "system": false,
141 |         "value_type": "text",
142 |         "visible": true
143 |       },
144 |       "description": {
145 |         "default": "Welcome message displayed on homepage"
146 |       },
147 |       "display_name": {
148 |         "default": "Homepage Welcome Message"
149 |       },
150 |       "id": "homepageWelcomeMessage",
151 |       "site_values": {
152 |         "RefArch": "Welcome to our amazing store!",
153 |         "RefArchGlobal": "Discover the best products worldwide!",
154 |         "pxl_1": "Pixels Store - Your tech destination",
155 |         "pxl_2": "Premium electronics await you",
156 |         "pxl_3": null,
157 |         "pxl_4": null,
158 |         "pxl_5": null,
159 |         "pxl_6": null
160 |       },
161 |       "value_type": "text"
162 |     },
163 |     {
164 |       "_type": "preference_value",
165 |       "attribute_definition": {
166 |         "_type": "object_attribute_definition",
167 |         "_resource_state": "storefront4-resource-state",
168 |         "creation_date": "2024-01-01T00:00:00.000Z",
169 |         "description": {
170 |           "default": "Discount percentage for new customers"
171 |         },
172 |         "display_name": {
173 |           "default": "New Customer Discount"
174 |         },
175 |         "effective_id": "c_newCustomerDiscount",
176 |         "externally_defined": false,
177 |         "externally_managed": false,
178 |         "id": "newCustomerDiscount",
179 |         "key": false,
180 |         "last_modified": "2024-01-01T00:00:00.000Z",
181 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/newCustomerDiscount",
182 |         "localizable": false,
183 |         "mandatory": false,
184 |         "multi_value_type": false,
185 |         "order_required": false,
186 |         "queryable": false,
187 |         "read_only": false,
188 |         "requires_encoding": false,
189 |         "searchable": false,
190 |         "set_value_type": false,
191 |         "site_specific": true,
192 |         "system": false,
193 |         "value_type": "double",
194 |         "visible": true
195 |       },
196 |       "description": {
197 |         "default": "Discount percentage for new customers"
198 |       },
199 |       "display_name": {
200 |         "default": "New Customer Discount"
201 |       },
202 |       "id": "newCustomerDiscount",
203 |       "site_values": {
204 |         "RefArch": 10.5,
205 |         "RefArchGlobal": 15.0,
206 |         "pxl_1": 5.0,
207 |         "pxl_2": 12.5,
208 |         "pxl_3": null,
209 |         "pxl_4": null,
210 |         "pxl_5": null,
211 |         "pxl_6": null
212 |       },
213 |       "value_type": "double"
214 |     },
215 |     {
216 |       "_type": "preference_value",
217 |       "attribute_definition": {
218 |         "_type": "object_attribute_definition",
219 |         "_resource_state": "storefront5-resource-state",
220 |         "creation_date": "2024-01-01T00:00:00.000Z",
221 |         "description": {
222 |           "default": "Available payment methods for checkout"
223 |         },
224 |         "display_name": {
225 |           "default": "Payment Methods"
226 |         },
227 |         "effective_id": "c_paymentMethods",
228 |         "externally_defined": false,
229 |         "externally_managed": false,
230 |         "id": "paymentMethods",
231 |         "key": false,
232 |         "last_modified": "2024-01-01T00:00:00.000Z",
233 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/paymentMethods",
234 |         "localizable": false,
235 |         "mandatory": false,
236 |         "multi_value_type": true,
237 |         "order_required": false,
238 |         "queryable": false,
239 |         "read_only": false,
240 |         "requires_encoding": false,
241 |         "searchable": false,
242 |         "set_value_type": true,
243 |         "site_specific": true,
244 |         "system": false,
245 |         "value_type": "set_of_string",
246 |         "visible": true
247 |       },
248 |       "description": {
249 |         "default": "Available payment methods for checkout"
250 |       },
251 |       "display_name": {
252 |         "default": "Payment Methods"
253 |       },
254 |       "id": "paymentMethods",
255 |       "site_values": {
256 |         "RefArch": "[\"CREDIT_CARD\", \"PAYPAL\", \"APPLE_PAY\"]",
257 |         "RefArchGlobal": "[\"CREDIT_CARD\", \"PAYPAL\", \"GOOGLE_PAY\", \"BANK_TRANSFER\"]",
258 |         "pxl_1": "[\"CREDIT_CARD\", \"PAYPAL\"]",
259 |         "pxl_2": "[\"CREDIT_CARD\", \"APPLE_PAY\", \"GOOGLE_PAY\"]",
260 |         "pxl_3": null,
261 |         "pxl_4": null,
262 |         "pxl_5": null,
263 |         "pxl_6": null
264 |       },
265 |       "value_type": "set_of_string"
266 |     },
267 |     {
268 |       "_type": "preference_value",
269 |       "attribute_definition": {
270 |         "_type": "object_attribute_definition",
271 |         "_resource_state": "storefront6-resource-state",
272 |         "creation_date": "2024-01-01T00:00:00.000Z",
273 |         "description": {
274 |           "default": "Date when the current promotion ends"
275 |         },
276 |         "display_name": {
277 |           "default": "Promotion End Date"
278 |         },
279 |         "effective_id": "c_promotionEndDate",
280 |         "externally_defined": false,
281 |         "externally_managed": false,
282 |         "id": "promotionEndDate",
283 |         "key": false,
284 |         "last_modified": "2024-01-01T00:00:00.000Z",
285 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/promotionEndDate",
286 |         "localizable": false,
287 |         "mandatory": false,
288 |         "multi_value_type": false,
289 |         "order_required": false,
290 |         "queryable": false,
291 |         "read_only": false,
292 |         "requires_encoding": false,
293 |         "searchable": false,
294 |         "set_value_type": false,
295 |         "site_specific": true,
296 |         "system": false,
297 |         "value_type": "date",
298 |         "visible": true
299 |       },
300 |       "description": {
301 |         "default": "Date when the current promotion ends"
302 |       },
303 |       "display_name": {
304 |         "default": "Promotion End Date"
305 |       },
306 |       "id": "promotionEndDate",
307 |       "site_values": {
308 |         "RefArch": "2025-12-31",
309 |         "RefArchGlobal": "2025-11-30",
310 |         "pxl_1": "2025-10-31",
311 |         "pxl_2": "2025-12-25",
312 |         "pxl_3": null,
313 |         "pxl_4": null,
314 |         "pxl_5": null,
315 |         "pxl_6": null
316 |       },
317 |       "value_type": "date"
318 |     },
319 |     {
320 |       "_type": "preference_value",
321 |       "attribute_definition": {
322 |         "_type": "object_attribute_definition",
323 |         "_resource_state": "storefront7-resource-state",
324 |         "creation_date": "2024-01-01T00:00:00.000Z",
325 |         "description": {
326 |           "default": "Custom CSS for site styling"
327 |         },
328 |         "display_name": {
329 |           "default": "Custom Site CSS"
330 |         },
331 |         "effective_id": "c_customSiteCSS",
332 |         "externally_defined": false,
333 |         "externally_managed": false,
334 |         "field_height": 15,
335 |         "id": "customSiteCSS",
336 |         "key": false,
337 |         "last_modified": "2024-01-01T00:00:00.000Z",
338 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/customSiteCSS",
339 |         "localizable": false,
340 |         "mandatory": false,
341 |         "multi_value_type": false,
342 |         "order_required": false,
343 |         "queryable": false,
344 |         "read_only": false,
345 |         "requires_encoding": true,
346 |         "searchable": false,
347 |         "set_value_type": false,
348 |         "site_specific": true,
349 |         "system": false,
350 |         "value_type": "html",
351 |         "visible": true
352 |       },
353 |       "description": {
354 |         "default": "Custom CSS for site styling"
355 |       },
356 |       "display_name": {
357 |         "default": "Custom Site CSS"
358 |       },
359 |       "id": "customSiteCSS",
360 |       "site_values": {
361 |         "RefArch": ".header { background-color: #ff6b35; }",
362 |         "RefArchGlobal": ".global-header { background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); }",
363 |         "pxl_1": ".tech-theme { color: #00ff00; background: #000; }",
364 |         "pxl_2": ".premium-theme { border: 2px solid gold; }",
365 |         "pxl_3": null,
366 |         "pxl_4": null,
367 |         "pxl_5": null,
368 |         "pxl_6": null
369 |       },
370 |       "value_type": "html"
371 |     },
372 |     {
373 |       "_type": "preference_value",
374 |       "attribute_definition": {
375 |         "_type": "object_attribute_definition",
376 |         "_resource_state": "storefront8-resource-state",
377 |         "creation_date": "2024-01-01T00:00:00.000Z",
378 |         "description": {
379 |           "default": "Password strength requirement level"
380 |         },
381 |         "display_name": {
382 |           "default": "Password Strength Level"
383 |         },
384 |         "effective_id": "c_passwordStrengthLevel",
385 |         "externally_defined": false,
386 |         "externally_managed": false,
387 |         "id": "passwordStrengthLevel",
388 |         "key": false,
389 |         "last_modified": "2024-01-01T00:00:00.000Z",
390 |         "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/passwordStrengthLevel",
391 |         "localizable": false,
392 |         "mandatory": true,
393 |         "multi_value_type": false,
394 |         "order_required": false,
395 |         "queryable": false,
396 |         "read_only": false,
397 |         "requires_encoding": false,
398 |         "searchable": false,
399 |         "set_value_type": false,
400 |         "site_specific": false,
401 |         "system": false,
402 |         "value_type": "enum_of_string",
403 |         "value_definitions": {
404 |           "WEAK": {
405 |             "display_value": {"default": "Weak"},
406 |             "value": "WEAK"
407 |           },
408 |           "MEDIUM": {
409 |             "display_value": {"default": "Medium"},
410 |             "value": "MEDIUM"
411 |           },
412 |           "STRONG": {
413 |             "display_value": {"default": "Strong"},
414 |             "value": "STRONG"
415 |           }
416 |         },
417 |         "visible": true
418 |       },
419 |       "description": {
420 |         "default": "Password strength requirement level"
421 |       },
422 |       "display_name": {
423 |         "default": "Password Strength Level"
424 |       },
425 |       "id": "passwordStrengthLevel",
426 |       "site_values": {
427 |         "RefArch": "MEDIUM",
428 |         "RefArchGlobal": "STRONG",
429 |         "pxl_1": "WEAK",
430 |         "pxl_2": "STRONG",
431 |         "pxl_3": null,
432 |         "pxl_4": null,
433 |         "pxl_5": null,
434 |         "pxl_6": null
435 |       },
436 |       "value_type": "enum_of_string"
437 |     }
438 |   ],
439 |   "query": {
440 |     "match_all_query": {
441 |       "_type": "match_all_query"
442 |     }
443 |   },
444 |   "select": "(**)",
445 |   "start": 0,
446 |   "total": 8
447 | }
```

--------------------------------------------------------------------------------
/docs/dw_customer/Customer.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.customer
  2 | 
  3 | # Class Customer
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.customer.Customer
  9 | 
 10 | ## Description
 11 | 
 12 | Represents a customer.
 13 | 
 14 | ## Properties
 15 | 
 16 | ### activeData
 17 | 
 18 | **Type:** CustomerActiveData (Read Only)
 19 | 
 20 | The active data for this customer.
 21 | 
 22 | ### addressBook
 23 | 
 24 | **Type:** AddressBook (Read Only)
 25 | 
 26 | The address book for the profile of this customer,
 27 |  or null if this customer has no profile, such as for an
 28 |  anonymous customer.
 29 | 
 30 | ### anonymous
 31 | 
 32 | **Type:** boolean (Read Only)
 33 | 
 34 | Identifies if the customer is anonymous. An anonymous
 35 |  customer is the opposite of a registered customer.
 36 | 
 37 | ### authenticated
 38 | 
 39 | **Type:** boolean (Read Only)
 40 | 
 41 | Identifies if the customer is authenticated. This method checks whether
 42 |  this customer is the customer associated with the session and than checks
 43 |  whether the session in an authenticated state.
 44 | 
 45 |  Note: The pipeline debugger will always show 'false' for this value
 46 |  regardless of whether the customer is authenticated or not.
 47 | 
 48 | ### CDPData
 49 | 
 50 | **Type:** CustomerCDPData (Read Only)
 51 | 
 52 | The Salesforce CDP (Customer Data Platform) data for this customer.
 53 | 
 54 | ### customerGroups
 55 | 
 56 | **Type:** Collection (Static) (Read Only)
 57 | 
 58 | The customer groups this customer is member of.
 59 |  
 60 |  Result contains static customer groups in storefront and job session
 61 |  Result contains dynamic customer groups in storefront  and job session.
 62 |  Dynamic customer groups referring session or request data are not available
 63 |  when processing the customer in a job session, or when this customer is not the customer assigned to the current session.
 64 |  
 65 |  Result contains system groups 'Everyone', 'Unregistered', 'Registered' for all customers in storefront and job sessions
 66 | 
 67 | ### externallyAuthenticated
 68 | 
 69 | **Type:** boolean (Read Only)
 70 | 
 71 | Identifies if the customer is externally authenticated. An externally
 72 |  authenticated customer does not have the password stored in our system
 73 |  but logs in through an external OAuth provider (Google, Facebook, LinkedIn, etc.)
 74 | 
 75 | ### externalProfiles
 76 | 
 77 | **Type:** Collection (Read Only)
 78 | 
 79 | A collection of any external profiles the customer may have
 80 | 
 81 | ### globalPartyID
 82 | 
 83 | **Type:** String (Read Only)
 84 | 
 85 | The Global Party ID for the customer, if there is one.
 86 |  Global Party ID is created by Customer 360 and identifies a person across multiple systems.
 87 | 
 88 | ### ID
 89 | 
 90 | **Type:** String (Read Only)
 91 | 
 92 | The unique, system generated ID of the customer.
 93 | 
 94 | ### note
 95 | 
 96 | **Type:** String
 97 | 
 98 | The note for this customer, or null if this customer has no note, such as for an anonymous
 99 |  customer or when note has 0 length.
100 | 
101 | ### orderHistory
102 | 
103 | **Type:** OrderHistory (Read Only)
104 | 
105 | The customer order history.
106 | 
107 | ### profile
108 | 
109 | **Type:** Profile (Read Only)
110 | 
111 | The customer profile.
112 | 
113 | ### registered
114 | 
115 | **Type:** boolean (Read Only)
116 | 
117 | Identifies if the customer is registered. A registered customer
118 |  may or may not be authenticated. This method checks whether
119 |  the user has a profile.
120 | 
121 | ## Constructor Summary
122 | 
123 | ## Method Summary
124 | 
125 | ### createExternalProfile
126 | 
127 | **Signature:** `createExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile`
128 | 
129 | Creates an externalProfile and attaches it to the list of external profiles for the customer
130 | 
131 | ### getActiveData
132 | 
133 | **Signature:** `getActiveData() : CustomerActiveData`
134 | 
135 | Returns the active data for this customer.
136 | 
137 | ### getAddressBook
138 | 
139 | **Signature:** `getAddressBook() : AddressBook`
140 | 
141 | Returns the address book for the profile of this customer, or null if this customer has no profile, such as for an anonymous customer.
142 | 
143 | ### getCDPData
144 | 
145 | **Signature:** `getCDPData() : CustomerCDPData`
146 | 
147 | Returns the Salesforce CDP (Customer Data Platform) data for this customer.
148 | 
149 | ### getCustomerGroups
150 | 
151 | **Signature:** `getCustomerGroups() : Collection`
152 | 
153 | Returns the customer groups this customer is member of.
154 | 
155 | ### getExternalProfile
156 | 
157 | **Signature:** `getExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile`
158 | 
159 | A convenience method for finding an external profile among the customer's external profiles collection
160 | 
161 | ### getExternalProfiles
162 | 
163 | **Signature:** `getExternalProfiles() : Collection`
164 | 
165 | Returns a collection of any external profiles the customer may have
166 | 
167 | ### getGlobalPartyID
168 | 
169 | **Signature:** `getGlobalPartyID() : String`
170 | 
171 | Returns the Global Party ID for the customer, if there is one.
172 | 
173 | ### getID
174 | 
175 | **Signature:** `getID() : String`
176 | 
177 | Returns the unique, system generated ID of the customer.
178 | 
179 | ### getNote
180 | 
181 | **Signature:** `getNote() : String`
182 | 
183 | Returns the note for this customer, or null if this customer has no note, such as for an anonymous customer or when note has 0 length.
184 | 
185 | ### getOrderHistory
186 | 
187 | **Signature:** `getOrderHistory() : OrderHistory`
188 | 
189 | Returns the customer order history.
190 | 
191 | ### getProductLists
192 | 
193 | **Signature:** `getProductLists(type : Number) : Collection`
194 | 
195 | Returns the product lists of the specified type.
196 | 
197 | ### getProfile
198 | 
199 | **Signature:** `getProfile() : Profile`
200 | 
201 | Returns the customer profile.
202 | 
203 | ### isAnonymous
204 | 
205 | **Signature:** `isAnonymous() : boolean`
206 | 
207 | Identifies if the customer is anonymous.
208 | 
209 | ### isAuthenticated
210 | 
211 | **Signature:** `isAuthenticated() : boolean`
212 | 
213 | Identifies if the customer is authenticated.
214 | 
215 | ### isExternallyAuthenticated
216 | 
217 | **Signature:** `isExternallyAuthenticated() : boolean`
218 | 
219 | Identifies if the customer is externally authenticated.
220 | 
221 | ### isMemberOfAnyCustomerGroup
222 | 
223 | **Signature:** `isMemberOfAnyCustomerGroup(groupIDs : String...) : boolean`
224 | 
225 | Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of at least one of that groups.
226 | 
227 | ### isMemberOfCustomerGroup
228 | 
229 | **Signature:** `isMemberOfCustomerGroup(group : CustomerGroup) : boolean`
230 | 
231 | Returns true if the customer is member of the specified CustomerGroup.
232 | 
233 | ### isMemberOfCustomerGroup
234 | 
235 | **Signature:** `isMemberOfCustomerGroup(groupID : String) : boolean`
236 | 
237 | Returns true if there is a CustomerGroup with such an ID and the customer is member of that group.
238 | 
239 | ### isMemberOfCustomerGroups
240 | 
241 | **Signature:** `isMemberOfCustomerGroups(groupIDs : String...) : boolean`
242 | 
243 | Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of all that groups.
244 | 
245 | ### isRegistered
246 | 
247 | **Signature:** `isRegistered() : boolean`
248 | 
249 | Identifies if the customer is registered.
250 | 
251 | ### removeExternalProfile
252 | 
253 | **Signature:** `removeExternalProfile(externalProfile : ExternalProfile) : void`
254 | 
255 | Removes an external profile from the customer
256 | 
257 | ### setNote
258 | 
259 | **Signature:** `setNote(aValue : String) : void`
260 | 
261 | Sets the note for this customer.
262 | 
263 | ## Method Detail
264 | 
265 | ## Method Details
266 | 
267 | ### createExternalProfile
268 | 
269 | **Signature:** `createExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile`
270 | 
271 | **Description:** Creates an externalProfile and attaches it to the list of external profiles for the customer
272 | 
273 | **Parameters:**
274 | 
275 | - `authenticationProviderId`: the authenticationProviderId for the externalProfile
276 | - `externalId`: the externalId for the external Profile
277 | 
278 | **Returns:**
279 | 
280 | the new externalProfile
281 | 
282 | ---
283 | 
284 | ### getActiveData
285 | 
286 | **Signature:** `getActiveData() : CustomerActiveData`
287 | 
288 | **Description:** Returns the active data for this customer.
289 | 
290 | **Returns:**
291 | 
292 | the active data for this customer.
293 | 
294 | ---
295 | 
296 | ### getAddressBook
297 | 
298 | **Signature:** `getAddressBook() : AddressBook`
299 | 
300 | **Description:** Returns the address book for the profile of this customer, or null if this customer has no profile, such as for an anonymous customer.
301 | 
302 | ---
303 | 
304 | ### getCDPData
305 | 
306 | **Signature:** `getCDPData() : CustomerCDPData`
307 | 
308 | **Description:** Returns the Salesforce CDP (Customer Data Platform) data for this customer.
309 | 
310 | **Returns:**
311 | 
312 | the Salesforce CDP data for this customer.
313 | 
314 | ---
315 | 
316 | ### getCustomerGroups
317 | 
318 | **Signature:** `getCustomerGroups() : Collection`
319 | 
320 | **Description:** Returns the customer groups this customer is member of. Result contains static customer groups in storefront and job session Result contains dynamic customer groups in storefront and job session. Dynamic customer groups referring session or request data are not available when processing the customer in a job session, or when this customer is not the customer assigned to the current session. Result contains system groups 'Everyone', 'Unregistered', 'Registered' for all customers in storefront and job sessions
321 | 
322 | **Returns:**
323 | 
324 | Collection of customer groups of this customer
325 | 
326 | ---
327 | 
328 | ### getExternalProfile
329 | 
330 | **Signature:** `getExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile`
331 | 
332 | **Description:** A convenience method for finding an external profile among the customer's external profiles collection
333 | 
334 | **Parameters:**
335 | 
336 | - `authenticationProviderId`: the authenticationProviderId to look for
337 | - `externalId`: the externalId to look for
338 | 
339 | **Returns:**
340 | 
341 | the externalProfile found among the customer's external profile or null if not found
342 | 
343 | ---
344 | 
345 | ### getExternalProfiles
346 | 
347 | **Signature:** `getExternalProfiles() : Collection`
348 | 
349 | **Description:** Returns a collection of any external profiles the customer may have
350 | 
351 | **Returns:**
352 | 
353 | a collection of any external profiles the customer may have
354 | 
355 | ---
356 | 
357 | ### getGlobalPartyID
358 | 
359 | **Signature:** `getGlobalPartyID() : String`
360 | 
361 | **Description:** Returns the Global Party ID for the customer, if there is one. Global Party ID is created by Customer 360 and identifies a person across multiple systems.
362 | 
363 | **Returns:**
364 | 
365 | The global party ID
366 | 
367 | ---
368 | 
369 | ### getID
370 | 
371 | **Signature:** `getID() : String`
372 | 
373 | **Description:** Returns the unique, system generated ID of the customer.
374 | 
375 | **Returns:**
376 | 
377 | the ID of the customer.
378 | 
379 | ---
380 | 
381 | ### getNote
382 | 
383 | **Signature:** `getNote() : String`
384 | 
385 | **Description:** Returns the note for this customer, or null if this customer has no note, such as for an anonymous customer or when note has 0 length.
386 | 
387 | **Returns:**
388 | 
389 | the note for this customer.
390 | 
391 | ---
392 | 
393 | ### getOrderHistory
394 | 
395 | **Signature:** `getOrderHistory() : OrderHistory`
396 | 
397 | **Description:** Returns the customer order history.
398 | 
399 | **Returns:**
400 | 
401 | the customer order history.
402 | 
403 | ---
404 | 
405 | ### getProductLists
406 | 
407 | **Signature:** `getProductLists(type : Number) : Collection`
408 | 
409 | **Description:** Returns the product lists of the specified type.
410 | 
411 | **Parameters:**
412 | 
413 | - `type`: the type of product lists to return.
414 | 
415 | **Returns:**
416 | 
417 | the product lists of the specified type.
418 | 
419 | **See Also:**
420 | 
421 | ProductList
422 | 
423 | ---
424 | 
425 | ### getProfile
426 | 
427 | **Signature:** `getProfile() : Profile`
428 | 
429 | **Description:** Returns the customer profile.
430 | 
431 | **Returns:**
432 | 
433 | the customer profile.
434 | 
435 | ---
436 | 
437 | ### isAnonymous
438 | 
439 | **Signature:** `isAnonymous() : boolean`
440 | 
441 | **Description:** Identifies if the customer is anonymous. An anonymous customer is the opposite of a registered customer.
442 | 
443 | **Returns:**
444 | 
445 | true if the customer is anonymous, false otherwise. Note: this method handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12.
446 | 
447 | ---
448 | 
449 | ### isAuthenticated
450 | 
451 | **Signature:** `isAuthenticated() : boolean`
452 | 
453 | **Description:** Identifies if the customer is authenticated. This method checks whether this customer is the customer associated with the session and than checks whether the session in an authenticated state. Note: The pipeline debugger will always show 'false' for this value regardless of whether the customer is authenticated or not.
454 | 
455 | **Returns:**
456 | 
457 | true if the customer is authenticated, false otherwise.
458 | 
459 | ---
460 | 
461 | ### isExternallyAuthenticated
462 | 
463 | **Signature:** `isExternallyAuthenticated() : boolean`
464 | 
465 | **Description:** Identifies if the customer is externally authenticated. An externally authenticated customer does not have the password stored in our system but logs in through an external OAuth provider (Google, Facebook, LinkedIn, etc.)
466 | 
467 | **Returns:**
468 | 
469 | true if the customer is externally authenticated, false otherwise. Note: this method handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12.
470 | 
471 | ---
472 | 
473 | ### isMemberOfAnyCustomerGroup
474 | 
475 | **Signature:** `isMemberOfAnyCustomerGroup(groupIDs : String...) : boolean`
476 | 
477 | **Description:** Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of at least one of that groups.
478 | 
479 | **Parameters:**
480 | 
481 | - `groupIDs`: A list of unique semantic customer group IDs.
482 | 
483 | **Returns:**
484 | 
485 | True if customer groups exist for the given IDs and the customer is member of at least one of that existing groups. False if none of customer groups exist or if the customer is not a member of any of that existing groups.
486 | 
487 | ---
488 | 
489 | ### isMemberOfCustomerGroup
490 | 
491 | **Signature:** `isMemberOfCustomerGroup(group : CustomerGroup) : boolean`
492 | 
493 | **Description:** Returns true if the customer is member of the specified CustomerGroup.
494 | 
495 | **Parameters:**
496 | 
497 | - `group`: Customer group
498 | 
499 | **Returns:**
500 | 
501 | True if customer is member of the group, otherwise false.
502 | 
503 | ---
504 | 
505 | ### isMemberOfCustomerGroup
506 | 
507 | **Signature:** `isMemberOfCustomerGroup(groupID : String) : boolean`
508 | 
509 | **Description:** Returns true if there is a CustomerGroup with such an ID and the customer is member of that group.
510 | 
511 | **Parameters:**
512 | 
513 | - `groupID`: The unique semantic customer group ID.
514 | 
515 | **Returns:**
516 | 
517 | True if a customer group with such an ID exist and the customer is member of that group. False if no such customer group exist or, if the group exist, the customer is not member of that group.
518 | 
519 | ---
520 | 
521 | ### isMemberOfCustomerGroups
522 | 
523 | **Signature:** `isMemberOfCustomerGroups(groupIDs : String...) : boolean`
524 | 
525 | **Description:** Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of all that groups.
526 | 
527 | **Parameters:**
528 | 
529 | - `groupIDs`: A list of unique semantic customer group IDs.
530 | 
531 | **Returns:**
532 | 
533 | True if customer groups exist for all of the given IDs and the customer is member of all that groups. False if there is at least one ID for which no customer group exist or, if all groups exist, the customer is not member of all that groups.
534 | 
535 | ---
536 | 
537 | ### isRegistered
538 | 
539 | **Signature:** `isRegistered() : boolean`
540 | 
541 | **Description:** Identifies if the customer is registered. A registered customer may or may not be authenticated. This method checks whether the user has a profile.
542 | 
543 | **Returns:**
544 | 
545 | true if the customer is registered, false otherwise.
546 | 
547 | ---
548 | 
549 | ### removeExternalProfile
550 | 
551 | **Signature:** `removeExternalProfile(externalProfile : ExternalProfile) : void`
552 | 
553 | **Description:** Removes an external profile from the customer
554 | 
555 | **Parameters:**
556 | 
557 | - `externalProfile`: the externalProfile to be removed
558 | 
559 | ---
560 | 
561 | ### setNote
562 | 
563 | **Signature:** `setNote(aValue : String) : void`
564 | 
565 | **Description:** Sets the note for this customer. This is a no-op for an anonymous customer.
566 | 
567 | **Parameters:**
568 | 
569 | - `aValue`: the value of the note
570 | 
571 | ---
```

--------------------------------------------------------------------------------
/tests/cache.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { InMemoryCache, CacheManager } from '../src/utils/cache';
  2 | 
  3 | describe('InMemoryCache', () => {
  4 |   let cache: InMemoryCache<string>;
  5 | 
  6 |   beforeEach(() => {
  7 |     cache = new InMemoryCache<string>({
  8 |       maxSize: 3,
  9 |       ttlMs: 1000, // 1 second for testing
 10 |       cleanupIntervalMs: 100, // 100ms for faster testing
 11 |     });
 12 |   });
 13 | 
 14 |   afterEach(() => {
 15 |     cache.destroy();
 16 |   });
 17 | 
 18 |   describe('constructor', () => {
 19 |     it('should use default options when none provided', () => {
 20 |       const defaultCache = new InMemoryCache();
 21 |       const stats = defaultCache.getStats();
 22 | 
 23 |       expect(stats.maxSize).toBe(1000);
 24 |       defaultCache.destroy();
 25 |     });
 26 | 
 27 |     it('should accept custom options', () => {
 28 |       const customCache = new InMemoryCache({
 29 |         maxSize: 50,
 30 |         ttlMs: 5000,
 31 |         cleanupIntervalMs: 1000,
 32 |       });
 33 |       const stats = customCache.getStats();
 34 | 
 35 |       expect(stats.maxSize).toBe(50);
 36 |       customCache.destroy();
 37 |     });
 38 |   });
 39 | 
 40 |   describe('set and get', () => {
 41 |     it('should store and retrieve values', () => {
 42 |       cache.set('key1', 'value1');
 43 |       expect(cache.get('key1')).toBe('value1');
 44 |     });
 45 | 
 46 |     it('should return undefined for non-existent keys', () => {
 47 |       expect(cache.get('nonexistent')).toBeUndefined();
 48 |     });
 49 | 
 50 |     it('should update access statistics on get', () => {
 51 |       cache.set('key1', 'value1');
 52 |       cache.get('key1'); // First access
 53 |       cache.get('key1'); // Second access
 54 | 
 55 |       const stats = cache.getStats();
 56 |       const entry = stats.entries.find(e => e.key === 'key1');
 57 |       expect(entry?.accessCount).toBe(2);
 58 |     });
 59 | 
 60 |     it('should update lastAccessed timestamp on get', async () => {
 61 |       cache.set('key1', 'value1');
 62 |       const initialGet = cache.get('key1');
 63 | 
 64 |       // Wait a bit and access again
 65 |       await new Promise(resolve => setTimeout(resolve, 10));
 66 |       const secondGet = cache.get('key1');
 67 | 
 68 |       expect(initialGet).toBe('value1');
 69 |       expect(secondGet).toBe('value1');
 70 | 
 71 |       const stats = cache.getStats();
 72 |       const entry = stats.entries.find(e => e.key === 'key1');
 73 |       expect(entry?.accessCount).toBe(2);
 74 |     });
 75 |   });
 76 | 
 77 |   describe('has', () => {
 78 |     it('should return true for existing keys', () => {
 79 |       cache.set('key1', 'value1');
 80 |       expect(cache.has('key1')).toBe(true);
 81 |     });
 82 | 
 83 |     it('should return false for non-existent keys', () => {
 84 |       expect(cache.has('nonexistent')).toBe(false);
 85 |     });
 86 | 
 87 |     it('should not update access statistics', () => {
 88 |       cache.set('key1', 'value1');
 89 |       cache.has('key1');
 90 | 
 91 |       const stats = cache.getStats();
 92 |       const entry = stats.entries.find(e => e.key === 'key1');
 93 |       expect(entry?.accessCount).toBe(0);
 94 |     });
 95 |   });
 96 | 
 97 |   describe('delete', () => {
 98 |     it('should remove existing keys', () => {
 99 |       cache.set('key1', 'value1');
100 |       expect(cache.has('key1')).toBe(true);
101 | 
102 |       const deleted = cache.delete('key1');
103 |       expect(deleted).toBe(true);
104 |       expect(cache.has('key1')).toBe(false);
105 |     });
106 | 
107 |     it('should return false for non-existent keys', () => {
108 |       const deleted = cache.delete('nonexistent');
109 |       expect(deleted).toBe(false);
110 |     });
111 |   });
112 | 
113 |   describe('clear', () => {
114 |     it('should remove all entries', () => {
115 |       cache.set('key1', 'value1');
116 |       cache.set('key2', 'value2');
117 |       expect(cache.getStats().size).toBe(2);
118 | 
119 |       cache.clear();
120 |       expect(cache.getStats().size).toBe(0);
121 |     });
122 |   });
123 | 
124 |   describe('TTL (Time To Live)', () => {
125 |     it('should expire entries after TTL', async () => {
126 |       const shortTtlCache = new InMemoryCache<string>({
127 |         ttlMs: 50, // 50ms
128 |         cleanupIntervalMs: 1000, // Don't auto-cleanup for this test
129 |       });
130 | 
131 |       shortTtlCache.set('key1', 'value1');
132 |       expect(shortTtlCache.get('key1')).toBe('value1');
133 | 
134 |       // Wait for expiration
135 |       await new Promise(resolve => setTimeout(resolve, 60));
136 |       expect(shortTtlCache.get('key1')).toBeUndefined();
137 | 
138 |       shortTtlCache.destroy();
139 |     });
140 | 
141 |     it('should remove expired entries when checking has()', async () => {
142 |       const shortTtlCache = new InMemoryCache<string>({
143 |         ttlMs: 50,
144 |         cleanupIntervalMs: 1000,
145 |       });
146 | 
147 |       shortTtlCache.set('key1', 'value1');
148 |       expect(shortTtlCache.has('key1')).toBe(true);
149 | 
150 |       await new Promise(resolve => setTimeout(resolve, 60));
151 |       expect(shortTtlCache.has('key1')).toBe(false);
152 | 
153 |       shortTtlCache.destroy();
154 |     });
155 |   });
156 | 
157 |   describe('LRU (Least Recently Used) eviction', () => {
158 |     it('should evict LRU item when max size is reached', async () => {
159 |       // Fill cache to max capacity
160 |       cache.set('key1', 'value1');
161 |       await new Promise(resolve => setTimeout(resolve, 1)); // Small delay
162 |       cache.set('key2', 'value2');
163 |       await new Promise(resolve => setTimeout(resolve, 1)); // Small delay
164 |       cache.set('key3', 'value3');
165 |       expect(cache.getStats().size).toBe(3);
166 | 
167 |       // Wait a bit then access key1 and key2 to make key3 the least recently used
168 |       await new Promise(resolve => setTimeout(resolve, 5));
169 |       cache.get('key1');
170 |       cache.get('key2');
171 | 
172 |       // Add new item, should evict key3
173 |       cache.set('key4', 'value4');
174 |       expect(cache.getStats().size).toBe(3);
175 |       expect(cache.has('key3')).toBe(false);
176 |       expect(cache.has('key1')).toBe(true);
177 |       expect(cache.has('key2')).toBe(true);
178 |       expect(cache.has('key4')).toBe(true);
179 |     });
180 | 
181 |     it('should not evict when updating existing key', () => {
182 |       cache.set('key1', 'value1');
183 |       cache.set('key2', 'value2');
184 |       cache.set('key3', 'value3');
185 | 
186 |       // Update existing key
187 |       cache.set('key1', 'newvalue1');
188 |       expect(cache.getStats().size).toBe(3);
189 |       expect(cache.get('key1')).toBe('newvalue1');
190 |     });
191 |   });
192 | 
193 |   describe('getStats', () => {
194 |     it('should return correct cache statistics', () => {
195 |       cache.set('key1', 'value1');
196 |       cache.set('key2', 'value2');
197 |       cache.get('key1'); // Access key1
198 | 
199 |       const stats = cache.getStats();
200 |       expect(stats.size).toBe(2);
201 |       expect(stats.maxSize).toBe(3);
202 |       expect(stats.entries).toHaveLength(2);
203 | 
204 |       const key1Entry = stats.entries.find(e => e.key === 'key1');
205 |       const key2Entry = stats.entries.find(e => e.key === 'key2');
206 | 
207 |       expect(key1Entry?.accessCount).toBe(1);
208 |       expect(key2Entry?.accessCount).toBe(0);
209 |     });
210 | 
211 |     it('should calculate hit rate correctly', () => {
212 |       cache.set('key1', 'value1');
213 |       cache.set('key2', 'value2');
214 | 
215 |       cache.get('key1'); // Hit
216 |       cache.get('key1'); // Hit
217 |       // key2 never accessed
218 | 
219 |       const stats = cache.getStats();
220 |       // Total accesses: 2, hits: 1 (key1 was accessed, key2 wasn't)
221 |       expect(stats.hitRate).toBe(0.5);
222 |     });
223 |   });
224 | 
225 |   describe('cleanup', () => {
226 |     it('should automatically cleanup expired entries', async () => {
227 |       const autoCleanupCache = new InMemoryCache<string>({
228 |         ttlMs: 50,
229 |         cleanupIntervalMs: 60, // Very frequent cleanup
230 |       });
231 | 
232 |       autoCleanupCache.set('key1', 'value1');
233 |       expect(autoCleanupCache.getStats().size).toBe(1);
234 | 
235 |       // Wait for TTL + cleanup interval
236 |       await new Promise(resolve => setTimeout(resolve, 120));
237 | 
238 |       const stats = autoCleanupCache.getStats();
239 |       expect(stats.size).toBe(0);
240 | 
241 |       autoCleanupCache.destroy();
242 |     });
243 |   });
244 | 
245 |   describe('destroy', () => {
246 |     it('should clear cache and stop cleanup timer', () => {
247 |       cache.set('key1', 'value1');
248 |       expect(cache.getStats().size).toBe(1);
249 | 
250 |       cache.destroy();
251 |       expect(cache.getStats().size).toBe(0);
252 | 
253 |       // Cache should still be usable but without automatic cleanup
254 |       cache.set('key2', 'value2');
255 |       expect(cache.get('key2')).toBe('value2');
256 |     });
257 |   });
258 | });
259 | 
260 | describe('CacheManager', () => {
261 |   let cacheManager: CacheManager;
262 | 
263 |   beforeEach(() => {
264 |     cacheManager = new CacheManager();
265 |   });
266 | 
267 |   afterEach(() => {
268 |     cacheManager.destroy();
269 |   });
270 | 
271 |   describe('file content cache', () => {
272 |     it('should store and retrieve file content', () => {
273 |       const content = 'file content';
274 |       cacheManager.setFileContent('file1.ts', content);
275 | 
276 |       expect(cacheManager.getFileContent('file1.ts')).toBe(content);
277 |       expect(cacheManager.getFileContent('nonexistent.ts')).toBeUndefined();
278 |     });
279 |   });
280 | 
281 |   describe('class details cache', () => {
282 |     it('should store and retrieve class details', () => {
283 |       const details = { name: 'TestClass', methods: ['method1', 'method2'] };
284 |       cacheManager.setClassDetails('TestClass', details);
285 | 
286 |       expect(cacheManager.getClassDetails('TestClass')).toEqual(details);
287 |       expect(cacheManager.getClassDetails('NonExistentClass')).toBeUndefined();
288 |     });
289 |   });
290 | 
291 |   describe('search results cache', () => {
292 |     it('should store and retrieve search results', () => {
293 |       const results = [{ name: 'result1' }, { name: 'result2' }];
294 |       cacheManager.setSearchResults('query1', results);
295 | 
296 |       expect(cacheManager.getSearchResults('query1')).toEqual(results);
297 |       expect(cacheManager.getSearchResults('query2')).toBeUndefined();
298 |     });
299 |   });
300 | 
301 |   describe('method search cache', () => {
302 |     it('should store and retrieve method search results', () => {
303 |       const results = [{ method: 'getValue', class: 'TestClass' }];
304 |       cacheManager.setMethodSearch('getValue', results);
305 | 
306 |       expect(cacheManager.getMethodSearch('getValue')).toEqual(results);
307 |       expect(cacheManager.getMethodSearch('nonexistent')).toBeUndefined();
308 |     });
309 |   });
310 | 
311 |   describe('getAllStats', () => {
312 |     it('should return statistics for all caches', () => {
313 |       cacheManager.setFileContent('file1.ts', 'content');
314 |       cacheManager.setClassDetails('Class1', { name: 'Class1' });
315 |       cacheManager.setSearchResults('query1', []);
316 |       cacheManager.setMethodSearch('method1', []);
317 | 
318 |       const allStats = cacheManager.getAllStats();
319 | 
320 |       expect(allStats.fileContent.size).toBe(1);
321 |       expect(allStats.classDetails.size).toBe(1);
322 |       expect(allStats.searchResults.size).toBe(1);
323 |       expect(allStats.methodSearch.size).toBe(1);
324 | 
325 |       expect(allStats.fileContent.maxSize).toBe(500);
326 |       expect(allStats.classDetails.maxSize).toBe(300);
327 |       expect(allStats.searchResults.maxSize).toBe(200);
328 |       expect(allStats.methodSearch.maxSize).toBe(100);
329 |     });
330 |   });
331 | 
332 |   describe('clearAll', () => {
333 |     it('should clear all caches', () => {
334 |       cacheManager.setFileContent('file1.ts', 'content');
335 |       cacheManager.setClassDetails('Class1', { name: 'Class1' });
336 |       cacheManager.setSearchResults('query1', []);
337 |       cacheManager.setMethodSearch('method1', []);
338 | 
339 |       let allStats = cacheManager.getAllStats();
340 |       expect(allStats.fileContent.size).toBe(1);
341 |       expect(allStats.classDetails.size).toBe(1);
342 |       expect(allStats.searchResults.size).toBe(1);
343 |       expect(allStats.methodSearch.size).toBe(1);
344 | 
345 |       cacheManager.clearAll();
346 | 
347 |       allStats = cacheManager.getAllStats();
348 |       expect(allStats.fileContent.size).toBe(0);
349 |       expect(allStats.classDetails.size).toBe(0);
350 |       expect(allStats.searchResults.size).toBe(0);
351 |       expect(allStats.methodSearch.size).toBe(0);
352 |     });
353 |   });
354 | 
355 |   describe('destroy', () => {
356 |     it('should destroy all underlying caches', () => {
357 |       cacheManager.setFileContent('file1.ts', 'content');
358 | 
359 |       let allStats = cacheManager.getAllStats();
360 |       expect(allStats.fileContent.size).toBe(1);
361 | 
362 |       cacheManager.destroy();
363 | 
364 |       // After destroy, the caches should be cleared
365 |       allStats = cacheManager.getAllStats();
366 |       expect(allStats.fileContent.size).toBe(0);
367 |     });
368 |   });
369 | 
370 |   describe('different TTL configurations', () => {
371 |     it('should have different TTL settings for different cache types', () => {
372 |       // We can't directly test TTL values, but we can verify the caches work independently
373 |       cacheManager.setFileContent('file1.ts', 'content');
374 |       cacheManager.setClassDetails('Class1', { name: 'Class1' });
375 |       cacheManager.setSearchResults('query1', ['result1']);
376 |       cacheManager.setMethodSearch('method1', ['method1']);
377 | 
378 |       // All should be accessible
379 |       expect(cacheManager.getFileContent('file1.ts')).toBe('content');
380 |       expect(cacheManager.getClassDetails('Class1')).toEqual({ name: 'Class1' });
381 |       expect(cacheManager.getSearchResults('query1')).toEqual(['result1']);
382 |       expect(cacheManager.getMethodSearch('method1')).toEqual(['method1']);
383 |     });
384 |   });
385 | });
386 | 
387 | describe('Edge cases and error handling', () => {
388 |   describe('InMemoryCache edge cases', () => {
389 |     it('should handle zero max size gracefully', () => {
390 |       const zeroSizeCache = new InMemoryCache<string>({
391 |         maxSize: 0,
392 |         ttlMs: 1000,
393 |       });
394 | 
395 |       zeroSizeCache.set('key1', 'value1');
396 |       // With maxSize 0, nothing should be stored
397 |       expect(zeroSizeCache.get('key1')).toBeUndefined();
398 |       expect(zeroSizeCache.getStats().size).toBe(0);
399 | 
400 |       zeroSizeCache.destroy();
401 |     });
402 | 
403 |     it('should handle very short TTL', async () => {
404 |       const shortTtlCache = new InMemoryCache<string>({
405 |         ttlMs: 1, // 1ms
406 |         cleanupIntervalMs: 1000,
407 |       });
408 | 
409 |       shortTtlCache.set('key1', 'value1');
410 | 
411 |       // Wait longer than TTL
412 |       await new Promise(resolve => setTimeout(resolve, 5));
413 | 
414 |       expect(shortTtlCache.get('key1')).toBeUndefined();
415 | 
416 |       shortTtlCache.destroy();
417 |     });
418 | 
419 |     it('should handle multiple destroy calls', () => {
420 |       const cache = new InMemoryCache<string>();
421 |       cache.set('key1', 'value1');
422 | 
423 |       cache.destroy();
424 |       cache.destroy(); // Second destroy should not throw
425 | 
426 |       expect(cache.getStats().size).toBe(0);
427 |     });
428 |   });
429 | 
430 |   describe('Type safety', () => {
431 |     it('should maintain type safety for different value types', () => {
432 |       const stringCache = new InMemoryCache<string>();
433 |       const numberCache = new InMemoryCache<number>();
434 |       const objectCache = new InMemoryCache<{ id: number; name: string }>();
435 | 
436 |       stringCache.set('key1', 'string value');
437 |       numberCache.set('key1', 42);
438 |       objectCache.set('key1', { id: 1, name: 'test' });
439 | 
440 |       const stringValue: string | undefined = stringCache.get('key1');
441 |       const numberValue: number | undefined = numberCache.get('key1');
442 |       const objectValue: { id: number; name: string } | undefined = objectCache.get('key1');
443 | 
444 |       expect(typeof stringValue).toBe('string');
445 |       expect(typeof numberValue).toBe('number');
446 |       expect(typeof objectValue).toBe('object');
447 | 
448 |       stringCache.destroy();
449 |       numberCache.destroy();
450 |       objectCache.destroy();
451 |     });
452 |   });
453 | });
454 | 
```

--------------------------------------------------------------------------------
/tests/mcp/node/search-sfcc-classes.docs-only.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Programmatic tests for search_sfcc_classes tool
  3 |  * 
  4 |  * These tests provide advanced verification capabilities beyond YAML pattern matching,
  5 |  * including dynamic validation, error categorization and
  6 |  * comprehensive response structure analysis.
  7 |  * 
  8 |  * Response format discovered via aegis query:
  9 |  * - Success: { content: [{ type: "text", text: "["class1", "class2", ...]" }] }
 10 |  * - Empty: { content: [{ type: "text", text: "[]" }] }
 11 |  * - Error: { content: [{ type: "text", text: "Error: ..." }], isError: true }
 12 |  */
 13 | 
 14 | import { test, describe, before, after, beforeEach } from 'node:test';
 15 | import { strict as assert } from 'node:assert';
 16 | import { connect } from 'mcp-aegis';
 17 | 
 18 | describe('search_sfcc_classes Programmatic Tests', () => {
 19 |   let client;
 20 | 
 21 |   before(async () => {
 22 |     client = await connect('./aegis.config.docs-only.json');
 23 |   });
 24 | 
 25 |   after(async () => {
 26 |     if (client?.connected) {
 27 |       await client.disconnect();
 28 |     }
 29 |   });
 30 | 
 31 |   beforeEach(() => {
 32 |     // CRITICAL: Clear all buffers to prevent test interference
 33 |     client.clearAllBuffers(); // Recommended - comprehensive protection
 34 |   });
 35 | 
 36 |   describe('Protocol Compliance', () => {
 37 |     test('should be properly connected to MCP server', async () => {
 38 |       assert.ok(client.connected, 'Client should be connected');
 39 |     });
 40 | 
 41 |     test('should have search_sfcc_classes tool available', async () => {
 42 |       const tools = await client.listTools();
 43 |       const searchTool = tools.find(tool => tool.name === 'search_sfcc_classes');
 44 |       
 45 |       assert.ok(searchTool, 'search_sfcc_classes tool should be available');
 46 |       assert.equal(searchTool.name, 'search_sfcc_classes');
 47 |       assert.ok(searchTool.description, 'Tool should have description');
 48 |       assert.ok(searchTool.inputSchema, 'Tool should have input schema');
 49 |       assert.equal(searchTool.inputSchema.type, 'object');
 50 |       assert.ok(searchTool.inputSchema.properties.query, 'Tool should require query parameter');
 51 |     });
 52 |   });
 53 | 
 54 |   describe('Response Structure Validation', () => {
 55 |     test('should return properly structured MCP response for valid query', async () => {
 56 |       const result = await client.callTool('search_sfcc_classes', { query: 'catalog' });
 57 |       
 58 |       // Validate MCP response structure
 59 |       assertValidMCPResponse(result);
 60 |       assert.equal(result.isError, false, 'Should not be an error response');
 61 |       assert.equal(result.content.length, 1, 'Should have exactly one content item');
 62 |       assert.equal(result.content[0].type, 'text', 'Content should be text type');
 63 |       
 64 |       // Validate JSON array structure
 65 |       const classArray = parseClassArray(result.content[0].text);
 66 |       assert.ok(Array.isArray(classArray), 'Response should contain valid JSON array');
 67 |       assert.ok(classArray.length > 0, 'Should return at least one class for catalog query');
 68 |       
 69 |       // Validate class name format
 70 |       classArray.forEach(className => {
 71 |         assert.equal(typeof className, 'string', 'Each class name should be a string');
 72 |         // SFCC includes multiple namespaces
 73 |         assert.ok(
 74 |           className.startsWith('dw.') || 
 75 |           className.startsWith('TopLevel.') || 
 76 |           className.startsWith('best-practices.') || 
 77 |           className.startsWith('sfra.'),
 78 |           `Class name "${className}" should start with recognized namespace`
 79 |         );
 80 |         assert.ok(className.includes('catalog'), 'Results should be relevant to query');
 81 |       });
 82 |     });
 83 | 
 84 |     test('should return empty array for no matches', async () => {
 85 |       const result = await client.callTool('search_sfcc_classes', { query: 'zzznothingfound' });
 86 |       
 87 |       assertValidMCPResponse(result);
 88 |       assert.equal(result.isError, false, 'Should not be an error response');
 89 |       
 90 |       const classArray = parseClassArray(result.content[0].text);
 91 |       assert.ok(Array.isArray(classArray), 'Response should be valid JSON array');
 92 |       assert.equal(classArray.length, 0, 'Should return empty array for no matches');
 93 |     });
 94 | 
 95 |     test('should return error response for invalid parameters', async () => {
 96 |       const result = await client.callTool('search_sfcc_classes', { query: '' });
 97 |       
 98 |       assertValidMCPResponse(result);
 99 |       assert.equal(result.isError, true, 'Should be an error response');
100 |       assert.ok(result.content[0].text.includes('Error:'), 'Should contain error message');
101 |       assert.ok(result.content[0].text.includes('non-empty string'), 'Should specify validation requirement');
102 |     });
103 |   });
104 | 
105 |   describe('Dynamic Search Testing', () => {
106 |     const testQueries = [
107 |       { query: 'catalog', expectedMin: 10, category: 'namespace' },
108 |       { query: 'product', expectedMin: 5, category: 'common' },
109 |       { query: 'customer', expectedMin: 3, category: 'namespace' },
110 |       { query: 'order', expectedMin: 5, category: 'namespace' },
111 |       { query: 'system', expectedMin: 3, category: 'namespace' },
112 |       { query: 'Campaign', expectedMin: 1, category: 'specific' },
113 |       { query: 'Manager', expectedMin: 0, category: 'pattern' } // "Manager" returns no results - SFCC uses "Mgr"
114 |     ];
115 | 
116 |     testQueries.forEach(({ query, expectedMin, category }) => {
117 |       test(`should find relevant classes for ${category} query: "${query}"`, async () => {
118 |         const result = await client.callTool('search_sfcc_classes', { query });
119 |         
120 |         assertValidMCPResponse(result);
121 |         assert.equal(result.isError, false, 'Should not be an error');
122 |         
123 |         const classArray = parseClassArray(result.content[0].text);
124 |         assert.ok(classArray.length >= expectedMin, 
125 |           `Should find at least ${expectedMin} classes for "${query}", found ${classArray.length}`);
126 |         
127 |         // Validate relevance - all results should contain the query term (case insensitive)
128 |         classArray.forEach(className => {
129 |           const lowerClassName = className.toLowerCase();
130 |           const lowerQuery = query.toLowerCase();
131 |           assert.ok(lowerClassName.includes(lowerQuery), 
132 |             `Class "${className}" should contain query term "${query}"`);
133 |         });
134 |       });
135 |     });
136 |   });
137 | 
138 |   describe('Edge Case Validation', () => {
139 |     const edgeCases = [
140 |       { query: 'A', description: 'single character' },
141 |       { query: 'dw', description: 'namespace prefix' },
142 |       { query: 'CATALOG', description: 'uppercase query' },
143 |       { query: 'catalog.Product', description: 'full class path segment' },
144 |       { query: '123', description: 'numeric query' },
145 |       { query: 'xyz_nonexistent_abc', description: 'clearly non-existent term' }
146 |     ];
147 | 
148 |     edgeCases.forEach(({ query, description }) => {
149 |       test(`should handle ${description} query: "${query}"`, async () => {
150 |         const result = await client.callTool('search_sfcc_classes', { query });
151 |         
152 |         assertValidMCPResponse(result);
153 |         assert.equal(result.isError, false, 'Should not be an error for valid string');
154 |         
155 |         const classArray = parseClassArray(result.content[0].text);
156 |         assert.ok(Array.isArray(classArray), 'Should return valid array');
157 |         
158 |         // All results should be valid class names
159 |         classArray.forEach(className => {
160 |           assert.equal(typeof className, 'string', 'Should be string');
161 |           // SFCC includes dw.*, TopLevel.*, best-practices.*, and sfra.* classes
162 |           assert.ok(
163 |             className.startsWith('dw.') || 
164 |             className.startsWith('TopLevel.') || 
165 |             className.startsWith('best-practices.') || 
166 |             className.startsWith('sfra.'),
167 |             `Class name "${className}" should start with dw., TopLevel., best-practices., or sfra.`
168 |           );
169 |         });
170 |       });
171 |     });
172 |   });
173 | 
174 |   describe('Error Handling and Validation', () => {
175 |     const errorCases = [
176 |       { params: {}, expectedError: 'non-empty string', description: 'missing query parameter' },
177 |       { params: { query: '' }, expectedError: 'non-empty string', description: 'empty string query' },
178 |       { params: { query: null }, expectedError: 'string', description: 'null query' },
179 |       { params: { query: 123 }, expectedError: 'string', description: 'numeric query parameter' },
180 |       { params: { query: [] }, expectedError: 'string', description: 'array query parameter' },
181 |       { params: { query: {} }, expectedError: 'string', description: 'object query parameter' }
182 |     ];
183 | 
184 |     errorCases.forEach(({ params, expectedError, description }) => {
185 |       test(`should validate ${description}`, async () => {
186 |         const result = await client.callTool('search_sfcc_classes', params);
187 |         
188 |         assertValidMCPResponse(result);
189 |         assert.equal(result.isError, true, 'Should be an error response');
190 |         
191 |         const errorMessage = result.content[0].text.toLowerCase();
192 |         assert.ok(errorMessage.includes('error'), 'Should contain error indicator');
193 |         assert.ok(errorMessage.includes(expectedError.toLowerCase()), 
194 |           `Error message should mention "${expectedError}"`);
195 |         
196 |         // Error categorization
197 |         const errorType = categorizeError(result.content[0].text);
198 |         assert.equal(errorType, 'validation', 'Should be categorized as validation error');
199 |       });
200 |     });
201 |   });
202 | 
203 |   describe('Cross-Validation with YAML Tests', () => {
204 |     test('should match YAML test expectations for common queries', async () => {
205 |       // Test cases that mirror the YAML test file
206 |       const yamlTestCases = [
207 |         { query: 'catalog', shouldHave: ['dw.catalog.Catalog', 'dw.catalog.Product'] },
208 |         { query: 'customer', shouldHave: ['dw.customer.Customer'] },
209 |         { query: 'order', shouldHave: ['dw.order.Order'] },
210 |         { query: 'system', shouldHave: ['dw.system.System'] }
211 |       ];
212 | 
213 |       for (const testCase of yamlTestCases) {
214 |         const result = await client.callTool('search_sfcc_classes', { query: testCase.query });
215 |         
216 |         assertValidMCPResponse(result);
217 |         assert.equal(result.isError, false);
218 |         
219 |         const classArray = parseClassArray(result.content[0].text);
220 |         
221 |         // Verify expected classes are present
222 |         testCase.shouldHave.forEach(expectedClass => {
223 |           assert.ok(classArray.includes(expectedClass), 
224 |             `Results for "${testCase.query}" should include "${expectedClass}"`);
225 |         });
226 |       }
227 |     });
228 | 
229 |     test('should handle edge cases consistently with YAML tests', async () => {
230 |       // Mirror YAML edge case tests
231 |       const result = await client.callTool('search_sfcc_classes', { query: 'zzznothingfound' });
232 |       
233 |       assertValidMCPResponse(result);
234 |       assert.equal(result.isError, false, 'Should not error for non-matching query');
235 |       
236 |       const classArray = parseClassArray(result.content[0].text);
237 |       assert.equal(classArray.length, 0, 'Should return empty array for no matches');
238 |     });
239 |   });
240 | 
241 |   describe('Response Data Quality', () => {
242 |     test('should return unique class names without duplicates', async () => {
243 |       const result = await client.callTool('search_sfcc_classes', { query: 'catalog' });
244 |       
245 |       assertValidMCPResponse(result);
246 |       const classArray = parseClassArray(result.content[0].text);
247 |       
248 |       const uniqueClasses = new Set(classArray);
249 |       assert.equal(classArray.length, uniqueClasses.size, 
250 |         'Should not contain duplicate class names');
251 |     });
252 | 
253 |     test('should return results in consistent order', async () => {
254 |       // Call the same query multiple times sequentially to avoid message interference
255 |       const results = [];
256 |       
257 |       for (let i = 0; i < 3; i++) {
258 |         const result = await client.callTool('search_sfcc_classes', { query: 'catalog' });
259 |         results.push(result);
260 |         
261 |       }
262 |       
263 |       const arrays = results.map(result => parseClassArray(result.content[0].text));
264 |       
265 |       // All arrays should be identical
266 |       assert.deepEqual(arrays[0], arrays[1], 'Results should be consistent across calls');
267 |       assert.deepEqual(arrays[1], arrays[2], 'Results should be consistent across calls');
268 |     });
269 | 
270 |     test('should validate class name patterns and format', async () => {
271 |       const result = await client.callTool('search_sfcc_classes', { query: 'catalog' });
272 |       
273 |       assertValidMCPResponse(result);
274 |       const classArray = parseClassArray(result.content[0].text);
275 |       
276 |       classArray.forEach(className => {
277 |         // Validate class name format - updated for all SFCC namespaces
278 |         assert.match(className, /^(dw\.|TopLevel\.|best-practices\.|sfra\.)[a-zA-Z0-9_./-]+$/, 
279 |           `Class name "${className}" should follow valid pattern`);
280 |         
281 |         // Should not contain spaces or invalid characters
282 |         assert.ok(!className.includes(' '), `Class name "${className}" should not contain spaces`);
283 |         
284 |         // Should have reasonable length
285 |         assert.ok(className.length > 3, `Class name "${className}" should be reasonable length`);
286 |         assert.ok(className.length < 100, `Class name "${className}" should not be excessively long`);
287 |       });
288 |     });
289 |   });
290 | });
291 | 
292 | // Helper functions
293 | 
294 | /**
295 |  * Validates that a response follows proper MCP structure
296 |  */
297 | function assertValidMCPResponse(result) {
298 |   assert.ok(result.content, 'Response should have content property');
299 |   assert.ok(Array.isArray(result.content), 'Content should be an array');
300 |   assert.ok(result.content.length > 0, 'Content array should not be empty');
301 |   assert.equal(result.content[0].type, 'text', 'First content item should be text type');
302 |   assert.equal(typeof result.content[0].text, 'string', 'Text content should be a string');
303 |   
304 |   // isError property should always be present and boolean
305 |   assert.ok(Object.prototype.hasOwnProperty.call(result, 'isError'), 'isError property should always be present');
306 |   assert.equal(typeof result.isError, 'boolean', 'isError should be a boolean');
307 | }
308 | 
309 | /**
310 |  * Parses the class array from the response text
311 |  */
312 | function parseClassArray(text) {
313 |   try {
314 |     return JSON.parse(text);
315 |   } catch {
316 |     throw new Error(`Failed to parse class array from response: ${text}`);
317 |   }
318 | }
319 | 
320 | /**
321 |  * Categorizes error messages by type
322 |  */
323 | function categorizeError(errorText) {
324 |   const errorPatterns = [
325 |     { type: 'validation', keywords: ['required', 'invalid', 'missing', 'non-empty', 'string'] },
326 |     { type: 'not_found', keywords: ['not found', 'does not exist'] },
327 |     { type: 'permission', keywords: ['permission', 'unauthorized', 'forbidden'] },
328 |     { type: 'network', keywords: ['connection', 'timeout', 'unreachable'] }
329 |   ];
330 | 
331 |   const lowerText = errorText.toLowerCase();
332 |   for (const pattern of errorPatterns) {
333 |     if (pattern.keywords.some(keyword => lowerText.includes(keyword))) {
334 |       return pattern.type;
335 |     }
336 |   }
337 |   return 'unknown';
338 | }
339 | 
```

--------------------------------------------------------------------------------
/tests/sfcc-mock-server.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, test, expect, beforeAll, afterAll } from '@jest/globals';
  2 | import { SFCCMockServerManager, withSFCCMockServer } from './servers/sfcc-mock-server-manager';
  3 | 
  4 | /**
  5 |  * Integration tests for the Unified SFCC Mock Server
  6 |  *
  7 |  * These tests demonstrate how to use the unified SFCC mock server
  8 |  * for testing both WebDAV log functionality and OCAPI simulation.
  9 |  * The server combines both protocols into a single endpoint.
 10 |  */
 11 | 
 12 | describe('Unified SFCC Mock Server Integration', () => {
 13 |   let serverManager: SFCCMockServerManager;
 14 | 
 15 |   beforeAll(async () => {
 16 |     serverManager = new SFCCMockServerManager({
 17 |       port: 3002, // Use different port for tests
 18 |       dev: false,
 19 |       autoSetup: true,
 20 |     });
 21 | 
 22 |     // Check if server is available before running tests
 23 |     const isAvailable = await serverManager.isServerAvailable();
 24 |     if (!isAvailable) {
 25 |       console.warn('⚠️  SFCC mock server not available, skipping tests');
 26 |       return;
 27 |     }
 28 | 
 29 |     await serverManager.start();
 30 |   }, 20000); // 20 second timeout for server startup (includes log setup)
 31 | 
 32 |   afterAll(async () => {
 33 |     if (serverManager?.isRunning()) {
 34 |       await serverManager.stop();
 35 |     }
 36 |   }, 10000); // 10 second timeout for server shutdown
 37 | 
 38 |   describe('Server Startup and Basic Functionality', () => {
 39 |     test('should start server and be accessible', async () => {
 40 |       if (!await serverManager.isServerAvailable()) {
 41 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
 42 |         return;
 43 |       }
 44 | 
 45 |       expect(serverManager.isRunning()).toBe(true);
 46 |       expect(serverManager.getServerUrl()).toBe('http://localhost:3002');
 47 |       expect(serverManager.getWebDAVLogsUrl()).toBe('http://localhost:3002/on/demandware.servlet/webdav/Sites/Logs/');
 48 |       expect(serverManager.getDirectLogsUrl()).toBe('http://localhost:3002/Logs/');
 49 |       expect(serverManager.getOCAPIUrl()).toBe('http://localhost:3002/s/-/dw/data/v23_2');
 50 |       expect(serverManager.getOAuthUrl()).toBe('http://localhost:3002/dw/oauth2/access_token');
 51 |     });
 52 | 
 53 |     test('should respond to health check', async () => {
 54 |       if (!await serverManager.isServerAvailable()) {
 55 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
 56 |         return;
 57 |       }
 58 | 
 59 |       const response = await fetch(`${serverManager.getServerUrl()}/health`);
 60 |       expect(response.status).toBe(200);
 61 | 
 62 |       const healthData = await response.json();
 63 |       expect(healthData).toHaveProperty('status', 'ok');
 64 |       expect(healthData).toHaveProperty('message');
 65 |     });
 66 |   });
 67 | 
 68 |   describe('WebDAV Functionality', () => {
 69 |     test('should serve WebDAV directory listing', async () => {
 70 |       if (!await serverManager.isServerAvailable()) {
 71 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
 72 |         return;
 73 |       }
 74 | 
 75 |       // Test SFCC WebDAV path
 76 |       const response = await fetch(serverManager.getWebDAVLogsUrl(), {
 77 |         method: 'PROPFIND',
 78 |         headers: {
 79 |           'Depth': '1',
 80 |           'Content-Type': 'application/xml',
 81 |         },
 82 |       });
 83 | 
 84 |       expect(response.status).toBe(207); // Multi-Status (WebDAV response)
 85 | 
 86 |       const responseText = await response.text();
 87 |       expect(responseText).toContain('error-blade-');
 88 |       expect(responseText).toContain('warn-blade-');
 89 |       expect(responseText).toContain('info-blade-');
 90 |       expect(responseText).toContain('debug-blade-');
 91 |       expect(responseText).toContain('jobs');
 92 | 
 93 |       // Also test direct path for backward compatibility
 94 |       const directResponse = await fetch(serverManager.getDirectLogsUrl(), {
 95 |         method: 'PROPFIND',
 96 |         headers: {
 97 |           'Depth': '1',
 98 |           'Content-Type': 'application/xml',
 99 |         },
100 |       });
101 | 
102 |       expect(directResponse.status).toBe(207);
103 |     });
104 | 
105 |     test('should serve log file content', async () => {
106 |       if (!await serverManager.isServerAvailable()) {
107 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
108 |         return;
109 |       }
110 | 
111 |       // First get the directory listing to find an actual log file
112 |       const listResponse = await fetch(serverManager.getWebDAVLogsUrl(), {
113 |         method: 'PROPFIND',
114 |         headers: {
115 |           'Depth': '1',
116 |           'Content-Type': 'application/xml',
117 |         },
118 |       });
119 | 
120 |       const listingXml = await listResponse.text();
121 | 
122 |       // Extract error log filename from the XML response
123 |       const errorLogMatch = listingXml.match(/error-blade-[^<]+\.log/);
124 |       if (!errorLogMatch) {
125 |         throw new Error('No error log file found in directory listing');
126 |       }
127 | 
128 |       const errorLogFile = errorLogMatch[0];
129 |       const logFileUrl = `${serverManager.getWebDAVLogsUrl()}${errorLogFile}`;
130 | 
131 |       // Get the log file content
132 |       const logResponse = await fetch(logFileUrl);
133 |       expect(logResponse.status).toBe(200);
134 | 
135 |       const logContent = await logResponse.text();
136 |       expect(logContent).toContain('ERROR');
137 |       expect(logContent).toContain('SystemJobThread');
138 |       expect(logContent).toContain('PipelineCallServlet');
139 |     });
140 | 
141 |     test('should serve job logs directory', async () => {
142 |       if (!await serverManager.isServerAvailable()) {
143 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
144 |         return;
145 |       }
146 | 
147 |       const jobsUrl = `${serverManager.getWebDAVLogsUrl()}jobs/`;
148 |       const response = await fetch(jobsUrl, {
149 |         method: 'PROPFIND',
150 |         headers: {
151 |           'Depth': '1',
152 |           'Content-Type': 'application/xml',
153 |         },
154 |       });
155 | 
156 |       expect(response.status).toBe(207); // Multi-Status (WebDAV response)
157 | 
158 |       const responseText = await response.text();
159 |       expect(responseText).toContain('ProcessOrders');
160 |       expect(responseText).toContain('ImportCatalog');
161 |     });
162 | 
163 |     test('should serve job log file content', async () => {
164 |       if (!await serverManager.isServerAvailable()) {
165 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
166 |         return;
167 |       }
168 | 
169 |       const jobLogUrl = `${serverManager.getWebDAVLogsUrl()}jobs/ProcessOrders/Job-ProcessOrders-1234567890.log`;
170 |       const response = await fetch(jobLogUrl);
171 | 
172 |       expect(response.status).toBe(200);
173 | 
174 |       const logContent = await response.text();
175 |       expect(logContent).toContain('Executing job [ProcessOrders][1234567890]...');
176 |       expect(logContent).toContain('INFO');
177 |       expect(logContent).toContain('SystemJobThread');
178 |       expect(logContent).toContain('ValidateOrdersStep');
179 |     });
180 | 
181 |     test('should support range requests for log files', async () => {
182 |       if (!await serverManager.isServerAvailable()) {
183 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
184 |         return;
185 |       }
186 | 
187 |       // Get a log file with range request (last 100 bytes)
188 |       const listResponse = await fetch(serverManager.getWebDAVLogsUrl(), {
189 |         method: 'PROPFIND',
190 |         headers: { 'Depth': '1' },
191 |       });
192 | 
193 |       const listingXml = await listResponse.text();
194 |       const errorLogMatch = listingXml.match(/error-blade-[^<]+\.log/);
195 | 
196 |       if (!errorLogMatch) {
197 |         console.warn('⚠️  No error log file found, skipping range request test');
198 |         return;
199 |       }
200 | 
201 |       const errorLogFile = errorLogMatch[0];
202 |       const logFileUrl = `${serverManager.getWebDAVLogsUrl()}${errorLogFile}`;
203 | 
204 |       const rangeResponse = await fetch(logFileUrl, {
205 |         headers: {
206 |           'Range': 'bytes=-100', // Last 100 bytes
207 |         },
208 |       });
209 | 
210 |       expect([200, 206, 416])
211 |         .toContain(rangeResponse.status); // 200 OK, 206 Partial Content, or 416 Range Not Satisfiable
212 |     });
213 |   });
214 | 
215 |   describe('OCAPI Functionality', () => {
216 |     test('should handle OAuth token request', async () => {
217 |       if (!await serverManager.isServerAvailable()) {
218 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
219 |         return;
220 |       }
221 | 
222 |       const response = await fetch(serverManager.getOAuthUrl(), {
223 |         method: 'POST',
224 |         headers: {
225 |           'Content-Type': 'application/x-www-form-urlencoded',
226 |           'Authorization': 'Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0', // test-client-id:test-client-secret
227 |         },
228 |         body: 'grant_type=client_credentials',
229 |       });
230 | 
231 |       expect(response.status).toBe(200);
232 | 
233 |       const tokenData = await response.json();
234 |       expect(tokenData).toHaveProperty('access_token');
235 |       expect(tokenData).toHaveProperty('token_type', 'Bearer');
236 |       expect(tokenData).toHaveProperty('expires_in');
237 |     });
238 | 
239 |     test('should serve system object types', async () => {
240 |       if (!await serverManager.isServerAvailable()) {
241 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
242 |         return;
243 |       }
244 | 
245 |       // First get OAuth token
246 |       const tokenResponse = await fetch(serverManager.getOAuthUrl(), {
247 |         method: 'POST',
248 |         headers: {
249 |           'Content-Type': 'application/x-www-form-urlencoded',
250 |           'Authorization': 'Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0',
251 |         },
252 |         body: 'grant_type=client_credentials',
253 |       });
254 | 
255 |       const tokenData = await tokenResponse.json();
256 |       const accessToken = tokenData.access_token;
257 | 
258 |       // Test system object types endpoint
259 |       const response = await fetch(`${serverManager.getOCAPIUrl()}/system_object_definitions`, {
260 |         headers: {
261 |           'Authorization': `Bearer ${accessToken}`,
262 |           'Content-Type': 'application/json',
263 |         },
264 |       });
265 | 
266 |       expect(response.status).toBe(200);
267 | 
268 |       const data = await response.json();
269 |       expect(data).toHaveProperty('count');
270 |       expect(data).toHaveProperty('data');
271 |       expect(Array.isArray(data.data)).toBe(true);
272 | 
273 |       // Check for some common system objects
274 |       const objectIds = data.data.map((obj: any) => obj.object_type);
275 |       expect(objectIds).toContain('Basket');
276 |       expect(objectIds).toContain('CustomObject');
277 |     });
278 | 
279 |     test('should serve site preferences', async () => {
280 |       if (!await serverManager.isServerAvailable()) {
281 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
282 |         return;
283 |       }
284 | 
285 |       // First get OAuth token
286 |       const tokenResponse = await fetch(serverManager.getOAuthUrl(), {
287 |         method: 'POST',
288 |         headers: {
289 |           'Content-Type': 'application/x-www-form-urlencoded',
290 |           'Authorization': 'Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0',
291 |         },
292 |         body: 'grant_type=client_credentials',
293 |       });
294 | 
295 |       const tokenData = await tokenResponse.json();
296 |       const accessToken = tokenData.access_token;
297 | 
298 |       // Test site preferences search endpoint
299 |       const searchBody = {
300 |         query: {
301 |           match_all_query: {},
302 |         },
303 |         select: '(**)',
304 |         count: 200,
305 |       };
306 | 
307 |       const response = await fetch(`${serverManager.getOCAPIUrl()}/site_preferences/preference_groups/CCV/sandbox/preference_search`, {
308 |         method: 'POST',
309 |         headers: {
310 |           'Authorization': `Bearer ${accessToken}`,
311 |           'Content-Type': 'application/json',
312 |         },
313 |         body: JSON.stringify(searchBody),
314 |       });
315 | 
316 |       expect(response.status).toBe(200);
317 | 
318 |       const data = await response.json();
319 |       expect(data).toHaveProperty('count');
320 |       expect(data).toHaveProperty('hits');
321 |       expect(Array.isArray(data.hits)).toBe(true);
322 |     });
323 | 
324 |     test('should handle invalid OAuth requests', async () => {
325 |       if (!await serverManager.isServerAvailable()) {
326 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
327 |         return;
328 |       }
329 | 
330 |       const response = await fetch(serverManager.getOAuthUrl(), {
331 |         method: 'POST',
332 |         headers: {
333 |           'Content-Type': 'application/x-www-form-urlencoded',
334 |           'Authorization': 'Basic invalid_credentials',
335 |         },
336 |         body: 'grant_type=client_credentials',
337 |       });
338 | 
339 |       expect(response.status).toBe(401);
340 |     });
341 | 
342 |     test('should handle unauthorized OCAPI requests', async () => {
343 |       if (!await serverManager.isServerAvailable()) {
344 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
345 |         return;
346 |       }
347 | 
348 |       const response = await fetch(`${serverManager.getOCAPIUrl()}/system_object_definitions`, {
349 |         headers: {
350 |           'Content-Type': 'application/json',
351 |         },
352 |       });
353 | 
354 |       expect(response.status).toBe(401);
355 |     });
356 |   });
357 | 
358 |   describe('CORS and Cross-Origin Support', () => {
359 |     test('should include CORS headers', async () => {
360 |       if (!await serverManager.isServerAvailable()) {
361 |         console.warn('⚠️  Skipping test - SFCC mock server not available');
362 |         return;
363 |       }
364 | 
365 |       const response = await fetch(serverManager.getServerUrl(), {
366 |         method: 'OPTIONS',
367 |       });
368 | 
369 |       expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
370 |       expect(response.headers.get('Access-Control-Allow-Methods')).toContain('GET');
371 |       expect(response.headers.get('Access-Control-Allow-Methods')).toContain('POST');
372 |       expect(response.headers.get('Access-Control-Allow-Methods')).toContain('PROPFIND');
373 |     });
374 |   });
375 | });
376 | 
377 | /**
378 |  * Example of how to use withSFCCMockServer utility
379 |  */
380 | describe('SFCC Mock Server Utility Function', () => {
381 |   test('should work with utility function', async () => {
382 |     const manager = new SFCCMockServerManager();
383 |     const isAvailable = await manager.isServerAvailable();
384 |     if (!isAvailable) {
385 |       console.warn('⚠️  Skipping test - SFCC mock server not available');
386 |       return;
387 |     }
388 | 
389 |     const result = await withSFCCMockServer(
390 |       async (serverUrl, webdavLogsUrl, directLogsUrl, ocapiUrl, oauthUrl) => {
391 |         expect(serverUrl).toContain('http://localhost:');
392 |         expect(webdavLogsUrl).toContain('/on/demandware.servlet/webdav/Sites/Logs/');
393 |         expect(directLogsUrl).toContain('/Logs/');
394 |         expect(ocapiUrl).toContain('/s/-/dw/data');
395 |         expect(oauthUrl).toContain('/dw/oauth2/access_token');
396 | 
397 |         // Test both WebDAV and OCAPI functionality
398 |         const webdavResponse = await fetch(webdavLogsUrl, {
399 |           method: 'PROPFIND',
400 |           headers: { 'Depth': '1' },
401 |         });
402 | 
403 |         const oauthResponse = await fetch(oauthUrl, {
404 |           method: 'POST',
405 |           headers: {
406 |             'Content-Type': 'application/x-www-form-urlencoded',
407 |             'Authorization': 'Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0',
408 |           },
409 |           body: 'grant_type=client_credentials',
410 |         });
411 | 
412 |         return {
413 |           webdavStatus: webdavResponse.status,
414 |           oauthStatus: oauthResponse.status,
415 |         };
416 |       },
417 |       { port: 3005 },
418 |     );
419 | 
420 |     expect(result.webdavStatus).toBe(207); // WebDAV Multi-Status
421 |     expect(result.oauthStatus).toBe(200); // OAuth success
422 |   }, 25000); // 25 second timeout for utility test
423 | });
424 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get-sfra-documents-by-category.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # ==================================================================================
  2 | # SFCC MCP Server - get_sfra_documents_by_category Tool YAML Tests (full-mode mode)
  3 | # Tests SFRA document category filtering functionality with comprehensive validation
  4 | # 
  5 | # Tool: get_sfra_documents_by_category
  6 | # Purpose: Get SFRA documents filtered by category (core, product, order, customer, pricing, store, other)
  7 | # Parameters: category (required) - Category to filter by
  8 | # 
  9 | # Quick Test Commands:
 10 | # aegis "tests/mcp/yaml/get-sfra-documents-by-category.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose
 11 | # aegis "tests/mcp/yaml/get-sfra-documents-by-category.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --debug --timing
 12 | # aegis query get_sfra_documents_by_category '{"category": "core"}' --config "aegis.config.with-dw.json"
 13 | # ==================================================================================
 14 | description: "SFCC MCP Server - get_sfra_documents_by_category tool comprehensive tests"
 15 | 
 16 | # ==================================================================================
 17 | # SUCCESSFUL OPERATIONS - VALID CATEGORIES
 18 | # ==================================================================================
 19 | tests:
 20 |   - it: "should retrieve core SFRA documents with proper structure"
 21 |     request:
 22 |       jsonrpc: "2.0"
 23 |       id: "core-docs-1"
 24 |       method: "tools/call"
 25 |       params:
 26 |         name: "get_sfra_documents_by_category"
 27 |         arguments:
 28 |           category: "core"
 29 |     expect:
 30 |       response:
 31 |         jsonrpc: "2.0"
 32 |         id: "core-docs-1"
 33 |         result:
 34 |           content:
 35 |             - type: "text"
 36 |               text: "match:regex:\\[\\s*\\{[\\s\\S]*\\}\\s*\\]"  # Valid JSON array structure
 37 |           isError: false
 38 |       performance:
 39 |         maxResponseTime: "500ms"
 40 |       stderr: "toBeEmpty"
 41 | 
 42 |   - it: "should return valid JSON array for core category"
 43 |     request:
 44 |       jsonrpc: "2.0"
 45 |       id: "core-json-1"
 46 |       method: "tools/call"
 47 |       params:
 48 |         name: "get_sfra_documents_by_category"
 49 |         arguments:
 50 |           category: "core"
 51 |     expect:
 52 |       response:
 53 |         jsonrpc: "2.0"
 54 |         id: "core-json-1"
 55 |         result:
 56 |           content:
 57 |             - type: "text"
 58 |               text: "match:contains:server"  # Core category should contain server document
 59 |           isError: false
 60 |       stderr: "toBeEmpty"
 61 | 
 62 |   - it: "should include required document fields for core category"
 63 |     request:
 64 |       jsonrpc: "2.0"
 65 |       id: "core-fields-1"
 66 |       method: "tools/call"
 67 |       params:
 68 |         name: "get_sfra_documents_by_category"
 69 |         arguments:
 70 |           category: "core"
 71 |     expect:
 72 |       response:
 73 |         jsonrpc: "2.0"
 74 |         id: "core-fields-1"
 75 |         result:
 76 |           content:
 77 |             - type: "text"
 78 |               text: "match:regex:[\\s\\S]*name[\\s\\S]*title[\\s\\S]*description[\\s\\S]*type[\\s\\S]*category[\\s\\S]*filename"
 79 |           isError: false
 80 |       stderr: "toBeEmpty"
 81 | 
 82 |   - it: "should contain expected core documents"
 83 |     request:
 84 |       jsonrpc: "2.0"
 85 |       id: "core-content-1"
 86 |       method: "tools/call"
 87 |       params:
 88 |         name: "get_sfra_documents_by_category"
 89 |         arguments:
 90 |           category: "core"
 91 |     expect:
 92 |       response:
 93 |         jsonrpc: "2.0"
 94 |         id: "core-content-1"
 95 |         result:
 96 |           content:
 97 |             - type: "text"
 98 |               text: "match:regex:[\\s\\S]*querystring[\\s\\S]*render[\\s\\S]*request[\\s\\S]*response[\\s\\S]*server"  # Core SFRA classes in alphabetical order
 99 |           isError: false
100 |       stderr: "toBeEmpty"
101 | 
102 |   - it: "should retrieve product SFRA documents with proper structure"
103 |     request:
104 |       jsonrpc: "2.0"
105 |       id: "product-docs-1"
106 |       method: "tools/call"
107 |       params:
108 |         name: "get_sfra_documents_by_category"
109 |         arguments:
110 |           category: "product"
111 |     expect:
112 |       response:
113 |         jsonrpc: "2.0"
114 |         id: "product-docs-1"
115 |         result:
116 |           content:
117 |             - type: "text"
118 |               text: "match:regex:\\[\\s*\\{[\\s\\S]*\\}\\s*\\]"  # Valid JSON array structure
119 |           isError: false
120 |       performance:
121 |         maxResponseTime: "500ms"
122 |       stderr: "toBeEmpty"
123 | 
124 |   - it: "should contain product model documents"
125 |     request:
126 |       jsonrpc: "2.0"
127 |       id: "product-content-1"
128 |       method: "tools/call"
129 |       params:
130 |         name: "get_sfra_documents_by_category"
131 |         arguments:
132 |           category: "product"
133 |     expect:
134 |       response:
135 |         jsonrpc: "2.0"
136 |         id: "product-content-1"
137 |         result:
138 |           content:
139 |             - type: "text"
140 |               text: "match:regex:[\\s\\S]*product-full[\\s\\S]*product-tile"  # Product models
141 |           isError: false
142 |       stderr: "toBeEmpty"
143 | 
144 |   - it: "should retrieve order category documents"
145 |     request:
146 |       jsonrpc: "2.0"
147 |       id: "order-docs-1"
148 |       method: "tools/call"
149 |       params:
150 |         name: "get_sfra_documents_by_category"
151 |         arguments:
152 |           category: "order"
153 |     expect:
154 |       response:
155 |         jsonrpc: "2.0"
156 |         id: "order-docs-1"
157 |         result:
158 |           content:
159 |             - type: "text"
160 |               text: "match:regex:\\[[\\s\\S]*\\]"  # Valid JSON array (may be empty)
161 |           isError: false
162 |       stderr: "toBeEmpty"
163 | 
164 |   - it: "should retrieve customer category documents"
165 |     request:
166 |       jsonrpc: "2.0"
167 |       id: "customer-docs-1"
168 |       method: "tools/call"
169 |       params:
170 |         name: "get_sfra_documents_by_category"
171 |         arguments:
172 |           category: "customer"
173 |     expect:
174 |       response:
175 |         jsonrpc: "2.0"
176 |         id: "customer-docs-1"
177 |         result:
178 |           content:
179 |             - type: "text"
180 |               text: "match:regex:\\[[\\s\\S]*\\]"  # Valid JSON array
181 |           isError: false
182 |       stderr: "toBeEmpty"
183 | 
184 |   - it: "should retrieve pricing category documents"
185 |     request:
186 |       jsonrpc: "2.0"
187 |       id: "pricing-docs-1"
188 |       method: "tools/call"
189 |       params:
190 |         name: "get_sfra_documents_by_category"
191 |         arguments:
192 |           category: "pricing"
193 |     expect:
194 |       response:
195 |         jsonrpc: "2.0"
196 |         id: "pricing-docs-1"
197 |         result:
198 |           content:
199 |             - type: "text"
200 |               text: "match:regex:\\[[\\s\\S]*\\]"  # Valid JSON array
201 |           isError: false
202 |       stderr: "toBeEmpty"
203 | 
204 |   - it: "should retrieve store category documents"
205 |     request:
206 |       jsonrpc: "2.0"
207 |       id: "store-docs-1"
208 |       method: "tools/call"
209 |       params:
210 |         name: "get_sfra_documents_by_category"
211 |         arguments:
212 |           category: "store"
213 |     expect:
214 |       response:
215 |         jsonrpc: "2.0"
216 |         id: "store-docs-1"
217 |         result:
218 |           content:
219 |             - type: "text"
220 |               text: "match:regex:\\[[\\s\\S]*\\]"  # Valid JSON array
221 |           isError: false
222 |       stderr: "toBeEmpty"
223 | 
224 |   - it: "should retrieve other category documents"
225 |     request:
226 |       jsonrpc: "2.0"
227 |       id: "other-docs-1"
228 |       method: "tools/call"
229 |       params:
230 |         name: "get_sfra_documents_by_category"
231 |         arguments:
232 |           category: "other"
233 |     expect:
234 |       response:
235 |         jsonrpc: "2.0"
236 |         id: "other-docs-1"
237 |         result:
238 |           content:
239 |             - type: "text"
240 |               text: "match:regex:\\[[\\s\\S]*\\]"  # Valid JSON array
241 |           isError: false
242 |       stderr: "toBeEmpty"
243 | 
244 | # ==================================================================================
245 | # EDGE CASES - INVALID/EMPTY CATEGORIES
246 | # ==================================================================================
247 | 
248 |   - it: "should handle invalid category gracefully"
249 |     request:
250 |       jsonrpc: "2.0"
251 |       id: "invalid-category-1"
252 |       method: "tools/call"
253 |       params:
254 |         name: "get_sfra_documents_by_category"
255 |         arguments:
256 |           category: "invalid_category_xyz"
257 |     expect:
258 |       response:
259 |         jsonrpc: "2.0"
260 |         id: "invalid-category-1"
261 |         result:
262 |           content:
263 |             - type: "text"
264 |               text: "match:regex:^\\[\\s*\\]$"  # Empty array for invalid category
265 |           isError: false
266 |       stderr: "toBeEmpty"
267 | 
268 |   - it: "should handle empty category gracefully"
269 |     request:
270 |       jsonrpc: "2.0"
271 |       id: "empty-category-1"
272 |       method: "tools/call"
273 |       params:
274 |         name: "get_sfra_documents_by_category"
275 |         arguments:
276 |           category: ""
277 |     expect:
278 |       response:
279 |         jsonrpc: "2.0"
280 |         id: "empty-category-1"
281 |         result:
282 |           content:
283 |             - type: "text"
284 |               text: "match:contains:Error"
285 |           isError: true
286 |       stderr: "toBeEmpty"
287 | 
288 | # ==================================================================================
289 | # ERROR HANDLING - MISSING PARAMETERS
290 | # ==================================================================================
291 | 
292 |   - it: "should require category parameter"
293 |     request:
294 |       jsonrpc: "2.0"
295 |       id: "missing-category-1"
296 |       method: "tools/call"
297 |       params:
298 |         name: "get_sfra_documents_by_category"
299 |         arguments: {}
300 |     expect:
301 |       response:
302 |         jsonrpc: "2.0"
303 |         id: "missing-category-1"
304 |         result:
305 |           content:
306 |             - type: "text"
307 |               text: "match:contains:category must be a non-empty string"
308 |           isError: true
309 |       stderr: "toBeEmpty"
310 | 
311 |   - it: "should handle null category parameter"
312 |     request:
313 |       jsonrpc: "2.0"
314 |       id: "null-category-1"
315 |       method: "tools/call"
316 |       params:
317 |         name: "get_sfra_documents_by_category"
318 |         arguments:
319 |           category: null
320 |     expect:
321 |       response:
322 |         jsonrpc: "2.0"
323 |         id: "null-category-1"
324 |         result:
325 |           content:
326 |             - type: "text"
327 |               text: "match:contains:Error"
328 |           isError: true
329 |       stderr: "toBeEmpty"
330 | 
331 | # ==================================================================================
332 | # DATA VALIDATION - CONTENT STRUCTURE
333 | # ==================================================================================
334 | 
335 |   - it: "should return documents with valid category field in core"
336 |     request:
337 |       jsonrpc: "2.0"
338 |       id: "category-field-1"
339 |       method: "tools/call"
340 |       params:
341 |         name: "get_sfra_documents_by_category"
342 |         arguments:
343 |           category: "core"
344 |     expect:
345 |       response:
346 |         jsonrpc: "2.0"
347 |         id: "category-field-1"
348 |         result:
349 |           content:
350 |             - type: "text"
351 |               text: "match:regex:[\\s\\S]*category[\\s\\S]*:[\\s\\S]*core"  # Category field should match request
352 |           isError: false
353 |       stderr: "toBeEmpty"
354 | 
355 |   - it: "should return documents with type field in product category"
356 |     request:
357 |       jsonrpc: "2.0"
358 |       id: "type-field-1"
359 |       method: "tools/call"
360 |       params:
361 |         name: "get_sfra_documents_by_category"
362 |         arguments:
363 |           category: "product"
364 |     expect:
365 |       response:
366 |         jsonrpc: "2.0"
367 |         id: "type-field-1"
368 |         result:
369 |           content:
370 |             - type: "text"
371 |               text: "match:regex:[\\s\\S]*type[\\s\\S]*:[\\s\\S]*(model|class|module)"  # Valid type values
372 |           isError: false
373 |       stderr: "toBeEmpty"
374 | 
375 |   - it: "should return documents with filename field"
376 |     request:
377 |       jsonrpc: "2.0"
378 |       id: "filename-field-1"
379 |       method: "tools/call"
380 |       params:
381 |         name: "get_sfra_documents_by_category"
382 |         arguments:
383 |           category: "core"
384 |     expect:
385 |       response:
386 |         jsonrpc: "2.0"
387 |         id: "filename-field-1"
388 |         result:
389 |           content:
390 |             - type: "text"
391 |               text: "match:regex:[\\s\\S]*filename[\\s\\S]*:[\\s\\S]*\\.md"  # Markdown filename
392 |           isError: false
393 |       stderr: "toBeEmpty"
394 | 
395 | # ==================================================================================
396 | # PERFORMANCE VALIDATION
397 | # ==================================================================================
398 | 
399 |   - it: "should respond quickly for core category lookup"
400 |     request:
401 |       jsonrpc: "2.0"
402 |       id: "perf-core-1"
403 |       method: "tools/call"
404 |       params:
405 |         name: "get_sfra_documents_by_category"
406 |         arguments:
407 |           category: "core"
408 |     expect:
409 |       response:
410 |         jsonrpc: "2.0"
411 |         id: "perf-core-1"
412 |         result:
413 |           content:
414 |             - type: "text"
415 |               text: "match:type:string"
416 |           isError: false
417 |       performance:
418 |         maxResponseTime: "300ms"  # Fast metadata operation
419 |       stderr: "toBeEmpty"
420 | 
421 |   - it: "should respond quickly for product category lookup"
422 |     request:
423 |       jsonrpc: "2.0"
424 |       id: "perf-product-1"
425 |       method: "tools/call"
426 |       params:
427 |         name: "get_sfra_documents_by_category"
428 |         arguments:
429 |           category: "product"
430 |     expect:
431 |       response:
432 |         jsonrpc: "2.0"
433 |         id: "perf-product-1"
434 |         result:
435 |           content:
436 |             - type: "text"
437 |               text: "match:type:string"
438 |           isError: false
439 |       performance:
440 |         maxResponseTime: "300ms"  # Fast metadata operation
441 |       stderr: "toBeEmpty"
442 | 
443 |   - it: "should handle error cases quickly"
444 |     request:
445 |       jsonrpc: "2.0"
446 |       id: "perf-error-1"
447 |       method: "tools/call"
448 |       params:
449 |         name: "get_sfra_documents_by_category"
450 |         arguments:
451 |           category: ""
452 |     expect:
453 |       response:
454 |         jsonrpc: "2.0"
455 |         id: "perf-error-1"
456 |         result:
457 |           content:
458 |             - type: "text"
459 |               text: "match:contains:Error"
460 |           isError: true
461 |       performance:
462 |         maxResponseTime: "200ms"  # Error handling should be very fast
463 |       stderr: "toBeEmpty"
464 | 
465 | # ==================================================================================
466 | # CASE SENSITIVITY TESTING
467 | # ==================================================================================
468 | 
469 |   - it: "should handle uppercase category names"
470 |     request:
471 |       jsonrpc: "2.0"
472 |       id: "case-upper-1"
473 |       method: "tools/call"
474 |       params:
475 |         name: "get_sfra_documents_by_category"
476 |         arguments:
477 |           category: "CORE"
478 |     expect:
479 |       response:
480 |         jsonrpc: "2.0"
481 |         id: "case-upper-1"
482 |         result:
483 |           content:
484 |             - type: "text"
485 |               text: "match:regex:^\\[\\s*\\]$"  # Expect empty array for case mismatch
486 |           isError: false
487 |       stderr: "toBeEmpty"
488 | 
489 |   - it: "should handle mixed case category names"
490 |     request:
491 |       jsonrpc: "2.0"
492 |       id: "case-mixed-1"
493 |       method: "tools/call"
494 |       params:
495 |         name: "get_sfra_documents_by_category"
496 |         arguments:
497 |           category: "Core"
498 |     expect:
499 |       response:
500 |         jsonrpc: "2.0"
501 |         id: "case-mixed-1"
502 |         result:
503 |           content:
504 |             - type: "text"
505 |               text: "match:regex:^\\[\\s*\\]$"  # Expect empty array for case mismatch
506 |           isError: false
507 |       stderr: "toBeEmpty"
508 | 
```
Page 26/61FirstPrevNextLast