This is page 26 of 43. Use http://codebase.md/taurgis/sfcc-dev-mcp?page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/yaml/search-sfcc-methods.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml # ================================================================================== # SFCC MCP Server - search_sfcc_methods Tool YAML Tests # Comprehensive testing for SFCC method search functionality # Tests both successful responses and error handling scenarios # # Quick Test Commands: # aegis "tests/mcp/yaml/search-sfcc-methods.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose # aegis "tests/mcp/yaml/search-sfcc-methods.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --debug --timing # aegis query search_sfcc_methods '{"methodName": "get"}' --config "aegis.config.with-dw.json" # aegis query search_sfcc_methods '{"methodName": "create"}' --config "aegis.config.with-dw.json" # ================================================================================== description: "SFCC MCP Server search_sfcc_methods tool - comprehensive validation" # ================================================================================== # BASIC TOOL STRUCTURE VALIDATION # ================================================================================== tests: - it: "should list search_sfcc_methods tool in available tools" request: jsonrpc: "2.0" id: "tool-available" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-available" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" - it: "should have search_sfcc_methods in tools list with proper structure" request: jsonrpc: "2.0" id: "tool-metadata" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-metadata" result: tools: "match:arrayContains:name:search_sfcc_methods" stderr: "toBeEmpty" - it: "should have tool with meaningful description" request: jsonrpc: "2.0" id: "tool-description-quality" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-description-quality" result: tools: match:arrayContains:name:search_sfcc_methods stderr: "toBeEmpty" - it: "should have proper input schema for methodName parameter" request: jsonrpc: "2.0" id: "tool-schema-validation" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-schema-validation" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" # ================================================================================== # SUCCESSFUL METHOD SEARCH OPERATIONS # ================================================================================== - it: "should successfully search for 'get' methods returning valid JSON array" request: jsonrpc: "2.0" id: "search-get-methods" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-methods" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array structure isError: false stderr: "toBeEmpty" - it: "should find multiple get methods with proper structure" request: jsonrpc: "2.0" id: "search-get-structure" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-structure" result: content: - type: "text" text: "match:contains:className" # Should contain className field isError: false stderr: "toBeEmpty" - it: "should find get methods with method objects containing name and signature" request: jsonrpc: "2.0" id: "search-get-method-details" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-method-details" result: content: - type: "text" text: "match:contains:signature" # Should contain signature information isError: false stderr: "toBeEmpty" - it: "should successfully search for 'create' methods" request: jsonrpc: "2.0" id: "search-create-methods" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "search-create-methods" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should find create methods containing relevant class information" request: jsonrpc: "2.0" id: "search-create-content" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "search-create-content" result: content: - type: "text" text: "match:contains:dw_" # Should contain SFCC class references isError: false stderr: "toBeEmpty" - it: "should successfully search for 'toString' method" request: jsonrpc: "2.0" id: "search-tostring-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "toString" expect: response: jsonrpc: "2.0" id: "search-tostring-method" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should successfully search for 'getValue' method" request: jsonrpc: "2.0" id: "search-getvalue-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "getValue" expect: response: jsonrpc: "2.0" id: "search-getvalue-method" result: content: - type: "text" text: "match:contains:getValue" # Should contain the searched method name isError: false stderr: "toBeEmpty" - it: "should handle case-sensitive method search" request: jsonrpc: "2.0" id: "search-case-sensitive" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "GET" # Uppercase should be different from 'get' expect: response: jsonrpc: "2.0" id: "search-case-sensitive" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Should still return valid array (might be empty) isError: false stderr: "toBeEmpty" # ================================================================================== # EMPTY RESULT VALIDATION # ================================================================================== - it: "should return empty array for non-existent method names" request: jsonrpc: "2.0" id: "search-nonexistent-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "zzznothingfound" expect: response: jsonrpc: "2.0" id: "search-nonexistent-method" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Empty JSON array isError: false stderr: "toBeEmpty" - it: "should return empty array for very specific method name" request: jsonrpc: "2.0" id: "search-specific-nonexistent" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "verySpecificMethodThatDoesNotExist123" expect: response: jsonrpc: "2.0" id: "search-specific-nonexistent" result: content: - type: "text" text: "[]" # Exact empty array isError: false stderr: "toBeEmpty" - it: "should return empty array for special characters in method name" request: jsonrpc: "2.0" id: "search-special-chars" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method@#$%" expect: response: jsonrpc: "2.0" id: "search-special-chars" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Empty array for invalid method names isError: false stderr: "toBeEmpty" # ================================================================================== # ERROR HANDLING VALIDATION # ================================================================================== - it: "should return error for empty method name" request: jsonrpc: "2.0" id: "error-empty-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "error-empty-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return specific error message for empty method name" request: jsonrpc: "2.0" id: "error-empty-message" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "error-empty-message" result: content: - type: "text" text: "match:contains:non-empty string" isError: true stderr: "toBeEmpty" - it: "should return error for missing methodName parameter" request: jsonrpc: "2.0" id: "error-missing-param" method: "tools/call" params: name: "search_sfcc_methods" arguments: {} expect: response: jsonrpc: "2.0" id: "error-missing-param" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for whitespace-only method name" request: jsonrpc: "2.0" id: "error-whitespace-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: " " expect: response: jsonrpc: "2.0" id: "error-whitespace-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for null method name" request: jsonrpc: "2.0" id: "error-null-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: null expect: response: jsonrpc: "2.0" id: "error-null-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for non-string method name (number)" request: jsonrpc: "2.0" id: "error-number-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: 123 expect: response: jsonrpc: "2.0" id: "error-number-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for non-string method name (boolean)" request: jsonrpc: "2.0" id: "error-boolean-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: true expect: response: jsonrpc: "2.0" id: "error-boolean-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" # ================================================================================== # PERFORMANCE AND RESPONSE TIME VALIDATION # ================================================================================== - it: "should complete method search within reasonable time (large result set)" request: jsonrpc: "2.0" id: "perf-large-search" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" # Common method name with many results expect: response: jsonrpc: "2.0" id: "perf-large-search" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" isError: false performance: maxResponseTime: "3000ms" # Allow more time for comprehensive search stderr: "toBeEmpty" - it: "should complete method search quickly for empty results" request: jsonrpc: "2.0" id: "perf-empty-search" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "zzznothingfound" expect: response: jsonrpc: "2.0" id: "perf-empty-search" result: content: - type: "text" text: "[]" # Exact empty array isError: false performance: maxResponseTime: "1000ms" # Empty searches should be faster stderr: "toBeEmpty" - it: "should handle error cases quickly" request: jsonrpc: "2.0" id: "perf-error-handling" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "perf-error-handling" result: content: - type: "text" text: "match:contains:Error" isError: true performance: maxResponseTime: "500ms" # Error validation should be very fast stderr: "toBeEmpty" # ================================================================================== # EDGE CASE VALIDATION # ================================================================================== - it: "should handle single character method search" request: jsonrpc: "2.0" id: "edge-single-char" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "a" expect: response: jsonrpc: "2.0" id: "edge-single-char" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should handle very long method name search" request: jsonrpc: "2.0" id: "edge-long-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "thisIsAVeryLongMethodNameThatProbablyDoesNotExistInTheSystem" expect: response: jsonrpc: "2.0" id: "edge-long-method" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Likely empty array for very long names isError: false stderr: "toBeEmpty" - it: "should handle method names with numbers" request: jsonrpc: "2.0" id: "edge-method-with-numbers" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method123" expect: response: jsonrpc: "2.0" id: "edge-method-with-numbers" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid response isError: false stderr: "toBeEmpty" - it: "should handle method names with underscores" request: jsonrpc: "2.0" id: "edge-method-underscore" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method_name" expect: response: jsonrpc: "2.0" id: "edge-method-underscore" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid response isError: false stderr: "toBeEmpty" # ================================================================================== # CONTENT STRUCTURE VALIDATION # ================================================================================== - it: "should return methods with complete information structure (if results exist)" request: jsonrpc: "2.0" id: "content-structure-complete" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" # Method likely to have results expect: response: jsonrpc: "2.0" id: "content-structure-complete" result: content: - type: "text" text: "match:contains:description" # Should include method descriptions isError: false stderr: "toBeEmpty" - it: "should include both className and method information" request: jsonrpc: "2.0" id: "content-both-class-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "content-both-class-method" result: content: - type: "text" text: "match:regex:[\\s\\S]*className[\\s\\S]*method[\\s\\S]*" # Contains both className and method isError: false stderr: "toBeEmpty" - it: "should maintain consistent response format across different searches" request: jsonrpc: "2.0" id: "content-consistent-format" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "getValue" expect: response: jsonrpc: "2.0" id: "content-consistent-format" result: content: - type: "text" text: "match:type:string" # Always returns string content isError: false stderr: "toBeEmpty" # ================================================================================== # INTEGRATION VALIDATION # ================================================================================== - it: "should be available in full-mode mode" request: jsonrpc: "2.0" id: "integration-full-mode" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "integration-full-mode" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" - it: "should work with SFCC namespace prefixes in results" request: jsonrpc: "2.0" id: "integration-namespace-prefix" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "integration-namespace-prefix" result: content: - type: "text" text: "match:regex:[\\s\\S]*dw_[a-z]+\\.[A-Z][\\s\\S]*" # Contains SFCC class patterns like dw_catalog.Product isError: false stderr: "toBeEmpty" - it: "should return methods from multiple SFCC namespaces" request: jsonrpc: "2.0" id: "integration-multiple-namespaces" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "toString" # Common method across many classes expect: response: jsonrpc: "2.0" id: "integration-multiple-namespaces" result: content: - type: "text" text: "match:regex:[\\s\\S]*dw_[a-z]+[\\s\\S]*" # Should find methods in various dw_ namespaces isError: false stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/search-sfcc-methods.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml # ================================================================================== # SFCC MCP Server - search_sfcc_methods Tool YAML Tests # Comprehensive testing for SFCC method search functionality # Tests both successful responses and error handling scenarios # # Quick Test Commands: # aegis "tests/mcp/yaml/search-sfcc-methods.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose # aegis "tests/mcp/yaml/search-sfcc-methods.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --debug --timing # aegis query search_sfcc_methods '{"methodName": "get"}' --config "aegis.config.docs-only.json" # aegis query search_sfcc_methods '{"methodName": "create"}' --config "aegis.config.docs-only.json" # ================================================================================== description: "SFCC MCP Server search_sfcc_methods tool - comprehensive validation" # ================================================================================== # BASIC TOOL STRUCTURE VALIDATION # ================================================================================== tests: - it: "should list search_sfcc_methods tool in available tools" request: jsonrpc: "2.0" id: "tool-available" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-available" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" - it: "should have search_sfcc_methods in tools list with proper structure" request: jsonrpc: "2.0" id: "tool-metadata" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-metadata" result: tools: "match:arrayContains:name:search_sfcc_methods" stderr: "toBeEmpty" - it: "should have tool with meaningful description" request: jsonrpc: "2.0" id: "tool-description-quality" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-description-quality" result: tools: match:arrayContains:name:search_sfcc_methods stderr: "toBeEmpty" - it: "should have proper input schema for methodName parameter" request: jsonrpc: "2.0" id: "tool-schema-validation" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-schema-validation" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" # ================================================================================== # SUCCESSFUL METHOD SEARCH OPERATIONS # ================================================================================== - it: "should successfully search for 'get' methods returning valid JSON array" request: jsonrpc: "2.0" id: "search-get-methods" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-methods" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array structure isError: false stderr: "toBeEmpty" - it: "should find multiple get methods with proper structure" request: jsonrpc: "2.0" id: "search-get-structure" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-structure" result: content: - type: "text" text: "match:contains:className" # Should contain className field isError: false stderr: "toBeEmpty" - it: "should find get methods with method objects containing name and signature" request: jsonrpc: "2.0" id: "search-get-method-details" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "search-get-method-details" result: content: - type: "text" text: "match:contains:signature" # Should contain signature information isError: false stderr: "toBeEmpty" - it: "should successfully search for 'create' methods" request: jsonrpc: "2.0" id: "search-create-methods" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "search-create-methods" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should find create methods containing relevant class information" request: jsonrpc: "2.0" id: "search-create-content" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "search-create-content" result: content: - type: "text" text: "match:contains:dw_" # Should contain SFCC class references isError: false stderr: "toBeEmpty" - it: "should successfully search for 'toString' method" request: jsonrpc: "2.0" id: "search-tostring-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "toString" expect: response: jsonrpc: "2.0" id: "search-tostring-method" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should successfully search for 'getValue' method" request: jsonrpc: "2.0" id: "search-getvalue-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "getValue" expect: response: jsonrpc: "2.0" id: "search-getvalue-method" result: content: - type: "text" text: "match:contains:getValue" # Should contain the searched method name isError: false stderr: "toBeEmpty" - it: "should handle case-sensitive method search" request: jsonrpc: "2.0" id: "search-case-sensitive" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "GET" # Uppercase should be different from 'get' expect: response: jsonrpc: "2.0" id: "search-case-sensitive" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Should still return valid array (might be empty) isError: false stderr: "toBeEmpty" # ================================================================================== # EMPTY RESULT VALIDATION # ================================================================================== - it: "should return empty array for non-existent method names" request: jsonrpc: "2.0" id: "search-nonexistent-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "zzznothingfound" expect: response: jsonrpc: "2.0" id: "search-nonexistent-method" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Empty JSON array isError: false stderr: "toBeEmpty" - it: "should return empty array for very specific method name" request: jsonrpc: "2.0" id: "search-specific-nonexistent" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "verySpecificMethodThatDoesNotExist123" expect: response: jsonrpc: "2.0" id: "search-specific-nonexistent" result: content: - type: "text" text: "[]" # Exact empty array isError: false stderr: "toBeEmpty" - it: "should return empty array for special characters in method name" request: jsonrpc: "2.0" id: "search-special-chars" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method@#$%" expect: response: jsonrpc: "2.0" id: "search-special-chars" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Empty array for invalid method names isError: false stderr: "toBeEmpty" # ================================================================================== # ERROR HANDLING VALIDATION # ================================================================================== - it: "should return error for empty method name" request: jsonrpc: "2.0" id: "error-empty-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "error-empty-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return specific error message for empty method name" request: jsonrpc: "2.0" id: "error-empty-message" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "error-empty-message" result: content: - type: "text" text: "match:contains:non-empty string" isError: true stderr: "toBeEmpty" - it: "should return error for missing methodName parameter" request: jsonrpc: "2.0" id: "error-missing-param" method: "tools/call" params: name: "search_sfcc_methods" arguments: {} expect: response: jsonrpc: "2.0" id: "error-missing-param" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for whitespace-only method name" request: jsonrpc: "2.0" id: "error-whitespace-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: " " expect: response: jsonrpc: "2.0" id: "error-whitespace-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for null method name" request: jsonrpc: "2.0" id: "error-null-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: null expect: response: jsonrpc: "2.0" id: "error-null-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for non-string method name (number)" request: jsonrpc: "2.0" id: "error-number-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: 123 expect: response: jsonrpc: "2.0" id: "error-number-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" - it: "should return error for non-string method name (boolean)" request: jsonrpc: "2.0" id: "error-boolean-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: true expect: response: jsonrpc: "2.0" id: "error-boolean-method" result: content: - type: "text" text: "match:contains:Error" isError: true stderr: "toBeEmpty" # ================================================================================== # PERFORMANCE AND RESPONSE TIME VALIDATION # ================================================================================== - it: "should complete method search within reasonable time (large result set)" request: jsonrpc: "2.0" id: "perf-large-search" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" # Common method name with many results expect: response: jsonrpc: "2.0" id: "perf-large-search" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" isError: false performance: maxResponseTime: "3000ms" # Allow more time for comprehensive search stderr: "toBeEmpty" - it: "should complete method search quickly for empty results" request: jsonrpc: "2.0" id: "perf-empty-search" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "zzznothingfound" expect: response: jsonrpc: "2.0" id: "perf-empty-search" result: content: - type: "text" text: "[]" # Exact empty array isError: false performance: maxResponseTime: "1000ms" # Empty searches should be faster stderr: "toBeEmpty" - it: "should handle error cases quickly" request: jsonrpc: "2.0" id: "perf-error-handling" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "" expect: response: jsonrpc: "2.0" id: "perf-error-handling" result: content: - type: "text" text: "match:contains:Error" isError: true performance: maxResponseTime: "500ms" # Error validation should be very fast stderr: "toBeEmpty" # ================================================================================== # EDGE CASE VALIDATION # ================================================================================== - it: "should handle single character method search" request: jsonrpc: "2.0" id: "edge-single-char" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "a" expect: response: jsonrpc: "2.0" id: "edge-single-char" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid JSON array isError: false stderr: "toBeEmpty" - it: "should handle very long method name search" request: jsonrpc: "2.0" id: "edge-long-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "thisIsAVeryLongMethodNameThatProbablyDoesNotExistInTheSystem" expect: response: jsonrpc: "2.0" id: "edge-long-method" result: content: - type: "text" text: "match:regex:^\\[\\s*\\]$" # Likely empty array for very long names isError: false stderr: "toBeEmpty" - it: "should handle method names with numbers" request: jsonrpc: "2.0" id: "edge-method-with-numbers" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method123" expect: response: jsonrpc: "2.0" id: "edge-method-with-numbers" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid response isError: false stderr: "toBeEmpty" - it: "should handle method names with underscores" request: jsonrpc: "2.0" id: "edge-method-underscore" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "method_name" expect: response: jsonrpc: "2.0" id: "edge-method-underscore" result: content: - type: "text" text: "match:regex:\\[[\\s\\S]*\\]" # Valid response isError: false stderr: "toBeEmpty" # ================================================================================== # CONTENT STRUCTURE VALIDATION # ================================================================================== - it: "should return methods with complete information structure (if results exist)" request: jsonrpc: "2.0" id: "content-structure-complete" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" # Method likely to have results expect: response: jsonrpc: "2.0" id: "content-structure-complete" result: content: - type: "text" text: "match:contains:description" # Should include method descriptions isError: false stderr: "toBeEmpty" - it: "should include both className and method information" request: jsonrpc: "2.0" id: "content-both-class-method" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "create" expect: response: jsonrpc: "2.0" id: "content-both-class-method" result: content: - type: "text" text: "match:regex:[\\s\\S]*className[\\s\\S]*method[\\s\\S]*" # Contains both className and method isError: false stderr: "toBeEmpty" - it: "should maintain consistent response format across different searches" request: jsonrpc: "2.0" id: "content-consistent-format" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "getValue" expect: response: jsonrpc: "2.0" id: "content-consistent-format" result: content: - type: "text" text: "match:type:string" # Always returns string content isError: false stderr: "toBeEmpty" # ================================================================================== # INTEGRATION VALIDATION # ================================================================================== - it: "should be available in docs-only mode" request: jsonrpc: "2.0" id: "integration-docs-only" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "integration-docs-only" result: match:extractField: "tools.*.name" value: "match:arrayContains:search_sfcc_methods" stderr: "toBeEmpty" - it: "should work with SFCC namespace prefixes in results" request: jsonrpc: "2.0" id: "integration-namespace-prefix" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "get" expect: response: jsonrpc: "2.0" id: "integration-namespace-prefix" result: content: - type: "text" text: "match:regex:[\\s\\S]*dw_[a-z]+\\.[A-Z][\\s\\S]*" # Contains SFCC class patterns like dw_catalog.Product isError: false stderr: "toBeEmpty" - it: "should return methods from multiple SFCC namespaces" request: jsonrpc: "2.0" id: "integration-multiple-namespaces" method: "tools/call" params: name: "search_sfcc_methods" arguments: methodName: "toString" # Common method across many classes expect: response: jsonrpc: "2.0" id: "integration-multiple-namespaces" result: content: - type: "text" text: "match:regex:[\\s\\S]*dw_[a-z]+[\\s\\S]*" # Should find methods in various dw_ namespaces isError: false stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-sfra-document.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript import { test, describe, before, after, beforeEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { connect } from 'mcp-aegis'; describe('get_sfra_document Tool - Programmatic Tests', () => { let client; before(async () => { client = await connect('./aegis.config.docs-only.json'); }); after(async () => { if (client?.connected) { await client.disconnect(); } }); beforeEach(() => { client.clearAllBuffers(); }); describe('Protocol Compliance & Tool Discovery', () => { test('should be registered and accessible', async () => { const tools = await client.listTools(); const sfraDocTool = tools.find(tool => tool.name === 'get_sfra_document'); assert.ok(sfraDocTool, 'get_sfra_document tool should be available'); // Validate tool schema structure assertToolSchema(sfraDocTool); // Validate specific schema requirements const { inputSchema } = sfraDocTool; assert.ok(inputSchema.properties.documentName, 'Should have documentName parameter'); assert.equal(inputSchema.properties.documentName.type, 'string'); assert.ok(inputSchema.required.includes('documentName'), 'documentName should be required'); }); test('should have comprehensive tool description', async () => { const tools = await client.listTools(); const sfraDocTool = tools.find(tool => tool.name === 'get_sfra_document'); const description = sfraDocTool.description.toLowerCase(); assert.ok(description.includes('sfra'), 'Description should mention SFRA'); assert.ok(description.includes('documentation'), 'Description should mention documentation'); assert.ok(description.includes('sfra') || description.includes('class') || description.includes('model'), 'Description should mention SFRA classes or models'); assert.ok(description.length > 100, 'Description should be comprehensive'); }); }); describe('Core SFRA Documents - Dynamic Discovery', () => { const coreDocuments = [ { name: 'server', expectedContent: ['Class Server', 'middleware', 'routing'] }, { name: 'request', expectedContent: ['Class Request', 'HTTP', 'session'] }, { name: 'response', expectedContent: ['Class Response', 'render', 'redirect'] }, { name: 'querystring', expectedContent: ['QueryString', 'parameters', 'URL'] }, { name: 'render', expectedContent: ['render', 'template', 'ISML'] } ]; coreDocuments.forEach(({ name, expectedContent }) => { test(`should retrieve ${name} document with rich content`, async () => { const result = await client.callTool('get_sfra_document', { documentName: name }); // Basic response validation assertValidMCPResponse(result); assert.equal(result.isError, false, `${name} document should load successfully`); // Parse and validate JSON content const documentData = parseDocumentJSON(result.content[0].text); // Validate document structure assertDocumentStructure(documentData, name); // Validate expected content is present expectedContent.forEach(content => { assert.ok( documentData.content.toLowerCase().includes(content.toLowerCase()), `${name} document should contain "${content}"` ); }); }); }); test('should maintain consistent document structure across all core docs', async () => { const results = []; // Process documents sequentially to avoid message stream interference for (let i = 0; i < coreDocuments.length; i++) { const { name } = coreDocuments[i]; const result = await client.callTool('get_sfra_document', { documentName: name }); const documentData = parseDocumentJSON(result.content[0].text); results.push({ name, documentData }); } // Validate all documents have consistent structure results.forEach(({ name, documentData }) => { assert.ok(documentData.title, `${name} should have title`); assert.ok(Array.isArray(documentData.sections), `${name} should have sections array`); assert.ok(documentData.content, `${name} should have content`); assert.ok(documentData.type, `${name} should have type`); assert.ok(documentData.category, `${name} should have category`); assert.ok(documentData.filename, `${name} should have filename`); assert.ok(documentData.lastModified, `${name} should have lastModified`); // Validate sections are comprehensive assert.ok(documentData.sections.length >= 5, `${name} should have at least 5 sections`); // Validate content is substantial assert.ok(documentData.content.length > 1000, `${name} should have substantial content`); }); assert.ok(results.length > 0, 'Should have processed core documents'); }); }); describe('Model Documents - Advanced Validation', () => { const modelDocuments = [ { name: 'product-full', category: 'product', keywords: ['product', 'pricing', 'variation'] }, { name: 'cart', category: 'order', keywords: ['cart', 'basket', 'items'] }, { name: 'account', category: 'customer', keywords: ['account', 'customer', 'profile'] }, { name: 'billing', category: 'order', keywords: ['billing', 'payment', 'address'] }, { name: 'shipping', category: 'order', keywords: ['shipping', 'delivery', 'address'] } ]; test('should retrieve all model documents with correct categorization', async () => { const results = []; // Process documents sequentially to avoid message stream interference for (let i = 0; i < modelDocuments.length; i++) { const { name, category, keywords } = modelDocuments[i]; const result = await client.callTool('get_sfra_document', { documentName: name }); assert.equal(result.isError, false, `${name} should load successfully`); const documentData = parseDocumentJSON(result.content[0].text); // Validate keywords are present in content const content = documentData.content.toLowerCase(); const keywordMatches = keywords.filter(keyword => content.includes(keyword.toLowerCase()) ); assert.ok(keywordMatches.length >= 1, `${name} should contain at least one keyword: ${keywords.join(', ')}`); results.push({ name, documentData, category, keywordMatches }); } // Group by category and validate const categoryGroups = results.reduce((groups, { name, category }) => { if (!groups[category]) groups[category] = []; groups[category].push(name); return groups; }, {}); // Validate category distribution Object.entries(categoryGroups).forEach(([category, docs]) => { assert.ok(docs.length > 0, `Category ${category} should have documents`); }); }); test('should handle hyphenated document names correctly', async () => { const hyphenatedDocs = modelDocuments .filter(({ name }) => name.includes('-')) .map(({ name }) => name); assert.ok(hyphenatedDocs.length > 0, 'Should have hyphenated document names to test'); for (const docName of hyphenatedDocs) { const result = await client.callTool('get_sfra_document', { documentName: docName }); assert.equal(result.isError, false, `Hyphenated document ${docName} should work`); const documentData = parseDocumentJSON(result.content[0].text); // More flexible filename checking - just ensure it's related to the document const filenameRelated = documentData.filename.toLowerCase().includes(docName.toLowerCase()) || documentData.filename.toLowerCase().replace(/-/g, '').includes(docName.toLowerCase().replace(/-/g, '')) || documentData.title.toLowerCase().includes(docName.toLowerCase().split('-')[0]); assert.ok(filenameRelated, `Filename should be related to ${docName}: ${documentData.filename}`); } }); }); describe('Case Sensitivity & Input Validation', () => { const caseTestCases = [ { input: 'server', expected: true, description: 'lowercase' }, { input: 'SERVER', expected: true, description: 'uppercase' }, { input: 'Server', expected: true, description: 'mixed case' }, { input: 'SeRvEr', expected: true, description: 'random case' }, { input: 'request', expected: true, description: 'lowercase request' }, { input: 'REQUEST', expected: true, description: 'uppercase request' } ]; test('should handle case variations consistently', async () => { const results = []; // Process case variations sequentially to avoid message stream interference for (let i = 0; i < caseTestCases.length; i++) { const { input, expected, description } = caseTestCases[i]; const result = await client.callTool('get_sfra_document', { documentName: input }); const success = !result.isError; assert.equal(success, expected, `${description} "${input}" should ${expected ? 'succeed' : 'fail'}`); if (success) { const documentData = parseDocumentJSON(result.content[0].text); // Case variations should return the same document assert.ok(documentData.title.includes('Server') || documentData.title.includes('Request'), 'Should return valid SFRA document'); } results.push({ input, success, description }); } // Verify case insensitivity const serverVariants = results .filter(({ input }) => input.toLowerCase() === 'server') .filter(({ success }) => success); assert.ok(serverVariants.length > 1, 'Multiple case variations should work for server'); assert.ok(serverVariants.length > 0, 'Should handle case insensitive variations'); }); test('should validate input parameters thoroughly', async () => { const invalidInputs = [ { args: {}, expectedError: 'documentName', description: 'missing parameter' }, { args: { documentName: '' }, expectedError: 'non-empty', description: 'empty string' }, { args: { documentName: null }, expectedError: 'string', description: 'null value' }, { args: { documentName: 123 }, expectedError: 'string', description: 'numeric value' }, { args: { documentName: 'invalid/path/name' }, expectedError: 'not found', description: 'invalid path' }, { args: { documentName: 'definitely-does-not-exist' }, expectedError: 'not found', description: 'nonexistent doc' } ]; for (const { args, expectedError, description } of invalidInputs) { const result = await client.callTool('get_sfra_document', args); assert.equal(result.isError, true, `${description} should result in error`); assert.ok(result.content[0].text.toLowerCase().includes(expectedError.toLowerCase()), `${description} should mention "${expectedError}"`); } }); }); describe('Content Quality & Completeness Analysis', () => { test('should provide comprehensive documentation content', async () => { const qualityChecks = [ { doc: 'server', minSections: 10, minContentLength: 5000, requiredSections: ['Method Summary', 'Method Detail', 'Usage Examples'] }, { doc: 'request', minSections: 8, minContentLength: 4000, requiredSections: ['Properties', 'Constructor Summary', 'Property Details'] }, { doc: 'response', minSections: 8, minContentLength: 4000, requiredSections: ['Method Summary', 'Properties'] } ]; for (const { doc, minSections, minContentLength, requiredSections } of qualityChecks) { const result = await client.callTool('get_sfra_document', { documentName: doc }); const documentData = parseDocumentJSON(result.content[0].text); // Section count validation assert.ok(documentData.sections.length >= minSections, `${doc} should have at least ${minSections} sections (has ${documentData.sections.length})`); // Content length validation assert.ok(documentData.content.length >= minContentLength, `${doc} should have substantial content (${documentData.content.length} chars, min ${minContentLength})`); // Required sections validation requiredSections.forEach(requiredSection => { const hasSection = documentData.sections.some(section => section.toLowerCase().includes(requiredSection.toLowerCase()) ); assert.ok(hasSection, `${doc} should have "${requiredSection}" section`); }); } }); test('should include code examples and usage patterns', async () => { const docsWithExamples = ['server', 'request', 'response']; for (const doc of docsWithExamples) { const result = await client.callTool('get_sfra_document', { documentName: doc }); const documentData = parseDocumentJSON(result.content[0].text); const content = documentData.content.toLowerCase(); // Check for code examples const hasCodeBlocks = content.includes('```') || content.includes('```javascript'); const hasUsageExamples = content.includes('example') && ( content.includes('function') || content.includes('var ') || content.includes('new ') || content.includes('server.') ); // Check for method signatures and constructor patterns const hasSignatures = content.includes('signature:') || (content.includes('(') && content.includes(')')) || content.includes('constructor') || content.includes('method'); assert.ok(hasCodeBlocks || hasUsageExamples || hasSignatures, `${doc} should contain code examples, usage patterns, or method signatures`); // Additional signature verification assert.ok(hasSignatures, `${doc} should contain method signatures or constructors`); // Note: Validated code examples and signatures for ${doc} } }); test('should maintain cross-reference consistency', async () => { const docs = ['server', 'request', 'response']; const documentContents = {}; // Load all documents for (const doc of docs) { const result = await client.callTool('get_sfra_document', { documentName: doc }); const documentData = parseDocumentJSON(result.content[0].text); documentContents[doc] = documentData.content.toLowerCase(); } // Check cross-references const crossRefs = [ { from: 'server', to: 'request', mention: 'request' }, { from: 'server', to: 'response', mention: 'response' }, { from: 'request', to: 'session', mention: 'session' } ]; crossRefs.forEach(({ from, to, mention }) => { if (documentContents[from] && documentContents[to]) { assert.ok(documentContents[from].includes(mention), `${from} document should reference ${mention}`); } }); }); }); describe('Error Recovery & Resilience', () => { test('should recover gracefully from various error conditions', async () => { const errorScenarios = [ { name: 'nonexistent-doc', expectError: true }, { name: 'server', expectError: false }, // Should work after error { name: '', expectError: true }, { name: 'request', expectError: false }, // Should work after another error { name: 'invalid/path', expectError: true }, { name: 'response', expectError: false } // Final success ]; for (const { name, expectError } of errorScenarios) { const result = await client.callTool('get_sfra_document', { documentName: name }); if (expectError) { assert.equal(result.isError, true, `${name || 'empty'} should result in error`); } else { assert.equal(result.isError, false, `${name} should succeed after error`); const documentData = parseDocumentJSON(result.content[0].text); assert.ok(documentData.title, `${name} should return valid document`); } } }); test('should maintain consistent behavior under stress', async () => { const stressTests = [ // Mix of valid and invalid requests ...Array(5).fill('server'), ...Array(3).fill('nonexistent'), ...Array(5).fill('request'), ...Array(2).fill(''), ...Array(5).fill('response') ]; let successCount = 0; let errorCount = 0; for (const docName of stressTests) { const result = await client.callTool('get_sfra_document', { documentName: docName }); if (result.isError) { errorCount++; } else { successCount++; // Validate successful responses are still valid const documentData = parseDocumentJSON(result.content[0].text); assert.ok(documentData.title, 'Successful responses should remain valid'); } } assert.ok(successCount > 0, 'Should have some successful requests'); assert.ok(errorCount > 0, 'Should have some failed requests'); assert.ok(successCount > 0, 'Should have successful requests'); assert.ok(errorCount >= 0, 'Should handle errors gracefully'); }); }); describe('Memory & Resource Management', () => { test('should handle large document content efficiently', async () => { // Test with documents that likely have substantial content const largeDocs = ['server', 'product-full', 'cart']; const memoryBefore = process.memoryUsage(); for (const doc of largeDocs) { const result = await client.callTool('get_sfra_document', { documentName: doc }); assert.equal(result.isError, false, `${doc} should load successfully`); const documentData = parseDocumentJSON(result.content[0].text); assert.ok(documentData.content.length > 0, `${doc} should have content`); } const memoryAfter = process.memoryUsage(); const memoryIncrease = (memoryAfter.heapUsed - memoryBefore.heapUsed) / 1024 / 1024; // Memory increase should be reasonable (allowing for some overhead) assert.ok(memoryIncrease < 50, `Memory increase should be reasonable: ${memoryIncrease.toFixed(1)}MB`); }); }); }); // Helper Classes and Functions function assertValidMCPResponse(result) { assert.ok(result.content, 'Should have content'); assert.ok(Array.isArray(result.content), 'Content should be array'); assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); assert.equal(result.content.length, 1, 'Should have exactly one content item'); assert.equal(result.content[0].type, 'text', 'Content should be text type'); } function assertToolSchema(tool) { assert.ok(tool.name, 'Tool should have name'); assert.ok(tool.description, 'Tool should have description'); assert.ok(tool.inputSchema, 'Tool should have schema'); assert.equal(tool.inputSchema.type, 'object', 'Schema should be object type'); assert.ok(tool.inputSchema.properties, 'Schema should have properties'); assert.ok(tool.inputSchema.required, 'Schema should have required fields'); } function parseDocumentJSON(textContent) { try { return JSON.parse(textContent); } catch (error) { assert.fail(`Document content should be valid JSON: ${error.message}`); } } function assertDocumentStructure(documentData, documentName) { assert.ok(documentData.title, `${documentName} should have title`); assert.ok(Array.isArray(documentData.sections), `${documentName} should have sections array`); assert.ok(documentData.content, `${documentName} should have content`); assert.ok(documentData.type, `${documentName} should have type`); assert.ok(documentData.category, `${documentName} should have category`); assert.ok(documentData.filename, `${documentName} should have filename`); assert.ok(documentData.lastModified, `${documentName} should have lastModified`); // Validate lastModified is a valid date string assert.ok(!isNaN(Date.parse(documentData.lastModified)), `${documentName} lastModified should be valid date`); // Validate filename corresponds to document const filenameBase = documentData.filename.toLowerCase().replace(/\.md$/, ''); const docNameBase = documentName.toLowerCase(); assert.ok(filenameBase.includes(docNameBase) || docNameBase.includes(filenameBase), `${documentName} filename should correspond to document name`); } ``` -------------------------------------------------------------------------------- /docs/dw_net/HTTPClient.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.net # Class HTTPClient ## Inheritance Hierarchy - Object - dw.net.HTTPClient ## Description The HTTPClient class supports the HTTP methods GET, POST, HEAD, PUT, PATCH, OPTIONS, and DELETE. If a secure connection via HTTPS is established the used server certificate or the signing CAs certificate needs to be imported into the customer key store via Business Manager. Note: when this class is used with sensitive data, be careful in persisting sensitive information. Key selection for mutual TLS: Check if there is an explicit identity requested setIdentity(KeyRef) Else, Check if there is a mapping for hostname in the keystore Deprecated: Select an arbitrary private key from the keystore var httpClient : HTTPClient = new HTTPClient(); var message : String; httpClient.open('GET', 'http://www.myinstance.com/feed.xml'); httpClient.setTimeout(3000); httpClient.send(); if (httpClient.statusCode == 200) { message = httpClient.text; } else { // error handling message="An error occurred with status code "+httpClient.statusCode; } ## Constants ### DEFAULT_GET_FILE_SIZE **Type:** Number = 5242880 The default size for sendAndReceiveToFile() returning a File is 5MB deprecated in favor of MAX_GET_FILE_SIZE ### MAX_GET_FILE_SIZE **Type:** Number = 209715200 The maximum permitted size (in bytes) of an HTTP response when calling operations which write the response to file. (200MB) ### MAX_GET_MEM_SIZE **Type:** Number = 10485760 The maximum permitted size (in bytes) of an HTTP response when calling operations which store the response in memory. (10MB) ## Properties ### allowRedirect **Type:** boolean Determines whether redirect handling is enabled. ### allResponseHeaders **Type:** HashMap (Read Only) All response headers as a map containing the name and value of the response header. ### bytes **Type:** Bytes (Read Only) The bytes in the message body for HTTP status codes between 200 and 299. ### errorBytes **Type:** Bytes (Read Only) The returned message body as bytes for HTTP status code greater or equal to 400. Error messages are not written to the response file. ### errorText **Type:** String (Read Only) The returned message body as text for HTTP status code greater or equal to 400. Error messages are not written to the response file. ### hostNameVerification **Type:** boolean Determines whether host name verification is enabled. ### identity **Type:** KeyRef Gets the identity used for mutual TLS (mTLS). ### responseHeaders **Type:** Map (Read Only) All response headers as a map in which each entry represents an individual header. The key of the entry holds the header name and the entry value holds a list of all header values. ### statusCode **Type:** Number (Read Only) The status code of the last HTTP operation. ### statusMessage **Type:** String (Read Only) The message text of the last HTTP operation. ### text **Type:** String (Read Only) The returned message body as text for HTTP status codes between 200 and 299. ### timeout **Type:** Number The timeout for this client, in milliseconds. ## Constructor Summary HTTPClient() ## Method Summary ### enableCaching **Signature:** `enableCaching(ttl : Number) : void` Calling this method enables caching for GET requests. ### getAllowRedirect **Signature:** `getAllowRedirect() : boolean` Determines whether redirect handling is enabled. ### getAllResponseHeaders **Signature:** `getAllResponseHeaders() : HashMap` Returns all response headers as a map containing the name and value of the response header. ### getBytes **Signature:** `getBytes() : Bytes` Returns the bytes in the message body for HTTP status codes between 200 and 299. ### getErrorBytes **Signature:** `getErrorBytes() : Bytes` Returns the returned message body as bytes for HTTP status code greater or equal to 400. ### getErrorText **Signature:** `getErrorText() : String` Returns the returned message body as text for HTTP status code greater or equal to 400. ### getHostNameVerification **Signature:** `getHostNameVerification() : boolean` Determines whether host name verification is enabled. ### getIdentity **Signature:** `getIdentity() : KeyRef` Gets the identity used for mutual TLS (mTLS). ### getResponseHeader **Signature:** `getResponseHeader(header : String) : String` Returns a specific response header from the last HTTP operation. ### getResponseHeaders **Signature:** `getResponseHeaders(name : String) : List` Returns all the values of a response header from the last HTTP operation as a list of strings. ### getResponseHeaders **Signature:** `getResponseHeaders() : Map` Returns all response headers as a map in which each entry represents an individual header. ### getStatusCode **Signature:** `getStatusCode() : Number` Returns the status code of the last HTTP operation. ### getStatusMessage **Signature:** `getStatusMessage() : String` Returns the message text of the last HTTP operation. ### getText **Signature:** `getText() : String` Returns the returned message body as text for HTTP status codes between 200 and 299. ### getText **Signature:** `getText(encoding : String) : String` Returns the returned message body as text for HTTP status codes between 200 and 299. ### getTimeout **Signature:** `getTimeout() : Number` Returns the timeout for this client, in milliseconds. ### open **Signature:** `open(method : String, url : String) : void` Opens the specified URL using the specified method. ### open **Signature:** `open(method : String, url : String, async : boolean, user : String, password : String) : void` Deprecated method. ### open **Signature:** `open(method : String, url : String, user : String, password : String) : void` Opens the specified URL with the in parameter method specified Http method with given credentials [user, password] using HTTP basic authentication. ### send **Signature:** `send() : void` Sends an HTTP request. ### send **Signature:** `send(text : String) : void` This method performs the actual HTTP communication. ### send **Signature:** `send(text : String, encoding : String) : void` This method performs the actual HTTP communication. ### send **Signature:** `send(file : File) : void` This method performs the actual HTTP communication. ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(file : File) : boolean` This method performs the actual HTTP communication. ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(text : String, outFile : File) : boolean` This method performs the actual HTTP communication. ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(text : String, encoding : String, outFile : File) : boolean` This method performs the actual HTTP communication. ### sendBytes **Signature:** `sendBytes(body : Bytes) : void` This method performs the actual HTTP communication. ### sendBytesAndReceiveToFile **Signature:** `sendBytesAndReceiveToFile(body : Bytes, outFile : File) : boolean` This method performs the actual HTTP communication. ### sendMultiPart **Signature:** `sendMultiPart(parts : HTTPRequestPart...) : boolean` Sends a multipart HTTP request. ### setAllowRedirect **Signature:** `setAllowRedirect(allowRedirect : boolean) : void` Sets whether automatic HTTP redirect handling is enabled. ### setHostNameVerification **Signature:** `setHostNameVerification(enable : boolean) : void` Sets whether certificate host name verification is enabled. ### setIdentity **Signature:** `setIdentity(keyRef : KeyRef) : void` Sets the identity (private key) to use when mutual TLS (mTLS) is configured. ### setRequestHeader **Signature:** `setRequestHeader(key : String, value : String) : void` Sets a request header for the next HTTP operation. ### setTimeout **Signature:** `setTimeout(timeoutMillis : Number) : void` Sets the timeout for connections made with this client to the given number of milliseconds. ## Constructor Detail ## Method Detail ## Method Details ### enableCaching **Signature:** `enableCaching(ttl : Number) : void` **Description:** Calling this method enables caching for GET requests. It basically means that a response is cached, and before making a request the HTTP client looks into the cache to determine whether the response is already available. Only responses with a status code of 2xx, with a content length, with a size less than 50k, and which are not intended to be immediately written to a file are cached. The provided parameter defines the TTL (time to live) for the cached content. A value of 0 disables caching. The URL and the username are used as cache keys. The total size of the cacheable content and the number of cached items is limited and automatically managed by the system. Cache control information send by the remote server is ignored. Caching HTTP responses should be done very carefully. It is important to ensure that the response really depends only on the URL and doesn't contain any remote state information or time information which is independent of the URL. It is also important to verify that the application sends exactly the same URL multiple times. **Parameters:** - `ttl`: the TTL for the cached content in secs --- ### getAllowRedirect **Signature:** `getAllowRedirect() : boolean` **Description:** Determines whether redirect handling is enabled. **Returns:** true if redirect handling is enabled, false otherwise. --- ### getAllResponseHeaders **Signature:** `getAllResponseHeaders() : HashMap` **Description:** Returns all response headers as a map containing the name and value of the response header. **Deprecated:** Use getResponseHeaders() instead. **Returns:** a map containing the names and corresponding values of the response headers. --- ### getBytes **Signature:** `getBytes() : Bytes` **Description:** Returns the bytes in the message body for HTTP status codes between 200 and 299. **Returns:** the returned message body as bytes. --- ### getErrorBytes **Signature:** `getErrorBytes() : Bytes` **Description:** Returns the returned message body as bytes for HTTP status code greater or equal to 400. Error messages are not written to the response file. **Returns:** the returned message body as bytes. --- ### getErrorText **Signature:** `getErrorText() : String` **Description:** Returns the returned message body as text for HTTP status code greater or equal to 400. Error messages are not written to the response file. **Returns:** the returned message body as text. --- ### getHostNameVerification **Signature:** `getHostNameVerification() : boolean` **Description:** Determines whether host name verification is enabled. **Returns:** true if verification is enabled, false otherwise --- ### getIdentity **Signature:** `getIdentity() : KeyRef` **Description:** Gets the identity used for mutual TLS (mTLS). **Returns:** Reference to the private key, or null if not configured --- ### getResponseHeader **Signature:** `getResponseHeader(header : String) : String` **Description:** Returns a specific response header from the last HTTP operation. The method returns null if the specific header was not returned. **Parameters:** - `header`: the name of the response header to locate. **Returns:** the value of the specified header or null if the header cannot be found. --- ### getResponseHeaders **Signature:** `getResponseHeaders(name : String) : List` **Description:** Returns all the values of a response header from the last HTTP operation as a list of strings. This reflects the fact that a specific header, e.g. "Set-Cookie", may be set multiple times. In case there is no such header, the method returns an empty list. **Parameters:** - `name`: The name of the response header to locate. **Returns:** The values of the specified header as a list of strings or an empty list if the header cannot be found. --- ### getResponseHeaders **Signature:** `getResponseHeaders() : Map` **Description:** Returns all response headers as a map in which each entry represents an individual header. The key of the entry holds the header name and the entry value holds a list of all header values. **Returns:** A map containing the names and corresponding values of the response headers. --- ### getStatusCode **Signature:** `getStatusCode() : Number` **Description:** Returns the status code of the last HTTP operation. **Returns:** the status code of the last HTTP operation. --- ### getStatusMessage **Signature:** `getStatusMessage() : String` **Description:** Returns the message text of the last HTTP operation. **Returns:** the message text of the last HTTP operation. --- ### getText **Signature:** `getText() : String` **Description:** Returns the returned message body as text for HTTP status codes between 200 and 299. **Returns:** the returned message body as text. --- ### getText **Signature:** `getText(encoding : String) : String` **Description:** Returns the returned message body as text for HTTP status codes between 200 and 299. **Parameters:** - `encoding`: the character encoding to use. **Returns:** String the encoded String. --- ### getTimeout **Signature:** `getTimeout() : Number` **Description:** Returns the timeout for this client, in milliseconds. **Returns:** the timeout in milliseconds --- ### open **Signature:** `open(method : String, url : String) : void` **Description:** Opens the specified URL using the specified method. The following methods are supported: GET, POST, HEAD, PUT, PATCH, OPTIONS, and DELETE **Parameters:** - `method`: the method to use for opening the URL. - `url`: the url to open. --- ### open **Signature:** `open(method : String, url : String, async : boolean, user : String, password : String) : void` **Description:** Deprecated method. **Deprecated:** Use open(String, String, String, String) instead. **Parameters:** - `method`: the method to use for opening the URL. - `url`: the url to open. - `async`: true if asynchronous. - `user`: name of the user. - `password`: password. --- ### open **Signature:** `open(method : String, url : String, user : String, password : String) : void` **Description:** Opens the specified URL with the in parameter method specified Http method with given credentials [user, password] using HTTP basic authentication. The following methods are supported: GET, POST, HEAD, PUT, PATCH, OPTIONS, and DELETE **Parameters:** - `method`: HTTP method - `url`: the url - `user`: name of the user - `password`: password --- ### send **Signature:** `send() : void` **Description:** Sends an HTTP request. --- ### send **Signature:** `send(text : String) : void` **Description:** This method performs the actual HTTP communication. The text is sent as a request body. If the text is null no data will be sent to the HTTP server. **Parameters:** - `text`: text String to be sent as request body. --- ### send **Signature:** `send(text : String, encoding : String) : void` **Description:** This method performs the actual HTTP communication. The text is sent as a request body. If the text is null no data will be sent to the HTTP server. **Parameters:** - `text`: text String to be sent as request body. - `encoding`: character encoding name. --- ### send **Signature:** `send(file : File) : void` **Description:** This method performs the actual HTTP communication. Sends the file to the HTTP server. The file content is sent as a request body and is sent "as-is" (text or binary). **Parameters:** - `file`: File to be sent. --- ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(file : File) : boolean` **Description:** This method performs the actual HTTP communication. If the file is null no data will be sent to the HTTP server. If this method is used with a GET then the file parameter will contain the contents retrieved. When using this method with a PUT/POST then the contents of the file parameter will be sent to the server. **Parameters:** - `file`: local file used to read from or write to, depending on the method used. **Returns:** true if the returned code was a positive status code --- ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(text : String, outFile : File) : boolean` **Description:** This method performs the actual HTTP communication. If the text is null no data will be sent to the HTTP server. **Parameters:** - `text`: text String to be sent. - `outFile`: local file to write to. **Returns:** true if the returned code was a positive status code --- ### sendAndReceiveToFile **Signature:** `sendAndReceiveToFile(text : String, encoding : String, outFile : File) : boolean` **Description:** This method performs the actual HTTP communication. If the text is null no data will be sent to the HTTP server. **Parameters:** - `text`: text String to be sent. - `encoding`: character encoding name. - `outFile`: local file to write to. **Returns:** true if the returned code was a positive status code --- ### sendBytes **Signature:** `sendBytes(body : Bytes) : void` **Description:** This method performs the actual HTTP communication. The bytes are sent as a request body. If the bytes are null no data will be sent to the HTTP server. **Parameters:** - `body`: Bytes to be sent as request body. --- ### sendBytesAndReceiveToFile **Signature:** `sendBytesAndReceiveToFile(body : Bytes, outFile : File) : boolean` **Description:** This method performs the actual HTTP communication. If the body is null no data will be sent to the HTTP server. **Parameters:** - `body`: Bytes to be sent. - `outFile`: local file to write to. **Returns:** true if the returned code was a positive status code **Throws:** - IOException --- ### sendMultiPart **Signature:** `sendMultiPart(parts : HTTPRequestPart...) : boolean` **Description:** Sends a multipart HTTP request. This method should only be called if the connection to the remote URL was opened with a POST or PATCH method. All other methods will result in an exception being thrown. The request is constructed from the passed array of parts. **Parameters:** - `parts`: List of part objects representing either string or file parts. **Returns:** true if the returned code was a positive status code. --- ### setAllowRedirect **Signature:** `setAllowRedirect(allowRedirect : boolean) : void` **Description:** Sets whether automatic HTTP redirect handling is enabled. The default value is true. Set it to false to disable all redirects. **Parameters:** - `allowRedirect`: true or false for enabling or disabling automatic HTTP redirect --- ### setHostNameVerification **Signature:** `setHostNameVerification(enable : boolean) : void` **Description:** Sets whether certificate host name verification is enabled. The default value is true. Set it to false to disable host name verification. **Parameters:** - `enable`: true to enable host name verification or false to disable it. --- ### setIdentity **Signature:** `setIdentity(keyRef : KeyRef) : void` **Description:** Sets the identity (private key) to use when mutual TLS (mTLS) is configured. If this is not set and mTLS is used then the private key will be chosen from the key store based on the host name. If this is set to a reference named "__NONE__" then no private key will be used even if one is requested by the remote server. **Parameters:** - `keyRef`: Reference to the private key --- ### setRequestHeader **Signature:** `setRequestHeader(key : String, value : String) : void` **Description:** Sets a request header for the next HTTP operation. **Parameters:** - `key`: the request header. - `value`: the request headers' value. --- ### setTimeout **Signature:** `setTimeout(timeoutMillis : Number) : void` **Description:** Sets the timeout for connections made with this client to the given number of milliseconds. If the given timeout is less than or equal to zero, the timeout is set to a maximum value of 2 or 15 minutes, depending on the context. This timeout value controls both the "connection timeout" (how long it takes to connect to the remote host) and the "socket timeout" (how long, after connecting, it will wait without any data being read). Therefore, in the worst case scenario, the total time of inactivity could be twice as long as the specified value. The maximum timeout is 15 minutes when the client is used in a job, and 2 minutes otherwise. The default timeout for a new client is the maximum timeout value. This method can be called at any time, and will affect the next connection made with this client. It is not possible to set the timeout for an open connection. You should always set a reasonable timeout (e.g., a few seconds). Allowing connections to run long can result in thread exhaustion. **Parameters:** - `timeoutMillis`: timeout, in milliseconds, up to a maximum of 2 or 15 minutes, depending on the context. --- ``` -------------------------------------------------------------------------------- /docs-site/pages/ConfigurationPage.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react'; import { NavLink } from 'react-router-dom'; import SEO from '../components/SEO'; import BreadcrumbSchema from '../components/BreadcrumbSchema'; import StructuredData from '../components/StructuredData'; import { PageSubtitle, H2, H3 } from '../components/Typography'; import CodeBlock, { InlineCode } from '../components/CodeBlock'; import ConfigHero from '../components/ConfigHero'; import ConfigModeTabs from '../components/ConfigModeTabs'; import ConfigBuilder from '../components/ConfigBuilder'; import { SITE_DATES } from '../constants'; import Collapsible from '../components/Collapsible'; const ConfigurationPage: React.FC = () => { const configurationStructuredData = { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Configuration Guide - SFCC Development MCP Server", "description": "Complete configuration guide for SFCC Development MCP Server. Learn dw.json setup, environment variables, operating modes, authentication, security best practices, and how to enable logs, job logs, system & custom objects, site preferences, and code versions.", "author": { "@type": "Person", "name": "Thomas Theunen" }, "publisher": { "@type": "Person", "name": "Thomas Theunen" }, "datePublished": SITE_DATES.PUBLISHED, "dateModified": SITE_DATES.MODIFIED, "url": "https://sfcc-mcp-dev.rhino-inquisitor.com/configuration/", "about": [ { "@type": "SoftwareApplication", "name": "SFCC Development MCP Server", "applicationCategory": "DeveloperApplication", "operatingSystem": "Node.js", "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD", "availability": "https://schema.org/InStock" } } ] }; return ( <div className="max-w-6xl mx-auto px-6 py-8"> <SEO title="Configuration Guide" description="Complete configuration guide for SFCC Development MCP Server. Learn dw.json setup, environment variables, operating modes, authentication, security best practices, and how to enable logs, job logs, system & custom objects, site preferences, and code versions." keywords="SFCC MCP configuration, dw.json setup, SFCC authentication, OCAPI credentials, WebDAV configuration, Commerce Cloud API setup, SFCC development environment" canonical="/configuration/" ogType="article" /> <BreadcrumbSchema items={[ { name: "Home", url: "/" }, { name: "Configuration", url: "/configuration/" } ]} /> <StructuredData structuredData={configurationStructuredData} /> <ConfigHero /> <div className="mb-16 bg-gradient-to-r from-blue-50 via-indigo-50 to-purple-50 rounded-2xl p-8 shadow-xl border border-blue-100"> <div className="text-center mb-10"> <H2 id="quick-start" className="text-3xl font-bold mb-3">🚀 Quick Start</H2> <p className="text-gray-700 max-w-3xl mx-auto text-lg">Pick a mode, generate <InlineCode>dw.json</InlineCode>, run the server. Minimal friction, fast feedback.</p> </div> <ConfigModeTabs /> </div> <div className="mb-20 bg-gradient-to-r from-emerald-50 via-teal-50 to-cyan-50 rounded-2xl p-8 shadow-xl border border-emerald-100"> <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6 mb-6"> <div> <H2 id="interactive-config" className="text-2xl font-bold mb-2">🧪 Interactive dw.json Builder</H2> <p className="text-sm text-gray-600 max-w-xl">Start minimal (only <InlineCode>hostname</InlineCode> + <InlineCode>username/password</InlineCode> for runtime & job logs). Add Data API credentials when you need system & custom objects, site preferences or code version tooling.</p> </div> <div className="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-xs text-emerald-800 max-w-sm"> <p className="font-semibold mb-1">Tips</p> <ul className="list-disc pl-4 space-y-1"> <li>Keep separate <InlineCode>dw.dev.json</InlineCode> / <InlineCode>dw.qa.json</InlineCode></li> <li>Use env vars to override secrets in CI</li> <li>Remove unused OAuth fields to stay minimal</li> </ul> </div> </div> <ConfigBuilder /> </div> <div className="space-y-20 mb-20"> <div className="space-y-16"> <section id="data-api" className="scroll-mt-24"> <div className="mb-4 space-y-2"> <span className="inline-block bg-yellow-100 text-yellow-700 text-[11px] font-semibold px-3 py-1 rounded-full tracking-wide uppercase">Optional – Enables Data API Tools</span> <h2 id="data-api-configuration" className="text-2xl font-bold text-gray-900">🔧 Data API Configuration</h2> </div> <p className="text-gray-600 mb-6 text-sm">Required for system object definitions, custom object attribute searches, site preferences & code version tools. Not required for runtime logs or job log analysis (basic auth only). Skip entirely if you only need docs + log visibility.</p> <div className="space-y-6"> <Collapsible id="api-client" title="Step 1: Create API Client (Account Manager)" intent="info" defaultOpen> <ol className="list-decimal list-inside space-y-1"> <li>Login to <strong>Account Manager</strong> (not Business Manager)</li> <li>Navigate to <strong>API Client</strong> → Add API Client</li> <li>Set name (e.g. <InlineCode>SFCC Dev MCP</InlineCode>) & generate password</li> <li>Select <strong>SFCC</strong> scope and grant required roles</li> </ol> </Collapsible> <Collapsible id="bm-settings" title="Step 2: Business Manager Data API Settings" intent="warn"> <ol className="list-decimal list-inside space-y-1 mb-4"> <li>Business Manager → Administration → Site Development → Open Commerce API Settings</li> <li>Open the <strong>Data API</strong> tab</li> <li>Add client with required resources (below)</li> </ol> <CodeBlock language="json" code={`{\n \"_v\": \"23.2\",\n \"clients\": [{\n \"client_id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n \"resources\": [\n { \"resource_id\": \"/system_object_definitions\", \"methods\": [\"get\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/system_object_definitions/*\", \"methods\": [\"get\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/system_object_definition_search\", \"methods\": [\"post\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/system_object_definitions/*/attribute_definition_search\", \"methods\": [\"post\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/system_object_definitions/*/attribute_group_search\", \"methods\": [\"post\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/custom_object_definitions/*/attribute_definition_search\", \"methods\": [\"post\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/site_preferences/preference_groups/*/*/preference_search\", \"methods\": [\"post\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/code_versions\", \"methods\": [\"get\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" },\n { \"resource_id\": \"/code_versions/*\", \"methods\": [\"get\", \"patch\"], \"read_attributes\": \"(**)\", \"write_attributes\": \"(**)\" }\n ]\n }]\n}\n`} /> <ul className="text-xs text-green-700 bg-green-50 border border-green-200 rounded-lg p-4 mt-4 space-y-1"> <li><strong>Client ID</strong> must match your credentials</li> <li>Allow <InlineCode>get</InlineCode>/<InlineCode>post</InlineCode> for search + retrieval (and <InlineCode>patch</InlineCode> for code version activation)</li> <li><InlineCode>(**)</InlineCode> attributes needed for broad introspection</li> </ul> </Collapsible> <Collapsible id="update-config" title="Step 3: Update dw.json" intent="plain"> <CodeBlock language="json" code={`{\n \"hostname\": \"your-instance.sandbox.us01.dx.commercecloud.salesforce.com\",\n \"username\": \"your-username\",\n \"password\": \"your-password\",\n \"client-id\": \"your-ocapi-client-id\",\n \"client-secret\": \"your-ocapi-client-secret\"\n}`} /> <p className="text-[11px] text-gray-500 -mt-3">Add <InlineCode>client-id</InlineCode>/<InlineCode>client-secret</InlineCode> only when you need Data API tooling (system & custom objects, site preferences, code versions). Omit for docs + log visibility only.</p> </Collapsible> <Collapsible id="data-api-troubleshooting" title="Troubleshooting Data API Access" intent="danger"> <ul className="list-disc pl-5 text-sm space-y-2"> <li><strong>403</strong>: Missing scope/role – verify Account Manager roles + BM resource mapping</li> <li><strong>401</strong>: Credential mismatch – regenerate client secret</li> <li><strong>Missing resource</strong>: Pattern mismatch – copy exact resource IDs above</li> </ul> <CodeBlock language="bash" code={`npx sfcc-dev-mcp --dw-json /Users/username/sfcc-project/dw.json --debug true`} /> </Collapsible> </div> <p className="text-[11px] text-gray-500 mt-2">Total surface: 36+ specialized tools spanning documentation, best practices, SFRA, cartridge generation, runtime logs, job logs, system & custom objects, site preferences, and code versions.</p> </section> <section id="security" className="scroll-mt-24 space-y-6"> <div className="mb-4 space-y-2"> <span className="inline-block bg-blue-100 text-blue-700 text-[11px] font-semibold px-3 py-1 rounded-full tracking-wide uppercase">Protect Credentials</span> <h2 id="security-basics" className="text-2xl font-bold text-gray-900">🔒 Security Basics</h2> </div> <div className="grid md:grid-cols-2 gap-6"> <div className="rounded-xl border border-gray-200 bg-white p-5"> <h4 className="font-semibold text-sm mb-3">.gitignore</h4> <CodeBlock language="bash" code={`echo 'dw.json' >> .gitignore\necho '*.dw.json' >> .gitignore`} /> </div> <div className="rounded-xl border border-gray-200 bg-white p-5"> <h4 className="font-semibold text-sm mb-3">Environment Overrides</h4> <CodeBlock language="bash" code={`export SFCC_CLIENT_SECRET=\"your-secret\"\nexport SFCC_PASSWORD=\"your-password\"\n\nnpx sfcc-dev-mcp --dw-json /Users/username/sfcc-project/dw.json`} /> </div> <div className="rounded-xl border border-gray-200 bg-white p-5"> <h4 className="font-semibold text-sm mb-3">Permissions</h4> <CodeBlock language="bash" code={`chmod 600 dw.json\nls -la dw.json`} /> </div> <div className="rounded-xl border border-gray-200 bg-white p-5"> <h4 className="font-semibold text-sm mb-3">Debug Mode</h4> <CodeBlock language="bash" code={`npx sfcc-dev-mcp --dw-json /Users/username/sfcc-project/dw.json --debug true`} /> </div> </div> </section> <section id="reference" className="scroll-mt-24 space-y-8"> <div className="mb-4 space-y-2"> <span className="inline-block bg-purple-100 text-purple-700 text-[11px] font-semibold px-3 py-1 rounded-full tracking-wide uppercase">Reference</span> <h2 id="key-reference-tables" className="text-2xl font-bold text-gray-900">📋 Key Reference Tables</h2> </div> <div> <H3 id="field-reference" className="text-lg font-semibold mb-3">Supported dw.json Fields</H3> <div className="overflow-x-auto text-sm"> <table className="min-w-full bg-white border border-gray-200 rounded-lg"> <thead className="bg-gray-50"> <tr> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Field</th> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Required For</th> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Notes</th> </tr> </thead> <tbody className="divide-y divide-gray-200"> <tr> <td className="px-3 py-2 font-mono text-xs">hostname</td> <td className="px-3 py-2 text-xs">All authenticated tools</td> <td className="px-3 py-2 text-xs">Sandbox domain</td> </tr> <tr> <td className="px-3 py-2 font-mono text-xs">username/password</td> <td className="px-3 py-2 text-xs">Logs & Job Logs (WebDAV)</td> <td className="px-3 py-2 text-xs">WebDAV auth (runtime + job log files)</td> </tr> <tr> <td className="px-3 py-2 font-mono text-xs">client-id/secret</td> <td className="px-3 py-2 text-xs">System & custom objects, site prefs, code versions</td> <td className="px-3 py-2 text-xs">OCAPI Data API + code version management</td> </tr> <tr> <td className="px-3 py-2 font-mono text-xs">code-version</td> <td className="px-3 py-2 text-xs">Code version operations</td> <td className="px-3 py-2 text-xs">Optional</td> </tr> <tr> <td className="px-3 py-2 font-mono text-xs">site-id</td> <td className="px-3 py-2 text-xs">Site-specific actions</td> <td className="px-3 py-2 text-xs">Optional</td> </tr> </tbody> </table> </div> </div> <div> <H3 id="tool-availability" className="text-lg font-semibold mb-3">Tool Availability by Mode</H3> <div className="overflow-x-auto text-sm"> <table className="min-w-full bg-white border border-gray-200 rounded-lg"> <thead className="bg-gray-50"> <tr> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Category</th> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Docs Only</th> <th className="px-3 py-2 text-left font-semibold text-gray-900 border-b">Full Mode</th> </tr> </thead> <tbody className="divide-y divide-gray-200"> <tr> <td className="px-3 py-2 text-xs font-medium">Documentation</td> <td className="px-3 py-2 text-xs">✔</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">Best Practices</td> <td className="px-3 py-2 text-xs">✔</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">SFRA Docs</td> <td className="px-3 py-2 text-xs">✔</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">Cartridge Generation</td> <td className="px-3 py-2 text-xs">✔</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">Log Analysis (runtime)</td> <td className="px-3 py-2 text-xs">—</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">Job Logs</td> <td className="px-3 py-2 text-xs">—</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">System & Custom Objects / Site Prefs</td> <td className="px-3 py-2 text-xs">—</td> <td className="px-3 py-2 text-xs">✔</td> </tr> <tr> <td className="px-3 py-2 text-xs font-medium">Code Versions</td> <td className="px-3 py-2 text-xs">—</td> <td className="px-3 py-2 text-xs">✔</td> </tr> </tbody> </table> </div> </div> </section> <section id="troubleshooting" className="scroll-mt-24 space-y-6"> <div className="mb-4 space-y-2"> <span className="inline-block bg-red-100 text-red-700 text-[11px] font-semibold px-3 py-1 rounded-full tracking-wide uppercase">Common Issues</span> <h2 id="troubleshooting" className="text-2xl font-bold text-gray-900">🐛 Troubleshooting</h2> </div> <div className="grid md:grid-cols-3 gap-5"> <div className="bg-white border border-gray-200 rounded-xl p-4"> <h4 className="font-semibold text-sm mb-2">Auth Failed</h4> <ul className="text-xs text-gray-600 space-y-1 list-disc pl-4"> <li>Verify hostname format</li> <li>Check username/password</li> <li>Match client-id/secret</li> <li>Run with <InlineCode>--debug</InlineCode></li> </ul> </div> <div className="bg-white border border-gray-200 rounded-xl p-4"> <h4 className="font-semibold text-sm mb-2">dw.json Missing</h4> <ul className="text-xs text-gray-600 space-y-1 list-disc pl-4"> <li>Add <InlineCode>--dw-json /Users/username/sfcc-project/dw.json</InlineCode></li> <li>Create file in current dir</li> <li>Use env vars instead</li> <li>Check permissions (600)</li> </ul> </div> <div className="bg-white border border-gray-200 rounded-xl p-4"> <h4 className="font-semibold text-sm mb-2">System Object Denied</h4> <ul className="text-xs text-gray-600 space-y-1 list-disc pl-4"> <li>Complete Data API config</li> <li>Verify resource IDs</li> <li>Ensure (**) attributes</li> <li>Check client roles</li> </ul> </div> </div> </section> </div> </div> <div className="mb-24"> <div className="text-center mb-8"> <h2 id="next-steps" className="text-2xl font-bold text-gray-900 mb-2">🔗 Next Steps</h2> <PageSubtitle className="text-base text-gray-600">Pick your next path—feature surface or concrete tool list.</PageSubtitle> </div> <div className="flex flex-col sm:flex-row gap-4 justify-center"> <NavLink to="/features/" 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"> Features Overview <span className="ml-2 group-hover:translate-x-1 inline-block transition-transform">→</span> </NavLink> <NavLink to="/tools/" 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"> Available Tools </NavLink> </div> </div> </div> ); }; export default ConfigurationPage; ``` -------------------------------------------------------------------------------- /docs/dw_util/Calendar.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.util # Class Calendar ## Inheritance Hierarchy - Object - dw.util.Calendar ## Description 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. ## Constants ### AM_PM **Type:** Number = 9 Indicates whether the HOUR is before or after noon. ### APRIL **Type:** Number = 3 Value for the month of year field representing April. ### AUGUST **Type:** Number = 7 Value for the month of year field representing August. ### DATE **Type:** Number = 5 Represents a date. ### DAY_OF_MONTH **Type:** Number = 5 Represents a day of the month. ### DAY_OF_WEEK **Type:** Number = 7 Represents a day of the week. ### DAY_OF_WEEK_IN_MONTH **Type:** Number = 8 Represents a day of the week in a month. ### DAY_OF_YEAR **Type:** Number = 6 Represents a day of the year. ### DECEMBER **Type:** Number = 11 Value for the month of year field representing December. ### DST_OFFSET **Type:** Number = 16 Indicates the daylight savings offset in milliseconds. ### ERA **Type:** Number = 0 Indicates the era such as 'AD' or 'BC' in the Julian calendar. ### FEBRUARY **Type:** Number = 1 Value for the month of year field representing February. ### FRIDAY **Type:** Number = 6 Value for the day of the week field representing Friday. ### HOUR **Type:** Number = 10 Represents an hour. ### HOUR_OF_DAY **Type:** Number = 11 Represents an hour of the day. ### INPUT_DATE_PATTERN **Type:** Number = 3 The input date pattern, for instance MM/dd/yyyy ### INPUT_DATE_TIME_PATTERN **Type:** Number = 5 The input date time pattern, for instance MM/dd/yyyy h:mm a ### INPUT_TIME_PATTERN **Type:** Number = 4 The input time pattern, for instance h:mm a ### JANUARY **Type:** Number = 0 Value for the month of year field representing January. ### JULY **Type:** Number = 6 Value for the month of year field representing July. ### JUNE **Type:** Number = 5 Value for the month of year field representing June. ### LONG_DATE_PATTERN **Type:** Number = 1 The long date pattern, for instance MMM/d/yyyy ### MARCH **Type:** Number = 2 Value for the month of year field representing March. ### MAY **Type:** Number = 4 Value for the month of year field representing May. ### MILLISECOND **Type:** Number = 14 Represents a millisecond. ### MINUTE **Type:** Number = 12 Represents a minute. ### MONDAY **Type:** Number = 2 Value for the day of the week field representing Monday. ### MONTH **Type:** Number = 2 Represents a month where the first month of the year is 0. ### NOVEMBER **Type:** Number = 10 Value for the month of year field representing November. ### OCTOBER **Type:** Number = 9 Value for the month of year field representing October. ### SATURDAY **Type:** Number = 7 Value for the day of the week field representing Saturday. ### SECOND **Type:** Number = 13 Represents a second. ### SEPTEMBER **Type:** Number = 8 Value for the month of year field representing September. ### SHORT_DATE_PATTERN **Type:** Number = 0 The short date pattern, for instance M/d/yy ### SUNDAY **Type:** Number = 1 Value for the day of the week field representing Sunday. ### THURSDAY **Type:** Number = 5 Value for the day of the week field representing Thursday. ### TIME_PATTERN **Type:** Number = 2 The time pattern, for instance h:mm:ss a ### TUESDAY **Type:** Number = 3 Value for the day of the week field representing Tuesday. ### WEDNESDAY **Type:** Number = 4 Value for the day of the week field representing Wednesday. ### WEEK_OF_MONTH **Type:** Number = 4 Represents a week of the month. ### WEEK_OF_YEAR **Type:** Number = 3 Represents a week in the year. ### YEAR **Type:** Number = 1 Represents a year. ### ZONE_OFFSET **Type:** Number = 15 Indicates the raw offset from GMT in milliseconds. ## Properties ### firstDayOfWeek **Type:** Number 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. ### time **Type:** Date 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. ### timeZone **Type:** String The current time zone of this calendar. ## Constructor Summary Calendar() Creates a new Calendar object that is set to the current time. Calendar(date : Date) Creates a new Calendar object for the given Date object. ## Method Summary ### add **Signature:** `add(field : Number, value : Number) : void` Adds or subtracts the specified amount of time to the given calendar field, based on the calendar's rules. ### after **Signature:** `after(obj : Object) : boolean` Indicates if this Calendar represents a time after the time represented by the specified Object. ### before **Signature:** `before(obj : Object) : boolean` Indicates if this Calendar represents a time before the time represented by the specified Object. ### clear **Signature:** `clear() : void` Sets all the calendar field values and the time value (millisecond offset from the Epoch) of this Calendar undefined. ### clear **Signature:** `clear(field : Number) : void` Sets the given calendar field value and the time value (millisecond offset from the Epoch) of this Calendar undefined. ### compareTo **Signature:** `compareTo(anotherCalendar : Calendar) : Number` Compares the time values (millisecond offsets from the Epoch) represented by two Calendar objects. ### equals **Signature:** `equals(other : Object) : boolean` Compares two calendar values whether they are equivalent. ### get **Signature:** `get(field : Number) : Number` Returns the value of the given calendar field. ### getActualMaximum **Signature:** `getActualMaximum(field : Number) : Number` Returns the maximum value that the specified calendar field could have. ### getActualMinimum **Signature:** `getActualMinimum(field : Number) : Number` Returns the minimum value that the specified calendar field could have. ### getFirstDayOfWeek **Signature:** `getFirstDayOfWeek() : Number` Returns the first day of the week base on locale context. ### getMaximum **Signature:** `getMaximum(field : Number) : Number` Returns the maximum value for the given calendar field. ### getMinimum **Signature:** `getMinimum(field : Number) : Number` Returns the minimum value for the given calendar field. ### getTime **Signature:** `getTime() : Date` Returns the current time stamp of this calendar. ### getTimeZone **Signature:** `getTimeZone() : String` Returns the current time zone of this calendar. ### hashCode **Signature:** `hashCode() : Number` Calculates the hash code for a calendar; ### isLeapYear **Signature:** `isLeapYear(year : Number) : boolean` Indicates if the specified year is a leap year. ### isSameDay **Signature:** `isSameDay(other : Calendar) : boolean` Checks, whether two calendar dates fall on the same day. ### isSameDayByTimestamp **Signature:** `isSameDayByTimestamp(other : Calendar) : boolean` Checks, whether two calendar dates fall on the same day. ### isSet **Signature:** `isSet(field : Number) : boolean` Indicates if the field is set. ### parseByFormat **Signature:** `parseByFormat(timeString : String, format : String) : void` Parses the string according to the date and time format pattern and set the time at this calendar object. ### parseByLocale **Signature:** `parseByLocale(timeString : String, locale : String, pattern : Number) : void` Parses the string according the date format pattern of the given locale. ### roll **Signature:** `roll(field : Number, up : boolean) : void` Rolls the specified field up or down one value. ### roll **Signature:** `roll(field : Number, amount : Number) : void` Rolls the specified field using the specified value. ### set **Signature:** `set(field : Number, value : Number) : void` Sets the given calendar field to the given value. ### set **Signature:** `set(year : Number, month : Number, date : Number) : void` Sets the values for the calendar fields YEAR, MONTH, and DAY_OF_MONTH. ### set **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number) : void` Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, and MINUTE. ### set **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number, second : Number) : void` Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE and SECOND. ### setFirstDayOfWeek **Signature:** `setFirstDayOfWeek(value : Number) : void` Sets what the first day of the week is. ### setTime **Signature:** `setTime(date : Date) : void` 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. ### setTimeZone **Signature:** `setTimeZone(timeZone : String) : void` 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. ## Constructor Detail ## Method Detail ## Method Details ### add **Signature:** `add(field : Number, value : Number) : void` **Description:** Adds or subtracts the specified amount of time to the given calendar field, based on the calendar's rules. **Parameters:** - `field`: the calendar field. - `value`: the amount of date or time to be added to the field --- ### after **Signature:** `after(obj : Object) : boolean` **Description:** Indicates if this Calendar represents a time after the time represented by the specified Object. **Parameters:** - `obj`: the object to test. **Returns:** true if this Calendar represents a time after the time represented by the specified Object, false otherwise. --- ### before **Signature:** `before(obj : Object) : boolean` **Description:** Indicates if this Calendar represents a time before the time represented by the specified Object. **Parameters:** - `obj`: the object to test. **Returns:** true if this Calendar represents a time before the time represented by the specified Object, false otherwise. --- ### clear **Signature:** `clear() : void` **Description:** Sets all the calendar field values and the time value (millisecond offset from the Epoch) of this Calendar undefined. --- ### clear **Signature:** `clear(field : Number) : void` **Description:** Sets the given calendar field value and the time value (millisecond offset from the Epoch) of this Calendar undefined. **Parameters:** - `field`: the calendar field to be cleared. --- ### compareTo **Signature:** `compareTo(anotherCalendar : Calendar) : Number` **Description:** Compares the time values (millisecond offsets from the Epoch) represented by two Calendar objects. **Parameters:** - `anotherCalendar`: the Calendar to be compared. **Returns:** 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. --- ### equals **Signature:** `equals(other : Object) : boolean` **Description:** Compares two calendar values whether they are equivalent. **Parameters:** - `other`: the object to compare against this calendar. --- ### get **Signature:** `get(field : Number) : Number` **Description:** Returns the value of the given calendar field. **Parameters:** - `field`: the calendar field to retrieve. **Returns:** the value for the given calendar field. --- ### getActualMaximum **Signature:** `getActualMaximum(field : Number) : Number` **Description:** Returns the maximum value that the specified calendar field could have. **Parameters:** - `field`: the calendar field. **Returns:** the maximum value that the specified calendar field could have. --- ### getActualMinimum **Signature:** `getActualMinimum(field : Number) : Number` **Description:** Returns the minimum value that the specified calendar field could have. **Parameters:** - `field`: the calendar field. **Returns:** the minimum value that the specified calendar field could have. --- ### getFirstDayOfWeek **Signature:** `getFirstDayOfWeek() : Number` **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. **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. --- ### getMaximum **Signature:** `getMaximum(field : Number) : Number` **Description:** Returns the maximum value for the given calendar field. **Parameters:** - `field`: the calendar field. **Returns:** the maximum value for the given calendar field. --- ### getMinimum **Signature:** `getMinimum(field : Number) : Number` **Description:** Returns the minimum value for the given calendar field. **Parameters:** - `field`: the calendar field. **Returns:** the minimum value for the given calendar field. --- ### getTime **Signature:** `getTime() : Date` **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. **Returns:** the current time stamp of this calendar as a Date. --- ### getTimeZone **Signature:** `getTimeZone() : String` **Description:** Returns the current time zone of this calendar. **Returns:** the current time zone of this calendar. --- ### hashCode **Signature:** `hashCode() : Number` **Description:** Calculates the hash code for a calendar; --- ### isLeapYear **Signature:** `isLeapYear(year : Number) : boolean` **Description:** Indicates if the specified year is a leap year. **Parameters:** - `year`: the year to test. **Returns:** true if the specified year is a leap year. --- ### isSameDay **Signature:** `isSameDay(other : Calendar) : boolean` **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. **Parameters:** - `other`: the calendar to compare against this calendar. --- ### isSameDayByTimestamp **Signature:** `isSameDayByTimestamp(other : Calendar) : boolean` **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. **Parameters:** - `other`: the calendar to compare against this calendar. --- ### isSet **Signature:** `isSet(field : Number) : boolean` **Description:** Indicates if the field is set. **Parameters:** - `field`: the field to test. **Returns:** true if the field is set, false otherwise. --- ### parseByFormat **Signature:** `parseByFormat(timeString : String, format : String) : void` **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. **Parameters:** - `timeString`: the time string to parsed - `format`: the time format string --- ### parseByLocale **Signature:** `parseByLocale(timeString : String, locale : String, pattern : Number) : void` **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. **Parameters:** - `timeString`: the time string to parsed - `locale`: the locale id, which defines the date format pattern - `pattern`: the pattern is one of calendar pattern e.g. SHORT_DATE_PATTERN as defined in the regional settings for the locale --- ### roll **Signature:** `roll(field : Number, up : boolean) : void` **Description:** Rolls the specified field up or down one value. **Parameters:** - `field`: the field to roll. - `up`: if true rolls the field up, if false rolls the field down. --- ### roll **Signature:** `roll(field : Number, amount : Number) : void` **Description:** Rolls the specified field using the specified value. **Parameters:** - `field`: the field to roll. - `amount`: the amount to roll the field. --- ### set **Signature:** `set(field : Number, value : Number) : void` **Description:** Sets the given calendar field to the given value. **Parameters:** - `field`: the calendar field to set. - `value`: the value to set in the field. --- ### set **Signature:** `set(year : Number, month : Number, date : Number) : void` **Description:** Sets the values for the calendar fields YEAR, MONTH, and DAY_OF_MONTH. **Parameters:** - `year`: the value for year. - `month`: the value for month. - `date`: the value for date. --- ### set **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number) : void` **Description:** Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, and MINUTE. **Parameters:** - `year`: the value for year. - `month`: the value for month. - `date`: the value for date. - `hourOfDay`: the value for hour of day. - `minute`: the value for minute. --- ### set **Signature:** `set(year : Number, month : Number, date : Number, hourOfDay : Number, minute : Number, second : Number) : void` **Description:** Sets the values for the calendar fields YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE and SECOND. **Parameters:** - `year`: the value for year. - `month`: the value for month. - `date`: the value for date. - `hourOfDay`: the value for hour of day. - `minute`: the value for minute. - `second`: the value for second. --- ### setFirstDayOfWeek **Signature:** `setFirstDayOfWeek(value : Number) : void` **Description:** Sets what the first day of the week is. **Parameters:** - `value`: the day to set as the first day of the week. --- ### setTime **Signature:** `setTime(date : Date) : void` **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. **Parameters:** - `date`: the current time stamp of this calendar. --- ### setTimeZone **Signature:** `setTimeZone(timeZone : String) : void` **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. **Parameters:** - `timeZone`: the current time zone value to set. --- ``` -------------------------------------------------------------------------------- /docs/dw_system/Request.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.system # Class Request ## Inheritance Hierarchy - Object - dw.system.Request ## Description 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. ## Properties ### clientId **Type:** String (Read Only) 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. ### custom **Type:** CustomAttributes (Read Only) All of the custom attributes associated with the request. The attributes are stored for the life time of the request. ### geolocation **Type:** Geolocation 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. ### httpCookies **Type:** Cookies (Read Only) 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. ### httpHeaders **Type:** Map (Read Only) A Map containing all HTTP header values. ### httpHost **Type:** String (Read Only) The host name or null if there is no host name. ### httpLocale **Type:** String (Read Only) The locale or null if there is no associated locale. ### httpMethod **Type:** String (Read Only) The name of the HTTP method with which this request was made, for example, GET, POST, or PUT. ### httpParameterMap **Type:** HttpParameterMap (Read Only) The parameter map that contains the HTTP parameters for the current request. ### httpParameters **Type:** Map (Read Only) 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. ### httpPath **Type:** String (Read Only) The path. ### httpProtocol **Type:** String (Read Only) 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. ### httpQueryString **Type:** String (Read Only) The query string or null if there is no query string. ### httpReferer **Type:** String (Read Only) The referer or null if there is no referer. ### httpRemoteAddress **Type:** String (Read Only) The remote address or null if no remote address is found. ### httpRequest **Type:** boolean (Read Only) Identifies if this request is an HTTP request. The method returns true, if the current processing is related to a HTTP request. ### httpSecure **Type:** boolean (Read Only) 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. ### httpURL **Type:** URL (Read Only) The complete URL of the request which was received at the server. This URL does not include SEO optimizations. ### httpUserAgent **Type:** String (Read Only) The HTTP user agent or null if there is no user agent. ### includeRequest **Type:** boolean (Read Only) Returns true if the request represents a request for a remote include, false if it is a top-level request. ### locale **Type:** String 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. ### ocapiVersion **Type:** String (Read Only) The OCAPI version of the current request. If this is not an OCAPI request, 'null' is returned. ### pageMetaData **Type:** PageMetaData (Read Only) The page meta data that are associated with the current request. ### requestID **Type:** String (Read Only) The unique identifier of the current request. The unique id is helpful for debugging purpose, e.g. relate debug messages to a particular request. ### SCAPI **Type:** boolean (Read Only) Returns whether the request originated in SCAPI. ### SCAPIPathParameters **Type:** Map (Read Only) 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. ### SCAPIPathPattern **Type:** String (Read Only) 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} ### session **Type:** Session (Read Only) The session associated with this request. ### triggeredForm **Type:** Form (Read Only) The form that was submitted by the client if the request represents a form submission. ### triggeredFormAction **Type:** FormAction (Read Only) The form action that was triggered by the client if the request represents a form submission. ## Constructor Summary ## Method Summary ### addHttpCookie **Signature:** `addHttpCookie(cookie : Cookie) : void` Adds the specified cookie to the outgoing response. ### getClientId **Signature:** `getClientId() : String` Returns the client id of the current SCAPI or OCAPI request. ### getCustom **Signature:** `getCustom() : CustomAttributes` Returns all of the custom attributes associated with the request. ### getGeolocation **Signature:** `getGeolocation() : Geolocation` Returns the physical location for the current request, if available. ### getHttpCookies **Signature:** `getHttpCookies() : Cookies` Returns the Cookies object, which can be used to read cookies sent by the client. ### getHttpHeaders **Signature:** `getHttpHeaders() : Map` Returns a Map containing all HTTP header values. ### getHttpHost **Signature:** `getHttpHost() : String` Returns the host name or null if there is no host name. ### getHttpLocale **Signature:** `getHttpLocale() : String` Returns the locale or null if there is no associated locale. ### getHttpMethod **Signature:** `getHttpMethod() : String` Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. ### getHttpParameterMap **Signature:** `getHttpParameterMap() : HttpParameterMap` Returns the parameter map that contains the HTTP parameters for the current request. ### getHttpParameters **Signature:** `getHttpParameters() : Map` Returns a Map containing the raw HTTP parameters sent to the server. ### getHttpPath **Signature:** `getHttpPath() : String` Returns the path. ### getHttpProtocol **Signature:** `getHttpProtocol() : String` Returns the HTTP protocol used for this request. ### getHttpQueryString **Signature:** `getHttpQueryString() : String` Returns the query string or null if there is no query string. ### getHttpReferer **Signature:** `getHttpReferer() : String` Returns the referer or null if there is no referer. ### getHttpRemoteAddress **Signature:** `getHttpRemoteAddress() : String` Returns the remote address or null if no remote address is found. ### getHttpURL **Signature:** `getHttpURL() : URL` Returns the complete URL of the request which was received at the server. ### getHttpUserAgent **Signature:** `getHttpUserAgent() : String` Returns the HTTP user agent or null if there is no user agent. ### getLocale **Signature:** `getLocale() : String` Returns the locale of the current request. ### getOcapiVersion **Signature:** `getOcapiVersion() : String` Returns the OCAPI version of the current request. ### getPageMetaData **Signature:** `getPageMetaData() : PageMetaData` Returns the page meta data that are associated with the current request. ### getRequestID **Signature:** `getRequestID() : String` Returns the unique identifier of the current request. ### getSCAPIPathParameters **Signature:** `getSCAPIPathParameters() : Map` 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. ### getSCAPIPathPattern **Signature:** `getSCAPIPathPattern() : String` 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. ### getSession **Signature:** `getSession() : Session` Returns the session associated with this request. ### getTriggeredForm **Signature:** `getTriggeredForm() : Form` Returns the form that was submitted by the client if the request represents a form submission. ### getTriggeredFormAction **Signature:** `getTriggeredFormAction() : FormAction` Returns the form action that was triggered by the client if the request represents a form submission. ### isHttpRequest **Signature:** `isHttpRequest() : boolean` Identifies if this request is an HTTP request. ### isHttpSecure **Signature:** `isHttpSecure() : boolean` Returns whether the HTTP communication is secure, which basically means that the communication happens via https. ### isIncludeRequest **Signature:** `isIncludeRequest() : boolean` Returns true if the request represents a request for a remote include, false if it is a top-level request. ### isSCAPI **Signature:** `isSCAPI() : boolean` Returns whether the request originated in SCAPI. ### setGeolocation **Signature:** `setGeolocation(geoLocation : Geolocation) : void` Sets the physical location for the current request and remembers the new value for the duration of the user session. ### setLocale **Signature:** `setLocale(localeID : String) : boolean` Sets the given locale for the request. ## Method Detail ## Method Details ### addHttpCookie **Signature:** `addHttpCookie(cookie : Cookie) : void` **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); **Deprecated:** Use Response.addHttpCookie(Cookie) instead. **Parameters:** - `cookie`: a Cookie object --- ### getClientId **Signature:** `getClientId() : String` **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. **Returns:** a client id or alias in case of an OCAPI request, otherwise null. --- ### getCustom **Signature:** `getCustom() : CustomAttributes` **Description:** Returns all of the custom attributes associated with the request. The attributes are stored for the life time of the request. **Returns:** all of the custom attributes associated with the request. --- ### getGeolocation **Signature:** `getGeolocation() : Geolocation` **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. **Returns:** The geolocation of the current request, or null if this is not available. --- ### getHttpCookies **Signature:** `getHttpCookies() : Cookies` **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. **Returns:** Cookies object or null if this is not an HTTP request --- ### getHttpHeaders **Signature:** `getHttpHeaders() : Map` **Description:** Returns a Map containing all HTTP header values. **Returns:** a Map containing all HTTP header values. --- ### getHttpHost **Signature:** `getHttpHost() : String` **Description:** Returns the host name or null if there is no host name. **Returns:** the host name or null if there is no host name. --- ### getHttpLocale **Signature:** `getHttpLocale() : String` **Description:** Returns the locale or null if there is no associated locale. **Returns:** the locale or null. --- ### getHttpMethod **Signature:** `getHttpMethod() : String` **Description:** Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. **Returns:** the HTTP method --- ### getHttpParameterMap **Signature:** `getHttpParameterMap() : HttpParameterMap` **Description:** Returns the parameter map that contains the HTTP parameters for the current request. **Returns:** the HTTP parameter map --- ### getHttpParameters **Signature:** `getHttpParameters() : Map` **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. **Returns:** a Map containing all the raw HTTP parameters send to the server. --- ### getHttpPath **Signature:** `getHttpPath() : String` **Description:** Returns the path. **Returns:** the path or null. --- ### getHttpProtocol **Signature:** `getHttpProtocol() : String` **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. **Returns:** "http", "https" or null --- ### getHttpQueryString **Signature:** `getHttpQueryString() : String` **Description:** Returns the query string or null if there is no query string. **Returns:** the query string or null. --- ### getHttpReferer **Signature:** `getHttpReferer() : String` **Description:** Returns the referer or null if there is no referer. **Returns:** the referer or null if there is no referer. --- ### getHttpRemoteAddress **Signature:** `getHttpRemoteAddress() : String` **Description:** Returns the remote address or null if no remote address is found. **Returns:** the remote address or null if no remote address is found. --- ### getHttpURL **Signature:** `getHttpURL() : URL` **Description:** Returns the complete URL of the request which was received at the server. This URL does not include SEO optimizations. **Returns:** the URL as URL object --- ### getHttpUserAgent **Signature:** `getHttpUserAgent() : String` **Description:** Returns the HTTP user agent or null if there is no user agent. **Returns:** the HTTP user agent or null if there is no user agent. --- ### getLocale **Signature:** `getLocale() : String` **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. **Returns:** the locale of the current request, like 'en_US' --- ### getOcapiVersion **Signature:** `getOcapiVersion() : String` **Description:** Returns the OCAPI version of the current request. If this is not an OCAPI request, 'null' is returned. **Returns:** OCAPI version of the current request --- ### getPageMetaData **Signature:** `getPageMetaData() : PageMetaData` **Description:** Returns the page meta data that are associated with the current request. **Returns:** the page meta data object --- ### getRequestID **Signature:** `getRequestID() : String` **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. **Returns:** the unique identifier of the current request. --- ### getSCAPIPathParameters **Signature:** `getSCAPIPathParameters() : Map` **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. **Returns:** the path parameter map or null --- ### getSCAPIPathPattern **Signature:** `getSCAPIPathPattern() : String` **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} **Returns:** the path pattern or null. --- ### getSession **Signature:** `getSession() : Session` **Description:** Returns the session associated with this request. **Returns:** the session associated with this request. --- ### getTriggeredForm **Signature:** `getTriggeredForm() : Form` **Description:** Returns the form that was submitted by the client if the request represents a form submission. **Returns:** the form which was triggered --- ### getTriggeredFormAction **Signature:** `getTriggeredFormAction() : FormAction` **Description:** Returns the form action that was triggered by the client if the request represents a form submission. **Returns:** the action of the form that was triggered --- ### isHttpRequest **Signature:** `isHttpRequest() : boolean` **Description:** Identifies if this request is an HTTP request. The method returns true, if the current processing is related to a HTTP request. **Deprecated:** Effectively always returns true. **Returns:** true if the current processing is related to a HTTP request, false otherwise. --- ### isHttpSecure **Signature:** `isHttpSecure() : boolean` **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. --- ### isIncludeRequest **Signature:** `isIncludeRequest() : boolean` **Description:** Returns true if the request represents a request for a remote include, false if it is a top-level request. --- ### isSCAPI **Signature:** `isSCAPI() : boolean` **Description:** Returns whether the request originated in SCAPI. **Returns:** true or false. --- ### setGeolocation **Signature:** `setGeolocation(geoLocation : Geolocation) : void` **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 **Parameters:** - `geoLocation`: the geolocation object to use --- ### setLocale **Signature:** `setLocale(localeID : String) : boolean` **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. **Parameters:** - `localeID`: the locale ID to be set, like 'en_US' **Returns:** true, if the locale was successfully set, false otherwise --- ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-sfra-documents-by-category.docs-only.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript /** * ================================================================================== * SFCC MCP Server - get_sfra_documents_by_category Tool Node.js Programmatic Tests * Comprehensive testing with dynamic validation logic and advanced test scenarios * * Tool: get_sfra_documents_by_category * Purpose: Get SFRA documents filtered by category (core, product, order, customer, pricing, store, other) * Parameters: category (required) - Category to filter by * * Quick Test Commands: * node --test tests/mcp/node/get-sfra-documents-by-category.docs-only.programmatic.test.js * npm run test:mcp:node -- --grep "get_sfra_documents_by_category" * ================================================================================== */ import { test, describe, before, after, beforeEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { connect } from 'mcp-aegis'; describe('SFCC MCP Server - get_sfra_documents_by_category Tool Programmatic Tests', () => { let client; before(async () => { client = await connect('./aegis.config.docs-only.json'); }); after(async () => { if (client?.connected) { await client.disconnect(); } }); beforeEach(() => { // CRITICAL: Clear all buffers to prevent test interference client.clearAllBuffers(); }); // ================================================================================== // SUCCESSFUL OPERATIONS - VALID CATEGORIES // ================================================================================== describe('Valid Category Operations', () => { test('should retrieve core SFRA documents with proper structure', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); // Validate MCP response structure assert.ok(result.content, 'Should have content'); assert.ok(Array.isArray(result.content), 'Content should be array'); assert.equal(result.content.length, 1, 'Should have one content item'); assert.equal(result.content[0].type, 'text', 'Content type should be text'); assert.equal(result.isError, false, 'Should not be error'); // Parse and validate JSON structure const jsonText = result.content[0].text; assert.ok(jsonText, 'Should have text content'); const documents = JSON.parse(jsonText); assert.ok(Array.isArray(documents), 'Should parse to array'); assert.ok(documents.length > 0, 'Should have documents'); }); test('should return expected core documents with correct fields', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const documents = JSON.parse(result.content[0].text); // Validate document count and names assert.equal(documents.length, 5, 'Core should have 5 documents'); const expectedNames = ['querystring', 'render', 'request', 'response', 'server']; const actualNames = documents.map(doc => doc.name).sort(); assert.deepEqual(actualNames, expectedNames, 'Should have expected core document names'); // Validate document structure documents.forEach((doc, index) => { assert.ok(doc.name, `Document ${index} should have name`); assert.ok(doc.title, `Document ${index} should have title`); assert.ok(doc.description !== undefined, `Document ${index} should have description`); assert.ok(doc.type, `Document ${index} should have type`); assert.equal(doc.category, 'core', `Document ${index} should have core category`); assert.ok(doc.filename, `Document ${index} should have filename`); assert.ok(doc.filename.endsWith('.md'), `Document ${index} filename should be markdown`); }); }); test('should return product category documents with model types', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'product' }); assert.equal(result.isError, false, 'Should not be error'); const documents = JSON.parse(result.content[0].text); assert.ok(Array.isArray(documents), 'Should return array'); assert.ok(documents.length > 0, 'Should have product documents'); // Validate product-specific content const hasProductFull = documents.some(doc => doc.name === 'product-full'); const hasProductTile = documents.some(doc => doc.name === 'product-tile'); assert.ok(hasProductFull, 'Should contain product-full document'); assert.ok(hasProductTile, 'Should contain product-tile document'); // Validate all documents have product category documents.forEach(doc => { assert.equal(doc.category, 'product', 'All documents should have product category'); }); }); test('should handle all valid categories', async () => { const validCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other']; for (const category of validCategories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); assert.equal(result.isError, false, `Category ${category} should not error`); assert.ok(result.content, `Category ${category} should have content`); const documents = JSON.parse(result.content[0].text); assert.ok(Array.isArray(documents), `Category ${category} should return array`); // If documents exist, they should have the correct category documents.forEach(doc => { assert.equal(doc.category, category, `Document should have ${category} category`); }); } }); }); // ================================================================================== // EDGE CASES AND ERROR HANDLING // ================================================================================== describe('Edge Cases and Error Handling', () => { test('should handle invalid category gracefully', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'invalid_category_xyz' }); assert.equal(result.isError, false, 'Invalid category should not be error'); const documents = JSON.parse(result.content[0].text); assert.ok(Array.isArray(documents), 'Should return array'); assert.equal(documents.length, 0, 'Should return empty array for invalid category'); }); test('should require category parameter', async () => { const result = await client.callTool('get_sfra_documents_by_category', {}); assert.equal(result.isError, true, 'Missing category should be error'); assert.ok(result.content[0].text.includes('category must be a non-empty string'), 'Should have specific error message'); }); test('should handle empty category string', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: '' }); assert.equal(result.isError, true, 'Empty category should be error'); assert.ok(result.content[0].text.includes('Error'), 'Should contain error message'); }); test('should handle null category parameter', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: null }); assert.equal(result.isError, true, 'Null category should be error'); assert.ok(result.content[0].text.includes('Error'), 'Should contain error message'); }); test('should be case sensitive for categories', async () => { const testCases = ['CORE', 'Core', 'PRODUCT', 'Product']; for (const category of testCases) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); assert.equal(result.isError, false, `Category ${category} should not error`); const documents = JSON.parse(result.content[0].text); assert.equal(documents.length, 0, `Case-sensitive ${category} should return empty array`); } }); }); // ================================================================================== // DATA VALIDATION AND STRUCTURE TESTING // ================================================================================== describe('Data Validation and Structure', () => { test('should validate document field types and values', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const documents = JSON.parse(result.content[0].text); documents.forEach((doc, index) => { // Field type validation assert.equal(typeof doc.name, 'string', `Document ${index} name should be string`); assert.equal(typeof doc.title, 'string', `Document ${index} title should be string`); assert.equal(typeof doc.description, 'string', `Document ${index} description should be string`); assert.equal(typeof doc.type, 'string', `Document ${index} type should be string`); assert.equal(typeof doc.category, 'string', `Document ${index} category should be string`); assert.equal(typeof doc.filename, 'string', `Document ${index} filename should be string`); // Field value validation assert.ok(doc.name.length > 0, `Document ${index} name should not be empty`); assert.ok(doc.title.length > 0, `Document ${index} title should not be empty`); assert.ok(['class', 'module', 'model'].includes(doc.type), `Document ${index} type should be valid: ${doc.type}`); assert.ok(doc.filename.endsWith('.md'), `Document ${index} filename should end with .md: ${doc.filename}`); }); }); test('should maintain consistent document structure across categories', async () => { const categories = ['core', 'product']; const allDocuments = []; for (const category of categories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const documents = JSON.parse(result.content[0].text); allDocuments.push(...documents); } // Validate all documents have the same structure const requiredFields = ['name', 'title', 'description', 'type', 'category', 'filename']; allDocuments.forEach((doc, index) => { requiredFields.forEach(field => { assert.ok(Object.prototype.hasOwnProperty.call(doc, field), `Document ${index} should have ${field} field`); }); // No extra fields beyond expected ones const docFields = Object.keys(doc); assert.equal(docFields.length, requiredFields.length, `Document ${index} should have exactly ${requiredFields.length} fields`); }); }); test('should return documents sorted alphabetically by name', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const documents = JSON.parse(result.content[0].text); const names = documents.map(doc => doc.name); const sortedNames = [...names].sort(); assert.deepEqual(names, sortedNames, 'Documents should be sorted alphabetically by name'); }); }); // ================================================================================== // DYNAMIC VALIDATION AND BUSINESS LOGIC // ================================================================================== describe('Dynamic Validation and Business Logic', () => { test('should validate document naming conventions', async () => { const categories = ['core', 'product']; for (const category of categories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const documents = JSON.parse(result.content[0].text); documents.forEach(doc => { // Name should be lowercase with hyphens assert.ok(/^[a-z][a-z0-9-]*$/.test(doc.name), `Document name should follow convention: ${doc.name}`); // Filename should match name + .md assert.equal(doc.filename, `${doc.name}.md`, `Filename should match name: ${doc.filename}`); }); } }); test('should validate type consistency by category', async () => { // Core category should have classes and modules const coreResult = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const coreDocuments = JSON.parse(coreResult.content[0].text); const coreTypes = [...new Set(coreDocuments.map(doc => doc.type))]; assert.ok(coreTypes.includes('class'), 'Core should have class types'); assert.ok(coreTypes.includes('module'), 'Core should have module types'); // Product category should primarily have models const productResult = await client.callTool('get_sfra_documents_by_category', { category: 'product' }); const productDocuments = JSON.parse(productResult.content[0].text); if (productDocuments.length > 0) { // Product documents should primarily be models const modelCount = productDocuments.filter(doc => doc.type === 'model').length; const totalCount = productDocuments.length; assert.ok(modelCount / totalCount > 0.5, 'Product category should be primarily models'); } }); test('should validate unique document names within category', async () => { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const documents = JSON.parse(result.content[0].text); const names = documents.map(doc => doc.name); const uniqueNames = [...new Set(names)]; assert.equal(names.length, uniqueNames.length, 'All document names within category should be unique'); }); test('should handle empty categories appropriately', async () => { // Test categories that might be empty const possiblyEmptyCategories = ['other']; for (const category of possiblyEmptyCategories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); assert.equal(result.isError, false, `Category ${category} should not error`); const documents = JSON.parse(result.content[0].text); assert.ok(Array.isArray(documents), `Category ${category} should return array`); // Empty is acceptable for some categories } }); }); // ================================================================================== // INTEGRATION AND WORKFLOW TESTING // ================================================================================== describe('Integration and Workflow Testing', () => { test('should support category discovery workflow', async () => { // Step 1: Get available categories by testing each one const allCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other']; const availableCategories = []; for (const category of allCategories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const documents = JSON.parse(result.content[0].text); if (documents.length > 0) { availableCategories.push(category); } } assert.ok(availableCategories.length >= 2, 'Should have at least 2 non-empty categories'); assert.ok(availableCategories.includes('core'), 'Core should be available'); // Step 2: Validate each available category has consistent structure for (const category of availableCategories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const documents = JSON.parse(result.content[0].text); assert.ok(documents.every(doc => doc.category === category), `All documents in ${category} should have correct category`); } }); test('should support document exploration workflow', async () => { // Step 1: Get core documents const coreResult = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); const coreDocuments = JSON.parse(coreResult.content[0].text); // Step 2: Find specific document types const serverDoc = coreDocuments.find(doc => doc.name === 'server'); assert.ok(serverDoc, 'Should find server document'); assert.equal(serverDoc.type, 'class', 'Server should be a class'); const renderDoc = coreDocuments.find(doc => doc.name === 'render'); assert.ok(renderDoc, 'Should find render document'); assert.equal(renderDoc.type, 'module', 'Render should be a module'); // Step 3: Validate document relationships assert.ok(serverDoc.filename !== renderDoc.filename, 'Different documents should have different filenames'); }); test('should validate cross-category document consistency', async () => { const allDocuments = []; const allCategories = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other']; // Collect all documents across categories for (const category of allCategories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const documents = JSON.parse(result.content[0].text); allDocuments.push(...documents); } // Validate no duplicate document names across categories const allNames = allDocuments.map(doc => doc.name); const uniqueNames = [...new Set(allNames)]; assert.equal(allNames.length, uniqueNames.length, 'Document names should be unique across all categories'); // Validate filename consistency allDocuments.forEach(doc => { assert.equal(doc.filename, `${doc.name}.md`, `Document ${doc.name} should have consistent filename`); }); }); }); // ================================================================================== // PERFORMANCE AND RELIABILITY TESTING // ================================================================================== describe('Performance and Reliability', () => { test('should handle repeated requests consistently', async () => { const requestCount = 5; const results = []; // Make multiple identical requests for (let i = 0; i < requestCount; i++) { const result = await client.callTool('get_sfra_documents_by_category', { category: 'core' }); results.push(result); } // Validate all responses are identical const firstResponse = results[0].content[0].text; results.forEach((result, index) => { assert.equal(result.isError, false, `Request ${index} should not error`); assert.equal(result.content[0].text, firstResponse, `Request ${index} should return identical response`); }); }); test('should handle concurrent category requests sequentially', async () => { // Note: Not using Promise.all due to MCP single-process limitations const categories = ['core', 'product']; const results = []; // Sequential requests to avoid concurrency issues for (const category of categories) { const result = await client.callTool('get_sfra_documents_by_category', { category: category }); results.push({ category, result }); } // Validate all requests succeeded results.forEach(({ category, result }) => { assert.equal(result.isError, false, `Category ${category} should not error`); const documents = JSON.parse(result.content[0].text); assert.ok(Array.isArray(documents), `Category ${category} should return array`); }); }); test('should maintain performance under different input sizes', async () => { const categories = ['core', 'product', 'invalid_long_category_name_that_should_not_exist']; for (const category of categories) { const startTime = process.hrtime.bigint(); const result = await client.callTool('get_sfra_documents_by_category', { category: category }); const endTime = process.hrtime.bigint(); const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds // Functional validation (performance is environment-dependent) assert.ok(result.content, `Category ${category} should return content`); assert.ok(duration < 5000, `Category ${category} should complete within 5 seconds`); } }); }); }); // ================================================================================== // HELPER FUNCTIONS // ================================================================================== // Helper functions removed to avoid unused function linting errors. // Validation logic is implemented inline within tests for better maintainability. ```