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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/docs/dw_util/Calendar.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.util
  2 | 
  3 | # Class Calendar
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.util.Calendar
  9 | 
 10 | ## Description
 11 | 
 12 | Represents a Calendar and is based on the java.util.Calendar class. Refer to the java.util.Calendar documentation for more information. IMPORTANT NOTE: Please use the StringUtils.formatCalendar(Calendar) functions to convert a Calendar object into a String.
 13 | 
 14 | ## Constants
 15 | 
 16 | ### AM_PM
 17 | 
 18 | **Type:** Number = 9
 19 | 
 20 | Indicates whether the HOUR is before or after noon.
 21 | 
 22 | ### APRIL
 23 | 
 24 | **Type:** Number = 3
 25 | 
 26 | Value for the month of year field representing April.
 27 | 
 28 | ### AUGUST
 29 | 
 30 | **Type:** Number = 7
 31 | 
 32 | Value for the month of year field representing August.
 33 | 
 34 | ### DATE
 35 | 
 36 | **Type:** Number = 5
 37 | 
 38 | Represents a date.
 39 | 
 40 | ### DAY_OF_MONTH
 41 | 
 42 | **Type:** Number = 5
 43 | 
 44 | Represents a day of the month.
 45 | 
 46 | ### DAY_OF_WEEK
 47 | 
 48 | **Type:** Number = 7
 49 | 
 50 | Represents a day of the week.
 51 | 
 52 | ### DAY_OF_WEEK_IN_MONTH
 53 | 
 54 | **Type:** Number = 8
 55 | 
 56 | Represents a day of the week in a month.
 57 | 
 58 | ### DAY_OF_YEAR
 59 | 
 60 | **Type:** Number = 6
 61 | 
 62 | Represents a day of the year.
 63 | 
 64 | ### DECEMBER
 65 | 
 66 | **Type:** Number = 11
 67 | 
 68 | Value for the month of year field representing December.
 69 | 
 70 | ### DST_OFFSET
 71 | 
 72 | **Type:** Number = 16
 73 | 
 74 | Indicates the daylight savings offset in milliseconds.
 75 | 
 76 | ### ERA
 77 | 
 78 | **Type:** Number = 0
 79 | 
 80 | Indicates the era such as 'AD' or 'BC' in the Julian calendar.
 81 | 
 82 | ### FEBRUARY
 83 | 
 84 | **Type:** Number = 1
 85 | 
 86 | Value for the month of year field representing February.
 87 | 
 88 | ### FRIDAY
 89 | 
 90 | **Type:** Number = 6
 91 | 
 92 | Value for the day of the week field representing Friday.
 93 | 
 94 | ### HOUR
 95 | 
 96 | **Type:** Number = 10
 97 | 
 98 | Represents an hour.
 99 | 
