This is page 29 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/yaml/search-system-object-attribute-definitions.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | # ================================================================================== 2 | # SFCC MCP Server - search_system_object_attribute_definitions Tool YAML Tests (Full Mode) 3 | # Streamlined smoke testing and declarative validation for core functionality 4 | # Complex business logic, edge cases, and workflows are covered in programmatic tests 5 | # 6 | # Quick Test Commands: 7 | # aegis "tests/mcp/yaml/search-system-object-attribute-definitions.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose 8 | # aegis query search_system_object_attribute_definitions '{"objectType": "Product", "searchRequest": {"query": {"match_all_query": {}}, "count": 5}}' --config "aegis.config.with-dw.json" 9 | # ================================================================================== 10 | 11 | description: "search_system_object_attribute_definitions tool smoke tests - Basic functionality validation" 12 | 13 | tests: 14 | # ================================================================================== 15 | # TOOL AVAILABILITY VALIDATION 16 | # ================================================================================== 17 | - it: "should have search_system_object_attribute_definitions tool available with proper schema" 18 | request: 19 | jsonrpc: "2.0" 20 | id: "tool-available" 21 | method: "tools/list" 22 | params: {} 23 | expect: 24 | response: 25 | jsonrpc: "2.0" 26 | id: "tool-available" 27 | result: 28 | tools: 29 | match:arrayElements: 30 | match:partial: 31 | name: "match:type:string" 32 | description: "match:type:string" 33 | match:extractField: "tools.*.name" 34 | value: "match:arrayContains:search_system_object_attribute_definitions" 35 | stderr: "toBeEmpty" 36 | 37 | # ================================================================================== 38 | # CORE FUNCTIONALITY VALIDATION - match_all_query 39 | # ================================================================================== 40 | - it: "should successfully search Product attributes with match_all_query and return valid structure" 41 | request: 42 | jsonrpc: "2.0" 43 | id: "match-all-success" 44 | method: "tools/call" 45 | params: 46 | name: "search_system_object_attribute_definitions" 47 | arguments: 48 | objectType: "Product" 49 | searchRequest: 50 | query: 51 | match_all_query: {} 52 | count: 5 53 | expect: 54 | response: 55 | jsonrpc: "2.0" 56 | id: "match-all-success" 57 | result: 58 | content: 59 | - type: "text" 60 | text: "match:contains:object_attribute_definition_search_result" 61 | isError: false 62 | performance: 63 | maxResponseTime: "2000ms" 64 | stderr: "toBeEmpty" 65 | 66 | - it: "should return valid JSON structure with pagination in match_all_query response" 67 | request: 68 | jsonrpc: "2.0" 69 | id: "match-all-structure" 70 | method: "tools/call" 71 | params: 72 | name: "search_system_object_attribute_definitions" 73 | arguments: 74 | objectType: "Product" 75 | searchRequest: 76 | query: 77 | match_all_query: {} 78 | count: 3 79 | expect: 80 | response: 81 | jsonrpc: "2.0" 82 | id: "match-all-structure" 83 | result: 84 | content: 85 | - type: "text" 86 | text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*" 87 | isError: false 88 | stderr: "toBeEmpty" 89 | 90 | - it: "should include query echo and pagination info in match_all_query response" 91 | request: 92 | jsonrpc: "2.0" 93 | id: "match-all-metadata" 94 | method: "tools/call" 95 | params: 96 | name: "search_system_object_attribute_definitions" 97 | arguments: 98 | objectType: "Product" 99 | searchRequest: 100 | query: 101 | match_all_query: {} 102 | count: 5 103 | start: 0 104 | expect: 105 | response: 106 | jsonrpc: "2.0" 107 | id: "match-all-metadata" 108 | result: 109 | content: 110 | - type: "text" 111 | text: "match:regex:[\\s\\S]*\"query\"[\\s\\S]*\"match_all_query\"[\\s\\S]*\"start\"[\\s\\S]*\"total\"[\\s\\S]*" 112 | isError: false 113 | stderr: "toBeEmpty" 114 | 115 | # ================================================================================== 116 | # CORE FUNCTIONALITY VALIDATION - text_query 117 | # ================================================================================== 118 | - it: "should successfully search with text_query and return matching results" 119 | request: 120 | jsonrpc: "2.0" 121 | id: "text-query-success" 122 | method: "tools/call" 123 | params: 124 | name: "search_system_object_attribute_definitions" 125 | arguments: 126 | objectType: "Product" 127 | searchRequest: 128 | query: 129 | text_query: 130 | fields: ["id", "display_name"] 131 | search_phrase: "UPC" 132 | count: 3 133 | expect: 134 | response: 135 | jsonrpc: "2.0" 136 | id: "text-query-success" 137 | result: 138 | content: 139 | - type: "text" 140 | text: "match:contains:object_attribute_definition_search_result" 141 | isError: false 142 | performance: 143 | maxResponseTime: "2000ms" 144 | stderr: "toBeEmpty" 145 | 146 | - it: "should echo text_query parameters in response" 147 | request: 148 | jsonrpc: "2.0" 149 | id: "text-query-echo" 150 | method: "tools/call" 151 | params: 152 | name: "search_system_object_attribute_definitions" 153 | arguments: 154 | objectType: "Product" 155 | searchRequest: 156 | query: 157 | text_query: 158 | fields: ["id", "display_name"] 159 | search_phrase: "UPC" 160 | expect: 161 | response: 162 | jsonrpc: "2.0" 163 | id: "text-query-echo" 164 | result: 165 | content: 166 | - type: "text" 167 | text: "match:regex:[\\s\\S]*\"text_query\"[\\s\\S]*\"fields\"[\\s\\S]*\"search_phrase\"[\\s\\S]*\"UPC\"[\\s\\S]*" 168 | isError: false 169 | stderr: "toBeEmpty" 170 | 171 | # ================================================================================== 172 | # CORE FUNCTIONALITY VALIDATION - term_query 173 | # ================================================================================== 174 | - it: "should successfully search with term_query using exact matching" 175 | request: 176 | jsonrpc: "2.0" 177 | id: "term-query-success" 178 | method: "tools/call" 179 | params: 180 | name: "search_system_object_attribute_definitions" 181 | arguments: 182 | objectType: "Product" 183 | searchRequest: 184 | query: 185 | term_query: 186 | fields: ["value_type"] 187 | operator: "is" 188 | values: ["string"] 189 | count: 5 190 | expect: 191 | response: 192 | jsonrpc: "2.0" 193 | id: "term-query-success" 194 | result: 195 | content: 196 | - type: "text" 197 | text: "match:contains:object_attribute_definition_search_result" 198 | isError: false 199 | performance: 200 | maxResponseTime: "2000ms" 201 | stderr: "toBeEmpty" 202 | 203 | - it: "should echo term_query parameters correctly" 204 | request: 205 | jsonrpc: "2.0" 206 | id: "term-query-echo" 207 | method: "tools/call" 208 | params: 209 | name: "search_system_object_attribute_definitions" 210 | arguments: 211 | objectType: "Product" 212 | searchRequest: 213 | query: 214 | term_query: 215 | fields: ["value_type"] 216 | operator: "is" 217 | values: ["string"] 218 | expect: 219 | response: 220 | jsonrpc: "2.0" 221 | id: "term-query-echo" 222 | result: 223 | content: 224 | - type: "text" 225 | text: "match:regex:[\\s\\S]*\"term_query\"[\\s\\S]*\"fields\"[\\s\\S]*\"operator\"[\\s\\S]*\"is\"[\\s\\S]*\"values\"[\\s\\S]*" 226 | isError: false 227 | stderr: "toBeEmpty" 228 | 229 | # ================================================================================== 230 | # PAGINATION AND SORTING VALIDATION 231 | # ================================================================================== 232 | - it: "should handle pagination parameters correctly" 233 | request: 234 | jsonrpc: "2.0" 235 | id: "pagination-test" 236 | method: "tools/call" 237 | params: 238 | name: "search_system_object_attribute_definitions" 239 | arguments: 240 | objectType: "Product" 241 | searchRequest: 242 | query: 243 | match_all_query: {} 244 | start: 5 245 | count: 3 246 | expect: 247 | response: 248 | jsonrpc: "2.0" 249 | id: "pagination-test" 250 | result: 251 | content: 252 | - type: "text" 253 | text: "match:regex:[\\s\\S]*\"start\"[\\s\\S]*5[\\s\\S]*\"count\"[\\s\\S]*3[\\s\\S]*" 254 | isError: false 255 | performance: 256 | maxResponseTime: "2000ms" 257 | stderr: "toBeEmpty" 258 | 259 | - it: "should handle sorting parameters correctly" 260 | request: 261 | jsonrpc: "2.0" 262 | id: "sorting-test" 263 | method: "tools/call" 264 | params: 265 | name: "search_system_object_attribute_definitions" 266 | arguments: 267 | objectType: "Product" 268 | searchRequest: 269 | query: 270 | match_all_query: {} 271 | sorts: 272 | - field: "id" 273 | sort_order: "asc" 274 | count: 5 275 | expect: 276 | response: 277 | jsonrpc: "2.0" 278 | id: "sorting-test" 279 | result: 280 | content: 281 | - type: "text" 282 | text: "match:contains:object_attribute_definition_search_result" 283 | isError: false 284 | performance: 285 | maxResponseTime: "2000ms" 286 | stderr: "toBeEmpty" 287 | 288 | # ================================================================================== 289 | # DIFFERENT OBJECT TYPES VALIDATION 290 | # ================================================================================== 291 | - it: "should handle Customer object type correctly" 292 | request: 293 | jsonrpc: "2.0" 294 | id: "customer-test" 295 | method: "tools/call" 296 | params: 297 | name: "search_system_object_attribute_definitions" 298 | arguments: 299 | objectType: "Customer" 300 | searchRequest: 301 | query: 302 | match_all_query: {} 303 | count: 3 304 | expect: 305 | response: 306 | jsonrpc: "2.0" 307 | id: "customer-test" 308 | result: 309 | content: 310 | - type: "text" 311 | text: "match:contains:object_attribute_definition_search_result" 312 | isError: false 313 | performance: 314 | maxResponseTime: "2000ms" 315 | stderr: "toBeEmpty" 316 | 317 | - it: "should handle Order object type correctly" 318 | request: 319 | jsonrpc: "2.0" 320 | id: "order-test" 321 | method: "tools/call" 322 | params: 323 | name: "search_system_object_attribute_definitions" 324 | arguments: 325 | objectType: "Order" 326 | searchRequest: 327 | query: 328 | match_all_query: {} 329 | count: 3 330 | expect: 331 | response: 332 | jsonrpc: "2.0" 333 | id: "order-test" 334 | result: 335 | content: 336 | - type: "text" 337 | text: "match:contains:object_attribute_definition_search_result" 338 | isError: false 339 | performance: 340 | maxResponseTime: "2000ms" 341 | stderr: "toBeEmpty" 342 | 343 | # ================================================================================== 344 | # PARAMETER VALIDATION 345 | # ================================================================================== 346 | - it: "should handle minimal parameters gracefully (only objectType)" 347 | request: 348 | jsonrpc: "2.0" 349 | id: "minimal-params" 350 | method: "tools/call" 351 | params: 352 | name: "search_system_object_attribute_definitions" 353 | arguments: 354 | objectType: "Product" 355 | expect: 356 | response: 357 | jsonrpc: "2.0" 358 | id: "minimal-params" 359 | result: 360 | content: 361 | - type: "text" 362 | text: "match:contains:object_attribute_definition_search_result" 363 | isError: false 364 | performance: 365 | maxResponseTime: "2000ms" 366 | stderr: "toBeEmpty" 367 | 368 | - it: "should handle invalid object type gracefully (returns empty results)" 369 | request: 370 | jsonrpc: "2.0" 371 | id: "invalid-object-type" 372 | method: "tools/call" 373 | params: 374 | name: "search_system_object_attribute_definitions" 375 | arguments: 376 | objectType: "NonExistentType" 377 | searchRequest: 378 | query: 379 | match_all_query: {} 380 | expect: 381 | response: 382 | jsonrpc: "2.0" 383 | id: "invalid-object-type" 384 | result: 385 | content: 386 | - type: "text" 387 | text: "match:contains:\"total\": 0" 388 | isError: false 389 | performance: 390 | maxResponseTime: "2000ms" 391 | stderr: "toBeEmpty" 392 | 393 | # ================================================================================== 394 | # PERFORMANCE VALIDATION 395 | # ================================================================================== 396 | - it: "should complete match_all_query operations within reasonable time" 397 | request: 398 | jsonrpc: "2.0" 399 | id: "perf-match-all" 400 | method: "tools/call" 401 | params: 402 | name: "search_system_object_attribute_definitions" 403 | arguments: 404 | objectType: "Product" 405 | searchRequest: 406 | query: 407 | match_all_query: {} 408 | count: 10 409 | expect: 410 | response: 411 | jsonrpc: "2.0" 412 | id: "perf-match-all" 413 | result: 414 | content: 415 | - type: "text" 416 | text: "match:contains:object_attribute_definition_search_result" 417 | isError: false 418 | performance: 419 | maxResponseTime: "1500ms" 420 | stderr: "toBeEmpty" 421 | 422 | - it: "should complete text_query operations within reasonable time" 423 | request: 424 | jsonrpc: "2.0" 425 | id: "perf-text-query" 426 | method: "tools/call" 427 | params: 428 | name: "search_system_object_attribute_definitions" 429 | arguments: 430 | objectType: "Product" 431 | searchRequest: 432 | query: 433 | text_query: 434 | fields: ["id", "display_name", "description"] 435 | search_phrase: "brand" 436 | count: 5 437 | expect: 438 | response: 439 | jsonrpc: "2.0" 440 | id: "perf-text-query" 441 | result: 442 | content: 443 | - type: "text" 444 | text: "match:contains:object_attribute_definition_search_result" 445 | isError: false 446 | performance: 447 | maxResponseTime: "2000ms" 448 | stderr: "toBeEmpty" 449 | 450 | # ================================================================================== 451 | # RESPONSE STRUCTURE VALIDATION 452 | # ================================================================================== 453 | - it: "should return properly structured SFCC API response format" 454 | request: 455 | jsonrpc: "2.0" 456 | id: "structure-validation" 457 | method: "tools/call" 458 | params: 459 | name: "search_system_object_attribute_definitions" 460 | arguments: 461 | objectType: "Product" 462 | searchRequest: 463 | query: 464 | match_all_query: {} 465 | count: 2 466 | expect: 467 | response: 468 | jsonrpc: "2.0" 469 | id: "structure-validation" 470 | result: 471 | content: 472 | - type: "text" 473 | text: "match:regex:[\\s\\S]*\"_v\"[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*\"hits\"[\\s\\S]*\"query\"[\\s\\S]*\"start\"[\\s\\S]*\"total\"[\\s\\S]*" 474 | isError: false 475 | stderr: "toBeEmpty" 476 | 477 | - it: "should include attribute definition details when available" 478 | request: 479 | jsonrpc: "2.0" 480 | id: "attribute-details" 481 | method: "tools/call" 482 | params: 483 | name: "search_system_object_attribute_definitions" 484 | arguments: 485 | objectType: "Product" 486 | searchRequest: 487 | query: 488 | match_all_query: {} 489 | count: 3 490 | select: "(**)" 491 | expect: 492 | response: 493 | jsonrpc: "2.0" 494 | id: "attribute-details" 495 | result: 496 | content: 497 | - type: "text" 498 | text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition\"[\\s\\S]*\"id\"[\\s\\S]*\"link\"[\\s\\S]*" 499 | isError: false 500 | stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/src/routes/ocapi/ocapi-utils.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * OCAPI Utilities 3 | * 4 | * Shared utility functions for OCAPI handlers including select parameter parsing, 5 | * pagination helpers, and object enhancement functions. 6 | */ 7 | 8 | class OCAPIUtils { 9 | /** 10 | * Apply select parameter to the root response structure based on SFCC select syntax 11 | * Examples: 12 | * - (**) = return full response 13 | * - (start, total) = return only start and total fields 14 | * - (data.(**)) = return only data array with all object properties 15 | * - (data.(object_type)) = return only data array with specific fields 16 | * - (start, data.(**)) = return start field plus data array with all properties 17 | */ 18 | static applyRootSelectParameter(response, select) { 19 | // Default behavior - return full response 20 | if (!select || select === '(**)') { 21 | return response; 22 | } 23 | 24 | // Parse select parameter - remove outer parentheses and split by commas 25 | const trimmed = select.replace(/^\(|\)$/g, ''); 26 | const parts = this.parseSelectParts(trimmed); 27 | 28 | const result = {}; 29 | 30 | // Always include metadata fields that SFCC includes 31 | result._v = response._v; 32 | result._type = response._type; 33 | 34 | for (const part of parts) { 35 | const trimmedPart = part.trim(); 36 | 37 | if (trimmedPart === 'start') { 38 | result.start = response.start; 39 | } else if (trimmedPart === 'total') { 40 | result.total = response.total; 41 | } else if (trimmedPart === 'count') { 42 | result.count = response.count; 43 | } else if (trimmedPart === 'next') { 44 | result.next = response.next; 45 | } else if (trimmedPart === 'previous') { 46 | result.previous = response.previous; 47 | } else if (trimmedPart === 'select') { 48 | result.select = response.select; 49 | } else if (trimmedPart.startsWith('data.')) { 50 | // Handle data.(...) patterns 51 | result.data = response.data; 52 | // Note: data object filtering is already applied by applySelectParameter 53 | } else if (trimmedPart === 'data') { 54 | result.data = response.data; 55 | } 56 | } 57 | 58 | return result; 59 | } 60 | 61 | /** 62 | * Parse select parts handling nested parentheses correctly 63 | */ 64 | static parseSelectParts(selectString) { 65 | const parts = []; 66 | let current = ''; 67 | let depth = 0; 68 | 69 | for (let i = 0; i < selectString.length; i++) { 70 | const char = selectString[i]; 71 | 72 | if (char === '(') { 73 | depth++; 74 | current += char; 75 | } else if (char === ')') { 76 | depth--; 77 | current += char; 78 | } else if (char === ',' && depth === 0) { 79 | if (current.trim()) { 80 | parts.push(current.trim()); 81 | } 82 | current = ''; 83 | } else { 84 | current += char; 85 | } 86 | } 87 | 88 | if (current.trim()) { 89 | parts.push(current.trim()); 90 | } 91 | 92 | return parts; 93 | } 94 | 95 | /** 96 | * Apply select parameter to modify object structure based on SFCC select syntax 97 | * Examples: 98 | * - Default (no select) = return basic format (_type, _resource_state, object_type, link) 99 | * - (**) = return all fields (enhanced detailed format) 100 | * - (data.(object_type)) = return only object_type field in data objects 101 | * - (data.(object_type,display_name)) = return specific fields in data objects 102 | */ 103 | static applySelectParameter(obj, select) { 104 | // Default behavior (no select) - return basic format 105 | if (!select) { 106 | return obj; // Keep original basic format 107 | } 108 | 109 | // Full select - return enhanced detailed format 110 | if (select === '(**)') { 111 | return this.enhanceObjectWithDetailedFields(obj); 112 | } 113 | 114 | // Specific field selection using (...) syntax (after data.() processing) 115 | if (select.startsWith('(') && select.endsWith(')') && !select.startsWith('(data.(')) { 116 | // Extract field list from (field1,field2) 117 | const fieldsMatch = select.match(/\(([^)]+)\)/); 118 | if (fieldsMatch) { 119 | const fields = fieldsMatch[1].split(',').map(f => f.trim()); 120 | return this.selectSpecificFields(obj, fields); 121 | } 122 | } 123 | 124 | // Specific field selection using data.(...) syntax 125 | if (select.startsWith('(data.(') && select.endsWith('))')) { 126 | // Extract field list from (data.(field1,field2)) 127 | const fieldsMatch = select.match(/\(data\.\(([^)]+)\)\)/); 128 | if (fieldsMatch) { 129 | const fields = fieldsMatch[1].split(',').map(f => f.trim()); 130 | return this.selectSpecificFields(obj, fields); 131 | } 132 | } 133 | 134 | // Other select patterns that don't work properly in real API 135 | // Return basic format as fallback 136 | return obj; 137 | } 138 | 139 | /** 140 | * Select only specific fields from the object 141 | */ 142 | static selectSpecificFields(obj, fields) { 143 | const result = { 144 | "_type": "object_type_definition", 145 | "_resource_state": obj._resource_state 146 | }; 147 | 148 | // Add requested fields 149 | fields.forEach(field => { 150 | if (field === 'object_type') { 151 | result.object_type = obj.object_type; 152 | } else if (field === 'display_name') { 153 | result.display_name = this.getObjectTypeDisplayName(obj.object_type); 154 | } else if (field === 'description') { 155 | result.description = this.getObjectTypeDescription(obj.object_type); 156 | } else if (field === 'link') { 157 | result.link = obj.link; 158 | } else if (field === 'read_only') { 159 | result.read_only = this.getObjectTypeReadOnly(obj.object_type); 160 | } else if (field === 'queryable') { 161 | result.queryable = this.getObjectTypeQueryable(obj.object_type); 162 | } else if (field === 'content_object') { 163 | result.content_object = this.getObjectTypeContentObject(obj.object_type); 164 | } else if (field === 'attribute_definition_count') { 165 | result.attribute_definition_count = this.getObjectTypeAttributeCount(obj.object_type); 166 | } else if (field === 'attribute_group_count') { 167 | result.attribute_group_count = this.getObjectTypeAttributeGroupCount(obj.object_type); 168 | } else if (field === 'creation_date') { 169 | result.creation_date = "2024-02-19T10:18:31.000Z"; 170 | } else if (field === 'last_modified') { 171 | result.last_modified = "2024-02-19T10:18:31.000Z"; 172 | } 173 | }); 174 | 175 | return result; 176 | } 177 | 178 | /** 179 | * Enhance basic object definition with detailed fields to match real SFCC API response 180 | */ 181 | static enhanceObjectWithDetailedFields(obj) { 182 | // Return enhanced version with detailed fields (like the real API with select=(**)) 183 | const enhanced = { 184 | "_type": "object_type_definition", 185 | "_resource_state": obj._resource_state, 186 | "attribute_definition_count": this.getObjectTypeAttributeCount(obj.object_type), 187 | "attribute_group_count": this.getObjectTypeAttributeGroupCount(obj.object_type), 188 | "content_object": this.getObjectTypeContentObject(obj.object_type), 189 | "creation_date": "2024-02-19T10:18:31.000Z", 190 | "description": this.getObjectTypeDescription(obj.object_type), 191 | "display_name": this.getObjectTypeDisplayName(obj.object_type), 192 | "last_modified": "2024-02-19T10:18:31.000Z", 193 | "link": obj.link, 194 | "object_type": obj.object_type, 195 | "queryable": this.getObjectTypeQueryable(obj.object_type), 196 | "read_only": this.getObjectTypeReadOnly(obj.object_type) 197 | }; 198 | 199 | return enhanced; 200 | } 201 | 202 | /** 203 | * Generate pagination URLs for API responses 204 | */ 205 | static generatePaginationUrls(req, start, count, total, select) { 206 | const baseUrl = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`; 207 | let nextUrl = null; 208 | let previousUrl = null; 209 | 210 | // Generate next URL if there are more items 211 | const hasMore = (start + count) < total; 212 | if (hasMore) { 213 | const nextStart = start + count; 214 | nextUrl = `${baseUrl}?start=${nextStart}&count=${count}`; 215 | if (select !== '(**)') { 216 | nextUrl += `&select=${encodeURIComponent(select)}`; 217 | } 218 | } 219 | 220 | // Generate previous URL if not at the beginning 221 | if (start > 0) { 222 | const prevStart = Math.max(0, start - count); 223 | previousUrl = `${baseUrl}?start=${prevStart}&count=${count}`; 224 | if (select !== '(**)') { 225 | previousUrl += `&select=${encodeURIComponent(select)}`; 226 | } 227 | } 228 | 229 | return { nextUrl, previousUrl }; 230 | } 231 | 232 | /** 233 | * Apply basic text search filtering to results 234 | */ 235 | static applyTextSearch(results, query) { 236 | if (!query || !query.text_query) { 237 | return results; 238 | } 239 | 240 | const searchTerm = query.text_query.search_phrase.toLowerCase(); 241 | const searchFields = query.text_query.fields || ["id"]; 242 | 243 | return results.filter(item => { 244 | return searchFields.some(field => { 245 | if (field === "id" && item.id) { 246 | return item.id.toLowerCase().includes(searchTerm); 247 | } 248 | if (field === "display_name" && item.display_name) { 249 | // Check if any display_name value contains the search term 250 | const displayNames = Object.values(item.display_name || {}); 251 | return displayNames.some(name => 252 | String(name).toLowerCase().includes(searchTerm) 253 | ); 254 | } 255 | if (field === "description" && item.description) { 256 | return String(item.description).toLowerCase().includes(searchTerm); 257 | } 258 | if (field === "object_type" && item.object_type) { 259 | return item.object_type.toLowerCase().includes(searchTerm); 260 | } 261 | return false; 262 | }); 263 | }); 264 | } 265 | 266 | /** 267 | * Build proper query response with SFCC-compliant _type fields 268 | */ 269 | static buildQueryResponse(originalQuery) { 270 | let queryResponse = originalQuery || {"match_all_query": {}}; 271 | 272 | if (queryResponse.match_all_query && !queryResponse.match_all_query._type) { 273 | queryResponse = { 274 | ...queryResponse, 275 | match_all_query: { 276 | ...queryResponse.match_all_query, 277 | "_type": "match_all_query" 278 | } 279 | }; 280 | } 281 | 282 | if (queryResponse.text_query && !queryResponse.text_query._type) { 283 | queryResponse = { 284 | ...queryResponse, 285 | text_query: { 286 | ...queryResponse.text_query, 287 | "_type": "text_query" 288 | } 289 | }; 290 | } 291 | 292 | return queryResponse; 293 | } 294 | 295 | // Object type metadata helpers 296 | 297 | /** 298 | * Get display name for object type (with internationalization) 299 | */ 300 | static getObjectTypeDisplayName(objectType) { 301 | const displayNames = { 302 | 'Appeasement': { 303 | "de": "Beschwerde", 304 | "default": "Appeasement", 305 | "ja": "譲歩", 306 | "it": "Riconciliazione", 307 | "fr": "Geste commercial", 308 | "zh-CN": "协调", 309 | "es": "Compensación", 310 | "nl": "Tegemoetkoming" 311 | }, 312 | 'AppeasementItem': { 313 | "de": "Beschwerde-Artikel", 314 | "default": "Appeasement Item", 315 | "ja": "譲歩項目", 316 | "it": "Articolo di riconciliazione", 317 | "fr": "Article du geste commercial", 318 | "zh-CN": "协调项目", 319 | "es": "Artículo de compensación", 320 | "nl": "Tegemoetkomingsitem" 321 | }, 322 | 'Basket': { 323 | "de": "Warenkorb", 324 | "default": "Basket", 325 | "ja": "買い物カゴ", 326 | "it": "Carrello", 327 | "fr": "Panier", 328 | "zh-CN": "购物车", 329 | "es": "Canasta", 330 | "nl": "Mandje" 331 | } 332 | }; 333 | 334 | return displayNames[objectType] || { 335 | "default": objectType 336 | }; 337 | } 338 | 339 | /** 340 | * Get description for object type (with internationalization) 341 | */ 342 | static getObjectTypeDescription(objectType) { 343 | const descriptions = { 344 | 'Appeasement': { "default": "Object type representing appeasements." }, 345 | 'AppeasementItem': { "default": "Object type representing appeasement items." }, 346 | 'Basket': { "default": "Object type representing baskets." }, 347 | 'Product': { "default": "Object type representing products." }, 348 | 'Category': { "default": "Object type representing categories." }, 349 | 'Content': { "default": "Object type representing content assets." } 350 | }; 351 | 352 | return descriptions[objectType] || { 353 | "default": `Object type representing ${objectType.toLowerCase()} entities.` 354 | }; 355 | } 356 | 357 | /** 358 | * Get read_only flag for object type 359 | */ 360 | static getObjectTypeReadOnly(objectType) { 361 | const readOnlyTypes = ['CustomerActiveData', 'CustomerCDPData']; 362 | return readOnlyTypes.includes(objectType); 363 | } 364 | 365 | /** 366 | * Get queryable flag for object type 367 | */ 368 | static getObjectTypeQueryable(objectType) { 369 | const nonQueryableTypes = ['CustomObject']; 370 | return !nonQueryableTypes.includes(objectType); 371 | } 372 | 373 | /** 374 | * Get content_object flag for object type 375 | */ 376 | static getObjectTypeContentObject(objectType) { 377 | const contentObjectTypes = ['Content']; 378 | return contentObjectTypes.includes(objectType); 379 | } 380 | 381 | /** 382 | * Get attribute definition count for object type (matching real API data) 383 | */ 384 | static getObjectTypeAttributeCount(objectType) { 385 | const attributeCounts = { 386 | 'Appeasement': 9, 387 | 'AppeasementItem': 7, 388 | 'Basket': 11, 389 | 'BonusDiscountLineItem': 5, 390 | 'Campaign': 25, 391 | 'Catalog': 12, 392 | 'Category': 45, 393 | 'CategoryAssignment': 8, 394 | 'Content': 60, 395 | 'Coupon': 18, 396 | 'CouponLineItem': 6, 397 | 'CustomObject': 5, 398 | 'CustomerActiveData': 10, 399 | 'CustomerAddress': 15, 400 | 'CustomerCDPData': 10, 401 | 'CustomerGroup': 8, 402 | 'CustomerPaymentInstrument': 10, 403 | 'Customer': 28, 404 | 'Order': 65, 405 | 'Product': 166 406 | }; 407 | return attributeCounts[objectType] || 10; 408 | } 409 | 410 | /** 411 | * Get attribute group count for object type (matching real API data) 412 | */ 413 | static getObjectTypeAttributeGroupCount(objectType) { 414 | const groupCounts = { 415 | 'Appeasement': 2, 416 | 'AppeasementItem': 1, 417 | 'Basket': 1, 418 | 'BonusDiscountLineItem': 1, 419 | 'Campaign': 2, 420 | 'Catalog': 1, 421 | 'Category': 3, 422 | 'CategoryAssignment': 1, 423 | 'Content': 3, 424 | 'Coupon': 2, 425 | 'CouponLineItem': 1, 426 | 'CustomObject': 1, 427 | 'CustomerActiveData': 2, 428 | 'CustomerAddress': 2, 429 | 'CustomerCDPData': 2, 430 | 'CustomerGroup': 1, 431 | 'CustomerPaymentInstrument': 2, 432 | 'Customer': 4, 433 | 'Order': 6, 434 | 'Product': 5 435 | }; 436 | return groupCounts[objectType] || 2; 437 | } 438 | } 439 | 440 | module.exports = OCAPIUtils; ``` -------------------------------------------------------------------------------- /docs/dw_object/CustomObjectMgr.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.object 2 | 3 | # Class CustomObjectMgr 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.CustomObjectMgr 9 | 10 | ## Description 11 | 12 | Manager class which provides methods for creating, retrieving, deleting, and searching for custom objects. To search for system objects, use SystemObjectMgr. 13 | 14 | ## Constructor Summary 15 | 16 | ## Method Summary 17 | 18 | ### createCustomObject 19 | 20 | **Signature:** `static createCustomObject(type : String, keyValue : String) : CustomObject` 21 | 22 | Returns a new custom object instance of the specified type, using the given key value. 23 | 24 | ### createCustomObject 25 | 26 | **Signature:** `static createCustomObject(type : String, keyValue : Number) : CustomObject` 27 | 28 | Returns a new custom object instance of the specified type, using the given key value. 29 | 30 | ### describe 31 | 32 | **Signature:** `static describe(type : String) : ObjectTypeDefinition` 33 | 34 | Returns the meta data for the given type. 35 | 36 | ### getAllCustomObjects 37 | 38 | **Signature:** `static getAllCustomObjects(type : String) : SeekableIterator` 39 | 40 | Returns all custom objects of a specific type. 41 | 42 | ### getCustomObject 43 | 44 | **Signature:** `static getCustomObject(type : String, keyValue : String) : CustomObject` 45 | 46 | Returns a custom object based on it's type and unique key. 47 | 48 | ### getCustomObject 49 | 50 | **Signature:** `static getCustomObject(type : String, keyValue : Number) : CustomObject` 51 | 52 | Returns a custom object based on it's type and unique key. 53 | 54 | ### queryCustomObject 55 | 56 | **Signature:** `static queryCustomObject(type : String, queryString : String, args : Object...) : CustomObject` 57 | 58 | Searches for a single custom object instance. 59 | 60 | ### queryCustomObjects 61 | 62 | **Signature:** `static queryCustomObjects(type : String, queryString : String, sortString : String, args : Object...) : SeekableIterator` 63 | 64 | Searches for custom object instances. 65 | 66 | ### queryCustomObjects 67 | 68 | **Signature:** `static queryCustomObjects(type : String, queryAttributes : Map, sortString : String) : SeekableIterator` 69 | 70 | Searches for custom object instances. 71 | 72 | ### remove 73 | 74 | **Signature:** `static remove(object : CustomObject) : void` 75 | 76 | Removes a given custom object. 77 | 78 | ## Method Detail 79 | 80 | ## Method Details 81 | 82 | ### createCustomObject 83 | 84 | **Signature:** `static createCustomObject(type : String, keyValue : String) : CustomObject` 85 | 86 | **Description:** Returns a new custom object instance of the specified type, using the given key value. Custom object keys need to be unique for custom object type. 87 | 88 | **Parameters:** 89 | 90 | - `type`: The unique name of the custom object type. 91 | - `keyValue`: The unique key value for the instance. 92 | 93 | **Returns:** 94 | 95 | The newly created custom object instance. 96 | 97 | **Throws:** 98 | 99 | IllegalArgumentException - if the given type is invalid 100 | 101 | --- 102 | 103 | ### createCustomObject 104 | 105 | **Signature:** `static createCustomObject(type : String, keyValue : Number) : CustomObject` 106 | 107 | **Description:** Returns a new custom object instance of the specified type, using the given key value. Custom object keys need to be unique for custom object type. 108 | 109 | **Parameters:** 110 | 111 | - `type`: The unique name of the custom object type. 112 | - `keyValue`: The unique key value for the instance. 113 | 114 | **Returns:** 115 | 116 | The newly created custom object instance. 117 | 118 | **Throws:** 119 | 120 | IllegalArgumentException - if the given type is invalid 121 | 122 | --- 123 | 124 | ### describe 125 | 126 | **Signature:** `static describe(type : String) : ObjectTypeDefinition` 127 | 128 | **Description:** Returns the meta data for the given type. 129 | 130 | **Parameters:** 131 | 132 | - `type`: the type whose meta data is returned. 133 | 134 | **Returns:** 135 | 136 | the meta data for the given type. 137 | 138 | **Throws:** 139 | 140 | IllegalArgumentException - if the given type is invalid 141 | 142 | --- 143 | 144 | ### getAllCustomObjects 145 | 146 | **Signature:** `static getAllCustomObjects(type : String) : SeekableIterator` 147 | 148 | **Description:** Returns all custom objects of a specific type. It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources. 149 | 150 | **Parameters:** 151 | 152 | - `type`: The name of the custom object type. 153 | 154 | **See Also:** 155 | 156 | SeekableIterator.close() 157 | 158 | **Throws:** 159 | 160 | IllegalArgumentException - if the given type is invalid 161 | 162 | --- 163 | 164 | ### getCustomObject 165 | 166 | **Signature:** `static getCustomObject(type : String, keyValue : String) : CustomObject` 167 | 168 | **Description:** Returns a custom object based on it's type and unique key. 169 | 170 | **Parameters:** 171 | 172 | - `type`: The name of the custom object type. 173 | - `keyValue`: The unique key value. 174 | 175 | **Returns:** 176 | 177 | The matching custom object instance or null in case no matching custom object instance could be found. 178 | 179 | **Throws:** 180 | 181 | IllegalArgumentException - if the given type is invalid 182 | 183 | --- 184 | 185 | ### getCustomObject 186 | 187 | **Signature:** `static getCustomObject(type : String, keyValue : Number) : CustomObject` 188 | 189 | **Description:** Returns a custom object based on it's type and unique key. 190 | 191 | **Parameters:** 192 | 193 | - `type`: The name of the custom object type. 194 | - `keyValue`: The unique key value. 195 | 196 | **Returns:** 197 | 198 | The matching custom object instance or null in case no matching custom object instance could be found. 199 | 200 | **Throws:** 201 | 202 | IllegalArgumentException - if the given type is invalid 203 | 204 | --- 205 | 206 | ### queryCustomObject 207 | 208 | **Signature:** `static queryCustomObject(type : String, queryString : String, args : Object...) : CustomObject` 209 | 210 | **Description:** Searches for a single custom object instance. The search can be configured using a simple query language, which provides most common filter and operator functionality. The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The following operators are supported in a condition: = Equals - All types; supports NULL value (thumbnail = NULL) != Not equals - All types; supports NULL value (thumbnail != NULL) < Less than - Integer, Number and Date types only > Greater than - Integer, Number and Date types only <= Less or equals than - Integer, Number and Date types only >= Greater or equals than - Integer, Number and Date types only LIKE Like - String types and Email only; use if leading or trailing wildcards will be used to support substring search(custom.country LIKE 'US*') ILIKE Case-independent Like - String types and Email only, use to support case insensitive query (custom.country ILIKE 'usa'), does also support wildcards for substring matching Conditions can be combined using logical expressions 'AND', 'OR' and 'NOT' and nested using parenthesis e.g. gender = {1} AND (age >= {2} OR (NOT profession LIKE {3})). The query language provides a placeholder syntax to pass objects as additional search parameters. Each passed object is related to a placeholder in the query string. The placeholder must be an Integer that is surrounded by braces. The first Integer value must be '0', the second '1' and so on, e.g. querySystemObjects("sample", "age = {0} or creationDate >= {1}", 18, date) If there is more than one object matching the specified query criteria, the result is not deterministic. In order to retrieve a single object from a sorted result set it is recommended to use the following code: queryCustomObjects("", "custom.myAttr asc", null).first(). The method first() returns only the next element and closes the iterator. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute. 211 | 212 | **Parameters:** 213 | 214 | - `type`: the custom object type for the query. 215 | - `queryString`: the actual query. 216 | - `args`: optional parameters for the queryString. 217 | 218 | **Returns:** 219 | 220 | the custom object defined by type which was found when executing the queryString. 221 | 222 | **Throws:** 223 | 224 | IllegalArgumentException - if the given type is invalid 225 | 226 | --- 227 | 228 | ### queryCustomObjects 229 | 230 | **Signature:** `static queryCustomObjects(type : String, queryString : String, sortString : String, args : Object...) : SeekableIterator` 231 | 232 | **Description:** Searches for custom object instances. The search can be configured using a simple query language, which provides most common filter and operator functionality. The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The following operators are supported in a condition: = Equals - All types; supports NULL value (thumbnail = NULL) != Not equals - All types; supports NULL value (thumbnail != NULL) < Less than - Integer, Number and Date types only > Greater than - Integer, Number and Date types only <= Less or equals than - Integer, Number and Date types only >= Greater or equals than - Integer, Number and Date types only LIKE Like - String types and Email only; use if leading or trailing wildcards will be used to support substring search(custom.country LIKE 'US*') ILIKE Caseindependent Like - String types and Email only, use to support case insensitive query (custom.country ILIKE 'usa'), does also support wildcards for substring matching Conditions can be combined using logical expressions 'AND', 'OR' and 'NOT' and nested using parenthesis e.g. gender = {1} AND (age >= {2} OR (NOT profession LIKE {3})). The query language provides a placeholder syntax to pass objects as additional search parameters. Each passed object is related to a placeholder in the query string. The placeholder must be an Integer that is surrounded by braces. The first Integer value must be '0', the second '1' and so on, e.g. querySystemObjects("sample", "age = {0} or creationDate >= {1}", 18, date) The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. Sometimes it is desired to get all instances of specified type with a special sorting condition. This can be easily done by providing the 'type' of the custom object and the 'sortString' in combination with an empty 'queryString', e.g. queryCustomObjects("sample", "", "custom.myAttr asc") It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute. 233 | 234 | **Parameters:** 235 | 236 | - `type`: the custom object type for the query. 237 | - `queryString`: the actual query. 238 | - `sortString`: an optional sorting or null if no sorting is necessary. 239 | - `args`: optional parameters for the queryString. 240 | 241 | **Returns:** 242 | 243 | SeekableIterator containing the result set of the query. 244 | 245 | **See Also:** 246 | 247 | SeekableIterator.close() 248 | 249 | **Throws:** 250 | 251 | IllegalArgumentException - if the given type is invalid 252 | 253 | --- 254 | 255 | ### queryCustomObjects 256 | 257 | **Signature:** `static queryCustomObjects(type : String, queryAttributes : Map, sortString : String) : SeekableIterator` 258 | 259 | **Description:** Searches for custom object instances. The search can be configured with a map, which key-value pairs are converted into a query expression. The key-value pairs are turned into a sequence of '=' or 'like' conditions, which are combined with AND statements. Example: A map with the key/value pairs: 'name'/'tom*', 'age'/66 will be converted as follows: "name like 'tom*' and age = 66" The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute. 260 | 261 | **Parameters:** 262 | 263 | - `type`: the custom object type for the query. 264 | - `queryAttributes`: key-value pairs, which define the query. 265 | - `sortString`: an optional sorting or null if no sorting is necessary. 266 | 267 | **Returns:** 268 | 269 | SeekableIterator containing the result set of the query. 270 | 271 | **See Also:** 272 | 273 | SeekableIterator.close() 274 | 275 | **Throws:** 276 | 277 | IllegalArgumentException - if the given type is invalid 278 | 279 | --- 280 | 281 | ### remove 282 | 283 | **Signature:** `static remove(object : CustomObject) : void` 284 | 285 | **Description:** Removes a given custom object. 286 | 287 | **Parameters:** 288 | 289 | - `object`: the custom object to remove, must not be null. 290 | 291 | --- ``` -------------------------------------------------------------------------------- /docs/best-practices/localserviceregistry.md: -------------------------------------------------------------------------------- ```markdown 1 | # SFCC B2C Commerce: LocalServiceRegistry Best Practices 2 | 3 | This guide provides a concise overview of best practices for creating server-to-server integrations in Salesforce B2C Commerce Cloud using the `dw.svc.LocalServiceRegistry`. 4 | 5 | ## 1. Core Architecture: Configuration and Code 6 | 7 | Integrations use a two-par5. Click **Apply** to save the new profile. 8 | 9 | ### Step 3: Create the Service Definition (Tying It All Together) 10 | 11 | The Service Definition links the Credential and Profile to create the final, named service that you will call from your code. 12 | 13 | 1. Navigate to **Administration > Operations > Services**. 14 | 2. Ensure you are on the **Services** tab and click **New**. 15 | 3. Fill in the following fields on the New Service page: 16 | - **Name (ID)**: Enter the unique ID for the service. This ID must exactly match the one used in your `LocalServiceRegistry.createService()` call. 17 | 18 | **Best Practice**: Use a period-delimited pattern like `{cartridge}.{protocol}.{service}.{operation}` (e.g., `int_myapi.http.customer.get`). This structure automatically organizes your service logs into a helpful hierarchy. 19 | 20 | - **Service Type**: Select the protocol, typically HTTP for REST APIs. 21 | 22 | - **Mode**: 23 | - **Live**: For making real calls to the external API. 24 | - **Mocked**: For testing. This mode will invoke the `mockCall` or `mockFull` function in your script instead of making a network request. 25 | 26 | - **Credential**: Select the Service Credential you created in Step 1 from the dropdown list. 27 | 28 | - **Profile**: Select the Service Profile you configured in Step 2 from the dropdown list. 29 | 30 | - **Log Name Prefix**: (Optional but recommended) Enter a prefix (e.g., `myapi`) to create a dedicated log file for this service (`service-myapi-....log`), which simplifies debugging. 31 | 32 | - **Communication Log Enabled**: Check this box to log the full request and response data. This is useful for debugging but should be used with caution in production if sensitive data is being transmitted. Always implement a `filterLogMessage` callback in your script to redact sensitive information from these logs. 33 | 34 | 4. Click **Apply** to save the service definition. 35 | 36 | Your service is now fully configured in the Business Manager and ready to be implemented and called from your SFRA cartridge code.rchitecture: 37 | 38 | **Declarative Configuration (Business Manager)**: Defines the what, who, and how of the service call. This includes the endpoint, credentials, and operational policies like timeouts and circuit breakers. 39 | 40 | **Programmatic Definition (Script Module)**: Defines the dynamic behavior using callbacks. This includes creating the request payload, parsing the response, and defining mock behavior for testing. 41 | 42 | ### Business Manager Configuration Summary 43 | 44 | | Component | Purpose | Key Settings | 45 | |-----------|---------|--------------| 46 | | Credential | Stores endpoint URL and authentication details. | ID, URL, User, Password | 47 | | Profile | Defines operational behavior and resilience. | Timeout, Rate Limiting, Circuit Breaker | 48 | | Service | Binds a Credential and Profile to a named service ID. | Name (ID), Service Type, Mode (Live/Mocked) | 49 | 50 | **Best Practice**: Use a period-delimited naming convention for the Service ID (e.g., `int_myapi.http.customer.get`) to organize logs effectively. 51 | 52 | ## 2. Script Implementation with LocalServiceRegistry 53 | 54 | The modern approach uses `dw.svc.LocalServiceRegistry` to define service behavior locally, eliminating the need for global initialization scripts. 55 | 56 | ### Key Callbacks 57 | 58 | The `createService` method takes a configuration object with the following core callback functions: 59 | 60 | - **`createRequest(svc,...params)`**: Configures the outgoing request (URL, method, headers) and returns the request body (e.g., a JSON string). 61 | 62 | - **`parseResponse(svc, client)`**: Processes the raw HTTP response (`dw.net.HTTPClient`). It should parse the response body (e.g., `JSON.parse(client.text)`) and throw an Error if the status code indicates a failure (e.g., `client.statusCode >= 400`). This ensures the `result.ok` flag is set correctly. 63 | 64 | - **`mockCall(svc, requestObj)`**: Mocks only the network execution. `createRequest` and `parseResponse` still run. Ideal for integration testing your service's logic. 65 | 66 | - **`mockFull(svc,...params)`**: Mocks the entire service call. No other callbacks are executed. Ideal for unit testing the consumer of the service (e.g., a controller). 67 | 68 | ### Reusable Service Module Pattern 69 | 70 | Encapsulate all logic for an integration into a single script module. Use a singleton pattern to avoid re-creating the service definition on every call. 71 | 72 | ```javascript 73 | 'use strict'; 74 | 75 | var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry'); 76 | var Logger = require('dw/system/Logger'); 77 | 78 | // --- Private Helper for Operation-Specific Details --- 79 | /** 80 | * Returns configuration details for a specific service operation. 81 | * @param {string} operation - The operation ID (e.g., 'getCustomer') 82 | * @returns {{method: string, path: string}} 83 | */ 84 | function getOperationDetails(operation) { 85 | switch (operation) { 86 | case 'getCustomer': 87 | return { method: 'GET', path: '/customers/' }; 88 | case 'createCustomer': 89 | return { method: 'POST', path: '/customers' }; 90 | default: 91 | Logger.error('Unknown operation in MyAPIService: {0}', operation); 92 | throw new Error('Operation not implemented: ' + operation); 93 | } 94 | } 95 | 96 | // --- Service Definition --- 97 | var myAPIService = LocalServiceRegistry.createService('int_myapi.http.customer', { 98 | /** 99 | * @param {dw.svc.HTTPService} svc 100 | * @param {string} operation - The name of the operation to perform 101 | * @param {Object} params - The parameters for the operation 102 | * @returns {string|null} - Request body or null 103 | */ 104 | createRequest: function (svc, operation, params) { 105 | var details = getOperationDetails(operation); 106 | var credential = svc.getConfiguration().getCredential(); 107 | 108 | svc.setRequestMethod(details.method); 109 | svc.addHeader('Content-Type', 'application/json'); 110 | svc.addHeader('X-API-Key', 'your-api-key'); // Example custom header 111 | 112 | var url = credential.getURL() + details.path; 113 | if (details.method === 'GET' && params && params.id) { 114 | svc.setURL(url + params.id); 115 | return null; // No body for GET 116 | } 117 | svc.setURL(url); 118 | return params? JSON.stringify(params) : null; 119 | }, 120 | 121 | /** 122 | * @param {dw.svc.HTTPService} svc 123 | * @param {dw.net.HTTPClient} client 124 | * @returns {Object} - The parsed JSON response 125 | */ 126 | parseResponse: function (svc, client) { 127 | if (client.statusCode >= 400) { 128 | Logger.error('MyAPIService error: {0} {1} - {2}', client.statusCode, client.statusMessage, client.text); 129 | throw new Error('API call failed with status ' + client.statusCode); 130 | } 131 | try { 132 | return JSON.parse(client.text); 133 | } catch (e) { 134 | Logger.error('Error parsing JSON response from MyAPIService: {0}', client.text); 135 | throw new Error('Invalid JSON response'); 136 | } 137 | }, 138 | 139 | /** 140 | * @param {dw.svc.HTTPService} svc 141 | * @param {string} operation 142 | * @param {Object} params 143 | * @returns {Object} - The final, parsed mock object 144 | */ 145 | mockFull: function (svc, operation, params) { 146 | var details = getOperationDetails(operation); 147 | if (details.method === 'GET') { 148 | return { id: params.id, name: 'Mock Customer', email: '[email protected]' }; 149 | } 150 | if (details.method === 'POST') { 151 | return { id: 'mock-' + new Date().getTime(), name: params.name, email: params.email }; 152 | } 153 | return { error: 'Mock not implemented for ' + operation }; 154 | }, 155 | 156 | /** 157 | * Redacts sensitive data from logs. 158 | * @param {string} msg - The log message 159 | * @returns {string} - The filtered message 160 | */ 161 | filterLogMessage: function (msg) { 162 | // Basic redaction example for JSON strings 163 | try { 164 | var logObject = JSON.parse(msg); 165 | if (logObject.password) { 166 | logObject.password = '<REDACTED>'; 167 | } 168 | return JSON.stringify(logObject); 169 | } catch (e) { 170 | return msg; // Not a JSON message, return as is 171 | } 172 | } 173 | }); 174 | 175 | // --- Public API --- 176 | module.exports = { 177 | OPERATION_GET_CUSTOMER: 'getCustomer', 178 | OPERATION_CREATE_CUSTOMER: 'createCustomer', 179 | call: function (operation, params) { 180 | return myAPIService.call(operation, params); 181 | } 182 | }; 183 | ``` 184 | ## 3. Practical Examples 185 | 186 | ### GET Request (SFRA Controller) 187 | 188 | ```javascript 189 | // In controller/MyController.js 190 | var server = require('server'); 191 | var MyAPIService = require('~/cartridge/scripts/services/MyAPIService'); 192 | 193 | server.get('ShowCustomer', function(req, res, next) { 194 | var customerId = req.querystring.id; 195 | var result = MyAPIService.call(MyAPIService.OPERATION_GET_CUSTOMER, { id: customerId }); 196 | 197 | if (result.ok) { 198 | res.render('customer/customerDetails', { 199 | customer: result.object 200 | }); 201 | } else { 202 | res.render('error', { 203 | message: 'Could not retrieve customer data.', 204 | error: result.errorMessage 205 | }); 206 | } 207 | next(); 208 | }); 209 | ``` 210 | 211 | ### POST Request with JSON (SFRA Controller) 212 | 213 | ```javascript 214 | // In controller/MyController.js 215 | var server = require('server'); 216 | var MyAPIService = require('~/cartridge/scripts/services/MyAPIService'); 217 | 218 | server.post('CreateCustomer', function(req, res, next) { 219 | var customerData = { 220 | name: req.form.name, 221 | email: req.form.email 222 | }; 223 | var result = MyAPIService.call(MyAPIService.OPERATION_CREATE_CUSTOMER, customerData); 224 | 225 | if (result.ok) { 226 | res.json({ success: true, customer: result.object }); 227 | } else { 228 | res.setStatusCode(500); 229 | res.json({ success: false, error: result.errorMessage }); 230 | } 231 | next(); 232 | }); 233 | ``` 234 | 235 | ### OAuth 2.0 Client Credentials Flow 236 | 237 | Use a **Two-Service Pattern** for efficiency: one service to get the token, and another to call the API. 238 | 239 | - **Auth Service (AuthTokenService.js)**: Handles the POST request to the token endpoint. 240 | 241 | - **API Service (MyAPIService.js)**: 242 | - Gets the token from the Auth Service. 243 | - Caches the token (`dw.system.CacheMgr`) to avoid re-authenticating on every call. 244 | - Adds the `Authorization: Bearer <token>` header in its `createRequest` callback. 245 | 246 | ```javascript 247 | // Conceptual snippet for API Service createRequest with OAuth 248 | createRequest: function (svc, params) { 249 | var CacheMgr = require('dw/system/CacheMgr'); 250 | var AuthTokenService = require('~/cartridge/scripts/services/AuthTokenService'); 251 | 252 | // Get token from cache or fetch a new one 253 | var tokenCache = CacheMgr.getCache('MyAPIToken'); 254 | var token = tokenCache.get('access_token', function () { 255 | var result = AuthTokenService.call(); 256 | if (result.ok && result.object.access_token) { 257 | // Set cache expiry to slightly less than the token's actual expiry 258 | tokenCache.put('access_token', result.object.access_token, result.object.expires_in - 300); 259 | return result.object.access_token; 260 | } 261 | return null; 262 | }); 263 | 264 | if (!token) { 265 | throw new Error('Unable to retrieve valid API token.'); 266 | } 267 | 268 | svc.setRequestMethod('GET'); 269 | svc.addHeader('Authorization', 'Bearer ' + token); 270 | //... set URL and other request details... 271 | return null; 272 | } 273 | ``` 274 | 275 | ## 4. Step-by-Step Business Manager Configuration Guide 276 | 277 | This guide walks through the three essential parts of setting up a new service in the Business Manager: creating the Credential, configuring the Profile, and defining the Service itself. 278 | 279 | ### Step 1: Create the Service Credential (The "Who" and "What") 280 | 281 | The Service Credential securely stores the endpoint URL and authentication details for the external system. 282 | 283 | 1. Navigate to **Administration > Operations > Services**. 284 | 2. Click the **Credentials** tab. 285 | 3. On the Service Credentials page, click **New**. 286 | 4. Fill in the following fields on the New Service Credential page: 287 | - **ID**: Enter a unique identifier. A recommended naming convention is `your.service.name.http.credentials`. This name cannot contain spaces. 288 | - **URL**: Enter the base URL for the third-party API. For example: `https://api.example.com/v2/`. 289 | - **User**: If using Basic Authentication, enter the Client ID or username provided by the third-party service. 290 | - **Password**: Enter the Client Secret or password. For security, this field is write-only. Once saved, the value cannot be viewed again, so be sure to store it in a secure location. 291 | 5. Click **Apply** to save the new credential. 292 | 293 | ### Step 2: Configure the Service Profile (The "How") 294 | 295 | The Service Profile defines the operational behavior, such as timeouts and resilience patterns, for the service call. 296 | 297 | 1. Navigate to **Administration > Operations > Services**. 298 | 2. Click the **Profiles** tab. 299 | 3. On the Service Profiles page, click **New**. 300 | 4. Fill in the following fields on the New Service Profile page: 301 | - **Name (ID)**: Enter a descriptive name for the profile (e.g., `default-api-profile`, `realtime-payment-profile`). 302 | - **Timeout**: Enter the connection timeout in milliseconds (e.g., `10000` for 10 seconds). This is the maximum time B2C Commerce will wait for a response before the call fails. 303 | - **Enable Circuit Breaker**: Check this box to enable this crucial fault-tolerance feature. It is highly recommended to always enable this. 304 | - **Calls**: The number of failed calls that will "trip" the circuit (e.g., `10`). 305 | - **Interval (ms)**: The time window in which the failures must occur (e.g., `60000` for 1 minute). When the circuit is tripped, B2C Commerce will stop making calls to the service for a period to allow the external system to recover. 306 | - **Enable Rate Limit**: Check this box if the third-party API has call limits. 307 | - **Calls**: The maximum number of calls allowed in the interval (e.g., `1000`). 308 | - **Interval (ms)**: The time window for the rate limit in milliseconds (e.g., `60000` for 1 minute). 309 | 5. Click **Apply** to save the new profile. 310 | 311 | ### Step 3: Create the Service Definition (Tying It All Together) 312 | 313 | The Service Definition links the Credential and Profile to create the final, named service that you will call from your code. 314 | 315 | 1. Navigate to **Administration > Operations > Services**. 316 | 2. Ensure you are on the **Services** tab and click **New**. 317 | 3. Fill in the following fields on the New Service page: 318 | - **Name (ID)**: Enter the unique ID for the service. This ID must exactly match the one used in your `LocalServiceRegistry.createService()` call. 319 | 320 | **Best Practice**: Use a period-delimited pattern like `{cartridge}.{protocol}.{service}.{operation}` (e.g., `int_myapi.http.customer.get`). This structure automatically organizes your service logs into a helpful hierarchy. 321 | 322 | - **Service Type**: Select the protocol, typically HTTP for REST APIs. 323 | 324 | - **Enabled**: Check this box to enable the service. 325 | 326 | - **Service Mode**: 327 | - **Live**: For making real calls to the external API. 328 | - **Mocked**: For testing. This mode will invoke the `mockCall` or `mockFull` function in your script instead of making a network request. 329 | 330 | - **Log Name Prefix**: (Optional but recommended) Enter a prefix (e.g., `myapi`) to create a dedicated log file for this service (`service-myapi-....log`), which simplifies debugging. 331 | 332 | - **Communication Log Enabled**: Check this box to log the full request and response data. This is useful for debugging but should be used with caution in production if sensitive data is being transmitted. Always implement a `filterLogMessage` callback in your script to redact sensitive information from these logs. 333 | 334 | - **Force PRD Behavior in Non-PRD Environments**: Check this box to force the service to behave as if it is in a production environment, even when it is not. This can be useful for testing how the service will behave in production. 335 | 336 | - **Profile**: Select the Service Profile you configured in Step 2 from the dropdown list. 337 | 338 | - **Credential**: Select the Service Credential you created in Step 1 from the dropdown list. 339 | 340 | 4. Click **Apply** to save the service definition.Your service is now fully configured in the Business Manager and ready to be implemented and called from your SFRA cartridge code. ``` -------------------------------------------------------------------------------- /docs/dw_customer/CustomerActiveData.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.customer 2 | 3 | # Class CustomerActiveData 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.object.ActiveData 11 | - dw.customer.CustomerActiveData 12 | 13 | ## Description 14 | 15 | Represents the active data for a Customer in Commerce Cloud Digital. Note: this class allows access to sensitive personal and private information. Pay attention to appropriate legal and regulatory requirements when developing. 16 | 17 | ## Properties 18 | 19 | ### avgOrderValue 20 | 21 | **Type:** Number (Read Only) 22 | 23 | The average order value of the customer, or null 24 | if none has been set or the value is no longer valid. 25 | 26 | ### discountValueWithCoupon 27 | 28 | **Type:** Number (Read Only) 29 | 30 | The discount value resulting from coupons, that has been applied 31 | to orders of the customer, or null if none has been set or 32 | the value is no longer valid. 33 | 34 | ### discountValueWithoutCoupon 35 | 36 | **Type:** Number (Read Only) 37 | 38 | The discount value resulting from promotions other than coupons, 39 | that has been applied to orders of the customer, or null 40 | if none has been set or the value is no longer valid. 41 | 42 | ### giftOrders 43 | 44 | **Type:** Number (Read Only) 45 | 46 | The number of orders for the Customer that contained at least 47 | one product unit marked as a gift, or null if none has been 48 | set or the value is no longer valid. 49 | 50 | ### giftUnits 51 | 52 | **Type:** Number (Read Only) 53 | 54 | The number of product units in orders for the customer 55 | that were marked as a gift, or null if none has been set 56 | or the value is no longer valid. 57 | 58 | ### lastOrderDate 59 | 60 | **Type:** Date (Read Only) 61 | 62 | The date of the last order for the customer, or null 63 | if there are no orders for the customer. 64 | 65 | ### orders 66 | 67 | **Type:** Number (Read Only) 68 | 69 | The orders of the customer, or null if none 70 | has been set or the value is no longer valid. 71 | 72 | ### orderValue 73 | 74 | **Type:** Number (Read Only) 75 | 76 | The lifetime order value of the customer, or null 77 | if none has been set or the value is no longer valid. 78 | 79 | ### orderValueMonth 80 | 81 | **Type:** Number (Read Only) 82 | 83 | The order value of the customer, over the most recent 30 days, 84 | or null if none has been set or the value is no longer valid. 85 | 86 | ### productMastersOrdered 87 | 88 | **Type:** String (Read Only) 89 | 90 | An array containing the master product SKUs of variation products 91 | in orders for the customer, or an empty collection if no SKUs have been 92 | set or the collection of SKUs is no longer valid. There is no specific 93 | limit on the number of SKUs that will be returned in the collection, but 94 | there is also no guarantee that it will contain the SKUs for all products 95 | ordered by the customer. 96 | 97 | ### productsAbandonedMonth 98 | 99 | **Type:** String (Read Only) 100 | 101 | An array containing the SKUs of products in baskets abandoned 102 | by the customer in the last 30 days, or an empty collection if no SKUs 103 | have been set or the collection is no longer valid. There is no specific 104 | limit on the number of SKUs that will be returned in the collection, but 105 | there is also no guarantee that it will contain the SKUs for all products 106 | in baskets abandoned by the customer. 107 | 108 | ### productsOrdered 109 | 110 | **Type:** String (Read Only) 111 | 112 | An array containing the SKUs of products in orders 113 | for the customer, or an empty collection if no SKUs have been set or the 114 | collection of SKUs is no longer valid. There is no specific limit on the 115 | number of SKUs that will be returned in the collection, but there is also 116 | no guarantee that it will contain the SKUs for all products ordered by 117 | the customer. 118 | 119 | ### productsViewedMonth 120 | 121 | **Type:** String (Read Only) 122 | 123 | An array containing the SKUs of products viewed by the 124 | customer in the last 30 days, or an empty collection if no SKUs have been 125 | set or the collection is no longer valid. There is no specific limit on 126 | the number of SKUs that will be returned in the collection, but there is 127 | also no guarantee that it will contain the SKUs for all products viewed 128 | by the customer. 129 | 130 | ### returns 131 | 132 | **Type:** Number (Read Only) 133 | 134 | The number of returns of the customer, or null 135 | if none has been set or the value is no longer valid. 136 | 137 | ### returnValue 138 | 139 | **Type:** Number (Read Only) 140 | 141 | The returned revenue of the customer, or null 142 | if none has been set or the value is no longer valid. 143 | 144 | ### sourceCodeOrders 145 | 146 | **Type:** Number (Read Only) 147 | 148 | The number of orders for the customer where a source code was 149 | in effect, or null if none has been set or the value 150 | is no longer valid. 151 | 152 | ### topCategoriesOrdered 153 | 154 | **Type:** String (Read Only) 155 | 156 | An array containing the IDs of up to the top 20 categories for 157 | customer orders, or an empty list if no categories have been set or the 158 | list of categories is no longer valid. The top category is the one for 159 | which the most orders for the customer contained at least one product 160 | found in that category. 161 | 162 | ### visitsMonth 163 | 164 | **Type:** Number (Read Only) 165 | 166 | The visits of the customer, over the most recent 30 days, 167 | or null if none has been set or the value 168 | is no longer valid. 169 | 170 | ### visitsWeek 171 | 172 | **Type:** Number (Read Only) 173 | 174 | The visits of the customer, over the most recent 7 days, 175 | or null if none has been set or the value 176 | is no longer valid. 177 | 178 | ### visitsYear 179 | 180 | **Type:** Number (Read Only) 181 | 182 | The visits of the customer, over the most recent 365 days, 183 | or null if none has been set or the value 184 | is no longer valid. 185 | 186 | ## Constructor Summary 187 | 188 | ## Method Summary 189 | 190 | ### getAvgOrderValue 191 | 192 | **Signature:** `getAvgOrderValue() : Number` 193 | 194 | Returns the average order value of the customer, or null if none has been set or the value is no longer valid. 195 | 196 | ### getDiscountValueWithCoupon 197 | 198 | **Signature:** `getDiscountValueWithCoupon() : Number` 199 | 200 | Returns the discount value resulting from coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid. 201 | 202 | ### getDiscountValueWithoutCoupon 203 | 204 | **Signature:** `getDiscountValueWithoutCoupon() : Number` 205 | 206 | Returns the discount value resulting from promotions other than coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid. 207 | 208 | ### getGiftOrders 209 | 210 | **Signature:** `getGiftOrders() : Number` 211 | 212 | Returns the number of orders for the Customer that contained at least one product unit marked as a gift, or null if none has been set or the value is no longer valid. 213 | 214 | ### getGiftUnits 215 | 216 | **Signature:** `getGiftUnits() : Number` 217 | 218 | Returns the number of product units in orders for the customer that were marked as a gift, or null if none has been set or the value is no longer valid. 219 | 220 | ### getLastOrderDate 221 | 222 | **Signature:** `getLastOrderDate() : Date` 223 | 224 | Returns the date of the last order for the customer, or null if there are no orders for the customer. 225 | 226 | ### getOrders 227 | 228 | **Signature:** `getOrders() : Number` 229 | 230 | Returns the orders of the customer, or null if none has been set or the value is no longer valid. 231 | 232 | ### getOrderValue 233 | 234 | **Signature:** `getOrderValue() : Number` 235 | 236 | Returns the lifetime order value of the customer, or null if none has been set or the value is no longer valid. 237 | 238 | ### getOrderValueMonth 239 | 240 | **Signature:** `getOrderValueMonth() : Number` 241 | 242 | Returns the order value of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid. 243 | 244 | ### getProductMastersOrdered 245 | 246 | **Signature:** `getProductMastersOrdered() : String[]` 247 | 248 | Returns an array containing the master product SKUs of variation products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. 249 | 250 | ### getProductsAbandonedMonth 251 | 252 | **Signature:** `getProductsAbandonedMonth() : String[]` 253 | 254 | Returns an array containing the SKUs of products in baskets abandoned by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. 255 | 256 | ### getProductsOrdered 257 | 258 | **Signature:** `getProductsOrdered() : String[]` 259 | 260 | Returns an array containing the SKUs of products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. 261 | 262 | ### getProductsViewedMonth 263 | 264 | **Signature:** `getProductsViewedMonth() : String[]` 265 | 266 | Returns an array containing the SKUs of products viewed by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. 267 | 268 | ### getReturns 269 | 270 | **Signature:** `getReturns() : Number` 271 | 272 | Returns the number of returns of the customer, or null if none has been set or the value is no longer valid. 273 | 274 | ### getReturnValue 275 | 276 | **Signature:** `getReturnValue() : Number` 277 | 278 | Returns the returned revenue of the customer, or null if none has been set or the value is no longer valid. 279 | 280 | ### getSourceCodeOrders 281 | 282 | **Signature:** `getSourceCodeOrders() : Number` 283 | 284 | Returns the number of orders for the customer where a source code was in effect, or null if none has been set or the value is no longer valid. 285 | 286 | ### getTopCategoriesOrdered 287 | 288 | **Signature:** `getTopCategoriesOrdered() : String[]` 289 | 290 | Returns an array containing the IDs of up to the top 20 categories for customer orders, or an empty list if no categories have been set or the list of categories is no longer valid. 291 | 292 | ### getVisitsMonth 293 | 294 | **Signature:** `getVisitsMonth() : Number` 295 | 296 | Returns the visits of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid. 297 | 298 | ### getVisitsWeek 299 | 300 | **Signature:** `getVisitsWeek() : Number` 301 | 302 | Returns the visits of the customer, over the most recent 7 days, or null if none has been set or the value is no longer valid. 303 | 304 | ### getVisitsYear 305 | 306 | **Signature:** `getVisitsYear() : Number` 307 | 308 | Returns the visits of the customer, over the most recent 365 days, or null if none has been set or the value is no longer valid. 309 | 310 | ## Method Detail 311 | 312 | ## Method Details 313 | 314 | ### getAvgOrderValue 315 | 316 | **Signature:** `getAvgOrderValue() : Number` 317 | 318 | **Description:** Returns the average order value of the customer, or null if none has been set or the value is no longer valid. 319 | 320 | **Returns:** 321 | 322 | the average order size. 323 | 324 | --- 325 | 326 | ### getDiscountValueWithCoupon 327 | 328 | **Signature:** `getDiscountValueWithCoupon() : Number` 329 | 330 | **Description:** Returns the discount value resulting from coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid. 331 | 332 | **Returns:** 333 | 334 | the discount value resulting from coupons. 335 | 336 | --- 337 | 338 | ### getDiscountValueWithoutCoupon 339 | 340 | **Signature:** `getDiscountValueWithoutCoupon() : Number` 341 | 342 | **Description:** Returns the discount value resulting from promotions other than coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid. 343 | 344 | **Returns:** 345 | 346 | the discount value resulting from promotions other than coupons. 347 | 348 | --- 349 | 350 | ### getGiftOrders 351 | 352 | **Signature:** `getGiftOrders() : Number` 353 | 354 | **Description:** Returns the number of orders for the Customer that contained at least one product unit marked as a gift, or null if none has been set or the value is no longer valid. 355 | 356 | **Returns:** 357 | 358 | the number of gift orders. 359 | 360 | --- 361 | 362 | ### getGiftUnits 363 | 364 | **Signature:** `getGiftUnits() : Number` 365 | 366 | **Description:** Returns the number of product units in orders for the customer that were marked as a gift, or null if none has been set or the value is no longer valid. 367 | 368 | **Returns:** 369 | 370 | the number of gift product units. 371 | 372 | --- 373 | 374 | ### getLastOrderDate 375 | 376 | **Signature:** `getLastOrderDate() : Date` 377 | 378 | **Description:** Returns the date of the last order for the customer, or null if there are no orders for the customer. 379 | 380 | **Returns:** 381 | 382 | the date of the last order for the customer. 383 | 384 | --- 385 | 386 | ### getOrders 387 | 388 | **Signature:** `getOrders() : Number` 389 | 390 | **Description:** Returns the orders of the customer, or null if none has been set or the value is no longer valid. 391 | 392 | **Returns:** 393 | 394 | the orders. 395 | 396 | --- 397 | 398 | ### getOrderValue 399 | 400 | **Signature:** `getOrderValue() : Number` 401 | 402 | **Description:** Returns the lifetime order value of the customer, or null if none has been set or the value is no longer valid. 403 | 404 | **Returns:** 405 | 406 | the lifetime value. 407 | 408 | --- 409 | 410 | ### getOrderValueMonth 411 | 412 | **Signature:** `getOrderValueMonth() : Number` 413 | 414 | **Description:** Returns the order value of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid. 415 | 416 | **Returns:** 417 | 418 | the value over the last 30 days. 419 | 420 | --- 421 | 422 | ### getProductMastersOrdered 423 | 424 | **Signature:** `getProductMastersOrdered() : String[]` 425 | 426 | **Description:** Returns an array containing the master product SKUs of variation products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products ordered by the customer. 427 | 428 | **Returns:** 429 | 430 | a collection containing the master product SKUs of variation products that were ordered. 431 | 432 | --- 433 | 434 | ### getProductsAbandonedMonth 435 | 436 | **Signature:** `getProductsAbandonedMonth() : String[]` 437 | 438 | **Description:** Returns an array containing the SKUs of products in baskets abandoned by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products in baskets abandoned by the customer. 439 | 440 | **Returns:** 441 | 442 | a collection containing the SKUs of products that were abandoned. 443 | 444 | --- 445 | 446 | ### getProductsOrdered 447 | 448 | **Signature:** `getProductsOrdered() : String[]` 449 | 450 | **Description:** Returns an array containing the SKUs of products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products ordered by the customer. 451 | 452 | **Returns:** 453 | 454 | a collection containing the SKUs of products that were ordered. 455 | 456 | --- 457 | 458 | ### getProductsViewedMonth 459 | 460 | **Signature:** `getProductsViewedMonth() : String[]` 461 | 462 | **Description:** Returns an array containing the SKUs of products viewed by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products viewed by the customer. 463 | 464 | **Returns:** 465 | 466 | a collection containing the SKUs of products that were ordered. 467 | 468 | --- 469 | 470 | ### getReturns 471 | 472 | **Signature:** `getReturns() : Number` 473 | 474 | **Description:** Returns the number of returns of the customer, or null if none has been set or the value is no longer valid. 475 | 476 | **Returns:** 477 | 478 | the returns. 479 | 480 | --- 481 | 482 | ### getReturnValue 483 | 484 | **Signature:** `getReturnValue() : Number` 485 | 486 | **Description:** Returns the returned revenue of the customer, or null if none has been set or the value is no longer valid. 487 | 488 | **Returns:** 489 | 490 | the returned revenue. 491 | 492 | --- 493 | 494 | ### getSourceCodeOrders 495 | 496 | **Signature:** `getSourceCodeOrders() : Number` 497 | 498 | **Description:** Returns the number of orders for the customer where a source code was in effect, or null if none has been set or the value is no longer valid. 499 | 500 | **Returns:** 501 | 502 | the number of orders with source codes in effect. 503 | 504 | --- 505 | 506 | ### getTopCategoriesOrdered 507 | 508 | **Signature:** `getTopCategoriesOrdered() : String[]` 509 | 510 | **Description:** Returns an array containing the IDs of up to the top 20 categories for customer orders, or an empty list if no categories have been set or the list of categories is no longer valid. The top category is the one for which the most orders for the customer contained at least one product found in that category. 511 | 512 | **Returns:** 513 | 514 | a list containing the top 20 categories. 515 | 516 | --- 517 | 518 | ### getVisitsMonth 519 | 520 | **Signature:** `getVisitsMonth() : Number` 521 | 522 | **Description:** Returns the visits of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid. 523 | 524 | **Returns:** 525 | 526 | the visits over the last 30 days. 527 | 528 | --- 529 | 530 | ### getVisitsWeek 531 | 532 | **Signature:** `getVisitsWeek() : Number` 533 | 534 | **Description:** Returns the visits of the customer, over the most recent 7 days, or null if none has been set or the value is no longer valid. 535 | 536 | **Returns:** 537 | 538 | the visits over the last 7 days. 539 | 540 | --- 541 | 542 | ### getVisitsYear 543 | 544 | **Signature:** `getVisitsYear() : Number` 545 | 546 | **Description:** Returns the visits of the customer, over the most recent 365 days, or null if none has been set or the value is no longer valid. 547 | 548 | **Returns:** 549 | 550 | the visits over the last 365 days. 551 | 552 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/search-best-practices.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | description: "SFCC Dev MCP Server - search_best_practices Tool Tests (Docs-Only Mode)" 2 | tests: 3 | # Basic functionality tests 4 | - it: "should return array of search results for validation query" 5 | request: 6 | jsonrpc: "2.0" 7 | id: "search-validation-1" 8 | method: "tools/call" 9 | params: 10 | name: "search_best_practices" 11 | arguments: 12 | query: "validation" 13 | expect: 14 | response: 15 | jsonrpc: "2.0" 16 | id: "search-validation-1" 17 | result: 18 | content: 19 | - type: "text" 20 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 21 | isError: false 22 | stderr: "toBeEmpty" 23 | 24 | - it: "should find security-related content in best practices" 25 | request: 26 | jsonrpc: "2.0" 27 | id: "search-security-1" 28 | method: "tools/call" 29 | params: 30 | name: "search_best_practices" 31 | arguments: 32 | query: "security" 33 | expect: 34 | response: 35 | jsonrpc: "2.0" 36 | id: "search-security-1" 37 | result: 38 | content: 39 | - type: "text" 40 | text: "match:contains:security" 41 | isError: false 42 | stderr: "toBeEmpty" 43 | 44 | - it: "should find performance-related content" 45 | request: 46 | jsonrpc: "2.0" 47 | id: "search-performance-1" 48 | method: "tools/call" 49 | params: 50 | name: "search_best_practices" 51 | arguments: 52 | query: "performance" 53 | expect: 54 | response: 55 | jsonrpc: "2.0" 56 | id: "search-performance-1" 57 | result: 58 | content: 59 | - type: "text" 60 | text: "match:contains:performance" 61 | isError: false 62 | stderr: "toBeEmpty" 63 | 64 | - it: "should find OCAPI hook references" 65 | request: 66 | jsonrpc: "2.0" 67 | id: "search-ocapi-1" 68 | method: "tools/call" 69 | params: 70 | name: "search_best_practices" 71 | arguments: 72 | query: "ocapi" 73 | expect: 74 | response: 75 | jsonrpc: "2.0" 76 | id: "search-ocapi-1" 77 | result: 78 | content: 79 | - type: "text" 80 | text: "match:contains:ocapi" 81 | isError: false 82 | stderr: "toBeEmpty" 83 | 84 | - it: "should find SCAPI-related content" 85 | request: 86 | jsonrpc: "2.0" 87 | id: "search-scapi-1" 88 | method: "tools/call" 89 | params: 90 | name: "search_best_practices" 91 | arguments: 92 | query: "scapi" 93 | expect: 94 | response: 95 | jsonrpc: "2.0" 96 | id: "search-scapi-1" 97 | result: 98 | content: 99 | - type: "text" 100 | text: "match:contains:scapi" 101 | isError: false 102 | stderr: "toBeEmpty" 103 | 104 | - it: "should find cartridge-related content" 105 | request: 106 | jsonrpc: "2.0" 107 | id: "search-cartridge-1" 108 | method: "tools/call" 109 | params: 110 | name: "search_best_practices" 111 | arguments: 112 | query: "cartridge" 113 | expect: 114 | response: 115 | jsonrpc: "2.0" 116 | id: "search-cartridge-1" 117 | result: 118 | content: 119 | - type: "text" 120 | text: "match:contains:cartridge" 121 | isError: false 122 | stderr: "toBeEmpty" 123 | 124 | - it: "should find ISML template references" 125 | request: 126 | jsonrpc: "2.0" 127 | id: "search-isml-1" 128 | method: "tools/call" 129 | params: 130 | name: "search_best_practices" 131 | arguments: 132 | query: "isml" 133 | expect: 134 | response: 135 | jsonrpc: "2.0" 136 | id: "search-isml-1" 137 | result: 138 | content: 139 | - type: "text" 140 | text: "match:contains:isml" 141 | isError: false 142 | stderr: "toBeEmpty" 143 | 144 | - it: "should find controller-related content" 145 | request: 146 | jsonrpc: "2.0" 147 | id: "search-controller-1" 148 | method: "tools/call" 149 | params: 150 | name: "search_best_practices" 151 | arguments: 152 | query: "controller" 153 | expect: 154 | response: 155 | jsonrpc: "2.0" 156 | id: "search-controller-1" 157 | result: 158 | content: 159 | - type: "text" 160 | text: "match:contains:controller" 161 | isError: false 162 | stderr: "toBeEmpty" 163 | 164 | - it: "should surface SFRA client-side JavaScript guidance" 165 | request: 166 | jsonrpc: "2.0" 167 | id: "search-client-js-1" 168 | method: "tools/call" 169 | params: 170 | name: "search_best_practices" 171 | arguments: 172 | query: "javascript" 173 | expect: 174 | response: 175 | jsonrpc: "2.0" 176 | id: "search-client-js-1" 177 | result: 178 | content: 179 | - type: "text" 180 | text: "match:contains:sfra_client_side_js" 181 | isError: false 182 | stderr: "toBeEmpty" 183 | 184 | - it: "should surface SFRA SCSS override guidance" 185 | request: 186 | jsonrpc: "2.0" 187 | id: "search-scss-1" 188 | method: "tools/call" 189 | params: 190 | name: "search_best_practices" 191 | arguments: 192 | query: "scss" 193 | expect: 194 | response: 195 | jsonrpc: "2.0" 196 | id: "search-scss-1" 197 | result: 198 | content: 199 | - type: "text" 200 | text: "match:contains:sfra_scss" 201 | isError: false 202 | stderr: "toBeEmpty" 203 | 204 | - it: "should find job framework references" 205 | request: 206 | jsonrpc: "2.0" 207 | id: "search-job-1" 208 | method: "tools/call" 209 | params: 210 | name: "search_best_practices" 211 | arguments: 212 | query: "job" 213 | expect: 214 | response: 215 | jsonrpc: "2.0" 216 | id: "search-job-1" 217 | result: 218 | content: 219 | - type: "text" 220 | text: "match:contains:job" 221 | isError: false 222 | stderr: "toBeEmpty" 223 | 224 | # Edge cases and error handling 225 | - it: "should return empty array for non-existent terms" 226 | request: 227 | jsonrpc: "2.0" 228 | id: "search-empty-1" 229 | method: "tools/call" 230 | params: 231 | name: "search_best_practices" 232 | arguments: 233 | query: "zzznomatchesexpected" 234 | expect: 235 | response: 236 | jsonrpc: "2.0" 237 | id: "search-empty-1" 238 | result: 239 | content: 240 | - type: "text" 241 | text: "match:regex:^\\[\\s*\\]$" # Empty array 242 | isError: false 243 | stderr: "toBeEmpty" 244 | 245 | - it: "should handle empty query with error" 246 | request: 247 | jsonrpc: "2.0" 248 | id: "search-error-1" 249 | method: "tools/call" 250 | params: 251 | name: "search_best_practices" 252 | arguments: 253 | query: "" 254 | expect: 255 | response: 256 | jsonrpc: "2.0" 257 | id: "search-error-1" 258 | result: 259 | content: 260 | - type: "text" 261 | text: "match:contains:Error" 262 | isError: true 263 | stderr: "toBeEmpty" 264 | 265 | - it: "should handle missing query parameter with error" 266 | request: 267 | jsonrpc: "2.0" 268 | id: "search-missing-1" 269 | method: "tools/call" 270 | params: 271 | name: "search_best_practices" 272 | arguments: {} 273 | expect: 274 | response: 275 | jsonrpc: "2.0" 276 | id: "search-missing-1" 277 | result: 278 | content: 279 | - type: "text" 280 | text: "match:contains:Error" 281 | isError: true 282 | stderr: "toBeEmpty" 283 | 284 | # Response structure validation tests 285 | - it: "should validate detailed response structure for validation search" 286 | request: 287 | jsonrpc: "2.0" 288 | id: "search-structure-1" 289 | method: "tools/call" 290 | params: 291 | name: "search_best_practices" 292 | arguments: 293 | query: "validation" 294 | expect: 295 | response: 296 | jsonrpc: "2.0" 297 | id: "search-structure-1" 298 | result: 299 | content: 300 | - type: "text" 301 | text: "match:regex:^\\[[\\s\\S]*\\]$" # JSON array format 302 | isError: false 303 | stderr: "toBeEmpty" 304 | 305 | # Content validation tests for specific guides 306 | - it: "should find form validation in ISML templates guide" 307 | request: 308 | jsonrpc: "2.0" 309 | id: "search-form-validation-1" 310 | method: "tools/call" 311 | params: 312 | name: "search_best_practices" 313 | arguments: 314 | query: "form" 315 | expect: 316 | response: 317 | jsonrpc: "2.0" 318 | id: "search-form-validation-1" 319 | result: 320 | content: 321 | - type: "text" 322 | text: "match:contains:form" 323 | isError: false 324 | stderr: "toBeEmpty" 325 | 326 | - it: "should find authorization patterns in hook guides" 327 | request: 328 | jsonrpc: "2.0" 329 | id: "search-authorization-1" 330 | method: "tools/call" 331 | params: 332 | name: "search_best_practices" 333 | arguments: 334 | query: "authorization" 335 | expect: 336 | response: 337 | jsonrpc: "2.0" 338 | id: "search-authorization-1" 339 | result: 340 | content: 341 | - type: "text" 342 | text: "match:contains:authorization" 343 | isError: false 344 | stderr: "toBeEmpty" 345 | 346 | - it: "should find middleware patterns" 347 | request: 348 | jsonrpc: "2.0" 349 | id: "search-middleware-1" 350 | method: "tools/call" 351 | params: 352 | name: "search_best_practices" 353 | arguments: 354 | query: "middleware" 355 | expect: 356 | response: 357 | jsonrpc: "2.0" 358 | id: "search-middleware-1" 359 | result: 360 | content: 361 | - type: "text" 362 | text: "match:contains:middleware" 363 | isError: false 364 | stderr: "toBeEmpty" 365 | 366 | - it: "should find transaction handling patterns" 367 | request: 368 | jsonrpc: "2.0" 369 | id: "search-transaction-1" 370 | method: "tools/call" 371 | params: 372 | name: "search_best_practices" 373 | arguments: 374 | query: "transaction" 375 | expect: 376 | response: 377 | jsonrpc: "2.0" 378 | id: "search-transaction-1" 379 | result: 380 | content: 381 | - type: "text" 382 | text: "match:contains:transaction" 383 | isError: false 384 | stderr: "toBeEmpty" 385 | 386 | - it: "should find error handling patterns" 387 | request: 388 | jsonrpc: "2.0" 389 | id: "search-error-handling-1" 390 | method: "tools/call" 391 | params: 392 | name: "search_best_practices" 393 | arguments: 394 | query: "error" 395 | expect: 396 | response: 397 | jsonrpc: "2.0" 398 | id: "search-error-handling-1" 399 | result: 400 | content: 401 | - type: "text" 402 | text: "match:contains:error" 403 | isError: false 404 | stderr: "toBeEmpty" 405 | 406 | # Case sensitivity tests 407 | - it: "should handle case insensitive search for SFRA" 408 | request: 409 | jsonrpc: "2.0" 410 | id: "search-case-1" 411 | method: "tools/call" 412 | params: 413 | name: "search_best_practices" 414 | arguments: 415 | query: "SFRA" 416 | expect: 417 | response: 418 | jsonrpc: "2.0" 419 | id: "search-case-1" 420 | result: 421 | content: 422 | - type: "text" 423 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 424 | isError: false 425 | stderr: "toBeEmpty" 426 | 427 | - it: "should handle lowercase search terms" 428 | request: 429 | jsonrpc: "2.0" 430 | id: "search-lowercase-1" 431 | method: "tools/call" 432 | params: 433 | name: "search_best_practices" 434 | arguments: 435 | query: "api" 436 | expect: 437 | response: 438 | jsonrpc: "2.0" 439 | id: "search-lowercase-1" 440 | result: 441 | content: 442 | - type: "text" 443 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 444 | isError: false 445 | stderr: "toBeEmpty" 446 | 447 | # Multiple word search tests 448 | - it: "should find authentication patterns" 449 | request: 450 | jsonrpc: "2.0" 451 | id: "search-auth-1" 452 | method: "tools/call" 453 | params: 454 | name: "search_best_practices" 455 | arguments: 456 | query: "authentication" 457 | expect: 458 | response: 459 | jsonrpc: "2.0" 460 | id: "search-auth-1" 461 | result: 462 | content: 463 | - type: "text" 464 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 465 | isError: false 466 | stderr: "toBeEmpty" 467 | 468 | - it: "should find encryption references" 469 | request: 470 | jsonrpc: "2.0" 471 | id: "search-encryption-1" 472 | method: "tools/call" 473 | params: 474 | name: "search_best_practices" 475 | arguments: 476 | query: "encryption" 477 | expect: 478 | response: 479 | jsonrpc: "2.0" 480 | id: "search-encryption-1" 481 | result: 482 | content: 483 | - type: "text" 484 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 485 | isError: false 486 | stderr: "toBeEmpty" 487 | 488 | # Special characters handling 489 | - it: "should handle search terms with special characters" 490 | request: 491 | jsonrpc: "2.0" 492 | id: "search-special-1" 493 | method: "tools/call" 494 | params: 495 | name: "search_best_practices" 496 | arguments: 497 | query: "REST" 498 | expect: 499 | response: 500 | jsonrpc: "2.0" 501 | id: "search-special-1" 502 | result: 503 | content: 504 | - type: "text" 505 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 506 | isError: false 507 | stderr: "toBeEmpty" 508 | 509 | - it: "should handle numeric search terms" 510 | request: 511 | jsonrpc: "2.0" 512 | id: "search-numeric-1" 513 | method: "tools/call" 514 | params: 515 | name: "search_best_practices" 516 | arguments: 517 | query: "HTTPS" 518 | expect: 519 | response: 520 | jsonrpc: "2.0" 521 | id: "search-numeric-1" 522 | result: 523 | content: 524 | - type: "text" 525 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 526 | isError: false 527 | stderr: "toBeEmpty" 528 | 529 | # Common development terms 530 | - it: "should find model-related patterns" 531 | request: 532 | jsonrpc: "2.0" 533 | id: "search-model-1" 534 | method: "tools/call" 535 | params: 536 | name: "search_best_practices" 537 | arguments: 538 | query: "model" 539 | expect: 540 | response: 541 | jsonrpc: "2.0" 542 | id: "search-model-1" 543 | result: 544 | content: 545 | - type: "text" 546 | text: "match:contains:model" 547 | isError: false 548 | stderr: "toBeEmpty" 549 | 550 | - it: "should find configuration patterns" 551 | request: 552 | jsonrpc: "2.0" 553 | id: "search-config-1" 554 | method: "tools/call" 555 | params: 556 | name: "search_best_practices" 557 | arguments: 558 | query: "config" 559 | expect: 560 | response: 561 | jsonrpc: "2.0" 562 | id: "search-config-1" 563 | result: 564 | content: 565 | - type: "text" 566 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 567 | isError: false 568 | stderr: "toBeEmpty" 569 | 570 | - it: "should find database transaction patterns" 571 | request: 572 | jsonrpc: "2.0" 573 | id: "search-database-1" 574 | method: "tools/call" 575 | params: 576 | name: "search_best_practices" 577 | arguments: 578 | query: "database" 579 | expect: 580 | response: 581 | jsonrpc: "2.0" 582 | id: "search-database-1" 583 | result: 584 | content: 585 | - type: "text" 586 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 587 | isError: false 588 | stderr: "toBeEmpty" 589 | 590 | # Performance testing with timing 591 | - it: "should respond quickly for validation search" 592 | request: 593 | jsonrpc: "2.0" 594 | id: "search-perf-1" 595 | method: "tools/call" 596 | params: 597 | name: "search_best_practices" 598 | arguments: 599 | query: "validation" 600 | expect: 601 | response: 602 | jsonrpc: "2.0" 603 | id: "search-perf-1" 604 | result: 605 | content: 606 | - type: "text" 607 | text: "match:regex:^\\[[\\s\\S]*\\]$" 608 | isError: false 609 | performance: 610 | maxResponseTime: "2000ms" # Documentation search should be fast 611 | stderr: "toBeEmpty" 612 | 613 | - it: "should respond quickly for empty results" 614 | request: 615 | jsonrpc: "2.0" 616 | id: "search-perf-empty-1" 617 | method: "tools/call" 618 | params: 619 | name: "search_best_practices" 620 | arguments: 621 | query: "zzznomatchesexpected" 622 | expect: 623 | response: 624 | jsonrpc: "2.0" 625 | id: "search-perf-empty-1" 626 | result: 627 | content: 628 | - type: "text" 629 | text: "match:regex:^\\[\\s*\\]$" 630 | isError: false 631 | performance: 632 | maxResponseTime: "1000ms" # Empty results should be very fast 633 | stderr: "toBeEmpty" 634 | 635 | - it: "should handle errors quickly" 636 | request: 637 | jsonrpc: "2.0" 638 | id: "search-perf-error-1" 639 | method: "tools/call" 640 | params: 641 | name: "search_best_practices" 642 | arguments: 643 | query: "" 644 | expect: 645 | response: 646 | jsonrpc: "2.0" 647 | id: "search-perf-error-1" 648 | result: 649 | content: 650 | - type: "text" 651 | text: "match:contains:Error" 652 | isError: true 653 | performance: 654 | maxResponseTime: "500ms" # Error responses should be fastest 655 | stderr: "toBeEmpty" 656 | ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/search-best-practices.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | description: "SFCC Dev MCP Server - search_best_practices Tool Tests (Docs-Only Mode)" 2 | tests: 3 | # Basic functionality tests 4 | - it: "should return array of search results for validation query" 5 | request: 6 | jsonrpc: "2.0" 7 | id: "search-validation-1" 8 | method: "tools/call" 9 | params: 10 | name: "search_best_practices" 11 | arguments: 12 | query: "validation" 13 | expect: 14 | response: 15 | jsonrpc: "2.0" 16 | id: "search-validation-1" 17 | result: 18 | content: 19 | - type: "text" 20 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 21 | isError: false 22 | stderr: "toBeEmpty" 23 | 24 | - it: "should find security-related content in best practices" 25 | request: 26 | jsonrpc: "2.0" 27 | id: "search-security-1" 28 | method: "tools/call" 29 | params: 30 | name: "search_best_practices" 31 | arguments: 32 | query: "security" 33 | expect: 34 | response: 35 | jsonrpc: "2.0" 36 | id: "search-security-1" 37 | result: 38 | content: 39 | - type: "text" 40 | text: "match:contains:security" 41 | isError: false 42 | stderr: "toBeEmpty" 43 | 44 | - it: "should find performance-related content" 45 | request: 46 | jsonrpc: "2.0" 47 | id: "search-performance-1" 48 | method: "tools/call" 49 | params: 50 | name: "search_best_practices" 51 | arguments: 52 | query: "performance" 53 | expect: 54 | response: 55 | jsonrpc: "2.0" 56 | id: "search-performance-1" 57 | result: 58 | content: 59 | - type: "text" 60 | text: "match:contains:performance" 61 | isError: false 62 | stderr: "toBeEmpty" 63 | 64 | - it: "should find OCAPI hook references" 65 | request: 66 | jsonrpc: "2.0" 67 | id: "search-ocapi-1" 68 | method: "tools/call" 69 | params: 70 | name: "search_best_practices" 71 | arguments: 72 | query: "ocapi" 73 | expect: 74 | response: 75 | jsonrpc: "2.0" 76 | id: "search-ocapi-1" 77 | result: 78 | content: 79 | - type: "text" 80 | text: "match:contains:ocapi" 81 | isError: false 82 | stderr: "toBeEmpty" 83 | 84 | - it: "should find SCAPI-related content" 85 | request: 86 | jsonrpc: "2.0" 87 | id: "search-scapi-1" 88 | method: "tools/call" 89 | params: 90 | name: "search_best_practices" 91 | arguments: 92 | query: "scapi" 93 | expect: 94 | response: 95 | jsonrpc: "2.0" 96 | id: "search-scapi-1" 97 | result: 98 | content: 99 | - type: "text" 100 | text: "match:contains:scapi" 101 | isError: false 102 | stderr: "toBeEmpty" 103 | 104 | - it: "should find cartridge-related content" 105 | request: 106 | jsonrpc: "2.0" 107 | id: "search-cartridge-1" 108 | method: "tools/call" 109 | params: 110 | name: "search_best_practices" 111 | arguments: 112 | query: "cartridge" 113 | expect: 114 | response: 115 | jsonrpc: "2.0" 116 | id: "search-cartridge-1" 117 | result: 118 | content: 119 | - type: "text" 120 | text: "match:contains:cartridge" 121 | isError: false 122 | stderr: "toBeEmpty" 123 | 124 | - it: "should find ISML template references" 125 | request: 126 | jsonrpc: "2.0" 127 | id: "search-isml-1" 128 | method: "tools/call" 129 | params: 130 | name: "search_best_practices" 131 | arguments: 132 | query: "isml" 133 | expect: 134 | response: 135 | jsonrpc: "2.0" 136 | id: "search-isml-1" 137 | result: 138 | content: 139 | - type: "text" 140 | text: "match:contains:isml" 141 | isError: false 142 | stderr: "toBeEmpty" 143 | 144 | - it: "should find controller-related content" 145 | request: 146 | jsonrpc: "2.0" 147 | id: "search-controller-1" 148 | method: "tools/call" 149 | params: 150 | name: "search_best_practices" 151 | arguments: 152 | query: "controller" 153 | expect: 154 | response: 155 | jsonrpc: "2.0" 156 | id: "search-controller-1" 157 | result: 158 | content: 159 | - type: "text" 160 | text: "match:contains:controller" 161 | isError: false 162 | stderr: "toBeEmpty" 163 | 164 | - it: "should surface SFRA client-side JavaScript guidance" 165 | request: 166 | jsonrpc: "2.0" 167 | id: "search-client-js-1" 168 | method: "tools/call" 169 | params: 170 | name: "search_best_practices" 171 | arguments: 172 | query: "javascript" 173 | expect: 174 | response: 175 | jsonrpc: "2.0" 176 | id: "search-client-js-1" 177 | result: 178 | content: 179 | - type: "text" 180 | text: "match:contains:sfra_client_side_js" 181 | isError: false 182 | stderr: "toBeEmpty" 183 | 184 | - it: "should surface SFRA SCSS override guidance" 185 | request: 186 | jsonrpc: "2.0" 187 | id: "search-scss-1" 188 | method: "tools/call" 189 | params: 190 | name: "search_best_practices" 191 | arguments: 192 | query: "scss" 193 | expect: 194 | response: 195 | jsonrpc: "2.0" 196 | id: "search-scss-1" 197 | result: 198 | content: 199 | - type: "text" 200 | text: "match:contains:sfra_scss" 201 | isError: false 202 | stderr: "toBeEmpty" 203 | 204 | - it: "should find job framework references" 205 | request: 206 | jsonrpc: "2.0" 207 | id: "search-job-1" 208 | method: "tools/call" 209 | params: 210 | name: "search_best_practices" 211 | arguments: 212 | query: "job" 213 | expect: 214 | response: 215 | jsonrpc: "2.0" 216 | id: "search-job-1" 217 | result: 218 | content: 219 | - type: "text" 220 | text: "match:contains:job" 221 | isError: false 222 | stderr: "toBeEmpty" 223 | 224 | # Edge cases and error handling 225 | - it: "should return empty array for non-existent terms" 226 | request: 227 | jsonrpc: "2.0" 228 | id: "search-empty-1" 229 | method: "tools/call" 230 | params: 231 | name: "search_best_practices" 232 | arguments: 233 | query: "zzznomatchesexpected" 234 | expect: 235 | response: 236 | jsonrpc: "2.0" 237 | id: "search-empty-1" 238 | result: 239 | content: 240 | - type: "text" 241 | text: "match:regex:^\\[\\s*\\]$" # Empty array 242 | isError: false 243 | stderr: "toBeEmpty" 244 | 245 | - it: "should handle empty query with error" 246 | request: 247 | jsonrpc: "2.0" 248 | id: "search-error-1" 249 | method: "tools/call" 250 | params: 251 | name: "search_best_practices" 252 | arguments: 253 | query: "" 254 | expect: 255 | response: 256 | jsonrpc: "2.0" 257 | id: "search-error-1" 258 | result: 259 | content: 260 | - type: "text" 261 | text: "match:contains:Error" 262 | isError: true 263 | stderr: "toBeEmpty" 264 | 265 | - it: "should handle missing query parameter with error" 266 | request: 267 | jsonrpc: "2.0" 268 | id: "search-missing-1" 269 | method: "tools/call" 270 | params: 271 | name: "search_best_practices" 272 | arguments: {} 273 | expect: 274 | response: 275 | jsonrpc: "2.0" 276 | id: "search-missing-1" 277 | result: 278 | content: 279 | - type: "text" 280 | text: "match:contains:Error" 281 | isError: true 282 | stderr: "toBeEmpty" 283 | 284 | # Response structure validation tests 285 | - it: "should validate detailed response structure for validation search" 286 | request: 287 | jsonrpc: "2.0" 288 | id: "search-structure-1" 289 | method: "tools/call" 290 | params: 291 | name: "search_best_practices" 292 | arguments: 293 | query: "validation" 294 | expect: 295 | response: 296 | jsonrpc: "2.0" 297 | id: "search-structure-1" 298 | result: 299 | content: 300 | - type: "text" 301 | text: "match:regex:^\\[[\\s\\S]*\\]$" # JSON array format 302 | isError: false 303 | stderr: "toBeEmpty" 304 | 305 | # Content validation tests for specific guides 306 | - it: "should find form validation in ISML templates guide" 307 | request: 308 | jsonrpc: "2.0" 309 | id: "search-form-validation-1" 310 | method: "tools/call" 311 | params: 312 | name: "search_best_practices" 313 | arguments: 314 | query: "form" 315 | expect: 316 | response: 317 | jsonrpc: "2.0" 318 | id: "search-form-validation-1" 319 | result: 320 | content: 321 | - type: "text" 322 | text: "match:contains:form" 323 | isError: false 324 | stderr: "toBeEmpty" 325 | 326 | - it: "should find authorization patterns in hook guides" 327 | request: 328 | jsonrpc: "2.0" 329 | id: "search-authorization-1" 330 | method: "tools/call" 331 | params: 332 | name: "search_best_practices" 333 | arguments: 334 | query: "authorization" 335 | expect: 336 | response: 337 | jsonrpc: "2.0" 338 | id: "search-authorization-1" 339 | result: 340 | content: 341 | - type: "text" 342 | text: "match:contains:authorization" 343 | isError: false 344 | stderr: "toBeEmpty" 345 | 346 | - it: "should find middleware patterns" 347 | request: 348 | jsonrpc: "2.0" 349 | id: "search-middleware-1" 350 | method: "tools/call" 351 | params: 352 | name: "search_best_practices" 353 | arguments: 354 | query: "middleware" 355 | expect: 356 | response: 357 | jsonrpc: "2.0" 358 | id: "search-middleware-1" 359 | result: 360 | content: 361 | - type: "text" 362 | text: "match:contains:middleware" 363 | isError: false 364 | stderr: "toBeEmpty" 365 | 366 | - it: "should find transaction handling patterns" 367 | request: 368 | jsonrpc: "2.0" 369 | id: "search-transaction-1" 370 | method: "tools/call" 371 | params: 372 | name: "search_best_practices" 373 | arguments: 374 | query: "transaction" 375 | expect: 376 | response: 377 | jsonrpc: "2.0" 378 | id: "search-transaction-1" 379 | result: 380 | content: 381 | - type: "text" 382 | text: "match:contains:transaction" 383 | isError: false 384 | stderr: "toBeEmpty" 385 | 386 | - it: "should find error handling patterns" 387 | request: 388 | jsonrpc: "2.0" 389 | id: "search-error-handling-1" 390 | method: "tools/call" 391 | params: 392 | name: "search_best_practices" 393 | arguments: 394 | query: "error" 395 | expect: 396 | response: 397 | jsonrpc: "2.0" 398 | id: "search-error-handling-1" 399 | result: 400 | content: 401 | - type: "text" 402 | text: "match:contains:error" 403 | isError: false 404 | stderr: "toBeEmpty" 405 | 406 | # Case sensitivity tests 407 | - it: "should handle case insensitive search for SFRA" 408 | request: 409 | jsonrpc: "2.0" 410 | id: "search-case-1" 411 | method: "tools/call" 412 | params: 413 | name: "search_best_practices" 414 | arguments: 415 | query: "SFRA" 416 | expect: 417 | response: 418 | jsonrpc: "2.0" 419 | id: "search-case-1" 420 | result: 421 | content: 422 | - type: "text" 423 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 424 | isError: false 425 | stderr: "toBeEmpty" 426 | 427 | - it: "should handle lowercase search terms" 428 | request: 429 | jsonrpc: "2.0" 430 | id: "search-lowercase-1" 431 | method: "tools/call" 432 | params: 433 | name: "search_best_practices" 434 | arguments: 435 | query: "api" 436 | expect: 437 | response: 438 | jsonrpc: "2.0" 439 | id: "search-lowercase-1" 440 | result: 441 | content: 442 | - type: "text" 443 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 444 | isError: false 445 | stderr: "toBeEmpty" 446 | 447 | # Multiple word search tests 448 | - it: "should find authentication patterns" 449 | request: 450 | jsonrpc: "2.0" 451 | id: "search-auth-1" 452 | method: "tools/call" 453 | params: 454 | name: "search_best_practices" 455 | arguments: 456 | query: "authentication" 457 | expect: 458 | response: 459 | jsonrpc: "2.0" 460 | id: "search-auth-1" 461 | result: 462 | content: 463 | - type: "text" 464 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 465 | isError: false 466 | stderr: "toBeEmpty" 467 | 468 | - it: "should find encryption references" 469 | request: 470 | jsonrpc: "2.0" 471 | id: "search-encryption-1" 472 | method: "tools/call" 473 | params: 474 | name: "search_best_practices" 475 | arguments: 476 | query: "encryption" 477 | expect: 478 | response: 479 | jsonrpc: "2.0" 480 | id: "search-encryption-1" 481 | result: 482 | content: 483 | - type: "text" 484 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 485 | isError: false 486 | stderr: "toBeEmpty" 487 | 488 | # Special characters handling 489 | - it: "should handle search terms with special characters" 490 | request: 491 | jsonrpc: "2.0" 492 | id: "search-special-1" 493 | method: "tools/call" 494 | params: 495 | name: "search_best_practices" 496 | arguments: 497 | query: "REST" 498 | expect: 499 | response: 500 | jsonrpc: "2.0" 501 | id: "search-special-1" 502 | result: 503 | content: 504 | - type: "text" 505 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 506 | isError: false 507 | stderr: "toBeEmpty" 508 | 509 | - it: "should handle numeric search terms" 510 | request: 511 | jsonrpc: "2.0" 512 | id: "search-numeric-1" 513 | method: "tools/call" 514 | params: 515 | name: "search_best_practices" 516 | arguments: 517 | query: "HTTPS" 518 | expect: 519 | response: 520 | jsonrpc: "2.0" 521 | id: "search-numeric-1" 522 | result: 523 | content: 524 | - type: "text" 525 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 526 | isError: false 527 | stderr: "toBeEmpty" 528 | 529 | # Common development terms 530 | - it: "should find model-related patterns" 531 | request: 532 | jsonrpc: "2.0" 533 | id: "search-model-1" 534 | method: "tools/call" 535 | params: 536 | name: "search_best_practices" 537 | arguments: 538 | query: "model" 539 | expect: 540 | response: 541 | jsonrpc: "2.0" 542 | id: "search-model-1" 543 | result: 544 | content: 545 | - type: "text" 546 | text: "match:contains:model" 547 | isError: false 548 | stderr: "toBeEmpty" 549 | 550 | - it: "should find configuration patterns" 551 | request: 552 | jsonrpc: "2.0" 553 | id: "search-config-1" 554 | method: "tools/call" 555 | params: 556 | name: "search_best_practices" 557 | arguments: 558 | query: "config" 559 | expect: 560 | response: 561 | jsonrpc: "2.0" 562 | id: "search-config-1" 563 | result: 564 | content: 565 | - type: "text" 566 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 567 | isError: false 568 | stderr: "toBeEmpty" 569 | 570 | - it: "should find database transaction patterns" 571 | request: 572 | jsonrpc: "2.0" 573 | id: "search-database-1" 574 | method: "tools/call" 575 | params: 576 | name: "search_best_practices" 577 | arguments: 578 | query: "database" 579 | expect: 580 | response: 581 | jsonrpc: "2.0" 582 | id: "search-database-1" 583 | result: 584 | content: 585 | - type: "text" 586 | text: "match:regex:^\\[[\\s\\S]*\\]$" # Valid JSON array 587 | isError: false 588 | stderr: "toBeEmpty" 589 | 590 | # Performance testing with timing 591 | - it: "should respond quickly for validation search" 592 | request: 593 | jsonrpc: "2.0" 594 | id: "search-perf-1" 595 | method: "tools/call" 596 | params: 597 | name: "search_best_practices" 598 | arguments: 599 | query: "validation" 600 | expect: 601 | response: 602 | jsonrpc: "2.0" 603 | id: "search-perf-1" 604 | result: 605 | content: 606 | - type: "text" 607 | text: "match:regex:^\\[[\\s\\S]*\\]$" 608 | isError: false 609 | performance: 610 | maxResponseTime: "2000ms" # Documentation search should be fast 611 | stderr: "toBeEmpty" 612 | 613 | - it: "should respond quickly for empty results" 614 | request: 615 | jsonrpc: "2.0" 616 | id: "search-perf-empty-1" 617 | method: "tools/call" 618 | params: 619 | name: "search_best_practices" 620 | arguments: 621 | query: "zzznomatchesexpected" 622 | expect: 623 | response: 624 | jsonrpc: "2.0" 625 | id: "search-perf-empty-1" 626 | result: 627 | content: 628 | - type: "text" 629 | text: "match:regex:^\\[\\s*\\]$" 630 | isError: false 631 | performance: 632 | maxResponseTime: "1000ms" # Empty results should be very fast 633 | stderr: "toBeEmpty" 634 | 635 | - it: "should handle errors quickly" 636 | request: 637 | jsonrpc: "2.0" 638 | id: "search-perf-error-1" 639 | method: "tools/call" 640 | params: 641 | name: "search_best_practices" 642 | arguments: 643 | query: "" 644 | expect: 645 | response: 646 | jsonrpc: "2.0" 647 | id: "search-perf-error-1" 648 | result: 649 | content: 650 | - type: "text" 651 | text: "match:contains:Error" 652 | isError: true 653 | performance: 654 | maxResponseTime: "500ms" # Error responses should be fastest 655 | stderr: "toBeEmpty" 656 | ``` -------------------------------------------------------------------------------- /docs/dw_net/SFTPClient.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.net 2 | 3 | # Class SFTPClient 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.net.SFTPClient 9 | 10 | ## Description 11 | 12 | The SFTPClient class supports the SFTP commands GET, PUT, DEL, MKDIR, RENAME, and LIST. The transfer of files can be text or binary. Note: when this class is used with sensitive data, be careful in persisting sensitive information. An example usage is as follows: var sftp : SFTPClient = new dw.net.SFTPClient(); sftp.connect("my.sftp-server.com", "username", "password"); var data : String = sftp.get("simple.txt"); sftp.disconnect(); The default connection timeout depends on the script context timeout and will be set to a maximum of 30 seconds (default script context timeout is 10 seconds within storefront requests and 15 minutes within jobs). IMPORTANT NOTE: Before you can make an outbound SFTP connection to a port other than 22, the SFTP server IP address must be enabled for outbound traffic at the Commerce Cloud Digital firewall for your POD. Please file a support request to request a new firewall rule. SSH Version 2 is supported with the following algorithms: TypeAlgorithms Host Keyssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, rsa-sha2-512, rsa-sha2-256, ssh-rsa, ssh-dss Key Exchange (KEX)curve25519-sha256, [email protected], ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1, diffie-hellman-group1-sha1 Cipheraes128-ctr, aes192-ctr, aes256-ctr, [email protected], [email protected], aes128-cbc, 3des-ctr, 3des-cbc, blowfish-cbc, aes192-cbc, aes256-cbc Message Authentication Code (MAC)[email protected], [email protected], [email protected], hmac-sha2-256, hmac-sha2-512, hmac-sha1, hmac-md5, hmac-sha1-96, hmac-md5-96 Public Key Authenticationrsa-sha2-512, rsa-sha2-256, ssh-rsa 13 | 14 | ## Constants 15 | 16 | ### MAX_GET_FILE_SIZE 17 | 18 | **Type:** Number = 209715200 19 | 20 | The maximum size for get() methods returning a File is 200 MB. 21 | 22 | ### MAX_GET_STRING_SIZE 23 | 24 | **Type:** Number = 10485760 25 | 26 | The maximum size for get() methods returning a String is 10 MB. 27 | 28 | ## Properties 29 | 30 | ### connected 31 | 32 | **Type:** boolean (Read Only) 33 | 34 | Identifies if the SFTP client is currently connected to the SFTP server. 35 | 36 | ### errorMessage 37 | 38 | **Type:** String (Read Only) 39 | 40 | The error message from the last SFTP action. 41 | 42 | ### identity 43 | 44 | **Type:** KeyRef 45 | 46 | Gets the identity (private key) used for the connection. 47 | 48 | The key is only associated to this instance of the SFTP client. 49 | 50 | ### timeout 51 | 52 | **Type:** Number 53 | 54 | The timeout for this client, in milliseconds. 55 | 56 | ## Constructor Summary 57 | 58 | SFTPClient() Constructor. 59 | 60 | ## Method Summary 61 | 62 | ### addKnownHostKey 63 | 64 | **Signature:** `addKnownHostKey(type : String, key : String) : void` 65 | 66 | Adds a known public host key for the next connection attempt. 67 | 68 | ### cd 69 | 70 | **Signature:** `cd(path : String) : boolean` 71 | 72 | Changes the current directory on the remote server to the given path. 73 | 74 | ### connect 75 | 76 | **Signature:** `connect(host : String, user : String, password : String) : boolean` 77 | 78 | Connects and logs on to a SFTP server and returns a boolean indicating success or failure. 79 | 80 | ### connect 81 | 82 | **Signature:** `connect(host : String, port : Number, user : String, password : String) : boolean` 83 | 84 | Connects and logs on to a SFTP server and returns a boolean indicating success or failure. 85 | 86 | ### del 87 | 88 | **Signature:** `del(path : String) : boolean` 89 | 90 | Deletes the remote file on the server identified by the path parameter. 91 | 92 | ### disconnect 93 | 94 | **Signature:** `disconnect() : void` 95 | 96 | The method first logs the current user out from the server and then disconnects from the server. 97 | 98 | ### get 99 | 100 | **Signature:** `get(path : String) : String` 101 | 102 | Reads the content of a remote file and returns it as a string using "ISO-8859-1" encoding to read it. 103 | 104 | ### get 105 | 106 | **Signature:** `get(path : String, encoding : String) : String` 107 | 108 | Reads the content of a remote file and returns it as a string using the specified encoding. 109 | 110 | ### get 111 | 112 | **Signature:** `get(path : String, encoding : String, file : File) : boolean` 113 | 114 | Reads the content of a remote file and creates a local copy in the given file using the passed string encoding to read the file content and using the system standard encoding "UTF-8" to write the file. 115 | 116 | ### getBinary 117 | 118 | **Signature:** `getBinary(path : String, file : File) : boolean` 119 | 120 | Reads the content of a remote file and creates a local copy in the given file. 121 | 122 | ### getConnected 123 | 124 | **Signature:** `getConnected() : boolean` 125 | 126 | Identifies if the SFTP client is currently connected to the SFTP server. 127 | 128 | ### getErrorMessage 129 | 130 | **Signature:** `getErrorMessage() : String` 131 | 132 | Returns the error message from the last SFTP action. 133 | 134 | ### getFileInfo 135 | 136 | **Signature:** `getFileInfo(path : String) : SFTPFileInfo` 137 | 138 | Returns a SFTPFileInfo objects containing information about the given file/directory path. 139 | 140 | ### getIdentity 141 | 142 | **Signature:** `getIdentity() : KeyRef` 143 | 144 | Gets the identity (private key) used for the connection. 145 | 146 | ### getTimeout 147 | 148 | **Signature:** `getTimeout() : Number` 149 | 150 | Returns the timeout for this client, in milliseconds. 151 | 152 | ### list 153 | 154 | **Signature:** `list() : SFTPFileInfo[]` 155 | 156 | Returns a list of SFTPFileInfo objects containing information about the files in the current directory. 157 | 158 | ### list 159 | 160 | **Signature:** `list(path : String) : SFTPFileInfo[]` 161 | 162 | Returns a list of SFTPFileInfo objects containing information about the files in the remote directory defined by the given path. 163 | 164 | ### mkdir 165 | 166 | **Signature:** `mkdir(path : String) : boolean` 167 | 168 | Creates a directory 169 | 170 | ### put 171 | 172 | **Signature:** `put(path : String, content : String) : boolean` 173 | 174 | Puts the specified content to the specified path using "ISO-8859-1" encoding. 175 | 176 | ### put 177 | 178 | **Signature:** `put(path : String, content : String, encoding : String) : boolean` 179 | 180 | Put the given content to a file on the given path on the SFTP server. 181 | 182 | ### putBinary 183 | 184 | **Signature:** `putBinary(path : String, file : File) : boolean` 185 | 186 | Put the content of the given file into a file on the remote SFTP server with the given absolute path. 187 | 188 | ### removeDirectory 189 | 190 | **Signature:** `removeDirectory(path : String) : boolean` 191 | 192 | Deletes the remote directory on the server identified by the path parameter. 193 | 194 | ### rename 195 | 196 | **Signature:** `rename(from : String, to : String) : boolean` 197 | 198 | Renames an existing file. 199 | 200 | ### setIdentity 201 | 202 | **Signature:** `setIdentity(keyRef : KeyRef) : void` 203 | 204 | Sets the identity (private key) to use for the next connection attempt. 205 | 206 | ### setTimeout 207 | 208 | **Signature:** `setTimeout(timeoutMillis : Number) : void` 209 | 210 | Sets the timeout for connections made with the SFTP client to the given number of milliseconds. 211 | 212 | ## Constructor Detail 213 | 214 | ## Method Detail 215 | 216 | ## Method Details 217 | 218 | ### addKnownHostKey 219 | 220 | **Signature:** `addKnownHostKey(type : String, key : String) : void` 221 | 222 | **Description:** Adds a known public host key for the next connection attempt. This method associates the key to the host used in the subsequent connect method, and must be called prior to connect. The key is not persisted, and is only associated to this instance of the SFTP client. Multiple keys may be added, and the validation will succeed if the remote host matches any of them. The default behavior is to persist and trust an unknown host key if there are no known host keys available. If addKnownHostKey is later used to trust specific a specific key or keys, then any previously persisted keys will be ignored. 223 | 224 | **Parameters:** 225 | 226 | - `type`: Type of the host key, such as ssh-rsa 227 | - `key`: Public host key, in the same format as OpenSSH. 228 | 229 | --- 230 | 231 | ### cd 232 | 233 | **Signature:** `cd(path : String) : boolean` 234 | 235 | **Description:** Changes the current directory on the remote server to the given path. 236 | 237 | **Parameters:** 238 | 239 | - `path`: the new current directory 240 | 241 | **Returns:** 242 | 243 | true if the directory change was okay 244 | 245 | --- 246 | 247 | ### connect 248 | 249 | **Signature:** `connect(host : String, user : String, password : String) : boolean` 250 | 251 | **Description:** Connects and logs on to a SFTP server and returns a boolean indicating success or failure. 252 | 253 | **Parameters:** 254 | 255 | - `host`: Name of the SFTP sever 256 | - `user`: Username for the login 257 | - `password`: Password for the login 258 | 259 | **Returns:** 260 | 261 | true when connection is successful, false otherwise. 262 | 263 | --- 264 | 265 | ### connect 266 | 267 | **Signature:** `connect(host : String, port : Number, user : String, password : String) : boolean` 268 | 269 | **Description:** Connects and logs on to a SFTP server and returns a boolean indicating success or failure. 270 | 271 | **Parameters:** 272 | 273 | - `host`: Name of the SFTP sever 274 | - `port`: Port for SFTP server 275 | - `user`: Username for the login 276 | - `password`: Password for the login 277 | 278 | **Returns:** 279 | 280 | true when connection is successful, false otherwise. 281 | 282 | --- 283 | 284 | ### del 285 | 286 | **Signature:** `del(path : String) : boolean` 287 | 288 | **Description:** Deletes the remote file on the server identified by the path parameter. 289 | 290 | **Parameters:** 291 | 292 | - `path`: the path to the file. 293 | 294 | **Returns:** 295 | 296 | true if the file was successfully deleted, false otherwise. 297 | 298 | --- 299 | 300 | ### disconnect 301 | 302 | **Signature:** `disconnect() : void` 303 | 304 | **Description:** The method first logs the current user out from the server and then disconnects from the server. 305 | 306 | --- 307 | 308 | ### get 309 | 310 | **Signature:** `get(path : String) : String` 311 | 312 | **Description:** Reads the content of a remote file and returns it as a string using "ISO-8859-1" encoding to read it. Files with at most MAX_GET_STRING_SIZE bytes are read. 313 | 314 | **Parameters:** 315 | 316 | - `path`: remote path of the file to be read. 317 | 318 | **Returns:** 319 | 320 | the contents of the file or null if an error occurred while reading the file. 321 | 322 | --- 323 | 324 | ### get 325 | 326 | **Signature:** `get(path : String, encoding : String) : String` 327 | 328 | **Description:** Reads the content of a remote file and returns it as a string using the specified encoding. Files with at most MAX_GET_STRING_SIZE bytes are read. 329 | 330 | **Parameters:** 331 | 332 | - `path`: the remote path of the file to be read. 333 | - `encoding`: the encoding to use. 334 | 335 | **Returns:** 336 | 337 | the contents of the file or null if an error occurred while reading the file. 338 | 339 | --- 340 | 341 | ### get 342 | 343 | **Signature:** `get(path : String, encoding : String, file : File) : boolean` 344 | 345 | **Description:** Reads the content of a remote file and creates a local copy in the given file using the passed string encoding to read the file content and using the system standard encoding "UTF-8" to write the file. Copies at most MAX_GET_FILE_SIZE bytes. 346 | 347 | **Parameters:** 348 | 349 | - `path`: the remote path of the file to be read. 350 | - `encoding`: the encoding to use. 351 | - `file`: the local file name 352 | 353 | **Returns:** 354 | 355 | true if complete remote file is fetched and copied into local file. false, otherwise. 356 | 357 | --- 358 | 359 | ### getBinary 360 | 361 | **Signature:** `getBinary(path : String, file : File) : boolean` 362 | 363 | **Description:** Reads the content of a remote file and creates a local copy in the given file. Copies at most MAX_GET_FILE_SIZE bytes. The SFTP transfer is done in binary mode. 364 | 365 | **Parameters:** 366 | 367 | - `path`: the remote path of the file to be read. 368 | - `file`: the local file name 369 | 370 | **Returns:** 371 | 372 | true if complete remote file is fetched and copied into local file. false otherwise 373 | 374 | --- 375 | 376 | ### getConnected 377 | 378 | **Signature:** `getConnected() : boolean` 379 | 380 | **Description:** Identifies if the SFTP client is currently connected to the SFTP server. 381 | 382 | **Returns:** 383 | 384 | true if the client is currently connected. 385 | 386 | --- 387 | 388 | ### getErrorMessage 389 | 390 | **Signature:** `getErrorMessage() : String` 391 | 392 | **Description:** Returns the error message from the last SFTP action. 393 | 394 | **Returns:** 395 | 396 | the error message from the last SFTP action 397 | 398 | --- 399 | 400 | ### getFileInfo 401 | 402 | **Signature:** `getFileInfo(path : String) : SFTPFileInfo` 403 | 404 | **Description:** Returns a SFTPFileInfo objects containing information about the given file/directory path. 405 | 406 | **Parameters:** 407 | 408 | - `path`: the remote path from which the file info should be listed. 409 | 410 | **Returns:** 411 | 412 | the remote file information or null if not present. 413 | 414 | --- 415 | 416 | ### getIdentity 417 | 418 | **Signature:** `getIdentity() : KeyRef` 419 | 420 | **Description:** Gets the identity (private key) used for the connection. The key is only associated to this instance of the SFTP client. 421 | 422 | **Returns:** 423 | 424 | Reference to the private key, or null if not configured 425 | 426 | --- 427 | 428 | ### getTimeout 429 | 430 | **Signature:** `getTimeout() : Number` 431 | 432 | **Description:** Returns the timeout for this client, in milliseconds. 433 | 434 | **Returns:** 435 | 436 | the timeout in milliseconds 437 | 438 | --- 439 | 440 | ### list 441 | 442 | **Signature:** `list() : SFTPFileInfo[]` 443 | 444 | **Description:** Returns a list of SFTPFileInfo objects containing information about the files in the current directory. 445 | 446 | **Returns:** 447 | 448 | list of objects with remote file information or null if not present. 449 | 450 | --- 451 | 452 | ### list 453 | 454 | **Signature:** `list(path : String) : SFTPFileInfo[]` 455 | 456 | **Description:** Returns a list of SFTPFileInfo objects containing information about the files in the remote directory defined by the given path. 457 | 458 | **Parameters:** 459 | 460 | - `path`: the remote path from which the file info should be listed. 461 | 462 | **Returns:** 463 | 464 | list of objects with remote file information or null if not present. 465 | 466 | --- 467 | 468 | ### mkdir 469 | 470 | **Signature:** `mkdir(path : String) : boolean` 471 | 472 | **Description:** Creates a directory 473 | 474 | **Parameters:** 475 | 476 | - `path`: the path to the directory to create. 477 | 478 | **Returns:** 479 | 480 | true if the directory was successfully created, false otherwise. 481 | 482 | --- 483 | 484 | ### put 485 | 486 | **Signature:** `put(path : String, content : String) : boolean` 487 | 488 | **Description:** Puts the specified content to the specified path using "ISO-8859-1" encoding. If the content of a local file is to be uploaded, please use method putBinary(String,File) instead. NOTE: If the remote file already exists, it is overwritten. 489 | 490 | **Parameters:** 491 | 492 | - `path`: the path on the remote SFTP server, where the file will be stored. 493 | - `content`: the content to put. 494 | 495 | **Returns:** 496 | 497 | true or false indicating success or failure. 498 | 499 | --- 500 | 501 | ### put 502 | 503 | **Signature:** `put(path : String, content : String, encoding : String) : boolean` 504 | 505 | **Description:** Put the given content to a file on the given path on the SFTP server. The transformation from String into binary data is done via the encoding provided with the method call. If the content of a local file is to be uploaded, please use method putBinary(String,File) instead. NOTE: If the remote file already exists, it is overwritten. 506 | 507 | **Parameters:** 508 | 509 | - `path`: the path on the remote SFTP server, where the file will be stored. 510 | - `content`: the content to put. 511 | - `encoding`: the encoding to use. 512 | 513 | **Returns:** 514 | 515 | true or false indicating success or failure. 516 | 517 | --- 518 | 519 | ### putBinary 520 | 521 | **Signature:** `putBinary(path : String, file : File) : boolean` 522 | 523 | **Description:** Put the content of the given file into a file on the remote SFTP server with the given absolute path. NOTE: If the remote file already exists, it is overwritten. 524 | 525 | **Parameters:** 526 | 527 | - `path`: the path on the remote SFTP server where the file will be stored. 528 | - `file`: the file on the local system, which content is send to the remote SFTP server. 529 | 530 | **Returns:** 531 | 532 | true or false indicating success or failure. 533 | 534 | --- 535 | 536 | ### removeDirectory 537 | 538 | **Signature:** `removeDirectory(path : String) : boolean` 539 | 540 | **Description:** Deletes the remote directory on the server identified by the path parameter. In order to delete the directory successfully the directory needs to be empty, otherwise the removeDirectory() method will return false. 541 | 542 | **Parameters:** 543 | 544 | - `path`: the path to the directory. 545 | 546 | **Returns:** 547 | 548 | true if the directory was successfully deleted, false otherwise. 549 | 550 | --- 551 | 552 | ### rename 553 | 554 | **Signature:** `rename(from : String, to : String) : boolean` 555 | 556 | **Description:** Renames an existing file. 557 | 558 | **Parameters:** 559 | 560 | - `from`: the file that will be renamed. 561 | - `to`: the name of the new file. 562 | 563 | **Returns:** 564 | 565 | true if the file was successfully renamed, false otherwise. 566 | 567 | --- 568 | 569 | ### setIdentity 570 | 571 | **Signature:** `setIdentity(keyRef : KeyRef) : void` 572 | 573 | **Description:** Sets the identity (private key) to use for the next connection attempt. The key is only associated to this instance of the SFTP client. 574 | 575 | **Parameters:** 576 | 577 | - `keyRef`: Reference to the private key 578 | 579 | --- 580 | 581 | ### setTimeout 582 | 583 | **Signature:** `setTimeout(timeoutMillis : Number) : void` 584 | 585 | **Description:** Sets the timeout for connections made with the SFTP client to the given number of milliseconds. If the given timeout is less than or equal to zero, the timeout is set to the same value as the script context timeout but will only be set to a maximum of 30 seconds. The maximum and default timeout depend on the script context timeout. The maximum timeout is set to a maximum of 2 minutes. The default timeout for a new client is set to a maximum of 30 seconds. 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. 586 | 587 | **Parameters:** 588 | 589 | - `timeoutMillis`: timeout, in milliseconds, up to a maximum of 2 minutes. 590 | 591 | --- ```