100 | ### HOUR_OF_DAY
101 | 
102 | **Type:** Number = 11
103 | 
104 | Represents an hour of the day.
105 | 
106 | ### INPUT_DATE_PATTERN
107 | 
108 | **Type:** Number = 3
109 | 
110 | The input date pattern, for instance MM/dd/yyyy
111 | 
112 | ### INPUT_DATE_TIME_PATTERN
113 | 
114 | **Type:** Number = 5
115 | 
116 | The input date time pattern, for instance MM/dd/yyyy h:mm a
117 | 
118 | ### INPUT_TIME_PATTERN
119 | 
120 | **Type:** Number = 4
121 | 
122 | The input time pattern, for instance h:mm a
123 | 
124 | ### JANUARY
125 | 
126 | **Type:** Number = 0
127 | 
128 | Value for the month of year field representing January.
129 | 
130 | ### JULY
131 | 
132 | **Type:** Number = 6
133 | 
134 | Value for the month of year field representing July.
135 | 
136 | ### JUNE
137 | 
138 | **Type:** Number = 5
139 | 
140 | Value for the month of year field representing June.
141 | 
142 | ### LONG_DATE_PATTERN
143 | 
144 | **Type:** Number = 1
145 | 
146 | The long date pattern, for instance MMM/d/yyyy
147 | 
148 | ### MARCH
149 | 
150 | **Type:** Number = 2
151 | 
152 | Value for the month of year field representing March.
153 | 
154 | ### MAY
155 | 
156 | **Type:** Number = 4
157 | 
158 | Value for the month of year field representing May.
159 | 
160 | ### MILLISECOND
161 | 
162 | **Type:** Number = 14
163 | 
164 | Represents a millisecond.
165 | 
166 | ### MINUTE
167 | 
168 | **Type:** Number = 12
169 | 
170 | Represents a minute.
171 | 
172 | ### MONDAY
173 | 
174 | **Type:** Number = 2
175 | 
176 | Value for the day of the week field representing Monday.
177 | 
178 | ### MONTH
179 | 
180 | **Type:** Number = 2
181 | 
182 | Represents a month where the first month of the year is 0.
183 | 
184 | ### NOVEMBER
185 | 
186 | **Type:** Number = 10
187 | 
188 | Value for the month of year field representing November.
189 | 
190 | ### OCTOBER
191 | 
192 | **Type:** Number = 9
193 | 
194 | Value for the month of year field representing October.
195 | 
196 | ### SATURDAY
197 | 
198 | **Type:** Number = 7
199 | 
200 | Value for the day of the week field representing Saturday.
201 | 
202 | ### SECOND
203 | 
204 | **Type:** Number = 13
205 | 
206 | Represents a second.
207 | 
208 | ### SEPTEMBER
209 | 
210 | **Type:** Number = 8
211 | 
212 | Value for the month of year field representing September.
213 | 
214 | ### SHORT_DATE_PATTERN
215 | 
216 | **Type:** Number = 0
217 | 
218 | The short date pattern, for instance M/d/yy
219 | 
220 | ### SUNDAY
221 | 
222 | **Type:** Number = 1
223 | 
224 | Value for the day of the week field representing Sunday.
225 | 
226 | ### THURSDAY
227 | 
228 | **Type:** Number = 5
229 | 
230 | Value for the day of the week field representing Thursday.
231 | 
232 | ### TIME_PATTERN
233 | 
234 | **Type:** Number = 2
235 | 
236 | The time pattern, for instance h:mm:ss a
237 | 
238 | ### TUESDAY
239 | 
240 | **Type:** Number = 3
241 | 
242 | Value for the day of the week field representing Tuesday.
243 | 
244 | ### WEDNESDAY
245 | 
246 | **Type:** Number = 4
247 | 
248 | Value for the day of the week field representing Wednesday.
249 | 
250 | ### WEEK_OF_MONTH
251 | 
252 | **Type:** Number = 4
253 | 
254 | Represents a week of the month.
255 | 
256 | ### WEEK_OF_YEAR
257 | 
258 | **Type:** Number = 3
259 | 
260 | Represents a week in the year.
261 | 
262 | ### YEAR
263 | 
264 | **Type:** Number = 1
265 | 
266 | Represents a year.
267 | 
268 | ### ZONE_OFFSET
269 | 
270 | **Type:** Number = 15
271 | 
272 | Indicates the raw offset from GMT in milliseconds.
273 | 
274 | ## Properties
275 | 
276 | ### firstDayOfWeek
277 | 
278 | **Type:** Number
279 | 
280 | The first day of the week base on locale context. For example, in the US
281 |  the first day of the week is SUNDAY. However, in France the
282 |  first day of the week is MONDAY.
283 | 
284 | ### time
285 | 
286 | **Type:** Date
287 | 
288 | The current time stamp of this calendar. This method
289 |  is also used to convert a Calendar into a Date.
290 |  
291 |  WARNING: Keep in mind that the returned Date object's time is always
292 |           interpreted in the time zone GMT. This means time zone information
293 |           set at the calendar object will not be honored and gets lost.
294 | 
295 | ### timeZone
296 | 
297 | **Type:** String
298 | 
299 | The current time zone of this calendar.
300 | 
301 | ## Constructor Summary
302 | 
303 | Calendar() Creates a new Calendar object that is set to the current time.
304 | 
305 | Calendar(date : Date) Creates a new Calendar object for the given Date object.
306 | 
307 | ## Method Summary
308 | 
309 | ### add
310 | 
311 | **Signature:** `add(field : Number, value : Number) : void`
312 | 
313 | Adds or subtracts the specified amount of time to the given calendar field, based on the calendar's rules.
314 | 
315 | ### after
316 | 
317 | **Signature:** `after(obj : Object) : boolean`
318 | 
319 | Indicates if this Calendar represents a time after the time represented by the specified Object.
320 | 
321 | ### before
322 | 
323 | **Signature:** `before(obj : Object) : boolean`
324 | 
325 | Indicates if this Calendar represents a time before the time represented by the specified Object.
326 | 
327 | ### clear
328 | 
329 | **Signature:** `clear() : void`
330 | 
331 | Sets all the calendar field values and the time value (millisecond offset from the Epoch) of this Calendar undefined.
332 | 
333 | ### clear
334 | 
335 | **Signature:** `clear(field : Number) : void`
336 | 
337 | Sets the given calendar field value and the time value (millisecond offset from the Epoch) of this Calendar undefined.
338 | 
339 | ### compareTo
340 | 
341 | **Signature:** `compareTo(anotherCalendar : Calendar) : Number`
342 | 
343 | Compares the time values (millisecond offsets from the Epoch) represented by two Calendar objects.
344 | 
345 | ### equals
346 | 
347 | **Signature:** `equals(other : Object) : boolean`
348 | 
349 | Compares two calendar values whether they are equivalent.
350 | 
351 | ### get
352 | 
353 | **Signature:** `get(field : Number) : Number`
354 | 
355 | Returns the value of the given calendar field.
356 | 
357 | ### getActualMaximum
358 | 
359 | **Signature:** `getActualMaximum(field : Number) : Number`
360 | 
361 | Returns the maximum value that the specified calendar field could have.
362 | 
363 | ### getActualMinimum
364 | 
365 | **Signature:** `getActualMinimum(field : Number) : Number`
366 | 
367 | Returns the minimum value that the specified calendar field could have.
368 | 
369 | ### getFirstDayOfWeek
370 | 
371 | **Signature:** `getFirstDayOfWeek() : Number`
372 | 
373 | Returns the first day of the week base on locale context.
374 | 
375 | ### getMaximum
376 | 
377 | **Signature:** `getMaximum(field : Number) : Number`
378 | 
379 | Returns the maximum value for the given calendar field.
380 | 
381 | ### getMinimum
382 | 
383 | **Signature:** `getMinimum(field : Number) : Number`
384 | 
385 | Returns the minimum value for the given calendar field.
386 | 
387 | ### getTime
388 | 
389 | **Signature:** `getTime() : Date`
390 | 
391 | Returns the current time stamp of this calendar.
392 | 
393 | ### getTimeZone
394 | 
395 | **Signature:** `getTimeZone() : String`
396 | 
397 | Returns the current time zone of this calendar.
398 | 
399 | ### hashCode
400 | 
401 | **Signature:** `hashCode() : Number`
402 | 
403 | Calculates the hash code for a calendar;
404 | 
405 | ### isLeapYear
406 | 
407 | **Signature:** `isLeapYear(year : Number) : boolean`
408 | 
409 | Indicates if the specified year is a leap year.
410 | 
411 | ### isSameDay
412 | 
413 | **Signature:** `isSameDay(other : Calendar) : boolean`
414 | 
415 | Checks, whether two calendar dates fall on the same day.
416 | 
417 | ### isSameDayByTimestamp
418 | 
419 | **Signature:** `isSameDayByTimestamp(other : Calendar) : boolean`
420 | 
421 | Checks, whether two calendar dates fall on the same day.
422 | 
423 | ### isSet
424 | 
425 | **Signature:** `isSet(field : Number) : boolean`
426 | 
427 | Indicates if the field is set.
428 | 
429 | ### parseByFormat
430 | 
431 | **Signature:** `parseByFormat(timeString : String, format : String) : void`
432 | 
433 | Parses the string according to the date and time format pattern and set the time at this calendar object.
434 | 
435 | ### parseByLocale
436 | 
437 | **Signature:** `parseByLocale(timeString : String, locale : String, pattern : Number) : void`
438 | 
439 | Parses the string according the date format pattern of the given locale.
440 | 
441 | ### roll
442 | 
443 | **Signature:** `roll(field : Number, up : boolean) : void`
444 | 
445 | Rolls the specified field up or down one value.
446 | 
447 | ### roll
448 | 
449 | **Signature:** `roll(field : Number, amount : Number) : void`
450 | 
451 | Rolls the specified field using the specified value.
452 | 
453 | ### set
454 | 
455 | **Signature:** `set(field : Number, value : Number) : void`
456 | 
457 | Sets the given calendar field to the given value.
458 | 
459 | ### set
460 | 
461 | **Signature:** `set(year : Number, month : Number, date : Number) : void`
462 | 
463 | Sets the values for the calendar fields YEAR, MONTH, and DAY_OF_MONTH.
464 | 
465 | ### set
466 | 
467 | **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number) : void`
468 | 
469 | Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, and MINUTE.
470 | 
471 | ### set
472 | 
473 | **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number, second : Number) : void`
474 | 
475 | Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE and SECOND.
476 | 
477 | ### setFirstDayOfWeek
478 | 
479 | **Signature:** `setFirstDayOfWeek(value : Number) : void`
480 | 
481 | Sets what the first day of the week is.
482 | 
483 | ### setTime
484 | 
485 | **Signature:** `setTime(date : Date) : void`
486 | 
487 | Sets the current time stamp of this calendar. WARNING: Keep in mind that the set Date object's time is always interpreted in the time zone GMT.
488 | 
489 | ### setTimeZone
490 | 
491 | **Signature:** `setTimeZone(timeZone : String) : void`
492 | 
493 | Sets the current time zone of this calendar. WARNING: Keep in mind that the time stamp represented by the calendar is always interpreted in the time zone GMT.
494 | 
495 | ## Constructor Detail
496 | 
497 | ## Method Detail
498 | 
499 | ## Method Details
500 | 
501 | ### add
502 | 
503 | **Signature:** `add(field : Number, value : Number) : void`
504 | 
505 | **Description:** Adds or subtracts the specified amount of time to the given calendar field, based on the calendar's rules.
506 | 
507 | **Parameters:**
508 | 
509 | - `field`: the calendar field.
510 | - `value`: the amount of date or time to be added to the field
511 | 
512 | ---
513 | 
514 | ### after
515 | 
516 | **Signature:** `after(obj : Object) : boolean`
517 | 
518 | **Description:** Indicates if this Calendar represents a time after the time represented by the specified Object.
519 | 
520 | **Parameters:**
521 | 
522 | - `obj`: the object to test.
523 | 
524 | **Returns:**
525 | 
526 | true if this Calendar represents a time after the time represented by the specified Object, false otherwise.
527 | 
528 | ---
529 | 
530 | ### before
531 | 
532 | **Signature:** `before(obj : Object) : boolean`
533 | 
534 | **Description:** Indicates if this Calendar represents a time before the time represented by the specified Object.
535 | 
536 | **Parameters:**
537 | 
538 | - `obj`: the object to test.
539 | 
540 | **Returns:**
541 | 
542 | true if this Calendar represents a time before the time represented by the specified Object, false otherwise.
543 | 
544 | ---
545 | 
546 | ### clear
547 | 
548 | **Signature:** `clear() : void`
549 | 
550 | **Description:** Sets all the calendar field values and the time value (millisecond offset from the Epoch) of this Calendar undefined.
551 | 
552 | ---
553 | 
554 | ### clear
555 | 
556 | **Signature:** `clear(field : Number) : void`
557 | 
558 | **Description:** Sets the given calendar field value and the time value (millisecond offset from the Epoch) of this Calendar undefined.
559 | 
560 | **Parameters:**
561 | 
562 | - `field`: the calendar field to be cleared.
563 | 
564 | ---
565 | 
566 | ### compareTo
567 | 
568 | **Signature:** `compareTo(anotherCalendar : Calendar) : Number`
569 | 
570 | **Description:** Compares the time values (millisecond offsets from the Epoch) represented by two Calendar objects.
571 | 
572 | **Parameters:**
573 | 
574 | - `anotherCalendar`: the Calendar to be compared.
575 | 
576 | **Returns:**
577 | 
578 | the value 0 if the time represented by the argument is equal to the time represented by this Calendar; a value less than 0 if the time of this Calendar is before the time represented by the argument; and a value greater than 0 if the time of this Calendar is after the time represented by the argument.
579 | 
580 | ---
581 | 
582 | ### equals
583 | 
584 | **Signature:** `equals(other : Object) : boolean`
585 | 
586 | **Description:** Compares two calendar values whether they are equivalent.
587 | 
588 | **Parameters:**
589 | 
590 | - `other`: the object to compare against this calendar.
591 | 
592 | ---
593 | 
594 | ### get
595 | 
596 | **Signature:** `get(field : Number) : Number`
597 | 
598 | **Description:** Returns the value of the given calendar field.
599 | 
600 | **Parameters:**
601 | 
602 | - `field`: the calendar field to retrieve.
603 | 
604 | **Returns:**
605 | 
606 | the value for the given calendar field.
607 | 
608 | ---
609 | 
610 | ### getActualMaximum
611 | 
612 | **Signature:** `getActualMaximum(field : Number) : Number`
613 | 
614 | **Description:** Returns the maximum value that the specified calendar field could have.
615 | 
616 | **Parameters:**
617 | 
618 | - `field`: the calendar field.
619 | 
620 | **Returns:**
621 | 
622 | the maximum value that the specified calendar field could have.
623 | 
624 | ---
625 | 
626 | ### getActualMinimum
627 | 
628 | **Signature:** `getActualMinimum(field : Number) : Number`
629 | 
630 | **Description:** Returns the minimum value that the specified calendar field could have.
631 | 
632 | **Parameters:**
633 | 
634 | - `field`: the calendar field.
635 | 
636 | **Returns:**
637 | 
638 | the minimum value that the specified calendar field could have.
639 | 
640 | ---
641 | 
642 | ### getFirstDayOfWeek
643 | 
644 | **Signature:** `getFirstDayOfWeek() : Number`
645 | 
646 | **Description:** Returns the first day of the week base on locale context. For example, in the US the first day of the week is SUNDAY. However, in France the first day of the week is MONDAY.
647 | 
648 | **Returns:**
649 | 
650 | the first day of the week base on locale context. For example, in the US the first day of the week is SUNDAY. However, in France the first day of the week is MONDAY.
651 | 
652 | ---
653 | 
654 | ### getMaximum
655 | 
656 | **Signature:** `getMaximum(field : Number) : Number`
657 | 
658 | **Description:** Returns the maximum value for the given calendar field.
659 | 
660 | **Parameters:**
661 | 
662 | - `field`: the calendar field.
663 | 
664 | **Returns:**
665 | 
666 | the maximum value for the given calendar field.
667 | 
668 | ---
669 | 
670 | ### getMinimum
671 | 
672 | **Signature:** `getMinimum(field : Number) : Number`
673 | 
674 | **Description:** Returns the minimum value for the given calendar field.
675 | 
676 | **Parameters:**
677 | 
678 | - `field`: the calendar field.
679 | 
680 | **Returns:**
681 | 
682 | the minimum value for the given calendar field.
683 | 
684 | ---
685 | 
686 | ### getTime
687 | 
688 | **Signature:** `getTime() : Date`
689 | 
690 | **Description:** Returns the current time stamp of this calendar. This method is also used to convert a Calendar into a Date. WARNING: Keep in mind that the returned Date object's time is always interpreted in the time zone GMT. This means time zone information set at the calendar object will not be honored and gets lost.
691 | 
692 | **Returns:**
693 | 
694 | the current time stamp of this calendar as a Date.
695 | 
696 | ---
697 | 
698 | ### getTimeZone
699 | 
700 | **Signature:** `getTimeZone() : String`
701 | 
702 | **Description:** Returns the current time zone of this calendar.
703 | 
704 | **Returns:**
705 | 
706 | the current time zone of this calendar.
707 | 
708 | ---
709 | 
710 | ### hashCode
711 | 
712 | **Signature:** `hashCode() : Number`
713 | 
714 | **Description:** Calculates the hash code for a calendar;
715 | 
716 | ---
717 | 
718 | ### isLeapYear
719 | 
720 | **Signature:** `isLeapYear(year : Number) : boolean`
721 | 
722 | **Description:** Indicates if the specified year is a leap year.
723 | 
724 | **Parameters:**
725 | 
726 | - `year`: the year to test.
727 | 
728 | **Returns:**
729 | 
730 | true if the specified year is a leap year.
731 | 
732 | ---
733 | 
734 | ### isSameDay
735 | 
736 | **Signature:** `isSameDay(other : Calendar) : boolean`
737 | 
738 | **Description:** Checks, whether two calendar dates fall on the same day. The method performs comparison based on both calendar's field values by honoring the defined time zones. Examples: new Calendar( new Date( "2002/02/28 13:45" ).isSameDay( new Calendar( new Date( "2002/02/28 06:01" ) ) ); would return true. new Calendar( new Date( "2002/02/28 13:45" ).isSameDay( new Calendar( new Date( "2002/02/12 13:45" ) ) ); would return false. new Calendar( new Date( "2002/02/28 13:45" ).isSameDay( new Calendar( new Date( "1970/02/28 13:45" ) ) ); would return false. var cal1 = new Calendar( new Date( "2002/02/28 02:00" ); cal1.setTimeZone( "Etc/GMT+1" ); var cal2 = new Calendar( new Date( "2002/02/28 00:00" ); cal2.setTimeZone( "Etc/GMT+1" ); cal1.isSameDay( cal2 ); would return false since the time zone is applied first which results in comparing 2002/02/28 01:00 for cal1 with 2002/02/27 23:00 for cal2.
739 | 
740 | **Parameters:**
741 | 
742 | - `other`: the calendar to compare against this calendar.
743 | 
744 | ---
745 | 
746 | ### isSameDayByTimestamp
747 | 
748 | **Signature:** `isSameDayByTimestamp(other : Calendar) : boolean`
749 | 
750 | **Description:** Checks, whether two calendar dates fall on the same day. The method performs comparison based on both calendar's time stamps by ignoring any defined time zones. Examples: new Calendar( new Date( "2002/02/28 13:45" ).isSameDayByTimestamp( new Calendar( new Date( "2002/02/28 06:01" ) ) ); would return true. new Calendar( new Date( "2002/02/28 13:45" ).isSameDayByTimestamp( new Calendar( new Date( "2002/02/12 13:45" ) ) ); would return false. new Calendar( new Date( "2002/02/28 13:45" ).isSameDayByTimestamp( new Calendar( new Date( "1970/02/28 13:45" ) ) ); would return false. var cal1 = new Calendar( new Date( "2002/02/28 02:00" ); cal1.setTimeZone( "Etc/GMT+1" ); var cal2 = new Calendar( new Date( "2002/02/28 00:00" ); cal2.setTimeZone( "Etc/GMT+1" ); cal1.isSameDayByTimestamp( cal2 ); would return true since the time zone is not applied first which results in comparing 2002/02/28 02:00 for cal1 with 2002/02/28 00:00 for cal2.
751 | 
752 | **Parameters:**
753 | 
754 | - `other`: the calendar to compare against this calendar.
755 | 
756 | ---
757 | 
758 | ### isSet
759 | 
760 | **Signature:** `isSet(field : Number) : boolean`
761 | 
762 | **Description:** Indicates if the field is set.
763 | 
764 | **Parameters:**
765 | 
766 | - `field`: the field to test.
767 | 
768 | **Returns:**
769 | 
770 | true if the field is set, false otherwise.
771 | 
772 | ---
773 | 
774 | ### parseByFormat
775 | 
776 | **Signature:** `parseByFormat(timeString : String, format : String) : void`
777 | 
778 | **Description:** Parses the string according to the date and time format pattern and set the time at this calendar object. For the specification of the date and time format pattern see the javadoc of the JDK class java.text.SimpleDateFormat. If a time zone is included in the format string, this time zone is used to interpet the time. Otherwise the currently set calendar time zone is used to parse the given time string.
779 | 
780 | **Parameters:**
781 | 
782 | - `timeString`: the time string to parsed
783 | - `format`: the time format string
784 | 
785 | ---
786 | 
787 | ### parseByLocale
788 | 
789 | **Signature:** `parseByLocale(timeString : String, locale : String, pattern : Number) : void`
790 | 
791 | **Description:** Parses the string according the date format pattern of the given locale. If the locale name is invalid, an exception is thrown. The currently set calendar time zone is used to parse the given time string.
792 | 
793 | **Parameters:**
794 | 
795 | - `timeString`: the time string to parsed
796 | - `locale`: the locale id, which defines the date format pattern
797 | - `pattern`: the pattern is one of calendar pattern e.g. SHORT_DATE_PATTERN as defined in the regional settings for the locale
798 | 
799 | ---
800 | 
801 | ### roll
802 | 
803 | **Signature:** `roll(field : Number, up : boolean) : void`
804 | 
805 | **Description:** Rolls the specified field up or down one value.
806 | 
807 | **Parameters:**
808 | 
809 | - `field`: the field to roll.
810 | - `up`: if true rolls the field up, if false rolls the field down.
811 | 
812 | ---
813 | 
814 | ### roll
815 | 
816 | **Signature:** `roll(field : Number, amount : Number) : void`
817 | 
818 | **Description:** Rolls the specified field using the specified value.
819 | 
820 | **Parameters:**
821 | 
822 | - `field`: the field to roll.
823 | - `amount`: the amount to roll the field.
824 | 
825 | ---
826 | 
827 | ### set
828 | 
829 | **Signature:** `set(field : Number, value : Number) : void`
830 | 
831 | **Description:** Sets the given calendar field to the given value.
832 | 
833 | **Parameters:**
834 | 
835 | - `field`: the calendar field to set.
836 | - `value`: the value to set in the field.
837 | 
838 | ---
839 | 
840 | ### set
841 | 
842 | **Signature:** `set(year : Number, month : Number, date : Number) : void`
843 | 
844 | **Description:** Sets the values for the calendar fields YEAR, MONTH, and DAY_OF_MONTH.
845 | 
846 | **Parameters:**
847 | 
848 | - `year`: the value for year.
849 | - `month`: the value for month.
850 | - `date`: the value for date.
851 | 
852 | ---
853 | 
854 | ### set
855 | 
856 | **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number) : void`
857 | 
858 | **Description:** Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, and MINUTE.
859 | 
860 | **Parameters:**
861 | 
862 | - `year`: the value for year.
863 | - `month`: the value for month.
864 | - `date`: the value for date.
865 | - `hourOfDay`: the value for hour of day.
866 | - `minute`: the value for minute.
867 | 
868 | ---
869 | 
870 | ### set
871 | 
872 | **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number, second : Number) : void`
873 | 
874 | **Description:** Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE and SECOND.
875 | 
876 | **Parameters:**
877 | 
878 | - `year`: the value for year.
879 | - `month`: the value for month.
880 | - `date`: the value for date.
881 | - `hourOfDay`: the value for hour of day.
882 | - `minute`: the value for minute.
883 | - `second`: the value for second.
884 | 
885 | ---
886 | 
887 | ### setFirstDayOfWeek
888 | 
889 | **Signature:** `setFirstDayOfWeek(value : Number) : void`
890 | 
891 | **Description:** Sets what the first day of the week is.
892 | 
893 | **Parameters:**
894 | 
895 | - `value`: the day to set as the first day of the week.
896 | 
897 | ---
898 | 
899 | ### setTime
900 | 
901 | **Signature:** `setTime(date : Date) : void`
902 | 
903 | **Description:** Sets the current time stamp of this calendar. WARNING: Keep in mind that the set Date object's time is always interpreted in the time zone GMT. This means that time zone information at the calendar object needs to be set separately by using the setTimeZone(String) method.
904 | 
905 | **Parameters:**
906 | 
907 | - `date`: the current time stamp of this calendar.
908 | 
909 | ---
910 | 
911 | ### setTimeZone
912 | 
913 | **Signature:** `setTimeZone(timeZone : String) : void`
914 | 
915 | **Description:** Sets the current time zone of this calendar. WARNING: Keep in mind that the time stamp represented by the calendar is always interpreted in the time zone GMT. Changing the time zone will not change the calendar's time stamp.
916 | 
917 | **Parameters:**
918 | 
919 | - `timeZone`: the current time zone value to set.
920 | 
921 | ---
```

--------------------------------------------------------------------------------
/docs/dw_system/Request.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.system
  2 | 
  3 | # Class Request
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.system.Request
  9 | 
 10 | ## Description
 11 | 
 12 | Represents a request in Commerce Cloud Digital. Each pipeline dictionary contains a CurrentRequest object, which is of type dw.system.Request. Most requests are HTTP requests, so you can use this object to get information about the HTTP request, such as the HTTP headers. You can also get a list of cookies, if any, associated with the request. If the request is issued from a job, the request is not an HTTP request, so HTTP-related methods return null.
 13 | 
 14 | ## Properties
 15 | 
 16 | ### clientId
 17 | 
 18 | **Type:** String (Read Only)
 19 | 
 20 | The client id of the current SCAPI or OCAPI request. If the request is not a SCAPI request or not an
 21 |  OCAPI request 'null' is returned. For client ids owned by Commerce Cloud Digital an alias is returned.
 22 | 
 23 | ### custom
 24 | 
 25 | **Type:** CustomAttributes (Read Only)
 26 | 
 27 | All of the custom attributes associated with the request. The attributes are stored for the life time of
 28 |  the request.
 29 | 
 30 | ### geolocation
 31 | 
 32 | **Type:** Geolocation
 33 | 
 34 | The physical location for the current request, if available. The
 35 |  location is calculated based on the IP address of the request. Note, if
 36 |  the geolocation tracking feature is not enabled, this method always
 37 |  returns null.
 38 | 
 39 | ### httpCookies
 40 | 
 41 | **Type:** Cookies (Read Only)
 42 | 
 43 | The Cookies object, which can be used to read cookies sent by the client. Use the method
 44 |  Response.addHttpCookie() to add a cookie to the outgoing response.
 45 | 
 46 | ### httpHeaders
 47 | 
 48 | **Type:** Map (Read Only)
 49 | 
 50 | A Map containing all HTTP header values.
 51 | 
 52 | ### httpHost
 53 | 
 54 | **Type:** String (Read Only)
 55 | 
 56 | The host name or null if there is no host name.
 57 | 
 58 | ### httpLocale
 59 | 
 60 | **Type:** String (Read Only)
 61 | 
 62 | The locale or null if there is no associated locale.
 63 | 
 64 | ### httpMethod
 65 | 
 66 | **Type:** String (Read Only)
 67 | 
 68 | The name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
 69 | 
 70 | ### httpParameterMap
 71 | 
 72 | **Type:** HttpParameterMap (Read Only)
 73 | 
 74 | The parameter map that contains the HTTP parameters for the current request.
 75 | 
 76 | ### httpParameters
 77 | 
 78 | **Type:** Map (Read Only)
 79 | 
 80 | A Map containing the raw HTTP parameters sent to the server. The Map contains name/value pairs. Each name
 81 |  is a String and each value is a String array.
 82 | 
 83 | ### httpPath
 84 | 
 85 | **Type:** String (Read Only)
 86 | 
 87 | The path.
 88 | 
 89 | ### httpProtocol
 90 | 
 91 | **Type:** String (Read Only)
 92 | 
 93 | The HTTP protocol used for this request. Possible values are "http" or "https". If the current activity
 94 |  is not related to an HTTP request, for example, when the request is part of a job, this method returns null.
 95 | 
 96 | ### httpQueryString
 97 | 
 98 | **Type:** String (Read Only)
 99 | 
100 | The query string or null if there is no query string.
101 | 
102 | ### httpReferer
103 | 
104 | **Type:** String (Read Only)
105 | 
106 | The referer or null if there is no referer.
107 | 
108 | ### httpRemoteAddress
109 | 
110 | **Type:** String (Read Only)
111 | 
112 | The remote address or null if no remote address is found.
113 | 
114 | ### httpRequest
115 | 
116 | **Type:** boolean (Read Only)
117 | 
118 | Identifies if this request is an HTTP request. The method returns true, if the current processing is related to a
119 |  HTTP request.
120 | 
121 | ### httpSecure
122 | 
123 | **Type:** boolean (Read Only)
124 | 
125 | Returns whether the HTTP communication is secure, which basically means that the communication happens via https.
126 |  If the current activity is not related to an HTTP request the method returns false.
127 | 
128 | ### httpURL
129 | 
130 | **Type:** URL (Read Only)
131 | 
132 | The complete URL of the request which was received at the server.
133 |  This URL does not include SEO optimizations.
134 | 
135 | ### httpUserAgent
136 | 
137 | **Type:** String (Read Only)
138 | 
139 | The HTTP user agent or null if there is no user agent.
140 | 
141 | ### includeRequest
142 | 
143 | **Type:** boolean (Read Only)
144 | 
145 | Returns true if the request represents a request for a remote include, false if it is a top-level request.
146 | 
147 | ### locale
148 | 
149 | **Type:** String
150 | 
151 | The locale of the current request. This locale is set by the system based on the information in the URL.
152 |  It may be different from the locale returned by getHttpLocale(), which is the preferred locale sent by the user agent.
153 | 
154 | ### ocapiVersion
155 | 
156 | **Type:** String (Read Only)
157 | 
158 | The OCAPI version of the current request. If this is not
159 |  an OCAPI request, 'null' is returned.
160 | 
161 | ### pageMetaData
162 | 
163 | **Type:** PageMetaData (Read Only)
164 | 
165 | The page meta data that are associated with the current request.
166 | 
167 | ### requestID
168 | 
169 | **Type:** String (Read Only)
170 | 
171 | The unique identifier of the current request. The unique id is helpful for debugging purpose, e.g. relate
172 |  debug messages to a particular request.
173 | 
174 | ### SCAPI
175 | 
176 | **Type:** boolean (Read Only)
177 | 
178 | Returns whether the request originated in SCAPI.
179 | 
180 | ### SCAPIPathParameters
181 | 
182 | **Type:** Map (Read Only)
183 | 
184 | A map containing all path parameters of current SCAPI request in the following way:
185 |  
186 |  keys: path parameter names from path pattern
187 |  values: corresponding path parameter values from current request
188 |  
189 | 
190 |  Returns null if isSCAPI() returns false i.e. if the request is not a SCAPI request.
191 |  
192 | 
193 |  For example:
194 |  
195 |  Current request: /product/shopper-products/v1/organizations/sfcc_org/products/apple-ipod-shuffle
196 |  Path pattern: /product/shopper-products/v1/organizations/{organizationId}/products/{id}
197 |  Result: Map with 2 key:value pairs: organizationId:sfcc_org and id:apple-ipod-shuffle.
198 | 
199 | ### SCAPIPathPattern
200 | 
201 | **Type:** String (Read Only)
202 | 
203 | The SCAPI path pattern in the following way:
204 | 
205 |  
206 |  The first three segments /api-family/api-name/version with concrete values.
207 |  The /organizations part with the path parameter name organizationId in curly brackets.
208 |  The actual resource path additional path parameter names in curly brackets.
209 |  
210 | 
211 |  Returns null if isSCAPI() returns false i.e. if the request is not a SCAPI request.
212 |  
213 | 
214 |  For example, in the context of a request to get a single product from shopper-products API, this method would
215 |  return /product/shopper-products/v1/organizations/{organizationId}/products/{id}
216 | 
217 | ### session
218 | 
219 | **Type:** Session (Read Only)
220 | 
221 | The session associated with this request.
222 | 
223 | ### triggeredForm
224 | 
225 | **Type:** Form (Read Only)
226 | 
227 | The form that was submitted by the client if the request represents a form submission.
228 | 
229 | ### triggeredFormAction
230 | 
231 | **Type:** FormAction (Read Only)
232 | 
233 | The form action that was triggered by the client if the request represents a form submission.
234 | 
235 | ## Constructor Summary
236 | 
237 | ## Method Summary
238 | 
239 | ### addHttpCookie
240 | 
241 | **Signature:** `addHttpCookie(cookie : Cookie) : void`
242 | 
243 | Adds the specified cookie to the outgoing response.
244 | 
245 | ### getClientId
246 | 
247 | **Signature:** `getClientId() : String`
248 | 
249 | Returns the client id of the current SCAPI or OCAPI request.
250 | 
251 | ### getCustom
252 | 
253 | **Signature:** `getCustom() : CustomAttributes`
254 | 
255 | Returns all of the custom attributes associated with the request.
256 | 
257 | ### getGeolocation
258 | 
259 | **Signature:** `getGeolocation() : Geolocation`
260 | 
261 | Returns the physical location for the current request, if available.
262 | 
263 | ### getHttpCookies
264 | 
265 | **Signature:** `getHttpCookies() : Cookies`
266 | 
267 | Returns the Cookies object, which can be used to read cookies sent by the client.
268 | 
269 | ### getHttpHeaders
270 | 
271 | **Signature:** `getHttpHeaders() : Map`
272 | 
273 | Returns a Map containing all HTTP header values.
274 | 
275 | ### getHttpHost
276 | 
277 | **Signature:** `getHttpHost() : String`
278 | 
279 | Returns the host name or null if there is no host name.
280 | 
281 | ### getHttpLocale
282 | 
283 | **Signature:** `getHttpLocale() : String`
284 | 
285 | Returns the locale or null if there is no associated locale.
286 | 
287 | ### getHttpMethod
288 | 
289 | **Signature:** `getHttpMethod() : String`
290 | 
291 | Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
292 | 
293 | ### getHttpParameterMap
294 | 
295 | **Signature:** `getHttpParameterMap() : HttpParameterMap`
296 | 
297 | Returns the parameter map that contains the HTTP parameters for the current request.
298 | 
299 | ### getHttpParameters
300 | 
301 | **Signature:** `getHttpParameters() : Map`
302 | 
303 | Returns a Map containing the raw HTTP parameters sent to the server.
304 | 
305 | ### getHttpPath
306 | 
307 | **Signature:** `getHttpPath() : String`
308 | 
309 | Returns the path.
310 | 
311 | ### getHttpProtocol
312 | 
313 | **Signature:** `getHttpProtocol() : String`
314 | 
315 | Returns the HTTP protocol used for this request.
316 | 
317 | ### getHttpQueryString
318 | 
319 | **Signature:** `getHttpQueryString() : String`
320 | 
321 | Returns the query string or null if there is no query string.
322 | 
323 | ### getHttpReferer
324 | 
325 | **Signature:** `getHttpReferer() : String`
326 | 
327 | Returns the referer or null if there is no referer.
328 | 
329 | ### getHttpRemoteAddress
330 | 
331 | **Signature:** `getHttpRemoteAddress() : String`
332 | 
333 | Returns the remote address or null if no remote address is found.
334 | 
335 | ### getHttpURL
336 | 
337 | **Signature:** `getHttpURL() : URL`
338 | 
339 | Returns the complete URL of the request which was received at the server.
340 | 
341 | ### getHttpUserAgent
342 | 
343 | **Signature:** `getHttpUserAgent() : String`
344 | 
345 | Returns the HTTP user agent or null if there is no user agent.
346 | 
347 | ### getLocale
348 | 
349 | **Signature:** `getLocale() : String`
350 | 
351 | Returns the locale of the current request.
352 | 
353 | ### getOcapiVersion
354 | 
355 | **Signature:** `getOcapiVersion() : String`
356 | 
357 | Returns the OCAPI version of the current request.
358 | 
359 | ### getPageMetaData
360 | 
361 | **Signature:** `getPageMetaData() : PageMetaData`
362 | 
363 | Returns the page meta data that are associated with the current request.
364 | 
365 | ### getRequestID
366 | 
367 | **Signature:** `getRequestID() : String`
368 | 
369 | Returns the unique identifier of the current request.
370 | 
371 | ### getSCAPIPathParameters
372 | 
373 | **Signature:** `getSCAPIPathParameters() : Map`
374 | 
375 | Returns a map containing all path parameters of current SCAPI request in the following way: keys: path parameter names from path pattern values: corresponding path parameter values from current request Returns null if isSCAPI() returns false i.e.
376 | 
377 | ### getSCAPIPathPattern
378 | 
379 | **Signature:** `getSCAPIPathPattern() : String`
380 | 
381 | Returns the SCAPI path pattern in the following way: The first three segments /api-family/api-name/version with concrete values. The /organizations part with the path parameter name organizationId in curly brackets. The actual resource path additional path parameter names in curly brackets. Returns null if isSCAPI() returns false i.e.
382 | 
383 | ### getSession
384 | 
385 | **Signature:** `getSession() : Session`
386 | 
387 | Returns the session associated with this request.
388 | 
389 | ### getTriggeredForm
390 | 
391 | **Signature:** `getTriggeredForm() : Form`
392 | 
393 | Returns the form that was submitted by the client if the request represents a form submission.
394 | 
395 | ### getTriggeredFormAction
396 | 
397 | **Signature:** `getTriggeredFormAction() : FormAction`
398 | 
399 | Returns the form action that was triggered by the client if the request represents a form submission.
400 | 
401 | ### isHttpRequest
402 | 
403 | **Signature:** `isHttpRequest() : boolean`
404 | 
405 | Identifies if this request is an HTTP request.
406 | 
407 | ### isHttpSecure
408 | 
409 | **Signature:** `isHttpSecure() : boolean`
410 | 
411 | Returns whether the HTTP communication is secure, which basically means that the communication happens via https.
412 | 
413 | ### isIncludeRequest
414 | 
415 | **Signature:** `isIncludeRequest() : boolean`
416 | 
417 | Returns true if the request represents a request for a remote include, false if it is a top-level request.
418 | 
419 | ### isSCAPI
420 | 
421 | **Signature:** `isSCAPI() : boolean`
422 | 
423 | Returns whether the request originated in SCAPI.
424 | 
425 | ### setGeolocation
426 | 
427 | **Signature:** `setGeolocation(geoLocation : Geolocation) : void`
428 | 
429 | Sets the physical location for the current request and remembers the new value for the duration of the user session.
430 | 
431 | ### setLocale
432 | 
433 | **Signature:** `setLocale(localeID : String) : boolean`
434 | 
435 | Sets the given locale for the request.
436 | 
437 | ## Method Detail
438 | 
439 | ## Method Details
440 | 
441 | ### addHttpCookie
442 | 
443 | **Signature:** `addHttpCookie(cookie : Cookie) : void`
444 | 
445 | **Description:** Adds the specified cookie to the outgoing response. This method can be called multiple times to set more than one cookie. If a cookie with the same cookie name, domain and path is set multiple times for the same response, only the last set cookie with this name is send to the client. This method can be used to set, update or delete cookies at the client. If the cookie doesn't exist at the client, it is set initially. If a cookie with the same name, domain and path already exists at the client, it is updated. A cookie can be deleted at the client by submitting a cookie with the maxAge attribute set to 0 (see Cookie.setMaxAge() for more information). Example, how a cookie can be deleted at the client: var cookie : Cookie = new Cookie("SomeName", "Simple Value"); cookie.setMaxAge(0); request.addHttpCookie(cookie);
446 | 
447 | **Deprecated:**
448 | 
449 | Use Response.addHttpCookie(Cookie) instead.
450 | 
451 | **Parameters:**
452 | 
453 | - `cookie`: a Cookie object
454 | 
455 | ---
456 | 
457 | ### getClientId
458 | 
459 | **Signature:** `getClientId() : String`
460 | 
461 | **Description:** Returns the client id of the current SCAPI or OCAPI request. If the request is not a SCAPI request or not an OCAPI request 'null' is returned. For client ids owned by Commerce Cloud Digital an alias is returned.
462 | 
463 | **Returns:**
464 | 
465 | a client id or alias in case of an OCAPI request, otherwise null.
466 | 
467 | ---
468 | 
469 | ### getCustom
470 | 
471 | **Signature:** `getCustom() : CustomAttributes`
472 | 
473 | **Description:** Returns all of the custom attributes associated with the request. The attributes are stored for the life time of the request.
474 | 
475 | **Returns:**
476 | 
477 | all of the custom attributes associated with the request.
478 | 
479 | ---
480 | 
481 | ### getGeolocation
482 | 
483 | **Signature:** `getGeolocation() : Geolocation`
484 | 
485 | **Description:** Returns the physical location for the current request, if available. The location is calculated based on the IP address of the request. Note, if the geolocation tracking feature is not enabled, this method always returns null.
486 | 
487 | **Returns:**
488 | 
489 | The geolocation of the current request, or null if this is not available.
490 | 
491 | ---
492 | 
493 | ### getHttpCookies
494 | 
495 | **Signature:** `getHttpCookies() : Cookies`
496 | 
497 | **Description:** Returns the Cookies object, which can be used to read cookies sent by the client. Use the method Response.addHttpCookie() to add a cookie to the outgoing response.
498 | 
499 | **Returns:**
500 | 
501 | Cookies object or null if this is not an HTTP request
502 | 
503 | ---
504 | 
505 | ### getHttpHeaders
506 | 
507 | **Signature:** `getHttpHeaders() : Map`
508 | 
509 | **Description:** Returns a Map containing all HTTP header values.
510 | 
511 | **Returns:**
512 | 
513 | a Map containing all HTTP header values.
514 | 
515 | ---
516 | 
517 | ### getHttpHost
518 | 
519 | **Signature:** `getHttpHost() : String`
520 | 
521 | **Description:** Returns the host name or null if there is no host name.
522 | 
523 | **Returns:**
524 | 
525 | the host name or null if there is no host name.
526 | 
527 | ---
528 | 
529 | ### getHttpLocale
530 | 
531 | **Signature:** `getHttpLocale() : String`
532 | 
533 | **Description:** Returns the locale or null if there is no associated locale.
534 | 
535 | **Returns:**
536 | 
537 | the locale or null.
538 | 
539 | ---
540 | 
541 | ### getHttpMethod
542 | 
543 | **Signature:** `getHttpMethod() : String`
544 | 
545 | **Description:** Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
546 | 
547 | **Returns:**
548 | 
549 | the HTTP method
550 | 
551 | ---
552 | 
553 | ### getHttpParameterMap
554 | 
555 | **Signature:** `getHttpParameterMap() : HttpParameterMap`
556 | 
557 | **Description:** Returns the parameter map that contains the HTTP parameters for the current request.
558 | 
559 | **Returns:**
560 | 
561 | the HTTP parameter map
562 | 
563 | ---
564 | 
565 | ### getHttpParameters
566 | 
567 | **Signature:** `getHttpParameters() : Map`
568 | 
569 | **Description:** Returns a Map containing the raw HTTP parameters sent to the server. The Map contains name/value pairs. Each name is a String and each value is a String array.
570 | 
571 | **Returns:**
572 | 
573 | a Map containing all the raw HTTP parameters send to the server.
574 | 
575 | ---
576 | 
577 | ### getHttpPath
578 | 
579 | **Signature:** `getHttpPath() : String`
580 | 
581 | **Description:** Returns the path.
582 | 
583 | **Returns:**
584 | 
585 | the path or null.
586 | 
587 | ---
588 | 
589 | ### getHttpProtocol
590 | 
591 | **Signature:** `getHttpProtocol() : String`
592 | 
593 | **Description:** Returns the HTTP protocol used for this request. Possible values are "http" or "https". If the current activity is not related to an HTTP request, for example, when the request is part of a job, this method returns null.
594 | 
595 | **Returns:**
596 | 
597 | "http", "https" or null
598 | 
599 | ---
600 | 
601 | ### getHttpQueryString
602 | 
603 | **Signature:** `getHttpQueryString() : String`
604 | 
605 | **Description:** Returns the query string or null if there is no query string.
606 | 
607 | **Returns:**
608 | 
609 | the query string or null.
610 | 
611 | ---
612 | 
613 | ### getHttpReferer
614 | 
615 | **Signature:** `getHttpReferer() : String`
616 | 
617 | **Description:** Returns the referer or null if there is no referer.
618 | 
619 | **Returns:**
620 | 
621 | the referer or null if there is no referer.
622 | 
623 | ---
624 | 
625 | ### getHttpRemoteAddress
626 | 
627 | **Signature:** `getHttpRemoteAddress() : String`
628 | 
629 | **Description:** Returns the remote address or null if no remote address is found.
630 | 
631 | **Returns:**
632 | 
633 | the remote address or null if no remote address is found.
634 | 
635 | ---
636 | 
637 | ### getHttpURL
638 | 
639 | **Signature:** `getHttpURL() : URL`
640 | 
641 | **Description:** Returns the complete URL of the request which was received at the server. This URL does not include SEO optimizations.
642 | 
643 | **Returns:**
644 | 
645 | the URL as URL object
646 | 
647 | ---
648 | 
649 | ### getHttpUserAgent
650 | 
651 | **Signature:** `getHttpUserAgent() : String`
652 | 
653 | **Description:** Returns the HTTP user agent or null if there is no user agent.
654 | 
655 | **Returns:**
656 | 
657 | the HTTP user agent or null if there is no user agent.
658 | 
659 | ---
660 | 
661 | ### getLocale
662 | 
663 | **Signature:** `getLocale() : String`
664 | 
665 | **Description:** Returns the locale of the current request. This locale is set by the system based on the information in the URL. It may be different from the locale returned by getHttpLocale(), which is the preferred locale sent by the user agent.
666 | 
667 | **Returns:**
668 | 
669 | the locale of the current request, like 'en_US'
670 | 
671 | ---
672 | 
673 | ### getOcapiVersion
674 | 
675 | **Signature:** `getOcapiVersion() : String`
676 | 
677 | **Description:** Returns the OCAPI version of the current request. If this is not an OCAPI request, 'null' is returned.
678 | 
679 | **Returns:**
680 | 
681 | OCAPI version of the current request
682 | 
683 | ---
684 | 
685 | ### getPageMetaData
686 | 
687 | **Signature:** `getPageMetaData() : PageMetaData`
688 | 
689 | **Description:** Returns the page meta data that are associated with the current request.
690 | 
691 | **Returns:**
692 | 
693 | the page meta data object
694 | 
695 | ---
696 | 
697 | ### getRequestID
698 | 
699 | **Signature:** `getRequestID() : String`
700 | 
701 | **Description:** Returns the unique identifier of the current request. The unique id is helpful for debugging purpose, e.g. relate debug messages to a particular request.
702 | 
703 | **Returns:**
704 | 
705 | the unique identifier of the current request.
706 | 
707 | ---
708 | 
709 | ### getSCAPIPathParameters
710 | 
711 | **Signature:** `getSCAPIPathParameters() : Map`
712 | 
713 | **Description:** Returns a map containing all path parameters of current SCAPI request in the following way: keys: path parameter names from path pattern values: corresponding path parameter values from current request Returns null if isSCAPI() returns false i.e. if the request is not a SCAPI request. For example: Current request: /product/shopper-products/v1/organizations/sfcc_org/products/apple-ipod-shuffle Path pattern: /product/shopper-products/v1/organizations/{organizationId}/products/{id} Result: Map with 2 key:value pairs: organizationId:sfcc_org and id:apple-ipod-shuffle.
714 | 
715 | **Returns:**
716 | 
717 | the path parameter map or null
718 | 
719 | ---
720 | 
721 | ### getSCAPIPathPattern
722 | 
723 | **Signature:** `getSCAPIPathPattern() : String`
724 | 
725 | **Description:** Returns the SCAPI path pattern in the following way: The first three segments /api-family/api-name/version with concrete values. The /organizations part with the path parameter name organizationId in curly brackets. The actual resource path additional path parameter names in curly brackets. Returns null if isSCAPI() returns false i.e. if the request is not a SCAPI request. For example, in the context of a request to get a single product from shopper-products API, this method would return /product/shopper-products/v1/organizations/{organizationId}/products/{id}
726 | 
727 | **Returns:**
728 | 
729 | the path pattern or null.
730 | 
731 | ---
732 | 
733 | ### getSession
734 | 
735 | **Signature:** `getSession() : Session`
736 | 
737 | **Description:** Returns the session associated with this request.
738 | 
739 | **Returns:**
740 | 
741 | the session associated with this request.
742 | 
743 | ---
744 | 
745 | ### getTriggeredForm
746 | 
747 | **Signature:** `getTriggeredForm() : Form`
748 | 
749 | **Description:** Returns the form that was submitted by the client if the request represents a form submission.
750 | 
751 | **Returns:**
752 | 
753 | the form which was triggered
754 | 
755 | ---
756 | 
757 | ### getTriggeredFormAction
758 | 
759 | **Signature:** `getTriggeredFormAction() : FormAction`
760 | 
761 | **Description:** Returns the form action that was triggered by the client if the request represents a form submission.
762 | 
763 | **Returns:**
764 | 
765 | the action of the form that was triggered
766 | 
767 | ---
768 | 
769 | ### isHttpRequest
770 | 
771 | **Signature:** `isHttpRequest() : boolean`
772 | 
773 | **Description:** Identifies if this request is an HTTP request. The method returns true, if the current processing is related to a HTTP request.
774 | 
775 | **Deprecated:**
776 | 
777 | Effectively always returns true.
778 | 
779 | **Returns:**
780 | 
781 | true if the current processing is related to a HTTP request, false otherwise.
782 | 
783 | ---
784 | 
785 | ### isHttpSecure
786 | 
787 | **Signature:** `isHttpSecure() : boolean`
788 | 
789 | **Description:** Returns whether the HTTP communication is secure, which basically means that the communication happens via https. If the current activity is not related to an HTTP request the method returns false.
790 | 
791 | ---
792 | 
793 | ### isIncludeRequest
794 | 
795 | **Signature:** `isIncludeRequest() : boolean`
796 | 
797 | **Description:** Returns true if the request represents a request for a remote include, false if it is a top-level request.
798 | 
799 | ---
800 | 
801 | ### isSCAPI
802 | 
803 | **Signature:** `isSCAPI() : boolean`
804 | 
805 | **Description:** Returns whether the request originated in SCAPI.
806 | 
807 | **Returns:**
808 | 
809 | true or false.
810 | 
811 | ---
812 | 
813 | ### setGeolocation
814 | 
815 | **Signature:** `setGeolocation(geoLocation : Geolocation) : void`
816 | 
817 | **Description:** Sets the physical location for the current request and remembers the new value for the duration of the user session. So any subsequent calls to getGeolocation() will return this value
818 | 
819 | **Parameters:**
820 | 
821 | - `geoLocation`: the geolocation object to use
822 | 
823 | ---
824 | 
825 | ### setLocale
826 | 
827 | **Signature:** `setLocale(localeID : String) : boolean`
828 | 
829 | **Description:** Sets the given locale for the request. The locale is only set if it is valid, if it is active and if it is allowed for the current site.
830 | 
831 | **Parameters:**
832 | 
833 | - `localeID`: the locale ID to be set, like 'en_US'
834 | 
835 | **Returns:**
836 | 
837 | true, if the locale was successfully set, false otherwise
838 | 
839 | ---
```

--------------------------------------------------------------------------------
/tests/mcp/node/get-sfra-documents-by-category.docs-only.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * ==================================================================================
  3 |  * SFCC MCP Server - get_sfra_documents_by_category Tool Node.js Programmatic Tests
  4 |  * Comprehensive testing with dynamic validation logic and advanced test scenarios
  5 |  * 
  6 |  * Tool: get_sfra_documents_by_category
  7 |  * Purpose: Get SFRA documents filtered by category (core, product, order, customer, pricing, store, other)
  8 |  * Parameters: category (required) - Category to filter by
  9 |  * 
 10 |  * Quick Test Commands:
 11 |  * node --test tests/mcp/node/get-sfra-documents-by-category.docs-only.programmatic.test.js
 12 |  * npm run test:mcp:node -- --grep "get_sfra_documents_by_category"
 13 |  * ==================================================================================
 14 |  */
 15 | 
 16 | import { test, describe, before, after, beforeEach } from 'node:test';
 17 | import { strict as assert } from 'node:assert';
 18 | import { connect } from 'mcp-aegis';
 19 | 
 20 | describe('SFCC MCP Server - get_sfra_documents_by_category Tool Programmatic Tests', () => {
 21 |   let client;
 22 | 
 23 |   before(async () => {
 24 |     client = await connect('./aegis.config.docs-only.json');
 25 |   });
 26 | 
 27 |   after(async () => {
 28 |     if (client?.connected) {
 29 |       await client.disconnect();
 30 |     }
 31 |   });
 32 | 
 33 |   beforeEach(() => {
 34 |     // CRITICAL: Clear all buffers to prevent test interference
 35 |     client.clearAllBuffers();
 36 |   });
 37 | 
 38 |   // ==================================================================================
 39 |   // SUCCESSFUL OPERATIONS - VALID CATEGORIES
 40 |   // ==================================================================================
 41 | 
 42 |   describe('Valid Category Operations', () => {
 43 |     test('should retrieve core SFRA documents with proper structure', async () => {
 44 |       const result = await client.callTool('get_sfra_documents_by_category', {
 45 |         category: 'core'
 46 |       });
 47 | 
 48 |       // Validate MCP response structure
 49 |       assert.ok(result.content, 'Should have content');
 50 |       assert.ok(Array.isArray(result.content), 'Content should be array');
 51 |       assert.equal(result.content.length, 1, 'Should have one content item');
 52 |       assert.equal(result.content[0].type, 'text', 'Content type should be text');
 53 |       assert.equal(result.isError, false, 'Should not be error');
 54 | 
 55 |       // Parse and validate JSON structure
 56 |       const jsonText = result.content[0].text;
 57 |       assert.ok(jsonText, 'Should have text content');
 58 |       
 59 |       const documents = JSON.parse(jsonText);
 60 |       assert.ok(Array.isArray(documents), 'Should parse to array');
 61 |       assert.ok(documents.length > 0, 'Should have documents');
 62 |     });
 63 | 
 64 |     test('should return expected core documents with correct fields', async () => {
 65 |       const result = await client.callTool('get_sfra_documents_by_category', {
 66 |         category: 'core'
 67 |       });
 68 | 
 69 |       const documents = JSON.parse(result.content[0].text);
 70 |       
 71 |       // Validate document count and names
 72 |       assert.equal(documents.length, 5, 'Core should have 5 documents');
 73 |       
 74 |       const expectedNames = ['querystring', 'render', 'request', 'response', 'server'];
 75 |       const actualNames = documents.map(doc => doc.name).sort();
 76 |       assert.deepEqual(actualNames, expectedNames, 'Should have expected core document names');
 77 | 
 78 |       // Validate document structure
 79 |       documents.forEach((doc, index) => {
 80 |         assert.ok(doc.name, `Document ${index} should have name`);
 81 |         assert.ok(doc.title, `Document ${index} should have title`);
 82 |         assert.ok(doc.description !== undefined, `Document ${index} should have description`);
 83 |         assert.ok(doc.type, `Document ${index} should have type`);
 84 |         assert.equal(doc.category, 'core', `Document ${index} should have core category`);
 85 |         assert.ok(doc.filename, `Document ${index} should have filename`);
 86 |         assert.ok(doc.filename.endsWith('.md'), `Document ${index} filename should be markdown`);
 87 |       });
 88 |     });
 89 | 
 90 |     test('should return product category documents with model types', async () => {
 91 |       const result = await client.callTool('get_sfra_documents_by_category', {
 92 |         category: 'product'
 93 |       });
 94 | 
 95 |       assert.equal(result.isError, false, 'Should not be error');
 96 |       
 97 |       const documents = JSON.parse(result.content[0].text);
 98 |       assert.ok(Array.isArray(documents), 'Should return array');
 99 |       assert.ok(documents.length > 0, 'Should have product documents');
100 | 
101 |       // Validate product-specific content
102 |       const hasProductFull = documents.some(doc => doc.name === 'product-full');
103 |       const hasProductTile = documents.some(doc => doc.name === 'product-tile');
104 |       assert.ok(hasProductFull, 'Should contain product-full document');
105 |       assert.ok(hasProductTile, 'Should contain product-tile document');
106 | 
107 |       // Validate all documents have product category
108 |       documents.forEach(doc => {
109 |         assert.equal(doc.category, 'product', 'All documents should have product category');
110 |       });
111 |     });
112 | 
113 |     test('should handle all valid categories', async () => {
114 |       const validCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other'];
115 |       
116 |       for (const category of validCategories) {
117 |         const result = await client.callTool('get_sfra_documents_by_category', {
118 |           category: category
119 |         });
120 | 
121 |         assert.equal(result.isError, false, `Category ${category} should not error`);
122 |         assert.ok(result.content, `Category ${category} should have content`);
123 |         
124 |         const documents = JSON.parse(result.content[0].text);
125 |         assert.ok(Array.isArray(documents), `Category ${category} should return array`);
126 |         
127 |         // If documents exist, they should have the correct category
128 |         documents.forEach(doc => {
129 |           assert.equal(doc.category, category, `Document should have ${category} category`);
130 |         });
131 |       }
132 |     });
133 |   });
134 | 
135 |   // ==================================================================================
136 |   // EDGE CASES AND ERROR HANDLING
137 |   // ==================================================================================
138 | 
139 |   describe('Edge Cases and Error Handling', () => {
140 |     test('should handle invalid category gracefully', async () => {
141 |       const result = await client.callTool('get_sfra_documents_by_category', {
142 |         category: 'invalid_category_xyz'
143 |       });
144 | 
145 |       assert.equal(result.isError, false, 'Invalid category should not be error');
146 |       
147 |       const documents = JSON.parse(result.content[0].text);
148 |       assert.ok(Array.isArray(documents), 'Should return array');
149 |       assert.equal(documents.length, 0, 'Should return empty array for invalid category');
150 |     });
151 | 
152 |     test('should require category parameter', async () => {
153 |       const result = await client.callTool('get_sfra_documents_by_category', {});
154 | 
155 |       assert.equal(result.isError, true, 'Missing category should be error');
156 |       assert.ok(result.content[0].text.includes('category must be a non-empty string'), 
157 |         'Should have specific error message');
158 |     });
159 | 
160 |     test('should handle empty category string', async () => {
161 |       const result = await client.callTool('get_sfra_documents_by_category', {
162 |         category: ''
163 |       });
164 | 
165 |       assert.equal(result.isError, true, 'Empty category should be error');
166 |       assert.ok(result.content[0].text.includes('Error'), 'Should contain error message');
167 |     });
168 | 
169 |     test('should handle null category parameter', async () => {
170 |       const result = await client.callTool('get_sfra_documents_by_category', {
171 |         category: null
172 |       });
173 | 
174 |       assert.equal(result.isError, true, 'Null category should be error');
175 |       assert.ok(result.content[0].text.includes('Error'), 'Should contain error message');
176 |     });
177 | 
178 |     test('should be case sensitive for categories', async () => {
179 |       const testCases = ['CORE', 'Core', 'PRODUCT', 'Product'];
180 |       
181 |       for (const category of testCases) {
182 |         const result = await client.callTool('get_sfra_documents_by_category', {
183 |           category: category
184 |         });
185 | 
186 |         assert.equal(result.isError, false, `Category ${category} should not error`);
187 |         
188 |         const documents = JSON.parse(result.content[0].text);
189 |         assert.equal(documents.length, 0, `Case-sensitive ${category} should return empty array`);
190 |       }
191 |     });
192 |   });
193 | 
194 |   // ==================================================================================
195 |   // DATA VALIDATION AND STRUCTURE TESTING
196 |   // ==================================================================================
197 | 
198 |   describe('Data Validation and Structure', () => {
199 |     test('should validate document field types and values', async () => {
200 |       const result = await client.callTool('get_sfra_documents_by_category', {
201 |         category: 'core'
202 |       });
203 | 
204 |       const documents = JSON.parse(result.content[0].text);
205 |       
206 |       documents.forEach((doc, index) => {
207 |         // Field type validation
208 |         assert.equal(typeof doc.name, 'string', `Document ${index} name should be string`);
209 |         assert.equal(typeof doc.title, 'string', `Document ${index} title should be string`);
210 |         assert.equal(typeof doc.description, 'string', `Document ${index} description should be string`);
211 |         assert.equal(typeof doc.type, 'string', `Document ${index} type should be string`);
212 |         assert.equal(typeof doc.category, 'string', `Document ${index} category should be string`);
213 |         assert.equal(typeof doc.filename, 'string', `Document ${index} filename should be string`);
214 | 
215 |         // Field value validation
216 |         assert.ok(doc.name.length > 0, `Document ${index} name should not be empty`);
217 |         assert.ok(doc.title.length > 0, `Document ${index} title should not be empty`);
218 |         assert.ok(['class', 'module', 'model'].includes(doc.type), 
219 |           `Document ${index} type should be valid: ${doc.type}`);
220 |         assert.ok(doc.filename.endsWith('.md'), 
221 |           `Document ${index} filename should end with .md: ${doc.filename}`);
222 |       });
223 |     });
224 | 
225 |     test('should maintain consistent document structure across categories', async () => {
226 |       const categories = ['core', 'product'];
227 |       const allDocuments = [];
228 | 
229 |       for (const category of categories) {
230 |         const result = await client.callTool('get_sfra_documents_by_category', {
231 |           category: category
232 |         });
233 |         
234 |         const documents = JSON.parse(result.content[0].text);
235 |         allDocuments.push(...documents);
236 |       }
237 | 
238 |       // Validate all documents have the same structure
239 |       const requiredFields = ['name', 'title', 'description', 'type', 'category', 'filename'];
240 |       
241 |       allDocuments.forEach((doc, index) => {
242 |         requiredFields.forEach(field => {
243 |           assert.ok(Object.prototype.hasOwnProperty.call(doc, field), 
244 |             `Document ${index} should have ${field} field`);
245 |         });
246 |         
247 |         // No extra fields beyond expected ones
248 |         const docFields = Object.keys(doc);
249 |         assert.equal(docFields.length, requiredFields.length, 
250 |           `Document ${index} should have exactly ${requiredFields.length} fields`);
251 |       });
252 |     });
253 | 
254 |     test('should return documents sorted alphabetically by name', async () => {
255 |       const result = await client.callTool('get_sfra_documents_by_category', {
256 |         category: 'core'
257 |       });
258 | 
259 |       const documents = JSON.parse(result.content[0].text);
260 |       const names = documents.map(doc => doc.name);
261 |       const sortedNames = [...names].sort();
262 |       
263 |       assert.deepEqual(names, sortedNames, 'Documents should be sorted alphabetically by name');
264 |     });
265 |   });
266 | 
267 |   // ==================================================================================
268 |   // DYNAMIC VALIDATION AND BUSINESS LOGIC
269 |   // ==================================================================================
270 | 
271 |   describe('Dynamic Validation and Business Logic', () => {
272 |     test('should validate document naming conventions', async () => {
273 |       const categories = ['core', 'product'];
274 |       
275 |       for (const category of categories) {
276 |         const result = await client.callTool('get_sfra_documents_by_category', {
277 |           category: category
278 |         });
279 |         
280 |         const documents = JSON.parse(result.content[0].text);
281 |         
282 |         documents.forEach(doc => {
283 |           // Name should be lowercase with hyphens
284 |           assert.ok(/^[a-z][a-z0-9-]*$/.test(doc.name), 
285 |             `Document name should follow convention: ${doc.name}`);
286 |           
287 |           // Filename should match name + .md
288 |           assert.equal(doc.filename, `${doc.name}.md`, 
289 |             `Filename should match name: ${doc.filename}`);
290 |         });
291 |       }
292 |     });
293 | 
294 |     test('should validate type consistency by category', async () => {
295 |       // Core category should have classes and modules
296 |       const coreResult = await client.callTool('get_sfra_documents_by_category', {
297 |         category: 'core'
298 |       });
299 |       
300 |       const coreDocuments = JSON.parse(coreResult.content[0].text);
301 |       const coreTypes = [...new Set(coreDocuments.map(doc => doc.type))];
302 |       assert.ok(coreTypes.includes('class'), 'Core should have class types');
303 |       assert.ok(coreTypes.includes('module'), 'Core should have module types');
304 | 
305 |       // Product category should primarily have models
306 |       const productResult = await client.callTool('get_sfra_documents_by_category', {
307 |         category: 'product'
308 |       });
309 |       
310 |       const productDocuments = JSON.parse(productResult.content[0].text);
311 |       if (productDocuments.length > 0) {
312 |         // Product documents should primarily be models
313 |         const modelCount = productDocuments.filter(doc => doc.type === 'model').length;
314 |         const totalCount = productDocuments.length;
315 |         assert.ok(modelCount / totalCount > 0.5, 'Product category should be primarily models');
316 |       }
317 |     });
318 | 
319 |     test('should validate unique document names within category', async () => {
320 |       const result = await client.callTool('get_sfra_documents_by_category', {
321 |         category: 'core'
322 |       });
323 | 
324 |       const documents = JSON.parse(result.content[0].text);
325 |       const names = documents.map(doc => doc.name);
326 |       const uniqueNames = [...new Set(names)];
327 |       
328 |       assert.equal(names.length, uniqueNames.length, 
329 |         'All document names within category should be unique');
330 |     });
331 | 
332 |     test('should handle empty categories appropriately', async () => {
333 |       // Test categories that might be empty
334 |       const possiblyEmptyCategories = ['other'];
335 |       
336 |       for (const category of possiblyEmptyCategories) {
337 |         const result = await client.callTool('get_sfra_documents_by_category', {
338 |           category: category
339 |         });
340 | 
341 |         assert.equal(result.isError, false, `Category ${category} should not error`);
342 |         
343 |         const documents = JSON.parse(result.content[0].text);
344 |         assert.ok(Array.isArray(documents), `Category ${category} should return array`);
345 |         // Empty is acceptable for some categories
346 |       }
347 |     });
348 |   });
349 | 
350 |   // ==================================================================================
351 |   // INTEGRATION AND WORKFLOW TESTING
352 |   // ==================================================================================
353 | 
354 |   describe('Integration and Workflow Testing', () => {
355 |     test('should support category discovery workflow', async () => {
356 |       // Step 1: Get available categories by testing each one
357 |       const allCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other'];
358 |       const availableCategories = [];
359 |       
360 |       for (const category of allCategories) {
361 |         const result = await client.callTool('get_sfra_documents_by_category', {
362 |           category: category
363 |         });
364 |         
365 |         const documents = JSON.parse(result.content[0].text);
366 |         if (documents.length > 0) {
367 |           availableCategories.push(category);
368 |         }
369 |       }
370 |       
371 |       assert.ok(availableCategories.length >= 2, 'Should have at least 2 non-empty categories');
372 |       assert.ok(availableCategories.includes('core'), 'Core should be available');
373 |       
374 |       // Step 2: Validate each available category has consistent structure
375 |       for (const category of availableCategories) {
376 |         const result = await client.callTool('get_sfra_documents_by_category', {
377 |           category: category
378 |         });
379 |         
380 |         const documents = JSON.parse(result.content[0].text);
381 |         assert.ok(documents.every(doc => doc.category === category), 
382 |           `All documents in ${category} should have correct category`);
383 |       }
384 |     });
385 | 
386 |     test('should support document exploration workflow', async () => {
387 |       // Step 1: Get core documents
388 |       const coreResult = await client.callTool('get_sfra_documents_by_category', {
389 |         category: 'core'
390 |       });
391 |       
392 |       const coreDocuments = JSON.parse(coreResult.content[0].text);
393 |       
394 |       // Step 2: Find specific document types
395 |       const serverDoc = coreDocuments.find(doc => doc.name === 'server');
396 |       assert.ok(serverDoc, 'Should find server document');
397 |       assert.equal(serverDoc.type, 'class', 'Server should be a class');
398 |       
399 |       const renderDoc = coreDocuments.find(doc => doc.name === 'render');
400 |       assert.ok(renderDoc, 'Should find render document');
401 |       assert.equal(renderDoc.type, 'module', 'Render should be a module');
402 |       
403 |       // Step 3: Validate document relationships
404 |       assert.ok(serverDoc.filename !== renderDoc.filename, 
405 |         'Different documents should have different filenames');
406 |     });
407 | 
408 |     test('should validate cross-category document consistency', async () => {
409 |       const allDocuments = [];
410 |       const allCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other'];
411 |       
412 |       // Collect all documents across categories
413 |       for (const category of allCategories) {
414 |         const result = await client.callTool('get_sfra_documents_by_category', {
415 |           category: category
416 |         });
417 |         
418 |         const documents = JSON.parse(result.content[0].text);
419 |         allDocuments.push(...documents);
420 |       }
421 |       
422 |       // Validate no duplicate document names across categories
423 |       const allNames = allDocuments.map(doc => doc.name);
424 |       const uniqueNames = [...new Set(allNames)];
425 |       assert.equal(allNames.length, uniqueNames.length, 
426 |         'Document names should be unique across all categories');
427 |       
428 |       // Validate filename consistency
429 |       allDocuments.forEach(doc => {
430 |         assert.equal(doc.filename, `${doc.name}.md`, 
431 |           `Document ${doc.name} should have consistent filename`);
432 |       });
433 |     });
434 |   });
435 | 
436 |   // ==================================================================================
437 |   // PERFORMANCE AND RELIABILITY TESTING
438 |   // ==================================================================================
439 | 
440 |   describe('Performance and Reliability', () => {
441 |     test('should handle repeated requests consistently', async () => {
442 |       const requestCount = 5;
443 |       const results = [];
444 |       
445 |       // Make multiple identical requests
446 |       for (let i = 0; i < requestCount; i++) {
447 |         const result = await client.callTool('get_sfra_documents_by_category', {
448 |           category: 'core'
449 |         });
450 |         results.push(result);
451 |       }
452 |       
453 |       // Validate all responses are identical
454 |       const firstResponse = results[0].content[0].text;
455 |       results.forEach((result, index) => {
456 |         assert.equal(result.isError, false, `Request ${index} should not error`);
457 |         assert.equal(result.content[0].text, firstResponse, 
458 |           `Request ${index} should return identical response`);
459 |       });
460 |     });
461 | 
462 |     test('should handle concurrent category requests sequentially', async () => {
463 |       // Note: Not using Promise.all due to MCP single-process limitations
464 |       const categories = ['core', 'product'];
465 |       const results = [];
466 |       
467 |       // Sequential requests to avoid concurrency issues
468 |       for (const category of categories) {
469 |         const result = await client.callTool('get_sfra_documents_by_category', {
470 |           category: category
471 |         });
472 |         results.push({ category, result });
473 |       }
474 |       
475 |       // Validate all requests succeeded
476 |       results.forEach(({ category, result }) => {
477 |         assert.equal(result.isError, false, `Category ${category} should not error`);
478 |         
479 |         const documents = JSON.parse(result.content[0].text);
480 |         assert.ok(Array.isArray(documents), `Category ${category} should return array`);
481 |       });
482 |     });
483 | 
484 |     test('should maintain performance under different input sizes', async () => {
485 |       const categories = ['core', 'product', 'invalid_long_category_name_that_should_not_exist'];
486 |       
487 |       for (const category of categories) {
488 |         const startTime = process.hrtime.bigint();
489 |         
490 |         const result = await client.callTool('get_sfra_documents_by_category', {
491 |           category: category
492 |         });
493 |         
494 |         const endTime = process.hrtime.bigint();
495 |         const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds
496 |         
497 |         // Functional validation (performance is environment-dependent)
498 |         assert.ok(result.content, `Category ${category} should return content`);
499 |         assert.ok(duration < 5000, `Category ${category} should complete within 5 seconds`);
500 |       }
501 |     });
502 |   });
503 | });
504 | 
505 | // ==================================================================================
506 | // HELPER FUNCTIONS
507 | // ==================================================================================
508 | 
509 | // Helper functions removed to avoid unused function linting errors.
510 | // Validation logic is implemented inline within tests for better maintainability.
511 | 
```

--------------------------------------------------------------------------------
/tests/mcp/node/search-site-preferences.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { test, describe, before, after, beforeEach } from 'node:test';
  2 | import { strict as assert } from 'node:assert';
  3 | import { connect } from 'mcp-aegis';
  4 | 
  5 | describe('search_site_preferences tool - Full Mode Programmatic Tests', () => {
  6 |   let client;
  7 | 
  8 |   before(async () => {
  9 |     client = await connect('./aegis.config.with-dw.json');
 10 |   });
 11 | 
 12 |   after(async () => {
 13 |     if (client?.connected) {
 14 |       await client.disconnect();
 15 |     }
 16 |   });
 17 | 
 18 |   beforeEach(() => {
 19 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 20 |     client.clearAllBuffers();
 21 |   });
 22 | 
 23 |   describe('Complex Query Structure Validation', () => {
 24 |     test('should validate complex boolean query with multiple conditions', async () => {
 25 |       const complexQuery = {
 26 |         groupId: 'Storefront',
 27 |         instanceType: 'sandbox',
 28 |         searchRequest: {
 29 |           query: {
 30 |             bool_query: {
 31 |               must: [
 32 |                 {
 33 |                   text_query: {
 34 |                     fields: ['id', 'display_name'],
 35 |                     search_phrase: 'cart'
 36 |                   }
 37 |                 }
 38 |               ],
 39 |               should: [
 40 |                 {
 41 |                   term_query: {
 42 |                     fields: ['value_type'],
 43 |                     operator: 'is',
 44 |                     values: ['boolean']
 45 |                   }
 46 |                 }
 47 |               ]
 48 |             }
 49 |           },
 50 |           count: 5,
 51 |           start: 0
 52 |         }
 53 |       };
 54 | 
 55 |       const result = await client.callTool('search_site_preferences', complexQuery);
 56 |       
 57 |       assert.equal(result.isError, false, 'Complex boolean query should succeed');
 58 |       assert.ok(result.content, 'Should have content');
 59 |       assert.equal(result.content[0].type, 'text', 'Content should be text type');
 60 |       
 61 |       const responseData = JSON.parse(result.content[0].text);
 62 |       assert.ok(responseData.hits, 'Response should have hits array');
 63 |       assert.ok(responseData.query, 'Response should echo query');
 64 |       assert.equal(responseData.query.bool_query.must.length, 1, 'Should preserve bool_query structure');
 65 |       assert.equal(responseData.query.bool_query.should.length, 1, 'Should preserve should conditions');
 66 |     });
 67 | 
 68 |     test('should handle nested boolean queries with must_not conditions', async () => {
 69 |       const nestedQuery = {
 70 |         groupId: 'System',
 71 |         instanceType: 'sandbox',
 72 |         searchRequest: {
 73 |           query: {
 74 |             bool_query: {
 75 |               must: [
 76 |                 {
 77 |                   match_all_query: {}
 78 |                 }
 79 |               ],
 80 |               must_not: [
 81 |                 {
 82 |                   term_query: {
 83 |                     fields: ['value_type'],
 84 |                     operator: 'is',
 85 |                     values: ['password']
 86 |                   }
 87 |                 }
 88 |               ]
 89 |             }
 90 |           },
 91 |           count: 10
 92 |         }
 93 |       };
 94 | 
 95 |       const result = await client.callTool('search_site_preferences', nestedQuery);
 96 |       
 97 |       assert.equal(result.isError, false, 'Nested boolean query should succeed');
 98 |       
 99 |       const responseData = JSON.parse(result.content[0].text);
100 |       assert.ok(responseData.hits, 'Should have hits');
101 |       assert.ok(responseData.query.bool_query.must_not, 'Should preserve must_not conditions');
102 |       
103 |       // Verify no password type preferences are returned
104 |       const passwordPrefs = responseData.hits.filter(hit => 
105 |         hit.attribute_definition?.value_type === 'password'
106 |       );
107 |       assert.equal(passwordPrefs.length, 0, 'Should exclude password type preferences');
108 |     });
109 |   });
110 | 
111 |   describe('Response Structure Deep Validation', () => {
112 |     test('should validate complete response structure with all fields', async () => {
113 |       const result = await client.callTool('search_site_preferences', {
114 |         groupId: 'Storefront',
115 |         instanceType: 'sandbox',
116 |         searchRequest: {
117 |           query: { match_all_query: {} },
118 |           count: 3,
119 |           start: 0
120 |         },
121 |         options: {
122 |           expand: 'value',
123 |           maskPasswords: false
124 |         }
125 |       });
126 | 
127 |       assert.equal(result.isError, false, 'Request should succeed');
128 |       
129 |       const responseData = JSON.parse(result.content[0].text);
130 |       
131 |       // Validate top-level structure
132 |       assert.ok(responseData._type, 'Should have _type field');
133 |       assert.equal(responseData._type, 'preference_value_search_result', 'Should have correct type');
134 |       assert.ok(Array.isArray(responseData.hits), 'Hits should be array');
135 |       assert.ok(typeof responseData.start === 'number', 'Start should be number');
136 |       assert.ok(typeof responseData.count === 'number', 'Count should be number');
137 |       assert.ok(typeof responseData.total === 'number', 'Total should be number');
138 |       assert.ok(responseData.query, 'Should have query echo');
139 | 
140 |       // Validate hit structure if any hits exist
141 |       if (responseData.hits.length > 0) {
142 |         const hit = responseData.hits[0];
143 |         assert.ok(hit.attribute_definition, 'Hit should have attribute_definition');
144 |         assert.ok(hit.site_values, 'Hit should have site_values');
145 |         
146 |         // Validate attribute definition structure
147 |         const attrDef = hit.attribute_definition;
148 |         assert.ok(typeof attrDef.id === 'string', 'Attribute definition should have id');
149 |         assert.ok(typeof attrDef.display_name === 'object', 'Display name should be object');
150 |         assert.ok(typeof attrDef.value_type === 'string', 'Should have value_type');
151 |         
152 |         // Validate site values structure
153 |         const siteValues = hit.site_values;
154 |         assert.ok(typeof siteValues === 'object', 'Site values should be object');
155 |         assert.ok(siteValues !== null, 'Site values should not be null');
156 |         
157 |         if (Object.keys(siteValues).length > 0) {
158 |           const firstSiteId = Object.keys(siteValues)[0];
159 |           assert.ok(typeof firstSiteId === 'string', 'Site ID should be string');
160 |           assert.ok(Object.prototype.hasOwnProperty.call(siteValues, firstSiteId), 'Site values should have site ID property');
161 |         }
162 |       }
163 |     });
164 | 
165 |     test('should validate pagination metadata consistency', async () => {
166 |       // Get total count first with reasonable count
167 |       const countResult = await client.callTool('search_site_preferences', {
168 |         groupId: 'Storefront',
169 |         instanceType: 'sandbox',
170 |         searchRequest: {
171 |           query: { match_all_query: {} },
172 |           count: 50, // Reasonable count instead of 1000
173 |           start: 0
174 |         }
175 |       });
176 | 
177 |       assert.equal(countResult.isError, false, 'Count request should succeed');
178 |       const countData = JSON.parse(countResult.content[0].text);
179 |       const totalPreferences = countData.total;
180 | 
181 |       if (totalPreferences > 5) {
182 |         // Test pagination with smaller pages
183 |         const pageSize = 3;
184 |         const page1Result = await client.callTool('search_site_preferences', {
185 |           groupId: 'Storefront',
186 |           instanceType: 'sandbox',
187 |           searchRequest: {
188 |             query: { match_all_query: {} },
189 |             count: pageSize,
190 |             start: 0
191 |           }
192 |         });
193 | 
194 |         const page2Result = await client.callTool('search_site_preferences', {
195 |           groupId: 'Storefront',
196 |           instanceType: 'sandbox',
197 |           searchRequest: {
198 |             query: { match_all_query: {} },
199 |             count: pageSize,
200 |             start: pageSize
201 |           }
202 |         });
203 | 
204 |         assert.equal(page1Result.isError, false, 'Page 1 should succeed');
205 |         assert.equal(page2Result.isError, false, 'Page 2 should succeed');
206 | 
207 |         const page1Data = JSON.parse(page1Result.content[0].text);
208 |         const page2Data = JSON.parse(page2Result.content[0].text);
209 | 
210 |         // Validate pagination metadata
211 |         assert.equal(page1Data.start, 0, 'Page 1 start should be 0');
212 |         assert.equal(page1Data.count, pageSize, 'Page 1 count should match request');
213 |         assert.equal(page2Data.start, pageSize, 'Page 2 start should be offset');
214 |         assert.equal(page2Data.count, pageSize, 'Page 2 count should match request');
215 |         
216 |         // Both pages should report same total
217 |         assert.equal(page1Data.total, page2Data.total, 'Total should be consistent across pages');
218 | 
219 |         // Validate no duplicate preferences between pages
220 |         const page1Ids = page1Data.hits.map(hit => hit.attribute_definition.id);
221 |         const page2Ids = page2Data.hits.map(hit => hit.attribute_definition.id);
222 |         const intersection = page1Ids.filter(id => page2Ids.includes(id));
223 |         assert.equal(intersection.length, 0, 'Pages should not have duplicate preferences');
224 |       }
225 |     });
226 |   });
227 | 
228 |   describe('Advanced Query Scenarios', () => {
229 |     test('should handle multiple preference groups sequentially', async () => {
230 |       const preferenceGroups = ['Storefront', 'System', 'SFRA'];
231 |       const results = new Map();
232 | 
233 |       // Test each group sequentially (no concurrent requests)
234 |       for (const groupId of preferenceGroups) {
235 |         const result = await client.callTool('search_site_preferences', {
236 |           groupId,
237 |           instanceType: 'sandbox',
238 |           searchRequest: {
239 |             query: { match_all_query: {} },
240 |             count: 5
241 |           }
242 |         });
243 | 
244 |         assert.equal(result.isError, false, `Group ${groupId} should be accessible`);
245 |         
246 |         const responseData = JSON.parse(result.content[0].text);
247 |         results.set(groupId, responseData);
248 |         
249 |         // Validate group-specific response
250 |         assert.ok(responseData.hits, `Group ${groupId} should have hits`);
251 |         assert.ok(responseData.total >= 0, `Group ${groupId} should have total count`);
252 |       }
253 | 
254 |       // Validate that different groups return different preferences
255 |       const storefrontIds = results.get('Storefront').hits.map(hit => hit.attribute_definition.id);
256 |       const systemIds = results.get('System').hits.map(hit => hit.attribute_definition.id);
257 |       
258 |       if (storefrontIds.length > 0 && systemIds.length > 0) {
259 |         const overlap = storefrontIds.filter(id => systemIds.includes(id));
260 |         assert.equal(overlap.length, 0, 'Different groups should have different preferences');
261 |       }
262 |     });
263 | 
264 |     test('should validate search with sorting and field selection', async () => {
265 |       const result = await client.callTool('search_site_preferences', {
266 |         groupId: 'Storefront',
267 |         instanceType: 'sandbox',
268 |         searchRequest: {
269 |           query: { match_all_query: {} },
270 |           count: 10,
271 |           start: 0,
272 |           select: '(*)',
273 |           sorts: [
274 |             {
275 |               field: 'id',
276 |               sort_order: 'asc'
277 |             }
278 |           ]
279 |         }
280 |       });
281 | 
282 |       assert.equal(result.isError, false, 'Sorted query should succeed');
283 |       
284 |       const responseData = JSON.parse(result.content[0].text);
285 |       assert.ok(responseData.hits, 'Should have hits');
286 |       
287 |       // Note: Sorting validation removed as mock server doesn't implement actual sorting
288 |       // This test now focuses on validating that sorting parameters are accepted
289 |       
290 |       // Validate that select parameter is echoed in response
291 |       assert.ok(responseData.select, 'Response should echo select parameter');
292 |       assert.equal(responseData.select, '(*)', 'Select parameter should be preserved as (*)');
293 |     });
294 |   });
295 | 
296 |   describe('Error Handling and Edge Cases', () => {
297 |     test('should handle query timeout gracefully', async () => {
298 |       // Test with a very complex query that might timeout
299 |       const complexTimeoutQuery = {
300 |         groupId: 'Storefront',
301 |         instanceType: 'sandbox',
302 |         searchRequest: {
303 |           query: {
304 |             bool_query: {
305 |               must: [
306 |                 {
307 |                   text_query: {
308 |                     fields: ['id', 'display_name', 'description'],
309 |                     search_phrase: 'a' // Very broad search
310 |                   }
311 |                 }
312 |               ],
313 |               should: Array.from({ length: 10 }, (_, i) => ({
314 |                 term_query: {
315 |                   fields: ['value_type'],
316 |                   operator: 'is',
317 |                   values: [`type_${i}`]
318 |                 }
319 |               }))
320 |             }
321 |           },
322 |           count: 1000, // Large count
323 |           start: 0
324 |         }
325 |       };
326 | 
327 |       const result = await client.callTool('search_site_preferences', complexTimeoutQuery);
328 |       
329 |       // Should either succeed or fail gracefully with timeout error
330 |       if (result.isError) {
331 |         assert.ok(
332 |           result.content[0].text.includes('timeout') || 
333 |           result.content[0].text.includes('error') ||
334 |           result.content[0].text.includes('invalid'),
335 |           'Timeout should produce meaningful error message'
336 |         );
337 |       } else {
338 |         // If it succeeds, validate response structure
339 |         const responseData = JSON.parse(result.content[0].text);
340 |         assert.ok(responseData._type, 'Should have valid response structure even for complex queries');
341 |       }
342 |     });
343 | 
344 |     test('should validate parameter combinations and constraints', async () => {
345 |       const testCases = [
346 |         {
347 |           name: 'negative start parameter',
348 |           params: {
349 |             groupId: 'Storefront',
350 |             instanceType: 'sandbox',
351 |             searchRequest: {
352 |               query: { match_all_query: {} },
353 |               start: -1
354 |             }
355 |           },
356 |           shouldSucceed: false
357 |         }
358 |       ];
359 | 
360 |       for (const testCase of testCases) {
361 |         const result = await client.callTool('search_site_preferences', testCase.params);
362 |         
363 |         if (testCase.shouldSucceed) {
364 |           assert.equal(result.isError, false, `${testCase.name} should succeed`);
365 |         } else {
366 |           assert.equal(result.isError, true, `${testCase.name} should fail with validation error`);
367 |           assert.ok(
368 |             result.content[0].text.includes('error') || 
369 |             result.content[0].text.includes('Error') ||
370 |             result.content[0].text.includes('invalid') ||
371 |             result.content[0].text.includes('required') ||
372 |             result.content[0].text.includes('must be'),
373 |             `${testCase.name} should provide meaningful error message. Got: ${result.content[0].text}`
374 |           );
375 |         }
376 |       }
377 |     });
378 |   });
379 | 
380 |   describe('Data Consistency and Business Logic', () => {
381 |     test('should validate preference value types and constraints', async () => {
382 |       const result = await client.callTool('search_site_preferences', {
383 |         groupId: 'Storefront',
384 |         instanceType: 'sandbox',
385 |         searchRequest: {
386 |           query: { match_all_query: {} },
387 |           count: 20
388 |         }
389 |       });
390 | 
391 |       assert.equal(result.isError, false, 'Query should succeed');
392 |       
393 |       const responseData = JSON.parse(result.content[0].text);
394 |       
395 |       if (responseData.hits.length > 0) {
396 |         const validValueTypes = ['string', 'boolean', 'int', 'double', 'password', 'email', 'text', 'html', 'date', 'enum_of_string', 'set_of_string'];
397 |         
398 |         responseData.hits.forEach((hit, index) => {
399 |           const attrDef = hit.attribute_definition;
400 |           
401 |           // Validate required fields
402 |           assert.ok(attrDef.id, `Hit ${index} should have attribute id`);
403 |           assert.ok(attrDef.value_type, `Hit ${index} should have value_type`);
404 |           assert.ok(validValueTypes.includes(attrDef.value_type), 
405 |             `Hit ${index} should have valid value_type: ${attrDef.value_type}`);
406 |           
407 |           // Validate display_name structure
408 |           if (attrDef.display_name) {
409 |             assert.ok(typeof attrDef.display_name === 'object', 
410 |               `Hit ${index} display_name should be object`);
411 |           }
412 |           
413 |           // Validate site_values structure
414 |           assert.ok(typeof hit.site_values === 'object', 
415 |             `Hit ${index} site_values should be object`);
416 |           assert.ok(hit.site_values !== null, 
417 |             `Hit ${index} site_values should not be null`);
418 |           
419 |           const siteValueEntries = Object.entries(hit.site_values);
420 |           siteValueEntries.forEach(([siteId, siteValue], siteIndex) => {
421 |             assert.ok(typeof siteId === 'string', 
422 |               `Hit ${index}, site entry ${siteIndex} should have string site ID`);
423 |             
424 |             // Validate value based on type (skip null values)
425 |             if (siteValue !== null) {
426 |               if (attrDef.value_type === 'boolean') {
427 |                 assert.ok(typeof siteValue === 'boolean', 
428 |                   `Hit ${index}, site ${siteId} should have boolean value for boolean type`);
429 |               }
430 |               
431 |               if (attrDef.value_type === 'int') {
432 |                 assert.ok(Number.isInteger(siteValue), 
433 |                   `Hit ${index}, site ${siteId} should have integer value for int type`);
434 |               }
435 |             }
436 |           });
437 |         });
438 |       }
439 |     });
440 | 
441 |     test('should validate query result consistency across calls', async () => {
442 |       const queryParams = {
443 |         groupId: 'Storefront',
444 |         instanceType: 'sandbox',
445 |         searchRequest: {
446 |           query: {
447 |             text_query: {
448 |               fields: ['id', 'display_name'],
449 |               search_phrase: 'cart'
450 |             }
451 |           },
452 |           count: 10
453 |         }
454 |       };
455 | 
456 |       // Execute same query multiple times
457 |       const results = [];
458 |       for (let i = 0; i < 3; i++) {
459 |         const result = await client.callTool('search_site_preferences', queryParams);
460 |         assert.equal(result.isError, false, `Call ${i + 1} should succeed`);
461 |         
462 |         const responseData = JSON.parse(result.content[0].text);
463 |         results.push(responseData);
464 |       }
465 | 
466 |       // Validate consistency across calls
467 |       const firstResult = results[0];
468 |       for (let i = 1; i < results.length; i++) {
469 |         const currentResult = results[i];
470 |         
471 |         assert.equal(currentResult.total, firstResult.total, 
472 |           `Call ${i + 1} total should match first call`);
473 |         assert.equal(currentResult.hits.length, firstResult.hits.length, 
474 |           `Call ${i + 1} hits count should match first call`);
475 |         
476 |         // Compare preference IDs (order should be consistent)
477 |         const firstIds = firstResult.hits.map(hit => hit.attribute_definition.id);
478 |         const currentIds = currentResult.hits.map(hit => hit.attribute_definition.id);
479 |         assert.deepEqual(currentIds, firstIds, 
480 |           `Call ${i + 1} should return same preferences in same order`);
481 |       }
482 |     });
483 |   });
484 | 
485 |   describe('Performance and Scalability Validation', () => {
486 |     test('should handle large result sets efficiently', async () => {
487 |       const largeQueryResult = await client.callTool('search_site_preferences', {
488 |         groupId: 'Storefront',
489 |         instanceType: 'sandbox',
490 |         searchRequest: {
491 |           query: { match_all_query: {} },
492 |           count: 50 // Reduced from 200 to avoid server issues
493 |         }
494 |       });
495 | 
496 |       assert.equal(largeQueryResult.isError, false, 'Large query should succeed');
497 |       
498 |       const responseData = JSON.parse(largeQueryResult.content[0].text);
499 |       
500 |       // Validate response structure is maintained for large results
501 |       assert.ok(responseData._type, 'Large response should have type');
502 |       assert.ok(Array.isArray(responseData.hits), 'Large response should have hits array');
503 |       assert.ok(typeof responseData.total === 'number', 'Large response should have total');
504 |       
505 |       // Validate all hits have required structure
506 |       responseData.hits.forEach((hit, index) => {
507 |         assert.ok(hit.attribute_definition, `Hit ${index} should have attribute_definition`);
508 |         assert.ok(hit.site_values, `Hit ${index} should have site_values`);
509 |         assert.ok(hit.attribute_definition.id, `Hit ${index} should have id`);
510 |       });
511 |     });
512 | 
513 |     test('should validate functional reliability over multiple operations', async () => {
514 |       const operations = [
515 |         { groupId: 'Storefront', query: { match_all_query: {} } },
516 |         { groupId: 'System', query: { text_query: { fields: ['id'], search_phrase: 'test' } } },
517 |         { groupId: 'SFRA', query: { match_all_query: {} } },
518 |         { groupId: 'Storefront', query: { term_query: { fields: ['value_type'], operator: 'is', values: ['boolean'] } } }
519 |       ];
520 | 
521 |       const results = [];
522 |       
523 |       for (const operation of operations) {
524 |         const result = await client.callTool('search_site_preferences', {
525 |           groupId: operation.groupId,
526 |           instanceType: 'sandbox',
527 |           searchRequest: {
528 |             query: operation.query,
529 |             count: 10
530 |           }
531 |         });
532 | 
533 |         results.push({
534 |           groupId: operation.groupId,
535 |           success: !result.isError,
536 |           hasContent: result.content && result.content.length > 0,
537 |           responseValid: !result.isError && result.content[0].text.startsWith('{')
538 |         });
539 |       }
540 | 
541 |       // Calculate reliability metrics
542 |       const successfulOperations = results.filter(r => r.success).length;
543 |       const validResponses = results.filter(r => r.responseValid).length;
544 |       
545 |       assert.ok(successfulOperations >= operations.length * 0.8, 
546 |         `At least 80% of operations should succeed (${successfulOperations}/${operations.length})`);
547 |       assert.ok(validResponses >= operations.length * 0.8, 
548 |         `At least 80% of responses should be valid JSON (${validResponses}/${operations.length})`);
549 | 
550 |       // Log reliability stats for monitoring
551 |       console.log(`\nReliability Stats:`);
552 |       console.log(`- Successful operations: ${successfulOperations}/${operations.length} (${(successfulOperations/operations.length*100).toFixed(1)}%)`);
553 |       console.log(`- Valid responses: ${validResponses}/${operations.length} (${(validResponses/operations.length*100).toFixed(1)}%)`);
554 |     });
555 |   });
556 | });
```

--------------------------------------------------------------------------------
/docs-site/pages/SecurityPage.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import React from 'react';
  2 | import { NavLink } from 'react-router-dom';
  3 | import SEO from '../components/SEO';
  4 | import BreadcrumbSchema from '../components/BreadcrumbSchema';
  5 | import StructuredData from '../components/StructuredData';
  6 | import { H1, PageSubtitle, H2, H3 } from '../components/Typography';
  7 | import { InlineCode } from '../components/CodeBlock';
  8 | import { SITE_DATES } from '../constants';
  9 | 
 10 | // Small utility card
 11 | const Pill: React.FC<React.PropsWithChildren<{ color?: string }>> = ({ children, color = 'from-blue-600 to-purple-600' }) => (
 12 |   <div className={`inline-flex items-center gap-2 bg-gradient-to-r ${color} text-white px-4 py-2 rounded-full text-sm font-medium`}>{children}</div>
 13 | );
 14 | 
 15 | const Bullet: React.FC<React.PropsWithChildren<{ icon?: string; className?: string }>> = ({ children, icon = '✔', className = '' }) => (
 16 |   <li className={`flex items-start gap-2 text-sm text-gray-700 ${className}`}>
 17 |     <span className="mt-0.5 text-green-600 flex-shrink-0">{icon}</span>
 18 |     <span>{children}</span>
 19 |   </li>
 20 | );
 21 | 
 22 | const SectionShell: React.FC<React.PropsWithChildren<{ gradient?: string; className?: string; border?: string }>> = ({ children, gradient = 'from-blue-50 via-indigo-50 to-purple-50', className = '', border = 'border-blue-100' }) => (
 23 |   <div className={`mb-20 last:mb-0 bg-gradient-to-r ${gradient} rounded-2xl p-8 shadow-xl ${border} border ${className}`}>{children}</div>
 24 | );
 25 | 
 26 | // Structured feature list rows for mode comparison
 27 | const ModeFeatureList: React.FC<{ color: 'green' | 'blue'; items: Array<{ icon: string; label: string; detail: string }> }> = ({ color, items }) => {
 28 |   const colorMap = {
 29 |     green: {
 30 |       badge: 'bg-green-100 text-green-800 border-green-200',
 31 |       icon: 'text-green-600',
 32 |       label: 'text-green-900',
 33 |       detail: 'text-green-700'
 34 |     },
 35 |     blue: {
 36 |       badge: 'bg-blue-100 text-blue-800 border-blue-200',
 37 |       icon: 'text-blue-600',
 38 |       label: 'text-blue-900',
 39 |       detail: 'text-blue-700'
 40 |     }
 41 |   } as const;
 42 |   const c = colorMap[color];
 43 |   return (
 44 |     <ul className="list-none p-0 m-0 space-y-3">
 45 |       {items.map(item => (
 46 |         <li key={item.label} className="group">
 47 |           <div className={`flex items-start gap-3 rounded-xl border ${c.badge} bg-white/70 backdrop-blur-sm px-3 py-2 hover:shadow-sm transition`}> 
 48 |             <span className={`text-base leading-none mt-0.5 ${c.icon}`}>{item.icon}</span>
 49 |             <div className="flex-1 min-w-0">
 50 |               <p className={`m-0 text-sm font-medium ${c.label}`}>{item.label}</p>
 51 |               <p className={`m-0 text-[11px] leading-snug ${c.detail}`}>{item.detail}</p>
 52 |             </div>
 53 |           </div>
 54 |         </li>
 55 |       ))}
 56 |     </ul>
 57 |   );
 58 | };
 59 | 
 60 | const SecurityPage: React.FC = () => {
 61 |   const securityStructuredData = {
 62 |     "@context": "https://schema.org",
 63 |     "@type": "TechArticle",
 64 |     "headline": "Security & Privacy - SFCC Development MCP Server",
 65 |     "description": "Security guidelines and privacy considerations for SFCC Development MCP Server. Credential protection, threat mitigations, data handling and secure usage checklist.",
 66 |     "author": {
 67 |       "@type": "Person",
 68 |       "name": "Thomas Theunen"
 69 |     },
 70 |     "publisher": {
 71 |       "@type": "Person",
 72 |       "name": "Thomas Theunen"
 73 |     },
 74 |     "datePublished": SITE_DATES.PUBLISHED,
 75 |     "dateModified": SITE_DATES.MODIFIED,
 76 |     "url": "https://sfcc-mcp-dev.rhino-inquisitor.com/security/",
 77 |     "mainEntity": {
 78 |       "@type": "Guide",
 79 |       "name": "SFCC MCP Security Guide"
 80 |     }
 81 |   };
 82 | 
 83 |   return (
 84 |     <div className="max-w-6xl mx-auto px-6 py-10">
 85 |       <SEO 
 86 |         title="Security & Privacy"
 87 |         description="Security guidelines and privacy considerations for SFCC Development MCP Server. Credential protection, threat mitigations, data handling and secure usage checklist."
 88 |         keywords="SFCC MCP security, Commerce Cloud security, MCP server privacy, SFCC credential protection, development security, API security, local development security, SFCC authentication security"
 89 |         canonical="/security/"
 90 |         ogType="article"
 91 |       />
 92 |       <BreadcrumbSchema items={[
 93 |         { name: "Home", url: "/" },
 94 |         { name: "Security", url: "/security/" }
 95 |       ]} />
 96 |       <StructuredData structuredData={securityStructuredData} />
 97 |       
 98 |       {/* Hero */}
 99 |       <header className="text-center mb-16">
100 |         <Pill>Security & Privacy</Pill>
101 |         <H1 id="security-guidelines" className="text-5xl md:text-6xl font-extrabold bg-gradient-to-r from-gray-900 via-blue-900 to-purple-900 bg-clip-text text-transparent mt-6 mb-6">Built-In Guardrails – You Add Discipline</H1>
102 |         <PageSubtitle className="text-xl md:text-2xl text-gray-600 max-w-4xl mx-auto leading-relaxed">
103 |           Opinionated local-only design: minimal credential footprint, scoped API access, defensive parsing. Use this page as a <strong>practical hardening checklist</strong>, not a marketing overview.
104 |         </PageSubtitle>
105 |         <p className="mt-4 text-[11px] uppercase tracking-wide text-gray-400">Surface: <strong>36+ specialized tools</strong> (docs, best practices, SFRA, cartridge gen, runtime logs, job logs, system & custom objects, site preferences, code versions)</p>
106 |       </header>
107 | 
108 |       {/* Quick Essentials */}
109 |       <div className="grid md:grid-cols-3 gap-6 mb-20">
110 |         {[
111 |           { title: 'Local Only', desc: 'Never deploy to shared or production infra. No multi-user isolation layer exists.' },
112 |           { title: 'Least Privilege', desc: 'Grant only OCAPI resources you actively need.' },
113 |           { title: 'No Persistent Secrets', desc: 'Credentials live in memory during execution; you own filesystem storage strategy.' }
114 |         ].map(card => (
115 |           <div key={card.title} className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
116 |             <h3 className="font-semibold text-gray-900 mb-2 text-lg">{card.title}</h3>
117 |             <p className="text-sm text-gray-600 leading-relaxed">{card.desc}</p>
118 |           </div>
119 |         ))}
120 |       </div>
121 | 
122 |       {/* Mode Comparison */}
123 |       <SectionShell>
124 |         <div className="text-center mb-10">
125 |           <H2 id="modes" className="text-3xl font-bold mb-3">🔐 Modes & Security Characteristics</H2>
126 |           <p className="text-gray-700 max-w-3xl mx-auto text-lg">
127 |             Both modes are designed for <strong>local single‑developer use</strong>. Docs mode has a <em>zero credential surface</em>; Full Mode’s profile is essentially the same as any normal SFCC development workflow using a <InlineCode>dw.json</InlineCode> for OCAPI + WebDAV access. Choose based on capability needs, not fear—just scope credentials sensibly.
128 |           </p>
129 |         </div>
130 |         <div className="grid lg:grid-cols-2 gap-8">
131 |           {/* Docs Mode Card */}
132 |           <div className="rounded-2xl bg-green-50 border border-green-200 p-6 flex flex-col">
133 |             <h3 className="text-xl font-semibold text-green-800 mb-4 flex items-center gap-2">
134 |               <span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-green-600 text-white text-sm shadow">D</span>
135 |               Docs Mode (Default)
136 |             </h3>
137 |             <ModeFeatureList
138 |               color="green"
139 |               items={[
140 |                 { icon: '❇', label: 'No credentials required', detail: 'Pure static: zero auth surface' },
141 |                 { icon: '📄', label: 'Static content only', detail: 'Docs, guides, cartridge scaffolding' },
142 |                 { icon: '🧱', label: 'No outbound authenticated calls', detail: 'Nothing to leak or revoke' },
143 |                 { icon: '🧪', label: 'Safe capability exploration', detail: 'Great for AI tool schema discovery' },
144 |                 { icon: '�', label: 'Instant reversible baseline', detail: 'Add credentials later without refactor' }
145 |               ]}
146 |             />
147 |             <div className="mt-5 text-[11px] text-green-700 font-medium bg-white/60 rounded-md px-3 py-2 border border-green-200">
148 |               Baseline mode: zero credential management, ideal first run.
149 |             </div>
150 |           </div>
151 |           {/* Full Mode Card */}
152 |           <div className="rounded-2xl bg-blue-50 border border-blue-200 p-6 flex flex-col">
153 |             <h3 className="text-xl font-semibold text-blue-800 mb-4 flex items-center gap-2">
154 |               <span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-600 text-white text-sm shadow">F</span>
155 |               Full Mode (<InlineCode>--dw-json</InlineCode>)
156 |             </h3>
157 |             <ModeFeatureList
158 |               color="blue"
159 |               items={[
160 |                 { icon: '🔑', label: 'Credential parity', detail: 'Same auth data you already use locally' },
161 |                 { icon: '🪵', label: 'Runtime + job logs', detail: 'Tail, search, summarize – read-only WebDAV access' },
162 |                 { icon: '🧭', label: 'System & custom object metadata', detail: 'OCAPI Data API – attribute & group definitions' },
163 |                 { icon: '⚙️', label: 'Site preference discovery', detail: 'Group-scoped search with masked password values' },
164 |                 { icon: '🚦', label: 'Explicit code version activation', detail: 'Never automatic; requires targeted command' },
165 |                 { icon: '🪄', label: 'Cartridge generation + docs', detail: 'Same as docs mode plus live capabilities' }
166 |               ]}
167 |             />
168 |             <div className="mt-5 text-[11px] text-blue-700 font-medium bg-white/60 rounded-md px-3 py-2 border border-blue-200">
169 |               Comparable risk to normal SFCC dev with <InlineCode>dw.json</InlineCode>; treat scope hygiene the same.
170 |             </div>
171 |           </div>
172 |         </div>
173 |         <div className="mt-8 text-xs text-gray-600 text-center">Switch between modes freely: omit <InlineCode>--dw-json</InlineCode> to return to a zero‑credential baseline.</div>
174 |       </SectionShell>
175 |       {/* Inline component definitions for mode feature rows */}
176 |       {/* Keeping them near usage for maintainability; extract later if reused */}
177 |       {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */}
178 | 
179 |       {/* Hardening Checklist */}
180 |       <SectionShell gradient="from-gray-50 via-slate-50 to-blue-50" border="border-gray-200">
181 |         <div className="text-center mb-8">
182 |           <H2 id="checklist" className="text-3xl font-bold mb-3">📋 Baseline Hardening Checklist</H2>
183 |           <p className="text-gray-700 max-w-2xl mx-auto">Perform these once per environment. Keep it lightweight; delete unused credentials.</p>
184 |         </div>
185 |         <ol className="grid md:grid-cols-2 gap-6 counter-reset list-none pl-0">
186 |           {[
187 |             'Confirm sandbox hostname (never production domain).',
188 |             'Add dw.json + *.dw.json to .gitignore and verify not tracked.',
189 |             'chmod 600 dw.json (owner read/write only).',
190 |             'Remove unused OAuth fields if only using logs.',
191 |             'Grant only required OCAPI resources (add incrementally).',
192 |             'Mask secrets with environment overrides in CI contexts.',
193 |             'Run docs mode first; validate tool set boundaries.',
194 |             'Rotate client secret + password on schedule (quarterly baseline).'
195 |           ].map(item => (
196 |             <li key={item} className="relative pl-10 text-sm text-gray-700 leading-relaxed">
197 |               <span className="absolute left-0 top-0 w-7 h-7 rounded-full bg-gradient-to-br from-blue-600 to-purple-600 text-white flex items-center justify-center text-xs font-semibold shadow">{String(([
198 |                 'Confirm sandbox hostname (never production domain).',
199 |                 'Add dw.json + *.dw.json to .gitignore and verify not tracked.',
200 |                 'chmod 600 dw.json (owner read/write only).',
201 |                 'Remove unused OAuth fields if only using logs.',
202 |                 'Grant only required OCAPI resources (add incrementally).',
203 |                 'Mask secrets with environment overrides in CI contexts.',
204 |                 'Run docs mode first; validate tool set boundaries.',
205 |                 'Rotate client secret + password on schedule (quarterly baseline).'
206 |               ].indexOf(item) + 1))}</span>
207 |               {item}
208 |             </li>
209 |           ))}
210 |         </ol>
211 |       </SectionShell>
212 | 
213 |       {/* Credential Handling */}
214 |       <SectionShell gradient="from-emerald-50 via-teal-50 to-cyan-50" border="border-emerald-200">
215 |         <div className="grid md:grid-cols-3 gap-8 mb-6 items-start">
216 |           <div className="md:col-span-3 max-w-2xl">
217 |             <H2 id="credentials" className="text-2xl font-bold mb-2">🛡️ Credential Handling</H2>
218 |             <p className="text-sm text-gray-700 leading-relaxed">You retain full control of persistence. The server reads your <InlineCode>dw.json</InlineCode> once, hydrates in-memory configuration, and performs authenticated calls. No outbound exfiltration logic exists.</p>
219 |           </div>
220 |         </div>
221 |         <div className="grid md:grid-cols-3 gap-6">
222 |           <div className="rounded-xl bg-white border border-gray-200 p-5">
223 |             <h3 className="font-semibold text-sm mb-2">Minimize Scope</h3>
224 |             <ul className="text-xs space-y-1 text-gray-600 list-disc pl-4">
225 |               <li>Start w/ no Data API resources</li>
226 |               <li>Add system objects only when needed</li>
227 |               <li>Remove stale resources quarterly</li>
228 |             </ul>
229 |           </div>
230 |           <div className="rounded-xl bg-white border border-gray-200 p-5">
231 |             <h3 className="font-semibold text-sm mb-2">Protect Files</h3>
232 |             <ul className="text-xs space-y-1 text-gray-600 list-disc pl-4">
233 |               <li><InlineCode>chmod 600 dw.json</InlineCode></li>
234 |               <li>Avoid shared directories (Sync/Drive)</li>
235 |               <li>Do not email secrets</li>
236 |             </ul>
237 |           </div>
238 |           <div className="rounded-xl bg-white border border-gray-200 p-5">
239 |             <h3 className="font-semibold text-sm mb-2">Rotate & Audit</h3>
240 |             <ul className="text-xs space-y-1 text-gray-600 list-disc pl-4">
241 |               <li>Quarterly secret rotation baseline</li>
242 |               <li>Remove orphaned API clients</li>
243 |               <li>Track creation dates (label names)</li>
244 |             </ul>
245 |           </div>
246 |         </div>
247 |       </SectionShell>
248 | 
249 |       {/* Threat Model & Mitigations */}
250 |       <SectionShell gradient="from-red-50 via-rose-50 to-orange-50" border="border-red-200">
251 |         <div className="text-center mb-10">
252 |           <H2 id="threat-model" className="text-3xl font-bold mb-3">🧪 Practical Threat Model (Local Context)</H2>
253 |           <p className="text-gray-700 max-w-3xl mx-auto text-lg">In a single‑developer local setup the incremental risk introduced by Full Mode is roughly equivalent to any normal use of <InlineCode>dw.json</InlineCode>. Core concerns remain <strong>credential scope creep</strong>, <strong>accidental sharing of log snippets containing business data</strong>, and <strong>copying sensitive preference values externally</strong>. Below: built‑in mitigations vs. your ongoing hygiene tasks.</p>
254 |         </div>
255 |         <div className="grid md:grid-cols-2 gap-8">
256 |           <div className="rounded-xl bg-white p-6 border border-gray-200">
257 |             <h3 className="font-semibold mb-3 text-gray-900">Mitigated In Design</h3>
258 |             <ul className="space-y-2 text-sm">
259 |               <Bullet>Path traversal (validated absolute paths)</Bullet>
260 |               <Bullet>Parameter schema/type validation</Bullet>
261 |               <Bullet>Read-only log operations (no writes)</Bullet>
262 |               <Bullet>Scoped tool registration (no dynamic eval)</Bullet>
263 |               <Bullet>Token refresh w/ expiration handling</Bullet>
264 |               <Bullet>Memory-only caching (no disk persistence)</Bullet>
265 |             </ul>
266 |           </div>
267 |           <div className="rounded-xl bg-white p-6 border border-gray-200">
268 |             <h3 className="font-semibold mb-3 text-gray-900">Your Responsibilities</h3>
269 |             <ul className="space-y-2 text-sm">
270 |               <Bullet icon="⚠">Do not run on shared multi-user servers</Bullet>
271 |               <Bullet icon="⚠">Keep secrets out of version control</Bullet>
272 |               <Bullet icon="⚠">Avoid copying raw logs with PII into tickets</Bullet>
273 |               <Bullet icon="⚠">Limit OCAPI resources to active feature work</Bullet>
274 |               <Bullet icon="⚠">Rotate credentials + revoke unused clients</Bullet>
275 |               <Bullet icon="⚠">Disable debug once diagnosing finished</Bullet>
276 |             </ul>
277 |           </div>
278 |         </div>
279 |         <div className="mt-8 grid md:grid-cols-3 gap-6 text-xs">
280 |           <div className="bg-green-50 border border-green-200 rounded-lg p-4">
281 |             <p className="font-semibold text-green-800 mb-1">Docs Mode</p>
282 |             <p className="text-green-700 leading-snug">Static reference + generation only. Zero credential or data surface.</p>
283 |           </div>
284 |           <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
285 |             <p className="font-semibold text-blue-800 mb-1">Full Mode (Scoped)</p>
286 |             <p className="text-blue-700 leading-snug">Typical local dev parity: targeted OCAPI, selective log tailing, metadata queries.</p>
287 |           </div>
288 |           <div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
289 |             <p className="font-semibold text-amber-800 mb-1">Broad Scope (Review)</p>
290 |             <p className="text-amber-700 leading-snug">Wide OCAPI grants + continuous debug + indiscriminate log sharing. Still local—but audit and prune.</p>
291 |           </div>
292 |         </div>
293 |       </SectionShell>
294 | 
295 |       {/* Data Protection */}
296 |       <SectionShell gradient="from-yellow-50 via-amber-50 to-orange-50" border="border-amber-200">
297 |         <div className="grid md:grid-cols-3 gap-8 mb-6 items-start">
298 |           <div className="md:col-span-2 max-w-2xl">
299 |             <H2 id="data-protection" className="text-2xl font-bold mb-2">💾 Data Handling & Privacy</H2>
300 |             <p className="text-sm text-gray-700 leading-relaxed">Runtime data (logs, attribute listings, preference search results) is streamed, parsed, optionally cached in memory, and discarded on process exit.</p>
301 |           </div>
302 |             <div className="md:col-span-1 rounded-lg border border-yellow-300 bg-white p-5 text-[13px] text-yellow-800 shadow-sm min-w-[260px]">
303 |             <p className="font-semibold mb-2 tracking-tight">Design Principles</p>
304 |             <ul className="list-disc pl-4 space-y-1.5">
305 |               <li>No silent disk writes</li>
306 |               <li>Bounded tail reads (~200KB)</li>
307 |               <li>Optional debug noise suppression</li>
308 |             </ul>
309 |           </div>
310 |         </div>
311 |         <div className="grid md:grid-cols-3 gap-6">
312 |           {[
313 |             { title: 'Log Processing', items: ['Tail/range reads only (≈200KB)', 'Pattern search constrained by limit', 'Analyzer strips obvious secret tokens'] },
314 |             { title: 'Preference Values', items: ['Password types masked (no bypass)', 'Group-limited search scope', 'No storage of raw values'] },
315 |             { title: 'System & Custom Objects', items: ['Metadata only (ids, flags, counts)', 'No record-level PII retrieval', 'You control query breadth'] }
316 |           ].map(card => (
317 |             <div key={card.title} className="rounded-xl bg-white border border-gray-200 p-5">
318 |               <h3 className="font-semibold text-sm mb-2">{card.title}</h3>
319 |               <ul className="text-xs space-y-1 text-gray-600 list-disc pl-4">
320 |                 {card.items.map(i => <li key={i}>{i}</li>)}
321 |               </ul>
322 |             </div>
323 |           ))}
324 |         </div>
325 |       </SectionShell>
326 | 
327 |       {/* Reporting */}
328 |       <SectionShell gradient="from-slate-50 via-gray-50 to-blue-50" border="border-gray-200">
329 |         <div className="text-center mb-8">
330 |           <H2 id="reporting" className="text-3xl font-bold mb-3">🔍 Responsible Disclosure</H2>
331 |           <p className="text-gray-700 max-w-2xl mx-auto">Found a vulnerability? Help strengthen the ecosystem—avoid public zero-days.</p>
332 |         </div>
333 |         <ol className="list-decimal pl-6 space-y-3 text-sm text-gray-700 max-w-3xl mx-auto">
334 |           <li><strong>Do NOT</strong> open a public GitHub issue containing exploit details.</li>
335 |           <li>Email maintainers with: version, environment, reproduction steps, impact summary.</li>
336 |           <li>Suggest a remediation direction if obvious (helps triage).</li>
337 |           <li>Allow a reasonable patch window before disclosure.</li>
338 |           <li>Re-test once patch is published; confirm mitigation completeness.</li>
339 |         </ol>
340 |       </SectionShell>
341 | 
342 |       {/* Final CTA */}
343 |       <section className="mt-24 text-center" aria-labelledby="next-steps-security">
344 |         <H2 id="next-steps-security" className="text-3xl font-bold mb-4">🔗 Next Steps</H2>
345 |         <p className="text-sm md:text-base text-gray-600 max-w-2xl mx-auto mb-8">Keep momentum: refine configuration or explore advanced tooling now that baseline security posture is set.</p>
346 |         <div className="flex flex-col sm:flex-row gap-4 justify-center mb-10">
347 |           <NavLink
348 |             to="/configuration/"
349 |             className="group bg-gradient-to-r from-blue-600 to-purple-600 text-white px-8 py-4 rounded-xl font-semibold text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 no-underline hover:no-underline focus:no-underline"
350 |           >
351 |             Configuration Guide
352 |             <span className="ml-2 group-hover:translate-x-1 inline-block transition-transform">→</span>
353 |           </NavLink>
354 |           <NavLink
355 |             to="/features/"
356 |             className="border-2 border-gray-300 text-gray-700 px-8 py-4 rounded-xl font-semibold text-lg hover:border-blue-500 hover:text-blue-600 transition-all duration-300 no-underline hover:no-underline focus:no-underline"
357 |           >
358 |             Explore Features
359 |           </NavLink>
360 |           <NavLink
361 |             to="/examples/"
362 |             className="border-2 border-gray-300 text-gray-700 px-8 py-4 rounded-xl font-semibold text-lg hover:border-blue-500 hover:text-blue-600 transition-all duration-300 no-underline hover:no-underline focus:no-underline"
363 |           >
364 |             See Examples
365 |           </NavLink>
366 |         </div>
367 |       </section>
368 |     </div>
369 |   );
370 | };
371 | 
372 | export default SecurityPage;
373 | 
```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/src/routes/ocapi/system-objects-handler.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * System Objects Handler
  3 |  * 
  4 |  * Handles system object definitions, attribute definitions, attribute groups,
  5 |  * and custom object definitions for OCAPI endpoints.
  6 |  */
  7 | 
  8 | const express = require('express');
  9 | const OCAPIUtils = require('./ocapi-utils');
 10 | const OCAPIErrorUtils = require('./ocapi-error-utils');
 11 | 
 12 | class SystemObjectsHandler {
 13 |     constructor(config, dataLoader) {
 14 |         this.config = config;
 15 |         this.ocapiConfig = config.getOcapiConfig();
 16 |         this.dataLoader = dataLoader;
 17 |         this.router = express.Router();
 18 |         this.setupRoutes();
 19 |     }
 20 | 
 21 |     setupRoutes() {
 22 |         // System Object Definitions - List all
 23 |         this.router.get(`/s/-/dw/data/${this.ocapiConfig.version}/system_object_definitions`, 
 24 |             this.handleGetSystemObjectDefinitions.bind(this)
 25 |         );
 26 |         
 27 |         // System Object Definition - Get specific
 28 |         this.router.get(`/s/-/dw/data/${this.ocapiConfig.version}/system_object_definitions/:objectType`, 
 29 |             this.handleGetSystemObjectDefinition.bind(this)
 30 |         );
 31 |         
 32 |         // System Object Definition Search
 33 |         this.router.post(`/s/-/dw/data/${this.ocapiConfig.version}/system_object_definition_search`, 
 34 |             this.handleSearchSystemObjectDefinitions.bind(this)
 35 |         );
 36 |         
 37 |         // System Object Attribute Definition Search
 38 |         this.router.post(`/s/-/dw/data/${this.ocapiConfig.version}/system_object_definitions/:objectType/attribute_definition_search`, 
 39 |             this.handleSearchSystemObjectAttributeDefinitions.bind(this)
 40 |         );
 41 |         
 42 |         // System Object Attribute Group Search
 43 |         this.router.post(`/s/-/dw/data/${this.ocapiConfig.version}/system_object_definitions/:objectType/attribute_group_search`, 
 44 |             this.handleSearchSystemObjectAttributeGroups.bind(this)
 45 |         );
 46 |         
 47 |         // Custom Object Attribute Definition Search  
 48 |         this.router.post(`/s/-/dw/data/${this.ocapiConfig.version}/custom_object_definitions/:objectType/attribute_definition_search`, 
 49 |             this.handleSearchCustomObjectAttributeDefinitions.bind(this)
 50 |         );
 51 |     }
 52 | 
 53 |     /**
 54 |      * Handle GET system object definitions
 55 |      */
 56 |     async handleGetSystemObjectDefinitions(req, res) {
 57 |         const { start = 0, count = 200, select = '(**)' } = req.query;
 58 |         
 59 |         let mockData = this.dataLoader.loadOcapiData('system-object-definitions.json');
 60 |         
 61 |         if (!mockData) {
 62 |             // Fallback mock data with proper SFCC format
 63 |             mockData = {
 64 |                 "_v": "24.4",
 65 |                 "_type": "system_object_definitions",
 66 |                 "count": 0,
 67 |                 "data": [],
 68 |                 "next": null,
 69 |                 "previous": null,
 70 |                 "start": 0,
 71 |                 "total": 0
 72 |             };
 73 |         }
 74 | 
 75 |         // Fix total count to match actual data length
 76 |         mockData.total = mockData.data.length;
 77 | 
 78 |         // Extract data-specific select pattern if present
 79 |         let dataSelectPattern = select;
 80 |         if (select && select.includes('data.(')) {
 81 |             const dataMatch = select.match(/data\.\(([^)]*)\)/);
 82 |             if (dataMatch) {
 83 |                 dataSelectPattern = `(${dataMatch[1]})`;
 84 |             }
 85 |         }
 86 | 
 87 |         // Apply select parameter to modify object structure
 88 |         let processedData = mockData.data.map(obj => OCAPIUtils.applySelectParameter(obj, dataSelectPattern));
 89 | 
 90 |         // Apply pagination
 91 |         const startInt = parseInt(start);
 92 |         const countInt = parseInt(count);
 93 |         const paginatedData = processedData.slice(startInt, startInt + countInt);
 94 |         
 95 |         // Calculate pagination URLs
 96 |         const { nextUrl, previousUrl } = OCAPIUtils.generatePaginationUrls(
 97 |             req, startInt, countInt, mockData.total, select
 98 |         );
 99 | 
100 |         const fullResponse = {
101 |             "_v": mockData._v,
102 |             "_type": mockData._type,
103 |             "count": paginatedData.length,
104 |             "data": paginatedData,
105 |             "next": nextUrl,
106 |             "previous": previousUrl,
107 |             "start": startInt,
108 |             "total": mockData.total
109 |         };
110 | 
111 |         // Add select field to response if provided (like real API)
112 |         if (select && select !== '(**)') {
113 |             fullResponse.select = select;
114 |         }
115 | 
116 |         // Apply root-level select parameter to the entire response
117 |         const response = OCAPIUtils.applyRootSelectParameter(fullResponse, select);
118 | 
119 |         res.json(response);
120 |     }
121 | 
122 |     /**
123 |      * Handle GET specific system object definition
124 |      */
125 |     async handleGetSystemObjectDefinition(req, res) {
126 |         const { objectType } = req.params;
127 |         
128 |         // Try to load specific object definition
129 |         let mockData = this.dataLoader.loadOcapiData(`system-object-definition-${objectType.toLowerCase()}.json`);
130 |         
131 |         if (!mockData) {
132 |             // Create a basic fallback
133 |             mockData = {
134 |                 _type: 'object_type_definition',
135 |                 object_type: objectType,
136 |                 display_name: { default: objectType },
137 |                 description: { default: `SFCC ${objectType} object` },
138 |                 attribute_definition_count: 0,
139 |                 attribute_group_count: 0,
140 |                 key_attribute_id: 'id',
141 |                 content_object: false,
142 |                 queryable: true,
143 |                 read_only: false,
144 |                 creation_date: new Date().toISOString(),
145 |                 last_modified: new Date().toISOString()
146 |             };
147 |         }
148 | 
149 |         res.json(mockData);
150 |     }
151 | 
152 |     /**
153 |      * Handle system object definition search
154 |      */
155 |     async handleSearchSystemObjectDefinitions(req, res) {
156 |         const searchRequest = req.body;
157 |         
158 |         // Load base data
159 |         let mockData = this.dataLoader.loadOcapiData('system-object-definitions.json');
160 |         if (!mockData) {
161 |             mockData = this.getDefaultSystemObjectDefinitions();
162 |         }
163 | 
164 |         // Simple search implementation (in real SFCC this would be more complex)
165 |         let results = mockData.hits;
166 | 
167 |         // Apply basic filtering if search criteria provided
168 |         results = OCAPIUtils.applyTextSearch(results, searchRequest.query);
169 | 
170 |         // Apply pagination
171 |         const start = searchRequest.start || 0;
172 |         const count = searchRequest.count || 200;
173 |         const paginatedResults = results.slice(start, start + count);
174 | 
175 |         res.json({
176 |             count: paginatedResults.length,
177 |             total: results.length,
178 |             start,
179 |             hits: paginatedResults
180 |         });
181 |     }
182 | 
183 |     /**
184 |      * Handle system object attribute definition search
185 |      */
186 |     async handleSearchSystemObjectAttributeDefinitions(req, res) {
187 |         const { objectType } = req.params;
188 |         const searchRequest = req.body;
189 |         
190 |         // Try to load specific attribute definitions based on select parameter
191 |         const isExpandedRequest = searchRequest.select === "(**)";
192 |         const mockDataFile = isExpandedRequest 
193 |             ? `system-object-attributes-${objectType.toLowerCase()}-expanded.json`
194 |             : `system-object-attributes-${objectType.toLowerCase()}.json`;
195 |         
196 |         let mockData = this.dataLoader.loadOcapiData(mockDataFile);
197 |         
198 |         // Fallback to basic data if expanded data doesn't exist
199 |         if (!mockData && isExpandedRequest) {
200 |             mockData = this.dataLoader.loadOcapiData(`system-object-attributes-${objectType.toLowerCase()}.json`);
201 |         }
202 |         
203 |         if (!mockData) {
204 |             // Create fallback data with realistic SFCC format
205 |             mockData = {
206 |                 "_v": "23.2",
207 |                 "_type": "object_attribute_definition_search_result",
208 |                 "count": 0,
209 |                 "hits": [],
210 |                 "query": searchRequest.query || {"match_all_query": {}},
211 |                 "start": 0,
212 |                 "total": 0
213 |             };
214 |         }
215 | 
216 |         // Apply search and pagination
217 |         let results = mockData.hits || [];
218 |         
219 |         // Apply text search if provided
220 |         results = OCAPIUtils.applyTextSearch(results, searchRequest.query);
221 |         
222 |         // Handle select parameter for expanded data
223 |         if (isExpandedRequest && mockData.expandedData) {
224 |             results = results.map(item => {
225 |                 const expandedItem = mockData.expandedData[item.id];
226 |                 return expandedItem || item;
227 |             });
228 |         }
229 |         
230 |         // Store total filtered results count
231 |         const totalFiltered = results.length;
232 |         
233 |         // Apply pagination
234 |         const start = searchRequest.start || 0;
235 |         const count = searchRequest.count || 200;
236 |         const paginatedResults = results.slice(start, start + count);
237 | 
238 |         // Build query response with proper _type fields to match real API
239 |         const queryResponse = OCAPIUtils.buildQueryResponse(searchRequest.query);
240 | 
241 |         // Build response in SFCC format
242 |         const response = {
243 |             "_v": mockData._v || "23.2",
244 |             "_type": "object_attribute_definition_search_result",
245 |             "count": paginatedResults.length,
246 |             "hits": paginatedResults,
247 |             "query": queryResponse,
248 |             "start": start,
249 |             "total": totalFiltered
250 |         };
251 | 
252 |         // Add select parameter to response if it was provided
253 |         if (searchRequest.select) {
254 |             response.select = searchRequest.select;
255 |         }
256 | 
257 |         // Add next page link if there are more results
258 |         if (start + count < totalFiltered) {
259 |             response.next = {
260 |                 "_type": "result_page",
261 |                 "count": Math.min(count, totalFiltered - (start + count)),
262 |                 "start": start + count
263 |             };
264 |         }
265 | 
266 |         res.json(response);
267 |     }
268 | 
269 |     /**
270 |      * Handle system object attribute group search
271 |      */
272 |     async handleSearchSystemObjectAttributeGroups(req, res) {
273 |         try {
274 |             const { objectType } = req.params;
275 |             const searchRequest = req.body;
276 | 
277 |             // 1. Validate object type
278 |             const objectTypeError = OCAPIErrorUtils.validateObjectType(objectType);
279 |             if (objectTypeError) {
280 |                 return OCAPIErrorUtils.sendErrorResponse(res, objectTypeError);
281 |             }
282 | 
283 |             // 2. Check for object types that don't support attribute groups (based on real API testing)
284 |             const unsupportedObjectTypes = ['Customer', 'Site', 'Inventory'];
285 |             if (unsupportedObjectTypes.includes(objectType)) {
286 |                 const notFoundError = OCAPIErrorUtils.createObjectTypeNotFound(objectType);
287 |                 return OCAPIErrorUtils.sendErrorResponse(res, notFoundError);
288 |             }
289 | 
290 |             // 3. Validate search request structure
291 |             const searchRequestError = OCAPIErrorUtils.validateSearchRequest(searchRequest);
292 |             if (searchRequestError) {
293 |                 return OCAPIErrorUtils.sendErrorResponse(res, searchRequestError);
294 |             }
295 | 
296 |             // 4. Validate pagination parameters
297 |             const paginationError = OCAPIErrorUtils.validatePagination(searchRequest.start, searchRequest.count);
298 |             if (paginationError) {
299 |                 return OCAPIErrorUtils.sendErrorResponse(res, paginationError);
300 |             }
301 | 
302 |             // 5. Validate specific query types
303 |             if (searchRequest.query.text_query) {
304 |                 const textQueryError = OCAPIErrorUtils.validateTextQuery(searchRequest.query.text_query);
305 |                 if (textQueryError) {
306 |                     return OCAPIErrorUtils.sendErrorResponse(res, textQueryError);
307 |                 }
308 |             }
309 | 
310 |             if (searchRequest.query.term_query) {
311 |                 const termQueryError = OCAPIErrorUtils.validateTermQuery(searchRequest.query.term_query);
312 |                 if (termQueryError) {
313 |                     return OCAPIErrorUtils.sendErrorResponse(res, termQueryError);
314 |                 }
315 |             }
316 | 
317 |             // 6. Simulate occasional server errors (1% chance) - only if randomErrors is enabled
318 |             if (this.config.features.randomErrors && Math.random() < 0.01) {
319 |                 const serverError = OCAPIErrorUtils.createInternalServerError(
320 |                     "Service temporarily unavailable. Please try again later."
321 |                 );
322 |                 return OCAPIErrorUtils.sendErrorResponse(res, serverError);
323 |             }
324 | 
325 |             // Try to load specific attribute groups with correct naming
326 |             let mockData = this.dataLoader.loadOcapiData(`system-object-attribute-groups-${objectType.toLowerCase()}.json`);
327 |             
328 |             if (!mockData) {
329 |                 // Create fallback data with proper SFCC format (matching real API structure)
330 |                 mockData = {
331 |                     "_v": "23.2",
332 |                     "_type": "object_attribute_group_search_result",
333 |                     "count": 0,
334 |                     "hits": [],
335 |                     "query": {"match_all_query": {"_type": "match_all_query"}},
336 |                     "start": 0,
337 |                     "total": 0
338 |                 };
339 |             }
340 | 
341 |             // Apply search and pagination
342 |             let results = mockData.hits || [];
343 |             
344 |             // Apply pagination
345 |             const start = searchRequest.start || 0;
346 |             const count = searchRequest.count || 200;
347 |             const paginatedResults = results.slice(start, start + count);
348 | 
349 |             // Build query response to match real API
350 |             const queryResponse = OCAPIUtils.buildQueryResponse(searchRequest.query);
351 | 
352 |             // Return response matching real SFCC API format
353 |             const response = {
354 |                 "_v": mockData._v || "23.2",
355 |                 "_type": "object_attribute_group_search_result",
356 |                 "count": paginatedResults.length,
357 |                 "hits": paginatedResults,
358 |                 "query": queryResponse,
359 |                 "start": start,
360 |                 "total": mockData.total || results.length
361 |             };
362 | 
363 |             res.json(response);
364 | 
365 |         } catch (error) {
366 |             // Handle unexpected errors
367 |             console.error('Unexpected error in handleSearchSystemObjectAttributeGroups:', error);
368 |             const serverError = OCAPIErrorUtils.createInternalServerError(
369 |                 "An unexpected error occurred while processing the request."
370 |             );
371 |             return OCAPIErrorUtils.sendErrorResponse(res, serverError);
372 |         }
373 |     }
374 | 
375 |     /**
376 |      * Handle custom object attribute definition search
377 |      */
378 |     async handleSearchCustomObjectAttributeDefinitions(req, res) {
379 |         const { objectType } = req.params;
380 |         const searchRequest = req.body;
381 |         
382 |         // Validate object type parameter
383 |         if (!objectType || objectType.trim() === '') {
384 |             const error = OCAPIErrorUtils.createInvalidRequest(
385 |                 "objectType must be a non-empty string",
386 |                 "objectType"
387 |             );
388 |             return OCAPIErrorUtils.sendErrorResponse(res, error);
389 |         }
390 | 
391 |         // Validate search request structure
392 |         const validationError = this.validateSearchRequest(searchRequest);
393 |         if (validationError) {
394 |             return OCAPIErrorUtils.sendErrorResponse(res, validationError);
395 |         }
396 | 
397 |         // Define known custom object types (case-insensitive)
398 |         const knownCustomObjects = ['customapi', 'versionhistory', 'globalsettings'];
399 |         const objectTypeLower = objectType.toLowerCase();
400 |         
401 |         // Try to load specific custom object attribute definitions
402 |         let mockData = this.dataLoader.loadOcapiData(`custom-object-attributes-${objectTypeLower}.json`);
403 |         
404 |         if (!mockData) {
405 |             // Check if this is a known custom object type with no data vs unknown object type
406 |             if (!knownCustomObjects.includes(objectTypeLower)) {
407 |                 // Return 404 for unknown custom object types (like real SFCC API)
408 |                 const error = OCAPIErrorUtils.createObjectTypeNotFound(objectType);
409 |                 return OCAPIErrorUtils.sendErrorResponse(res, error);
410 |             }
411 |             
412 |             // Create fallback data with proper SFCC format for known objects with no data
413 |             mockData = {
414 |                 "_v": "23.2",
415 |                 "_type": "object_attribute_definition_search_result",
416 |                 "count": 0,
417 |                 "hits": [],
418 |                 "query": searchRequest.query || {"match_all_query": {}},
419 |                 "start": 0,
420 |                 "total": 0
421 |             };
422 |         }
423 | 
424 |         // Apply search and pagination
425 |         let results = mockData.hits || [];
426 |         
427 |         // Apply pagination
428 |         const start = searchRequest.start || 0;
429 |         const count = searchRequest.count || 200;
430 |         const paginatedResults = results.slice(start, start + count);
431 | 
432 |         // Build query response to match real API
433 |         const queryResponse = OCAPIUtils.buildQueryResponse(searchRequest.query);
434 | 
435 |         res.json({
436 |             "_v": mockData._v,
437 |             "_type": mockData._type,
438 |             "count": paginatedResults.length,
439 |             "hits": paginatedResults,
440 |             "query": queryResponse,
441 |             "start": start,
442 |             "total": mockData.total
443 |         });
444 |     }
445 | 
446 |     /**
447 |      * Get default system object definitions for fallback
448 |      */
449 |     getDefaultSystemObjectDefinitions() {
450 |         return {
451 |             count: 25,
452 |             hits: [
453 |                 {
454 |                     _type: 'object_type_definition',
455 |                     object_type: 'Product',
456 |                     display_name: { default: 'Product' },
457 |                     description: { default: 'SFCC Product object' },
458 |                     attribute_definition_count: 45,
459 |                     attribute_group_count: 8,
460 |                     key_attribute_id: 'id',
461 |                     content_object: false,
462 |                     queryable: true,
463 |                     read_only: false,
464 |                     creation_date: '2021-01-01T00:00:00.000Z',
465 |                     last_modified: '2024-01-01T00:00:00.000Z'
466 |                 },
467 |                 {
468 |                     _type: 'object_type_definition',
469 |                     object_type: 'Customer',
470 |                     display_name: { default: 'Customer' },
471 |                     description: { default: 'SFCC Customer object' },
472 |                     attribute_definition_count: 32,
473 |                     attribute_group_count: 6,
474 |                     key_attribute_id: 'customer_no',
475 |                     content_object: false,
476 |                     queryable: true,
477 |                     read_only: false,
478 |                     creation_date: '2021-01-01T00:00:00.000Z',
479 |                     last_modified: '2024-01-01T00:00:00.000Z'
480 |                 },
481 |                 {
482 |                     _type: 'object_type_definition',
483 |                     object_type: 'Order',
484 |                     display_name: { default: 'Order' },
485 |                     description: { default: 'SFCC Order object' },
486 |                     attribute_definition_count: 28,
487 |                     attribute_group_count: 5,
488 |                     key_attribute_id: 'order_no',
489 |                     content_object: false,
490 |                     queryable: true,
491 |                     read_only: false,
492 |                     creation_date: '2021-01-01T00:00:00.000Z',
493 |                     last_modified: '2024-01-01T00:00:00.000Z'
494 |                 }
495 |             ]
496 |         };
497 |     }
498 | 
499 |     /**
500 |      * Validate search request structure and parameters
501 |      */
502 |     validateSearchRequest(searchRequest) {
503 |         // Must have a query property
504 |         if (!searchRequest.query) {
505 |             return OCAPIErrorUtils.createPropertyConstraintViolation("$.query", "search_request");
506 |         }
507 | 
508 |         // Validate count parameter
509 |         if (searchRequest.count !== undefined) {
510 |             if (typeof searchRequest.count !== 'number' || searchRequest.count < 0) {
511 |                 return OCAPIErrorUtils.createInvalidRequest("count must be a positive number", "count");
512 |             }
513 |         }
514 | 
515 |         // Validate start parameter  
516 |         if (searchRequest.start !== undefined) {
517 |             if (typeof searchRequest.start !== 'number' || searchRequest.start < 0) {
518 |                 return OCAPIErrorUtils.createInvalidRequest("start must be a positive number", "start");
519 |             }
520 |         }
521 | 
522 |         // Validate query object
523 |         const query = searchRequest.query;
524 |         const queryTypes = ['text_query', 'term_query', 'filtered_query', 'bool_query', 'match_all_query'];
525 |         const hasValidQueryType = queryTypes.some(type => query[type]);
526 |         
527 |         if (!hasValidQueryType) {
528 |             return OCAPIErrorUtils.createInvalidRequest(
529 |                 "Search query must contain at least one of: text_query, term_query, filtered_query, bool_query, match_all_query"
530 |             );
531 |         }
532 | 
533 |         // Validate text_query
534 |         if (query.text_query) {
535 |             const textQuery = query.text_query;
536 |             if (!textQuery.fields || !Array.isArray(textQuery.fields) || textQuery.fields.length === 0) {
537 |                 return OCAPIErrorUtils.createInvalidRequest("text_query.fields must be a non-empty array");
538 |             }
539 |             if (!textQuery.search_phrase || typeof textQuery.search_phrase !== 'string' || textQuery.search_phrase.trim() === '') {
540 |                 return OCAPIErrorUtils.createInvalidRequest("text_query.search_phrase must be a non-empty string");
541 |             }
542 |         }
543 | 
544 |         // Validate term_query
545 |         if (query.term_query) {
546 |             const termQuery = query.term_query;
547 |             if (!termQuery.fields || !Array.isArray(termQuery.fields) || termQuery.fields.length === 0) {
548 |                 return OCAPIErrorUtils.createInvalidRequest("term_query.fields must be a non-empty array");
549 |             }
550 |             if (!termQuery.values || !Array.isArray(termQuery.values) || termQuery.values.length === 0) {
551 |                 return OCAPIErrorUtils.createInvalidRequest("term_query.values must be a non-empty array");
552 |             }
553 |             if (termQuery.operator) {
554 |                 const validOperators = ['is', 'one_of', 'not_one_of', 'is_null', 'is_not_null'];
555 |                 if (!validOperators.includes(termQuery.operator)) {
556 |                     return OCAPIErrorUtils.createEnumConstraintViolation(termQuery.operator);
557 |                 }
558 |             }
559 |         }
560 | 
561 |         return null; // No validation errors
562 |     }
563 | 
564 |     /**
565 |      * Get the configured router
566 |      */
567 |     getRouter() {
568 |         return this.router;
569 |     }
570 | }
571 | 
572 | module.exports = SystemObjectsHandler;
```
Page 37/61FirstPrevNextLast