This is page 23 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/yaml/get-best-practice-guide.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | description: "Test get_best_practice_guide tool in docs-only mode" 2 | tests: 3 | # Test successful guide retrieval with cartridge_creation guide 4 | - it: "should return cartridge creation guide with complete structure" 5 | request: 6 | jsonrpc: "2.0" 7 | id: "cartridge-guide-1" 8 | method: "tools/call" 9 | params: 10 | name: "get_best_practice_guide" 11 | arguments: 12 | guideName: "cartridge_creation" 13 | expect: 14 | response: 15 | jsonrpc: "2.0" 16 | id: "cartridge-guide-1" 17 | result: 18 | content: 19 | - type: "text" 20 | text: "match:contains:Instructions for Creating a Salesforce B2C Commerce" 21 | isError: false 22 | stderr: "toBeEmpty" 23 | 24 | # Test that the response contains proper JSON structure 25 | - it: "should return valid JSON with title, description, sections, and content" 26 | request: 27 | jsonrpc: "2.0" 28 | id: "cartridge-structure-1" 29 | method: "tools/call" 30 | params: 31 | name: "get_best_practice_guide" 32 | arguments: 33 | guideName: "cartridge_creation" 34 | expect: 35 | response: 36 | jsonrpc: "2.0" 37 | id: "cartridge-structure-1" 38 | result: 39 | content: 40 | - type: "text" 41 | text: "match:regex:\\{[\\s\\S]*\"title\"[\\s\\S]*\"description\"[\\s\\S]*\"sections\"[\\s\\S]*\"content\"[\\s\\S]*\\}" 42 | isError: false 43 | stderr: "toBeEmpty" 44 | 45 | # Test security guide retrieval 46 | - it: "should return security best practices guide" 47 | request: 48 | jsonrpc: "2.0" 49 | id: "security-guide-1" 50 | method: "tools/call" 51 | params: 52 | name: "get_best_practice_guide" 53 | arguments: 54 | guideName: "security" 55 | expect: 56 | response: 57 | jsonrpc: "2.0" 58 | id: "security-guide-1" 59 | result: 60 | content: 61 | - type: "text" 62 | text: "match:contains:Secure Coding Best Practices" 63 | isError: false 64 | stderr: "toBeEmpty" 65 | 66 | # Test performance guide 67 | - it: "should return performance optimization guide" 68 | request: 69 | jsonrpc: "2.0" 70 | id: "performance-guide-1" 71 | method: "tools/call" 72 | params: 73 | name: "get_best_practice_guide" 74 | arguments: 75 | guideName: "performance" 76 | expect: 77 | response: 78 | jsonrpc: "2.0" 79 | id: "performance-guide-1" 80 | result: 81 | content: 82 | - type: "text" 83 | text: "match:contains:performance" 84 | isError: false 85 | stderr: "toBeEmpty" 86 | 87 | # Test SFRA controllers guide 88 | - it: "should return SFRA controllers guide" 89 | request: 90 | jsonrpc: "2.0" 91 | id: "sfra-controllers-1" 92 | method: "tools/call" 93 | params: 94 | name: "get_best_practice_guide" 95 | arguments: 96 | guideName: "sfra_controllers" 97 | expect: 98 | response: 99 | jsonrpc: "2.0" 100 | id: "sfra-controllers-1" 101 | result: 102 | content: 103 | - type: "text" 104 | text: "match:contains:SFRA" 105 | isError: false 106 | stderr: "toBeEmpty" 107 | 108 | # Test SFRA models guide 109 | - it: "should return SFRA models guide" 110 | request: 111 | jsonrpc: "2.0" 112 | id: "sfra-models-1" 113 | method: "tools/call" 114 | params: 115 | name: "get_best_practice_guide" 116 | arguments: 117 | guideName: "sfra_models" 118 | expect: 119 | response: 120 | jsonrpc: "2.0" 121 | id: "sfra-models-1" 122 | result: 123 | content: 124 | - type: "text" 125 | text: "match:contains:SFRA" 126 | isError: false 127 | stderr: "toBeEmpty" 128 | 129 | - it: "should return SFRA client-side JavaScript guide" 130 | request: 131 | jsonrpc: "2.0" 132 | id: "sfra-client-js-1" 133 | method: "tools/call" 134 | params: 135 | name: "get_best_practice_guide" 136 | arguments: 137 | guideName: "sfra_client_side_js" 138 | expect: 139 | response: 140 | jsonrpc: "2.0" 141 | id: "sfra-client-js-1" 142 | result: 143 | content: 144 | - type: "text" 145 | text: "match:contains:Client-Side JavaScript" 146 | isError: false 147 | stderr: "toBeEmpty" 148 | 149 | - it: "should return SFRA SCSS guide" 150 | request: 151 | jsonrpc: "2.0" 152 | id: "sfra-scss-1" 153 | method: "tools/call" 154 | params: 155 | name: "get_best_practice_guide" 156 | arguments: 157 | guideName: "sfra_scss" 158 | expect: 159 | response: 160 | jsonrpc: "2.0" 161 | id: "sfra-scss-1" 162 | result: 163 | content: 164 | - type: "text" 165 | text: "match:contains:SFRA SCSS" 166 | isError: false 167 | stderr: "toBeEmpty" 168 | 169 | # Test OCAPI hooks guide 170 | - it: "should return OCAPI hooks guide" 171 | request: 172 | jsonrpc: "2.0" 173 | id: "ocapi-hooks-1" 174 | method: "tools/call" 175 | params: 176 | name: "get_best_practice_guide" 177 | arguments: 178 | guideName: "ocapi_hooks" 179 | expect: 180 | response: 181 | jsonrpc: "2.0" 182 | id: "ocapi-hooks-1" 183 | result: 184 | content: 185 | - type: "text" 186 | text: "match:contains:OCAPI" 187 | isError: false 188 | stderr: "toBeEmpty" 189 | 190 | # Test SCAPI hooks guide 191 | - it: "should return SCAPI hooks guide" 192 | request: 193 | jsonrpc: "2.0" 194 | id: "scapi-hooks-1" 195 | method: "tools/call" 196 | params: 197 | name: "get_best_practice_guide" 198 | arguments: 199 | guideName: "scapi_hooks" 200 | expect: 201 | response: 202 | jsonrpc: "2.0" 203 | id: "scapi-hooks-1" 204 | result: 205 | content: 206 | - type: "text" 207 | text: "match:contains:SCAPI" 208 | isError: false 209 | stderr: "toBeEmpty" 210 | 211 | # Test SCAPI custom endpoint guide 212 | - it: "should return SCAPI custom endpoint guide" 213 | request: 214 | jsonrpc: "2.0" 215 | id: "scapi-endpoint-1" 216 | method: "tools/call" 217 | params: 218 | name: "get_best_practice_guide" 219 | arguments: 220 | guideName: "scapi_custom_endpoint" 221 | expect: 222 | response: 223 | jsonrpc: "2.0" 224 | id: "scapi-endpoint-1" 225 | result: 226 | content: 227 | - type: "text" 228 | text: "match:contains:SCAPI" 229 | isError: false 230 | stderr: "toBeEmpty" 231 | 232 | # Test ISML templates guide 233 | - it: "should return ISML templates guide" 234 | request: 235 | jsonrpc: "2.0" 236 | id: "isml-templates-1" 237 | method: "tools/call" 238 | params: 239 | name: "get_best_practice_guide" 240 | arguments: 241 | guideName: "isml_templates" 242 | expect: 243 | response: 244 | jsonrpc: "2.0" 245 | id: "isml-templates-1" 246 | result: 247 | content: 248 | - type: "text" 249 | text: "match:contains:ISML" 250 | isError: false 251 | stderr: "toBeEmpty" 252 | 253 | # Test job framework guide 254 | - it: "should return job framework guide" 255 | request: 256 | jsonrpc: "2.0" 257 | id: "job-framework-1" 258 | method: "tools/call" 259 | params: 260 | name: "get_best_practice_guide" 261 | arguments: 262 | guideName: "job_framework" 263 | expect: 264 | response: 265 | jsonrpc: "2.0" 266 | id: "job-framework-1" 267 | result: 268 | content: 269 | - type: "text" 270 | text: "match:contains:job" 271 | isError: false 272 | stderr: "toBeEmpty" 273 | 274 | # Test LocalServiceRegistry guide 275 | - it: "should return LocalServiceRegistry guide" 276 | request: 277 | jsonrpc: "2.0" 278 | id: "localservice-1" 279 | method: "tools/call" 280 | params: 281 | name: "get_best_practice_guide" 282 | arguments: 283 | guideName: "localserviceregistry" 284 | expect: 285 | response: 286 | jsonrpc: "2.0" 287 | id: "localservice-1" 288 | result: 289 | content: 290 | - type: "text" 291 | text: "match:contains:LocalServiceRegistry" 292 | isError: false 293 | stderr: "toBeEmpty" 294 | 295 | # Test response time performance 296 | - it: "should respond within reasonable time for guide retrieval" 297 | request: 298 | jsonrpc: "2.0" 299 | id: "perf-guide-1" 300 | method: "tools/call" 301 | params: 302 | name: "get_best_practice_guide" 303 | arguments: 304 | guideName: "cartridge_creation" 305 | expect: 306 | response: 307 | jsonrpc: "2.0" 308 | id: "perf-guide-1" 309 | result: 310 | content: 311 | - type: "text" 312 | text: "match:type:string" 313 | isError: false 314 | performance: 315 | maxResponseTime: "2000ms" 316 | stderr: "toBeEmpty" 317 | 318 | # Test invalid guide name handling 319 | - it: "should handle invalid guide name gracefully" 320 | request: 321 | jsonrpc: "2.0" 322 | id: "invalid-guide-1" 323 | method: "tools/call" 324 | params: 325 | name: "get_best_practice_guide" 326 | arguments: 327 | guideName: "nonexistent_guide" 328 | expect: 329 | response: 330 | jsonrpc: "2.0" 331 | id: "invalid-guide-1" 332 | result: 333 | content: 334 | - type: "text" 335 | text: "null" 336 | isError: false 337 | stderr: "toBeEmpty" 338 | 339 | # Test empty guide name handling 340 | - it: "should handle empty guide name" 341 | request: 342 | jsonrpc: "2.0" 343 | id: "empty-guide-1" 344 | method: "tools/call" 345 | params: 346 | name: "get_best_practice_guide" 347 | arguments: 348 | guideName: "" 349 | expect: 350 | response: 351 | jsonrpc: "2.0" 352 | id: "empty-guide-1" 353 | result: 354 | content: 355 | - type: "text" 356 | text: "Error: guideName must be a non-empty string" 357 | isError: true 358 | stderr: "toBeEmpty" 359 | 360 | # Test that guide contains essential structural elements 361 | - it: "should contain essential sections in cartridge creation guide" 362 | request: 363 | jsonrpc: "2.0" 364 | id: "structure-test-1" 365 | method: "tools/call" 366 | params: 367 | name: "get_best_practice_guide" 368 | arguments: 369 | guideName: "cartridge_creation" 370 | expect: 371 | response: 372 | jsonrpc: "2.0" 373 | id: "structure-test-1" 374 | result: 375 | content: 376 | - type: "text" 377 | text: "match:contains:Core Principles" 378 | isError: false 379 | stderr: "toBeEmpty" 380 | 381 | # Test security guide contains security-specific content 382 | - it: "should contain security-specific content in security guide" 383 | request: 384 | jsonrpc: "2.0" 385 | id: "security-content-1" 386 | method: "tools/call" 387 | params: 388 | name: "get_best_practice_guide" 389 | arguments: 390 | guideName: "security" 391 | expect: 392 | response: 393 | jsonrpc: "2.0" 394 | id: "security-content-1" 395 | result: 396 | content: 397 | - type: "text" 398 | text: "match:regex:[\\s\\S]*(CSRF|XSS|authentication|authorization)[\\s\\S]*" 399 | isError: false 400 | stderr: "toBeEmpty" 401 | 402 | # Test that guide content is substantial (not just placeholder) 403 | - it: "should return substantial content for cartridge creation guide" 404 | request: 405 | jsonrpc: "2.0" 406 | id: "content-length-1" 407 | method: "tools/call" 408 | params: 409 | name: "get_best_practice_guide" 410 | arguments: 411 | guideName: "cartridge_creation" 412 | expect: 413 | response: 414 | jsonrpc: "2.0" 415 | id: "content-length-1" 416 | result: 417 | content: 418 | - type: "text" 419 | text: "match:regex:.{1000,}" # At least 1000 characters 420 | isError: false 421 | stderr: "toBeEmpty" 422 | 423 | # Test that guide contains code examples for practical guides 424 | - it: "should contain code examples in cartridge creation guide" 425 | request: 426 | jsonrpc: "2.0" 427 | id: "code-examples-1" 428 | method: "tools/call" 429 | params: 430 | name: "get_best_practice_guide" 431 | arguments: 432 | guideName: "cartridge_creation" 433 | expect: 434 | response: 435 | jsonrpc: "2.0" 436 | id: "code-examples-1" 437 | result: 438 | content: 439 | - type: "text" 440 | text: "match:regex:[\\s\\S]*(```|javascript|module\\.exports)[\\s\\S]*" 441 | isError: false 442 | stderr: "toBeEmpty" 443 | 444 | # Test missing required parameter handling 445 | - it: "should handle missing guideName parameter" 446 | request: 447 | jsonrpc: "2.0" 448 | id: "missing-param-1" 449 | method: "tools/call" 450 | params: 451 | name: "get_best_practice_guide" 452 | arguments: {} 453 | expect: 454 | response: 455 | jsonrpc: "2.0" 456 | id: "missing-param-1" 457 | result: 458 | content: 459 | - type: "text" 460 | text: "Error: guideName must be a non-empty string" 461 | isError: true 462 | stderr: "toBeEmpty" 463 | ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/get-best-practice-guide.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | description: "Test get_best_practice_guide tool in full-mode mode" 2 | tests: 3 | # Test successful guide retrieval with cartridge_creation guide 4 | - it: "should return cartridge creation guide with complete structure" 5 | request: 6 | jsonrpc: "2.0" 7 | id: "cartridge-guide-1" 8 | method: "tools/call" 9 | params: 10 | name: "get_best_practice_guide" 11 | arguments: 12 | guideName: "cartridge_creation" 13 | expect: 14 | response: 15 | jsonrpc: "2.0" 16 | id: "cartridge-guide-1" 17 | result: 18 | content: 19 | - type: "text" 20 | text: "match:contains:Instructions for Creating a Salesforce B2C Commerce" 21 | isError: false 22 | stderr: "toBeEmpty" 23 | 24 | # Test that the response contains proper JSON structure 25 | - it: "should return valid JSON with title, description, sections, and content" 26 | request: 27 | jsonrpc: "2.0" 28 | id: "cartridge-structure-1" 29 | method: "tools/call" 30 | params: 31 | name: "get_best_practice_guide" 32 | arguments: 33 | guideName: "cartridge_creation" 34 | expect: 35 | response: 36 | jsonrpc: "2.0" 37 | id: "cartridge-structure-1" 38 | result: 39 | content: 40 | - type: "text" 41 | text: "match:regex:\\{[\\s\\S]*\"title\"[\\s\\S]*\"description\"[\\s\\S]*\"sections\"[\\s\\S]*\"content\"[\\s\\S]*\\}" 42 | isError: false 43 | stderr: "toBeEmpty" 44 | 45 | # Test security guide retrieval 46 | - it: "should return security best practices guide" 47 | request: 48 | jsonrpc: "2.0" 49 | id: "security-guide-1" 50 | method: "tools/call" 51 | params: 52 | name: "get_best_practice_guide" 53 | arguments: 54 | guideName: "security" 55 | expect: 56 | response: 57 | jsonrpc: "2.0" 58 | id: "security-guide-1" 59 | result: 60 | content: 61 | - type: "text" 62 | text: "match:contains:Secure Coding Best Practices" 63 | isError: false 64 | stderr: "toBeEmpty" 65 | 66 | # Test performance guide 67 | - it: "should return performance optimization guide" 68 | request: 69 | jsonrpc: "2.0" 70 | id: "performance-guide-1" 71 | method: "tools/call" 72 | params: 73 | name: "get_best_practice_guide" 74 | arguments: 75 | guideName: "performance" 76 | expect: 77 | response: 78 | jsonrpc: "2.0" 79 | id: "performance-guide-1" 80 | result: 81 | content: 82 | - type: "text" 83 | text: "match:contains:performance" 84 | isError: false 85 | stderr: "toBeEmpty" 86 | 87 | # Test SFRA controllers guide 88 | - it: "should return SFRA controllers guide" 89 | request: 90 | jsonrpc: "2.0" 91 | id: "sfra-controllers-1" 92 | method: "tools/call" 93 | params: 94 | name: "get_best_practice_guide" 95 | arguments: 96 | guideName: "sfra_controllers" 97 | expect: 98 | response: 99 | jsonrpc: "2.0" 100 | id: "sfra-controllers-1" 101 | result: 102 | content: 103 | - type: "text" 104 | text: "match:contains:SFRA" 105 | isError: false 106 | stderr: "toBeEmpty" 107 | 108 | # Test SFRA models guide 109 | - it: "should return SFRA models guide" 110 | request: 111 | jsonrpc: "2.0" 112 | id: "sfra-models-1" 113 | method: "tools/call" 114 | params: 115 | name: "get_best_practice_guide" 116 | arguments: 117 | guideName: "sfra_models" 118 | expect: 119 | response: 120 | jsonrpc: "2.0" 121 | id: "sfra-models-1" 122 | result: 123 | content: 124 | - type: "text" 125 | text: "match:contains:SFRA" 126 | isError: false 127 | stderr: "toBeEmpty" 128 | 129 | - it: "should return SFRA client-side JavaScript guide" 130 | request: 131 | jsonrpc: "2.0" 132 | id: "sfra-client-js-1" 133 | method: "tools/call" 134 | params: 135 | name: "get_best_practice_guide" 136 | arguments: 137 | guideName: "sfra_client_side_js" 138 | expect: 139 | response: 140 | jsonrpc: "2.0" 141 | id: "sfra-client-js-1" 142 | result: 143 | content: 144 | - type: "text" 145 | text: "match:contains:Client-Side JavaScript" 146 | isError: false 147 | stderr: "toBeEmpty" 148 | 149 | - it: "should return SFRA SCSS guide" 150 | request: 151 | jsonrpc: "2.0" 152 | id: "sfra-scss-1" 153 | method: "tools/call" 154 | params: 155 | name: "get_best_practice_guide" 156 | arguments: 157 | guideName: "sfra_scss" 158 | expect: 159 | response: 160 | jsonrpc: "2.0" 161 | id: "sfra-scss-1" 162 | result: 163 | content: 164 | - type: "text" 165 | text: "match:contains:SFRA SCSS" 166 | isError: false 167 | stderr: "toBeEmpty" 168 | 169 | # Test OCAPI hooks guide 170 | - it: "should return OCAPI hooks guide" 171 | request: 172 | jsonrpc: "2.0" 173 | id: "ocapi-hooks-1" 174 | method: "tools/call" 175 | params: 176 | name: "get_best_practice_guide" 177 | arguments: 178 | guideName: "ocapi_hooks" 179 | expect: 180 | response: 181 | jsonrpc: "2.0" 182 | id: "ocapi-hooks-1" 183 | result: 184 | content: 185 | - type: "text" 186 | text: "match:contains:OCAPI" 187 | isError: false 188 | stderr: "toBeEmpty" 189 | 190 | # Test SCAPI hooks guide 191 | - it: "should return SCAPI hooks guide" 192 | request: 193 | jsonrpc: "2.0" 194 | id: "scapi-hooks-1" 195 | method: "tools/call" 196 | params: 197 | name: "get_best_practice_guide" 198 | arguments: 199 | guideName: "scapi_hooks" 200 | expect: 201 | response: 202 | jsonrpc: "2.0" 203 | id: "scapi-hooks-1" 204 | result: 205 | content: 206 | - type: "text" 207 | text: "match:contains:SCAPI" 208 | isError: false 209 | stderr: "toBeEmpty" 210 | 211 | # Test SCAPI custom endpoint guide 212 | - it: "should return SCAPI custom endpoint guide" 213 | request: 214 | jsonrpc: "2.0" 215 | id: "scapi-endpoint-1" 216 | method: "tools/call" 217 | params: 218 | name: "get_best_practice_guide" 219 | arguments: 220 | guideName: "scapi_custom_endpoint" 221 | expect: 222 | response: 223 | jsonrpc: "2.0" 224 | id: "scapi-endpoint-1" 225 | result: 226 | content: 227 | - type: "text" 228 | text: "match:contains:SCAPI" 229 | isError: false 230 | stderr: "toBeEmpty" 231 | 232 | # Test ISML templates guide 233 | - it: "should return ISML templates guide" 234 | request: 235 | jsonrpc: "2.0" 236 | id: "isml-templates-1" 237 | method: "tools/call" 238 | params: 239 | name: "get_best_practice_guide" 240 | arguments: 241 | guideName: "isml_templates" 242 | expect: 243 | response: 244 | jsonrpc: "2.0" 245 | id: "isml-templates-1" 246 | result: 247 | content: 248 | - type: "text" 249 | text: "match:contains:ISML" 250 | isError: false 251 | stderr: "toBeEmpty" 252 | 253 | # Test job framework guide 254 | - it: "should return job framework guide" 255 | request: 256 | jsonrpc: "2.0" 257 | id: "job-framework-1" 258 | method: "tools/call" 259 | params: 260 | name: "get_best_practice_guide" 261 | arguments: 262 | guideName: "job_framework" 263 | expect: 264 | response: 265 | jsonrpc: "2.0" 266 | id: "job-framework-1" 267 | result: 268 | content: 269 | - type: "text" 270 | text: "match:contains:job" 271 | isError: false 272 | stderr: "toBeEmpty" 273 | 274 | # Test LocalServiceRegistry guide 275 | - it: "should return LocalServiceRegistry guide" 276 | request: 277 | jsonrpc: "2.0" 278 | id: "localservice-1" 279 | method: "tools/call" 280 | params: 281 | name: "get_best_practice_guide" 282 | arguments: 283 | guideName: "localserviceregistry" 284 | expect: 285 | response: 286 | jsonrpc: "2.0" 287 | id: "localservice-1" 288 | result: 289 | content: 290 | - type: "text" 291 | text: "match:contains:LocalServiceRegistry" 292 | isError: false 293 | stderr: "toBeEmpty" 294 | 295 | # Test response time performance 296 | - it: "should respond within reasonable time for guide retrieval" 297 | request: 298 | jsonrpc: "2.0" 299 | id: "perf-guide-1" 300 | method: "tools/call" 301 | params: 302 | name: "get_best_practice_guide" 303 | arguments: 304 | guideName: "cartridge_creation" 305 | expect: 306 | response: 307 | jsonrpc: "2.0" 308 | id: "perf-guide-1" 309 | result: 310 | content: 311 | - type: "text" 312 | text: "match:type:string" 313 | isError: false 314 | performance: 315 | maxResponseTime: "2000ms" 316 | stderr: "toBeEmpty" 317 | 318 | # Test invalid guide name handling 319 | - it: "should handle invalid guide name gracefully" 320 | request: 321 | jsonrpc: "2.0" 322 | id: "invalid-guide-1" 323 | method: "tools/call" 324 | params: 325 | name: "get_best_practice_guide" 326 | arguments: 327 | guideName: "nonexistent_guide" 328 | expect: 329 | response: 330 | jsonrpc: "2.0" 331 | id: "invalid-guide-1" 332 | result: 333 | content: 334 | - type: "text" 335 | text: "null" 336 | isError: false 337 | stderr: "toBeEmpty" 338 | 339 | # Test empty guide name handling 340 | - it: "should handle empty guide name" 341 | request: 342 | jsonrpc: "2.0" 343 | id: "empty-guide-1" 344 | method: "tools/call" 345 | params: 346 | name: "get_best_practice_guide" 347 | arguments: 348 | guideName: "" 349 | expect: 350 | response: 351 | jsonrpc: "2.0" 352 | id: "empty-guide-1" 353 | result: 354 | content: 355 | - type: "text" 356 | text: "Error: guideName must be a non-empty string" 357 | isError: true 358 | stderr: "toBeEmpty" 359 | 360 | # Test that guide contains essential structural elements 361 | - it: "should contain essential sections in cartridge creation guide" 362 | request: 363 | jsonrpc: "2.0" 364 | id: "structure-test-1" 365 | method: "tools/call" 366 | params: 367 | name: "get_best_practice_guide" 368 | arguments: 369 | guideName: "cartridge_creation" 370 | expect: 371 | response: 372 | jsonrpc: "2.0" 373 | id: "structure-test-1" 374 | result: 375 | content: 376 | - type: "text" 377 | text: "match:contains:Core Principles" 378 | isError: false 379 | stderr: "toBeEmpty" 380 | 381 | # Test security guide contains security-specific content 382 | - it: "should contain security-specific content in security guide" 383 | request: 384 | jsonrpc: "2.0" 385 | id: "security-content-1" 386 | method: "tools/call" 387 | params: 388 | name: "get_best_practice_guide" 389 | arguments: 390 | guideName: "security" 391 | expect: 392 | response: 393 | jsonrpc: "2.0" 394 | id: "security-content-1" 395 | result: 396 | content: 397 | - type: "text" 398 | text: "match:regex:[\\s\\S]*(CSRF|XSS|authentication|authorization)[\\s\\S]*" 399 | isError: false 400 | stderr: "toBeEmpty" 401 | 402 | # Test that guide content is substantial (not just placeholder) 403 | - it: "should return substantial content for cartridge creation guide" 404 | request: 405 | jsonrpc: "2.0" 406 | id: "content-length-1" 407 | method: "tools/call" 408 | params: 409 | name: "get_best_practice_guide" 410 | arguments: 411 | guideName: "cartridge_creation" 412 | expect: 413 | response: 414 | jsonrpc: "2.0" 415 | id: "content-length-1" 416 | result: 417 | content: 418 | - type: "text" 419 | text: "match:regex:.{1000,}" # At least 1000 characters 420 | isError: false 421 | stderr: "toBeEmpty" 422 | 423 | # Test that guide contains code examples for practical guides 424 | - it: "should contain code examples in cartridge creation guide" 425 | request: 426 | jsonrpc: "2.0" 427 | id: "code-examples-1" 428 | method: "tools/call" 429 | params: 430 | name: "get_best_practice_guide" 431 | arguments: 432 | guideName: "cartridge_creation" 433 | expect: 434 | response: 435 | jsonrpc: "2.0" 436 | id: "code-examples-1" 437 | result: 438 | content: 439 | - type: "text" 440 | text: "match:regex:[\\s\\S]*(```|javascript|module\\.exports)[\\s\\S]*" 441 | isError: false 442 | stderr: "toBeEmpty" 443 | 444 | # Test missing required parameter handling 445 | - it: "should handle missing guideName parameter" 446 | request: 447 | jsonrpc: "2.0" 448 | id: "missing-param-1" 449 | method: "tools/call" 450 | params: 451 | name: "get_best_practice_guide" 452 | arguments: {} 453 | expect: 454 | response: 455 | jsonrpc: "2.0" 456 | id: "missing-param-1" 457 | result: 458 | content: 459 | - type: "text" 460 | text: "Error: guideName must be a non-empty string" 461 | isError: true 462 | stderr: "toBeEmpty" 463 | ``` -------------------------------------------------------------------------------- /tests/validator.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for Validator utility 3 | * Tests input validation functionality 4 | */ 5 | 6 | import { Validator, ValidationError } from '../src/utils/validator.js'; 7 | 8 | describe('Validator', () => { 9 | describe('ValidationError', () => { 10 | it('should create validation error with message', () => { 11 | const error = new ValidationError('Test error message'); 12 | expect(error).toBeInstanceOf(Error); 13 | expect(error.name).toBe('ValidationError'); 14 | expect(error.message).toBe('Test error message'); 15 | }); 16 | }); 17 | 18 | describe('validateRequired', () => { 19 | it('should pass when all required fields are present', () => { 20 | const params = { 21 | field1: 'value1', 22 | field2: 'value2', 23 | field3: 123, 24 | }; 25 | 26 | expect(() => { 27 | Validator.validateRequired(params, ['field1', 'field2']); 28 | }).not.toThrow(); 29 | }); 30 | 31 | it('should throw error when required field is missing', () => { 32 | const params = { 33 | field1: 'value1', 34 | }; 35 | 36 | expect(() => { 37 | Validator.validateRequired(params, ['field1', 'field2']); 38 | }).toThrow(ValidationError); 39 | expect(() => { 40 | Validator.validateRequired(params, ['field1', 'field2']); 41 | }).toThrow('Required fields are missing or empty: field2'); 42 | }); 43 | 44 | it('should throw error when required field is empty string', () => { 45 | const params = { 46 | field1: 'value1', 47 | field2: '', 48 | }; 49 | 50 | expect(() => { 51 | Validator.validateRequired(params, ['field1', 'field2']); 52 | }).toThrow('Required fields are missing or empty: field2'); 53 | }); 54 | 55 | it('should throw error when required field is whitespace only', () => { 56 | const params = { 57 | field1: 'value1', 58 | field2: ' ', 59 | }; 60 | 61 | expect(() => { 62 | Validator.validateRequired(params, ['field1', 'field2']); 63 | }).toThrow('Required fields are missing or empty: field2'); 64 | }); 65 | 66 | it('should throw error for multiple missing fields', () => { 67 | const params = { 68 | field1: 'value1', 69 | }; 70 | 71 | expect(() => { 72 | Validator.validateRequired(params, ['field2', 'field3', 'field4']); 73 | }).toThrow('Required fields are missing or empty: field2, field3, field4'); 74 | }); 75 | 76 | it('should handle non-string values correctly', () => { 77 | const params = { 78 | number: 0, 79 | boolean: false, 80 | object: {}, 81 | array: [], 82 | }; 83 | 84 | expect(() => { 85 | Validator.validateRequired(params, ['number', 'boolean', 'object', 'array']); 86 | }).not.toThrow(); 87 | }); 88 | }); 89 | 90 | describe('validateInstanceType', () => { 91 | it('should accept valid instance types', () => { 92 | expect(Validator.validateInstanceType('staging')).toBe('staging'); 93 | expect(Validator.validateInstanceType('development')).toBe('development'); 94 | expect(Validator.validateInstanceType('sandbox')).toBe('sandbox'); 95 | expect(Validator.validateInstanceType('production')).toBe('production'); 96 | }); 97 | 98 | it('should throw error for invalid instance type', () => { 99 | expect(() => { 100 | Validator.validateInstanceType('invalid'); 101 | }).toThrow(ValidationError); 102 | expect(() => { 103 | Validator.validateInstanceType('invalid'); 104 | }).toThrow('Invalid instance type \'invalid\'. Must be one of: staging, development, sandbox, production'); 105 | }); 106 | 107 | it('should throw error for empty string', () => { 108 | expect(() => { 109 | Validator.validateInstanceType(''); 110 | }).toThrow('Invalid instance type \'\'. Must be one of: staging, development, sandbox, production'); 111 | }); 112 | 113 | it('should throw error for case-sensitive mismatch', () => { 114 | expect(() => { 115 | Validator.validateInstanceType('STAGING'); 116 | }).toThrow('Invalid instance type \'STAGING\'. Must be one of: staging, development, sandbox, production'); 117 | }); 118 | }); 119 | 120 | describe('validateNotEmpty', () => { 121 | it('should pass for non-empty string', () => { 122 | expect(() => { 123 | Validator.validateNotEmpty('valid value', 'testField'); 124 | }).not.toThrow(); 125 | }); 126 | 127 | it('should throw error for empty string', () => { 128 | expect(() => { 129 | Validator.validateNotEmpty('', 'testField'); 130 | }).toThrow(ValidationError); 131 | expect(() => { 132 | Validator.validateNotEmpty('', 'testField'); 133 | }).toThrow('testField cannot be empty'); 134 | }); 135 | 136 | it('should throw error for whitespace-only string', () => { 137 | expect(() => { 138 | Validator.validateNotEmpty(' ', 'testField'); 139 | }).toThrow('testField cannot be empty'); 140 | }); 141 | }); 142 | 143 | describe('validatePositiveNumber', () => { 144 | it('should pass for positive numbers', () => { 145 | expect(() => { 146 | Validator.validatePositiveNumber(1, 'testField'); 147 | Validator.validatePositiveNumber(100, 'testField'); 148 | Validator.validatePositiveNumber(0.5, 'testField'); 149 | }).not.toThrow(); 150 | }); 151 | 152 | it('should pass for zero', () => { 153 | expect(() => { 154 | Validator.validatePositiveNumber(0, 'testField'); 155 | }).not.toThrow(); 156 | }); 157 | 158 | it('should throw error for negative numbers', () => { 159 | expect(() => { 160 | Validator.validatePositiveNumber(-1, 'testField'); 161 | }).toThrow(ValidationError); 162 | expect(() => { 163 | Validator.validatePositiveNumber(-1, 'testField'); 164 | }).toThrow('testField must be a positive number'); 165 | }); 166 | }); 167 | 168 | describe('validateObjectType', () => { 169 | it('should pass for valid object types', () => { 170 | expect(() => { 171 | Validator.validateObjectType('Product'); 172 | Validator.validateObjectType('Customer'); 173 | Validator.validateObjectType('SitePreferences'); 174 | Validator.validateObjectType('Custom_Object'); 175 | Validator.validateObjectType('MyObject123'); 176 | }).not.toThrow(); 177 | }); 178 | 179 | it('should throw error for empty object type', () => { 180 | expect(() => { 181 | Validator.validateObjectType(''); 182 | }).toThrow('objectType cannot be empty'); 183 | }); 184 | 185 | it('should throw error for object type starting with number', () => { 186 | expect(() => { 187 | Validator.validateObjectType('123Object'); 188 | }).toThrow('Invalid object type \'123Object\'. Must start with a letter and contain only letters, numbers, and underscores.'); 189 | }); 190 | 191 | it('should throw error for object type with special characters', () => { 192 | expect(() => { 193 | Validator.validateObjectType('Product-Type'); 194 | }).toThrow('Invalid object type \'Product-Type\'. Must start with a letter and contain only letters, numbers, and underscores.'); 195 | }); 196 | 197 | it('should throw error for object type with spaces', () => { 198 | expect(() => { 199 | Validator.validateObjectType('Product Type'); 200 | }).toThrow('Invalid object type \'Product Type\'. Must start with a letter and contain only letters, numbers, and underscores.'); 201 | }); 202 | }); 203 | 204 | describe('validateSearchRequest', () => { 205 | it('should pass for valid search request with text_query', () => { 206 | const searchRequest = { 207 | query: { 208 | text_query: { 209 | fields: ['id', 'display_name'], 210 | search_phrase: 'test', 211 | }, 212 | }, 213 | }; 214 | 215 | expect(() => { 216 | Validator.validateSearchRequest(searchRequest); 217 | }).not.toThrow(); 218 | }); 219 | 220 | it('should pass for valid search request with term_query', () => { 221 | const searchRequest = { 222 | query: { 223 | term_query: { 224 | fields: ['value_type'], 225 | operator: 'is', 226 | values: ['string'], 227 | }, 228 | }, 229 | }; 230 | 231 | expect(() => { 232 | Validator.validateSearchRequest(searchRequest); 233 | }).not.toThrow(); 234 | }); 235 | 236 | it('should pass for valid search request with match_all_query', () => { 237 | const searchRequest = { 238 | query: { 239 | match_all_query: {}, 240 | }, 241 | }; 242 | 243 | expect(() => { 244 | Validator.validateSearchRequest(searchRequest); 245 | }).not.toThrow(); 246 | }); 247 | 248 | it('should pass for search request with sorts', () => { 249 | const searchRequest = { 250 | query: { 251 | match_all_query: {}, 252 | }, 253 | sorts: [ 254 | { field: 'id', sort_order: 'asc' }, 255 | { field: 'display_name' }, 256 | ], 257 | }; 258 | 259 | expect(() => { 260 | Validator.validateSearchRequest(searchRequest); 261 | }).not.toThrow(); 262 | }); 263 | 264 | it('should pass for search request with pagination', () => { 265 | const searchRequest = { 266 | query: { 267 | match_all_query: {}, 268 | }, 269 | start: 0, 270 | count: 25, 271 | }; 272 | 273 | expect(() => { 274 | Validator.validateSearchRequest(searchRequest); 275 | }).not.toThrow(); 276 | }); 277 | 278 | it('should throw error for non-object search request', () => { 279 | expect(() => { 280 | Validator.validateSearchRequest('invalid'); 281 | }).toThrow('Search request must be a valid object'); 282 | }); 283 | 284 | it('should throw error for null search request', () => { 285 | expect(() => { 286 | Validator.validateSearchRequest(null); 287 | }).toThrow('Search request must be a valid object'); 288 | }); 289 | 290 | it('should throw error for search request with no valid query types', () => { 291 | const searchRequest = { 292 | query: { 293 | invalid_query: {}, 294 | }, 295 | }; 296 | 297 | expect(() => { 298 | Validator.validateSearchRequest(searchRequest); 299 | }).toThrow('Search query must contain at least one of: text_query, term_query, filtered_query, bool_query, match_all_query'); 300 | }); 301 | 302 | it('should throw error for text_query with empty fields', () => { 303 | const searchRequest = { 304 | query: { 305 | text_query: { 306 | fields: [], 307 | search_phrase: 'test', 308 | }, 309 | }, 310 | }; 311 | 312 | expect(() => { 313 | Validator.validateSearchRequest(searchRequest); 314 | }).toThrow('text_query.fields must be a non-empty array'); 315 | }); 316 | 317 | it('should throw error for text_query with missing search_phrase', () => { 318 | const searchRequest = { 319 | query: { 320 | text_query: { 321 | fields: ['id'], 322 | }, 323 | }, 324 | }; 325 | 326 | expect(() => { 327 | Validator.validateSearchRequest(searchRequest); 328 | }).toThrow('text_query.search_phrase must be a non-empty string'); 329 | }); 330 | 331 | it('should throw error for term_query with invalid structure', () => { 332 | const searchRequest = { 333 | query: { 334 | term_query: { 335 | fields: [], 336 | operator: 'is', 337 | values: ['test'], 338 | }, 339 | }, 340 | }; 341 | 342 | expect(() => { 343 | Validator.validateSearchRequest(searchRequest); 344 | }).toThrow('term_query.fields must be a non-empty array'); 345 | }); 346 | 347 | it('should throw error for invalid sorts structure', () => { 348 | const searchRequest = { 349 | query: { 350 | match_all_query: {}, 351 | }, 352 | sorts: 'invalid', 353 | }; 354 | 355 | expect(() => { 356 | Validator.validateSearchRequest(searchRequest); 357 | }).toThrow('sorts must be an array'); 358 | }); 359 | 360 | it('should throw error for sort with missing field', () => { 361 | const searchRequest = { 362 | query: { 363 | match_all_query: {}, 364 | }, 365 | sorts: [ 366 | { sort_order: 'asc' }, 367 | ], 368 | }; 369 | 370 | expect(() => { 371 | Validator.validateSearchRequest(searchRequest); 372 | }).toThrow('sorts[0].field must be a non-empty string'); 373 | }); 374 | 375 | it('should throw error for sort with invalid sort_order', () => { 376 | const searchRequest = { 377 | query: { 378 | match_all_query: {}, 379 | }, 380 | sorts: [ 381 | { field: 'id', sort_order: 'invalid' }, 382 | ], 383 | }; 384 | 385 | expect(() => { 386 | Validator.validateSearchRequest(searchRequest); 387 | }).toThrow('sorts[0].sort_order must be either \'asc\' or \'desc\''); 388 | }); 389 | 390 | it('should throw error for negative start value', () => { 391 | const searchRequest = { 392 | query: { 393 | match_all_query: {}, 394 | }, 395 | start: -1, 396 | }; 397 | 398 | expect(() => { 399 | Validator.validateSearchRequest(searchRequest); 400 | }).toThrow('start must be a positive number'); 401 | }); 402 | 403 | it('should throw error for negative count value', () => { 404 | const searchRequest = { 405 | query: { 406 | match_all_query: {}, 407 | }, 408 | count: -5, 409 | }; 410 | 411 | expect(() => { 412 | Validator.validateSearchRequest(searchRequest); 413 | }).toThrow('count must be a positive number'); 414 | }); 415 | }); 416 | }); 417 | ``` -------------------------------------------------------------------------------- /docs/dw_web/HttpParameter.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.web 2 | 3 | # Class HttpParameter 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.web.HttpParameter 9 | 10 | ## Description 11 | 12 | Represents an HTTP parameter. 13 | 14 | ## Properties 15 | 16 | ### booleanValue 17 | 18 | **Type:** boolean (Read Only) 19 | 20 | The value of the current HttpParameter attribute as a boolean. If 21 | there is more than one value defined, only the first one is returned. For an 22 | undefined attribute it returns null. 23 | 24 | ### dateValue 25 | 26 | **Type:** Date (Read Only) 27 | 28 | The value of the current HttpParameter attribute as a date. If 29 | there is more than one value defined, only the first one is returned. For 30 | an undefined attribute and if attribute is not a date it return null. 31 | 32 | ### doubleValue 33 | 34 | **Type:** Number (Read Only) 35 | 36 | The value of the current HttpParameter attribute as a number. If 37 | there is more than one value defined, only the first one is returned. For 38 | an undefined attribute it returns 0.0. 39 | 40 | ### empty 41 | 42 | **Type:** boolean (Read Only) 43 | 44 | Identifies if there is a value for the http parameter attribute 45 | and whether the value is empty. 46 | A value is treated as empty if it's not blank. 47 | 48 | ### intValue 49 | 50 | **Type:** Number (Read Only) 51 | 52 | The value of the current HttpParameter attribute as int. If there 53 | is more than one value defined, only the first one is returned. For an 54 | undefined attribute it returns null. 55 | 56 | ### rawValue 57 | 58 | **Type:** String (Read Only) 59 | 60 | The raw value for this HttpParameter instance. 61 | The raw value is the not trimmed String value of this HTTP parameter. 62 | If there is more than one value defined, only the first one is returned. For an 63 | undefined attribute the method returns a null. 64 | 65 | ### rawValues 66 | 67 | **Type:** Collection (Read Only) 68 | 69 | A Collection of all raw values for this HTTP parameter. 70 | The raw value is the not trimmed String value of this HTTP parameter. 71 | 72 | ### stringValue 73 | 74 | **Type:** String (Read Only) 75 | 76 | The value of the current HttpParameter attribute. If there is 77 | more than one value defined, only the first one is returned. For an 78 | undefined attribute the method returns a null. 79 | 80 | ### stringValues 81 | 82 | **Type:** Collection (Read Only) 83 | 84 | A Collection of all defined values for this HTTP parameter. 85 | 86 | ### submitted 87 | 88 | **Type:** boolean (Read Only) 89 | 90 | Identifies if the parameter was submitted. This is equivalent to the 91 | check, whether the parameter has a value. 92 | 93 | ### value 94 | 95 | **Type:** String (Read Only) 96 | 97 | The value of the current HttpParameter attribute. If there is 98 | more than one value defined, only the first one is returned. For an 99 | undefined attribute the method returns null. 100 | 101 | ### values 102 | 103 | **Type:** Collection (Read Only) 104 | 105 | A Collection of all defined values for this current HTTP parameter. 106 | 107 | ## Constructor Summary 108 | 109 | ## Method Summary 110 | 111 | ### containsStringValue 112 | 113 | **Signature:** `containsStringValue(value : String) : boolean` 114 | 115 | Identifies if the given value is part of the actual values. 116 | 117 | ### getBooleanValue 118 | 119 | **Signature:** `getBooleanValue() : boolean` 120 | 121 | Returns the value of the current HttpParameter attribute as a boolean. 122 | 123 | ### getBooleanValue 124 | 125 | **Signature:** `getBooleanValue(defaultValue : boolean) : boolean` 126 | 127 | Returns the value of the current HttpParameter attribute as a boolean. 128 | 129 | ### getDateValue 130 | 131 | **Signature:** `getDateValue() : Date` 132 | 133 | Returns the value of the current HttpParameter attribute as a date. 134 | 135 | ### getDateValue 136 | 137 | **Signature:** `getDateValue(defaultValue : Date) : Date` 138 | 139 | Returns the value of the current HttpParameter attribute as a date. 140 | 141 | ### getDoubleValue 142 | 143 | **Signature:** `getDoubleValue() : Number` 144 | 145 | Returns the value of the current HttpParameter attribute as a number. 146 | 147 | ### getDoubleValue 148 | 149 | **Signature:** `getDoubleValue(defaultValue : Number) : Number` 150 | 151 | Returns the value of the current HttpParameter attribute as a number. 152 | 153 | ### getIntValue 154 | 155 | **Signature:** `getIntValue() : Number` 156 | 157 | Returns the value of the current HttpParameter attribute as int. 158 | 159 | ### getIntValue 160 | 161 | **Signature:** `getIntValue(defaultValue : Number) : Number` 162 | 163 | Returns the value of the current HttpParameter attribute as an integer. 164 | 165 | ### getRawValue 166 | 167 | **Signature:** `getRawValue() : String` 168 | 169 | Returns the raw value for this HttpParameter instance. 170 | 171 | ### getRawValues 172 | 173 | **Signature:** `getRawValues() : Collection` 174 | 175 | Returns a Collection of all raw values for this HTTP parameter. 176 | 177 | ### getStringValue 178 | 179 | **Signature:** `getStringValue() : String` 180 | 181 | Returns the value of the current HttpParameter attribute. 182 | 183 | ### getStringValue 184 | 185 | **Signature:** `getStringValue(defaultValue : String) : String` 186 | 187 | Returns the value of the current HttpParameter attribute. 188 | 189 | ### getStringValues 190 | 191 | **Signature:** `getStringValues() : Collection` 192 | 193 | Returns a Collection of all defined values for this HTTP parameter. 194 | 195 | ### getValue 196 | 197 | **Signature:** `getValue() : String` 198 | 199 | Returns the value of the current HttpParameter attribute. 200 | 201 | ### getValues 202 | 203 | **Signature:** `getValues() : Collection` 204 | 205 | Returns a Collection of all defined values for this current HTTP parameter. 206 | 207 | ### isChecked 208 | 209 | **Signature:** `isChecked(value : String) : boolean` 210 | 211 | Identifies if the given String is an actual value of this http parameter. 212 | 213 | ### isEmpty 214 | 215 | **Signature:** `isEmpty() : boolean` 216 | 217 | Identifies if there is a value for the http parameter attribute and whether the value is empty. 218 | 219 | ### isSubmitted 220 | 221 | **Signature:** `isSubmitted() : boolean` 222 | 223 | Identifies if the parameter was submitted. 224 | 225 | ### toString 226 | 227 | **Signature:** `toString() : String` 228 | 229 | Returns the value of the current HttpParameter attribute. 230 | 231 | ## Method Detail 232 | 233 | ## Method Details 234 | 235 | ### containsStringValue 236 | 237 | **Signature:** `containsStringValue(value : String) : boolean` 238 | 239 | **Description:** Identifies if the given value is part of the actual values. 240 | 241 | **Parameters:** 242 | 243 | - `value`: the value to check. 244 | 245 | **Returns:** 246 | 247 | true if the value is among the actual values, false otherwise. 248 | 249 | --- 250 | 251 | ### getBooleanValue 252 | 253 | **Signature:** `getBooleanValue() : boolean` 254 | 255 | **Description:** Returns the value of the current HttpParameter attribute as a boolean. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns null. 256 | 257 | **Returns:** 258 | 259 | the actual value as a boolean or null of no value is available. 260 | 261 | --- 262 | 263 | ### getBooleanValue 264 | 265 | **Signature:** `getBooleanValue(defaultValue : boolean) : boolean` 266 | 267 | **Description:** Returns the value of the current HttpParameter attribute as a boolean. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns the given default value. 268 | 269 | **Parameters:** 270 | 271 | - `defaultValue`: the default value to use. 272 | 273 | **Returns:** 274 | 275 | the value of the parameter or the default value if empty. 276 | 277 | --- 278 | 279 | ### getDateValue 280 | 281 | **Signature:** `getDateValue() : Date` 282 | 283 | **Description:** Returns the value of the current HttpParameter attribute as a date. If there is more than one value defined, only the first one is returned. For an undefined attribute and if attribute is not a date it return null. 284 | 285 | **Returns:** 286 | 287 | the actual value as date or null if empty. 288 | 289 | --- 290 | 291 | ### getDateValue 292 | 293 | **Signature:** `getDateValue(defaultValue : Date) : Date` 294 | 295 | **Description:** Returns the value of the current HttpParameter attribute as a date. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns the given default value and if the attributes is not a date it returns null. 296 | 297 | **Parameters:** 298 | 299 | - `defaultValue`: the default value to use. 300 | 301 | **Returns:** 302 | 303 | the data value of the attribute or the default value if empty 304 | 305 | --- 306 | 307 | ### getDoubleValue 308 | 309 | **Signature:** `getDoubleValue() : Number` 310 | 311 | **Description:** Returns the value of the current HttpParameter attribute as a number. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns 0.0. 312 | 313 | **Returns:** 314 | 315 | the actual value as double or null if the parameter has no value. 316 | 317 | --- 318 | 319 | ### getDoubleValue 320 | 321 | **Signature:** `getDoubleValue(defaultValue : Number) : Number` 322 | 323 | **Description:** Returns the value of the current HttpParameter attribute as a number. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns the given default value. 324 | 325 | **Parameters:** 326 | 327 | - `defaultValue`: the default value to use. 328 | 329 | **Returns:** 330 | 331 | the actual value as double or the default value if empty. 332 | 333 | --- 334 | 335 | ### getIntValue 336 | 337 | **Signature:** `getIntValue() : Number` 338 | 339 | **Description:** Returns the value of the current HttpParameter attribute as int. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns null. 340 | 341 | **Returns:** 342 | 343 | the actual value as an integer or null of no value is available. 344 | 345 | --- 346 | 347 | ### getIntValue 348 | 349 | **Signature:** `getIntValue(defaultValue : Number) : Number` 350 | 351 | **Description:** Returns the value of the current HttpParameter attribute as an integer. If there is more than one value defined, only the first one is returned. For an undefined attribute it returns the given default value. 352 | 353 | **Parameters:** 354 | 355 | - `defaultValue`: the default value to use. 356 | 357 | **Returns:** 358 | 359 | the value of the parameter or the default value if empty. 360 | 361 | --- 362 | 363 | ### getRawValue 364 | 365 | **Signature:** `getRawValue() : String` 366 | 367 | **Description:** Returns the raw value for this HttpParameter instance. The raw value is the not trimmed String value of this HTTP parameter. If there is more than one value defined, only the first one is returned. For an undefined attribute the method returns a null. 368 | 369 | **Returns:** 370 | 371 | the actual value or null. 372 | 373 | **See Also:** 374 | 375 | getStringValue() 376 | 377 | --- 378 | 379 | ### getRawValues 380 | 381 | **Signature:** `getRawValues() : Collection` 382 | 383 | **Description:** Returns a Collection of all raw values for this HTTP parameter. The raw value is the not trimmed String value of this HTTP parameter. 384 | 385 | **Returns:** 386 | 387 | the raw values as a Collection of String, might be empty 388 | 389 | **See Also:** 390 | 391 | getStringValues() 392 | 393 | --- 394 | 395 | ### getStringValue 396 | 397 | **Signature:** `getStringValue() : String` 398 | 399 | **Description:** Returns the value of the current HttpParameter attribute. If there is more than one value defined, only the first one is returned. For an undefined attribute the method returns a null. 400 | 401 | **Returns:** 402 | 403 | the actual value or null. 404 | 405 | --- 406 | 407 | ### getStringValue 408 | 409 | **Signature:** `getStringValue(defaultValue : String) : String` 410 | 411 | **Description:** Returns the value of the current HttpParameter attribute. If there is more than one value defined, only the first one is returned. For an undefined attribute the method returns the given default value. 412 | 413 | **Parameters:** 414 | 415 | - `defaultValue`: the default value to use. 416 | 417 | **Returns:** 418 | 419 | the actual value or the default value. 420 | 421 | --- 422 | 423 | ### getStringValues 424 | 425 | **Signature:** `getStringValues() : Collection` 426 | 427 | **Description:** Returns a Collection of all defined values for this HTTP parameter. 428 | 429 | **Returns:** 430 | 431 | the actual values as Collection. 432 | 433 | --- 434 | 435 | ### getValue 436 | 437 | **Signature:** `getValue() : String` 438 | 439 | **Description:** Returns the value of the current HttpParameter attribute. If there is more than one value defined, only the first one is returned. For an undefined attribute the method returns null. 440 | 441 | **Returns:** 442 | 443 | the actual value or null. 444 | 445 | --- 446 | 447 | ### getValues 448 | 449 | **Signature:** `getValues() : Collection` 450 | 451 | **Description:** Returns a Collection of all defined values for this current HTTP parameter. 452 | 453 | **Returns:** 454 | 455 | the actual values as Collection. 456 | 457 | **See Also:** 458 | 459 | getStringValues() 460 | 461 | --- 462 | 463 | ### isChecked 464 | 465 | **Signature:** `isChecked(value : String) : boolean` 466 | 467 | **Description:** Identifies if the given String is an actual value of this http parameter. 468 | 469 | **Parameters:** 470 | 471 | - `value`: the value to check. 472 | 473 | **Returns:** 474 | 475 | true if the value is among the actual values, false otherwise. 476 | 477 | --- 478 | 479 | ### isEmpty 480 | 481 | **Signature:** `isEmpty() : boolean` 482 | 483 | **Description:** Identifies if there is a value for the http parameter attribute and whether the value is empty. A value is treated as empty if it's not blank. 484 | 485 | **Returns:** 486 | 487 | true if a value is empty, false otherwise. 488 | 489 | --- 490 | 491 | ### isSubmitted 492 | 493 | **Signature:** `isSubmitted() : boolean` 494 | 495 | **Description:** Identifies if the parameter was submitted. This is equivalent to the check, whether the parameter has a value. 496 | 497 | **Returns:** 498 | 499 | true if a value is there, false otherwise. 500 | 501 | --- 502 | 503 | ### toString 504 | 505 | **Signature:** `toString() : String` 506 | 507 | **Description:** Returns the value of the current HttpParameter attribute. If there is more than one value defined, only the first one is returned. For an undefined attribute the method returns an empty string. 508 | 509 | **Returns:** 510 | 511 | the actual value or an empty String. 512 | 513 | --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/src/routes/ocapi/site-preferences-handler.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Site Preferences Handler 3 | * 4 | * Handles site preferences search operations for OCAPI endpoints. 5 | */ 6 | 7 | const express = require('express'); 8 | 9 | class SitePreferencesHandler { 10 | constructor(config, dataLoader) { 11 | this.config = config; 12 | this.ocapiConfig = config.getOcapiConfig(); 13 | this.dataLoader = dataLoader; 14 | this.router = express.Router(); 15 | this.setupRoutes(); 16 | } 17 | 18 | setupRoutes() { 19 | // Site Preferences Search - proper SFCC route pattern 20 | this.router.post(`/s/-/dw/data/${this.ocapiConfig.version}/site_preferences/preference_groups/:groupId/:instanceType/preference_search`, 21 | this.handleSearchSitePreferences.bind(this) 22 | ); 23 | } 24 | 25 | /** 26 | * Handle site preferences search 27 | */ 28 | async handleSearchSitePreferences(req, res) { 29 | const { groupId, instanceType } = req.params; 30 | const searchRequest = req.body; 31 | 32 | try { 33 | // Validate input parameters 34 | const validationError = this.validateSearchRequest(searchRequest, groupId, instanceType); 35 | if (validationError) { 36 | return res.status(validationError.status).json(validationError.body); 37 | } 38 | 39 | // Try to load specific site preferences 40 | let mockData = this.dataLoader.loadOcapiData(`site-preferences-${groupId.toLowerCase()}.json`); 41 | 42 | // Check if group exists (simulate 404 for unknown groups that aren't in our known list) 43 | const knownGroups = ['ccv', 'fastforward', 'system', 'storefront', 'sfra', 'integration']; 44 | if (!mockData && !knownGroups.includes(groupId.toLowerCase())) { 45 | return res.status(404).json({ 46 | "_v": "23.2", 47 | "fault": { 48 | "arguments": {"preferenceGroupId": groupId}, 49 | "type": "CustomPreferenceGroupNotFoundException", 50 | "message": `No preference group with ID '${groupId}' could be found.` 51 | } 52 | }); 53 | } 54 | 55 | if (!mockData) { 56 | // Create fallback data with proper SFCC format (matching real API) 57 | mockData = { 58 | "_v": "23.2", 59 | "_type": "preference_value_search_result", 60 | "count": 0, 61 | "hits": [], 62 | "query": searchRequest.query || {"match_all_query": {"_type": "match_all_query"}}, 63 | "select": searchRequest.select || "(**)", 64 | "start": 0, 65 | "total": 0 66 | }; 67 | } 68 | 69 | // Apply search and pagination 70 | let results = mockData.hits || []; 71 | 72 | // Apply search filtering 73 | results = this.applySearchFiltering(results, searchRequest.query); 74 | 75 | // Apply pagination 76 | const start = searchRequest.start || 0; 77 | const count = searchRequest.count || 200; 78 | const paginatedResults = results.slice(start, start + count); 79 | 80 | // Build response with proper SFCC format 81 | const response = { 82 | "_v": mockData._v || "23.2", 83 | "_type": "preference_value_search_result", 84 | "count": paginatedResults.length, 85 | "hits": paginatedResults, 86 | "query": this.enhanceQueryWithTypes(searchRequest.query || {"match_all_query": {"_type": "match_all_query"}}), 87 | "select": searchRequest.select || "(**)", 88 | "start": start, 89 | "total": results.length 90 | }; 91 | 92 | res.json(response); 93 | } catch (error) { 94 | // Handle unexpected errors 95 | res.status(500).json({ 96 | "_v": "23.2", 97 | "fault": { 98 | "type": "InternalServerError", 99 | "message": "An internal server error occurred while processing your request." 100 | } 101 | }); 102 | } 103 | } 104 | 105 | /** 106 | * Validate search request parameters 107 | */ 108 | validateSearchRequest(searchRequest, groupId, instanceType) { 109 | // Validate pagination parameters 110 | if (searchRequest.start && searchRequest.start < 0) { 111 | return { 112 | status: 400, 113 | body: { 114 | "_v": "23.2", 115 | "fault": { 116 | "arguments": {"path": "$.start", "document": "search_request"}, 117 | "type": "PropertyConstraintViolationException", 118 | "message": "An error occurred while decoding the request. There's a value constraint violation of property '$.start' in document 'search_request'." 119 | } 120 | } 121 | }; 122 | } 123 | 124 | if (searchRequest.count && (searchRequest.count < 0 || searchRequest.count > 200)) { 125 | return { 126 | status: 400, 127 | body: { 128 | "_v": "23.2", 129 | "fault": { 130 | "arguments": {"path": "$.count", "document": "search_request"}, 131 | "type": "PropertyConstraintViolationException", 132 | "message": "An error occurred while decoding the request. There's a value constraint violation of property '$.count' in document 'search_request'." 133 | } 134 | } 135 | }; 136 | } 137 | 138 | // Validate query structure 139 | if (searchRequest.query && !this.isValidQueryStructure(searchRequest.query)) { 140 | return { 141 | status: 400, 142 | body: { 143 | "_v": "23.2", 144 | "fault": { 145 | "type": "InvalidQueryException", 146 | "message": "Search query must contain at least one of: text_query, term_query, filtered_query, bool_query, match_all_query" 147 | } 148 | } 149 | }; 150 | } 151 | 152 | // Validate text query parameters 153 | if (searchRequest.query?.text_query) { 154 | const textQuery = searchRequest.query.text_query; 155 | if (!textQuery.search_phrase || textQuery.search_phrase.trim() === '') { 156 | return { 157 | status: 400, 158 | body: { 159 | "_v": "23.2", 160 | "fault": { 161 | "type": "ValidationException", 162 | "message": "text_query.search_phrase must be a non-empty string" 163 | } 164 | } 165 | }; 166 | } 167 | } 168 | 169 | return null; // No validation errors 170 | } 171 | 172 | /** 173 | * Check if query structure is valid 174 | */ 175 | isValidQueryStructure(query) { 176 | const validQueryTypes = ['text_query', 'term_query', 'filtered_query', 'bool_query', 'match_all_query']; 177 | return validQueryTypes.some(type => query.hasOwnProperty(type)); 178 | } 179 | 180 | /** 181 | * Apply search filtering based on query 182 | */ 183 | applySearchFiltering(results, query) { 184 | if (!query) return results; 185 | 186 | if (query.text_query) { 187 | return this.applyTextQuery(results, query.text_query); 188 | } 189 | 190 | if (query.bool_query) { 191 | return this.applyBoolQuery(results, query.bool_query); 192 | } 193 | 194 | if (query.term_query) { 195 | return this.applyTermQuery(results, query.term_query); 196 | } 197 | 198 | if (query.match_all_query) { 199 | return results; // Match all returns everything 200 | } 201 | 202 | return results; 203 | } 204 | 205 | /** 206 | * Apply text query filtering 207 | */ 208 | applyTextQuery(results, textQuery) { 209 | const searchPhrase = textQuery.search_phrase?.toLowerCase(); 210 | const searchFields = textQuery.fields || ['id', 'display_name']; 211 | 212 | if (!searchPhrase) return results; 213 | 214 | return results.filter(item => { 215 | return searchFields.some(field => { 216 | const fieldValue = field === 'display_name' 217 | ? item.display_name?.default || item.attribute_definition?.display_name?.default 218 | : item[field]; 219 | return fieldValue?.toLowerCase().includes(searchPhrase); 220 | }); 221 | }); 222 | } 223 | 224 | /** 225 | * Apply boolean query filtering (simplified implementation) 226 | */ 227 | applyBoolQuery(results, boolQuery) { 228 | let filteredResults = results; 229 | 230 | // Apply must conditions (AND) 231 | if (boolQuery.must && boolQuery.must.length > 0) { 232 | for (const condition of boolQuery.must) { 233 | filteredResults = this.applySearchFiltering(filteredResults, condition); 234 | } 235 | } 236 | 237 | // Apply must_not conditions (exclude) 238 | if (boolQuery.must_not && boolQuery.must_not.length > 0) { 239 | for (const condition of boolQuery.must_not) { 240 | const excludeResults = this.applySearchFiltering(results, condition); 241 | const excludeIds = new Set(excludeResults.map(item => item.id)); 242 | filteredResults = filteredResults.filter(item => !excludeIds.has(item.id)); 243 | } 244 | } 245 | 246 | // Apply should conditions (OR) - for now, just return if any match 247 | if (boolQuery.should && boolQuery.should.length > 0) { 248 | const shouldResults = []; 249 | for (const condition of boolQuery.should) { 250 | const conditionResults = this.applySearchFiltering(results, condition); 251 | shouldResults.push(...conditionResults); 252 | } 253 | // Remove duplicates and combine with must results 254 | const shouldIds = new Set(shouldResults.map(item => item.id)); 255 | if (boolQuery.must && boolQuery.must.length > 0) { 256 | // Intersect with must results 257 | filteredResults = filteredResults.filter(item => shouldIds.has(item.id)); 258 | } else { 259 | // Use should results if no must conditions 260 | filteredResults = shouldResults.filter((item, index, self) => 261 | self.findIndex(i => i.id === item.id) === index 262 | ); 263 | } 264 | } 265 | 266 | return filteredResults; 267 | } 268 | 269 | /** 270 | * Apply term query filtering 271 | */ 272 | applyTermQuery(results, termQuery) { 273 | const { fields, operator, values } = termQuery; 274 | 275 | return results.filter(item => { 276 | return fields.some(field => { 277 | const fieldValue = item[field] || item.attribute_definition?.[field]; 278 | 279 | switch (operator) { 280 | case 'is': 281 | return values.includes(fieldValue); 282 | case 'one_of': 283 | return values.some(value => fieldValue === value); 284 | case 'contains': 285 | return values.some(value => fieldValue?.includes(value)); 286 | default: 287 | return false; 288 | } 289 | }); 290 | }); 291 | } 292 | 293 | /** 294 | * Enhance query object with proper _type fields for response 295 | */ 296 | enhanceQueryWithTypes(query) { 297 | const enhanced = { ...query }; 298 | 299 | if (enhanced.text_query) { 300 | enhanced.text_query._type = 'text_query'; 301 | } 302 | if (enhanced.bool_query) { 303 | enhanced.bool_query._type = 'bool_query'; 304 | if (enhanced.bool_query.must) { 305 | enhanced.bool_query.must = enhanced.bool_query.must.map(q => this.enhanceQueryWithTypes(q)); 306 | } 307 | if (enhanced.bool_query.must_not) { 308 | enhanced.bool_query.must_not = enhanced.bool_query.must_not.map(q => this.enhanceQueryWithTypes(q)); 309 | } 310 | if (enhanced.bool_query.should) { 311 | enhanced.bool_query.should = enhanced.bool_query.should.map(q => this.enhanceQueryWithTypes(q)); 312 | } 313 | } 314 | if (enhanced.term_query) { 315 | enhanced.term_query._type = 'term_query'; 316 | } 317 | if (enhanced.match_all_query) { 318 | enhanced.match_all_query._type = 'match_all_query'; 319 | } 320 | 321 | return enhanced; 322 | } 323 | 324 | /** 325 | * Get the configured router 326 | */ 327 | getRouter() { 328 | return this.router; 329 | } 330 | } 331 | 332 | module.exports = SitePreferencesHandler; ``` -------------------------------------------------------------------------------- /tests/site-preferences-client.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for OCAPISitePreferencesClient 3 | * Tests site preferences operations 4 | */ 5 | 6 | import { OCAPISitePreferencesClient } from '../src/clients/ocapi/site-preferences-client.js'; 7 | import { OCAPIConfig } from '../src/types/types.js'; 8 | import { QueryBuilder } from '../src/utils/query-builder.js'; 9 | import { Validator } from '../src/utils/validator.js'; 10 | 11 | // Mock dependencies 12 | jest.mock('../src/clients/base/ocapi-auth-client.js'); 13 | jest.mock('../src/utils/query-builder.js'); 14 | jest.mock('../src/utils/validator.js'); 15 | 16 | describe('OCAPISitePreferencesClient', () => { 17 | let client: OCAPISitePreferencesClient; 18 | let mockValidateRequired: jest.MockedFunction<typeof Validator.validateRequired>; 19 | let mockValidateInstanceType: jest.MockedFunction<typeof Validator.validateInstanceType>; 20 | let mockValidateSearchRequest: jest.MockedFunction<typeof Validator.validateSearchRequest>; 21 | let mockQueryBuilderFromObject: jest.MockedFunction<typeof QueryBuilder.fromObject>; 22 | 23 | const mockConfig: OCAPIConfig = { 24 | hostname: 'test-instance.demandware.net', 25 | clientId: 'test-client-id', 26 | clientSecret: 'test-client-secret', 27 | version: 'v21_3', 28 | }; 29 | 30 | beforeEach(() => { 31 | jest.clearAllMocks(); 32 | 33 | // Mock Validator methods 34 | mockValidateRequired = Validator.validateRequired as jest.MockedFunction<typeof Validator.validateRequired>; 35 | mockValidateInstanceType = Validator.validateInstanceType as jest.MockedFunction< 36 | typeof Validator.validateInstanceType 37 | >; 38 | mockValidateSearchRequest = Validator.validateSearchRequest as jest.MockedFunction< 39 | typeof Validator.validateSearchRequest 40 | >; 41 | 42 | // Reset mock implementations to default behavior 43 | mockValidateRequired.mockImplementation(() => {}); 44 | mockValidateInstanceType.mockImplementation(() => 'sandbox'); // Return a valid instance type 45 | mockValidateSearchRequest.mockImplementation(() => {}); 46 | 47 | // Mock QueryBuilder 48 | mockQueryBuilderFromObject = QueryBuilder.fromObject as jest.MockedFunction<typeof QueryBuilder.fromObject>; 49 | 50 | client = new OCAPISitePreferencesClient(mockConfig); 51 | 52 | // Mock the inherited methods by adding them as properties - avoid protected access 53 | (client as any).post = jest.fn().mockResolvedValue({ data: 'mocked' }); 54 | }); 55 | 56 | describe('constructor', () => { 57 | it('should initialize with correct base URL', () => { 58 | expect(client).toBeInstanceOf(OCAPISitePreferencesClient); 59 | }); 60 | 61 | it('should use default version when not provided', () => { 62 | const configWithoutVersion = { 63 | hostname: 'test.demandware.net', 64 | clientId: 'client-id', 65 | clientSecret: 'client-secret', 66 | }; 67 | 68 | const clientWithDefaults = new OCAPISitePreferencesClient(configWithoutVersion); 69 | expect(clientWithDefaults).toBeInstanceOf(OCAPISitePreferencesClient); 70 | }); 71 | }); 72 | 73 | describe('searchSitePreferences', () => { 74 | const groupId = 'SiteGeneral'; 75 | const instanceType = 'sandbox'; 76 | const searchRequest = { 77 | query: { match_all_query: {} }, 78 | }; 79 | 80 | beforeEach(() => { 81 | mockValidateInstanceType.mockReturnValue('sandbox'); 82 | }); 83 | 84 | it('should validate all required parameters', async () => { 85 | await client.searchSitePreferences(groupId, instanceType, searchRequest); 86 | 87 | expect(Validator.validateRequired).toHaveBeenCalledWith( 88 | { groupId, instanceType }, 89 | ['groupId', 'instanceType'], 90 | ); 91 | expect(Validator.validateInstanceType).toHaveBeenCalledWith(instanceType); 92 | expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest); 93 | }); 94 | 95 | it('should make POST request to site preferences search endpoint', async () => { 96 | await client.searchSitePreferences(groupId, instanceType, searchRequest); 97 | 98 | expect((client as any).post).toHaveBeenCalledWith( 99 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search', 100 | searchRequest, 101 | ); 102 | }); 103 | 104 | it('should encode group ID in URL', async () => { 105 | const encodedGroupId = 'Site General Settings'; 106 | 107 | await client.searchSitePreferences(encodedGroupId, instanceType, searchRequest); 108 | 109 | expect((client as any).post).toHaveBeenCalledWith( 110 | '/site_preferences/preference_groups/Site%20General%20Settings/sandbox/preference_search', 111 | searchRequest, 112 | ); 113 | }); 114 | 115 | it('should handle different instance types', async () => { 116 | const testCases = [ 117 | { type: 'staging', expected: 'staging' }, 118 | { type: 'development', expected: 'development' }, 119 | { type: 'production', expected: 'production' }, 120 | ]; 121 | 122 | for (const testCase of testCases) { 123 | mockValidateInstanceType.mockReturnValue(testCase.expected as any); 124 | 125 | await client.searchSitePreferences(groupId, testCase.type, searchRequest); 126 | 127 | expect((client as any).post).toHaveBeenCalledWith( 128 | `/site_preferences/preference_groups/${groupId}/${testCase.expected}/preference_search`, 129 | searchRequest, 130 | ); 131 | } 132 | }); 133 | 134 | it('should include options in query string when provided', async () => { 135 | const options = { 136 | maskPasswords: true, 137 | expand: 'value', 138 | }; 139 | mockQueryBuilderFromObject.mockReturnValue('maskPasswords=true&expand=value'); 140 | 141 | await client.searchSitePreferences(groupId, instanceType, searchRequest, options); 142 | 143 | expect(QueryBuilder.fromObject).toHaveBeenCalledWith(options); 144 | expect((client as any).post).toHaveBeenCalledWith( 145 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search?maskPasswords=true&expand=value', 146 | searchRequest, 147 | ); 148 | }); 149 | 150 | it('should not include query string when options are empty', async () => { 151 | const options = {}; 152 | mockQueryBuilderFromObject.mockReturnValue(''); 153 | 154 | await client.searchSitePreferences(groupId, instanceType, searchRequest, options); 155 | 156 | expect((client as any).post).toHaveBeenCalledWith( 157 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search', 158 | searchRequest, 159 | ); 160 | }); 161 | 162 | it('should handle complex search request', async () => { 163 | const complexSearchRequest = { 164 | query: { 165 | text_query: { 166 | fields: ['id', 'display_name', 'description'], 167 | search_phrase: 'email', 168 | }, 169 | }, 170 | sorts: [ 171 | { field: 'display_name', sort_order: 'asc' as const }, 172 | { field: 'id' }, 173 | ], 174 | start: 0, 175 | count: 25, 176 | select: '(**)', 177 | }; 178 | 179 | await client.searchSitePreferences(groupId, instanceType, complexSearchRequest); 180 | 181 | expect(Validator.validateSearchRequest).toHaveBeenCalledWith(complexSearchRequest); 182 | expect((client as any).post).toHaveBeenCalledWith( 183 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search', 184 | complexSearchRequest, 185 | ); 186 | }); 187 | 188 | it('should handle term query for value types', async () => { 189 | const termSearchRequest = { 190 | query: { 191 | term_query: { 192 | fields: ['value_type'], 193 | operator: 'one_of', 194 | values: ['string', 'boolean', 'int'], 195 | }, 196 | }, 197 | }; 198 | 199 | await client.searchSitePreferences(groupId, instanceType, termSearchRequest); 200 | 201 | expect((client as any).post).toHaveBeenCalledWith( 202 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search', 203 | termSearchRequest, 204 | ); 205 | }); 206 | 207 | it('should handle boolean query combinations', async () => { 208 | const boolSearchRequest = { 209 | query: { 210 | bool_query: { 211 | must: [ 212 | { 213 | text_query: { 214 | fields: ['display_name'], 215 | search_phrase: 'payment', 216 | }, 217 | }, 218 | ], 219 | must_not: [ 220 | { 221 | term_query: { 222 | fields: ['value_type'], 223 | operator: 'is', 224 | values: ['password'], 225 | }, 226 | }, 227 | ], 228 | }, 229 | }, 230 | }; 231 | 232 | await client.searchSitePreferences(groupId, instanceType, boolSearchRequest); 233 | 234 | expect((client as any).post).toHaveBeenCalledWith( 235 | '/site_preferences/preference_groups/SiteGeneral/sandbox/preference_search', 236 | boolSearchRequest, 237 | ); 238 | }); 239 | }); 240 | 241 | describe('error handling', () => { 242 | it('should propagate validation errors for required fields', async () => { 243 | const validationError = new Error('Required field missing'); 244 | mockValidateRequired.mockImplementation(() => { 245 | throw validationError; 246 | }); 247 | 248 | await expect( 249 | client.searchSitePreferences('groupId', 'sandbox', { query: {} }), 250 | ).rejects.toThrow(validationError); 251 | }); 252 | 253 | it('should propagate instance type validation errors', async () => { 254 | const instanceTypeError = new Error('Invalid instance type'); 255 | mockValidateInstanceType.mockImplementation(() => { 256 | throw instanceTypeError; 257 | }); 258 | 259 | await expect( 260 | client.searchSitePreferences('groupId', 'invalid', { query: {} }), 261 | ).rejects.toThrow(instanceTypeError); 262 | }); 263 | 264 | it('should propagate search request validation errors', async () => { 265 | const searchValidationError = new Error('Invalid search request'); 266 | mockValidateSearchRequest.mockImplementation(() => { 267 | throw searchValidationError; 268 | }); 269 | 270 | await expect( 271 | client.searchSitePreferences('groupId', 'sandbox', { query: {} }), 272 | ).rejects.toThrow(searchValidationError); 273 | }); 274 | 275 | it('should propagate HTTP errors from base client', async () => { 276 | const httpError = new Error('HTTP request failed'); 277 | (client as any).post = jest.fn().mockRejectedValue(httpError); 278 | 279 | await expect( 280 | client.searchSitePreferences('groupId', 'sandbox', { query: { match_all_query: {} } }), 281 | ).rejects.toThrow(httpError); 282 | }); 283 | }); 284 | 285 | describe('integration scenarios', () => { 286 | it('should handle complete site preferences search workflow', async () => { 287 | const groupId = 'CustomPreferences'; 288 | const instanceType = 'development'; 289 | const searchRequest = { 290 | query: { 291 | bool_query: { 292 | must: [ 293 | { 294 | text_query: { 295 | fields: ['id', 'display_name'], 296 | search_phrase: 'api', 297 | }, 298 | }, 299 | { 300 | term_query: { 301 | fields: ['value_type'], 302 | operator: 'one_of', 303 | values: ['string', 'text'], 304 | }, 305 | }, 306 | ], 307 | }, 308 | }, 309 | sorts: [ 310 | { field: 'display_name', sort_order: 'asc' as const }, 311 | ], 312 | start: 0, 313 | count: 50, 314 | }; 315 | const options = { 316 | maskPasswords: false, 317 | expand: 'value', 318 | }; 319 | 320 | mockValidateInstanceType.mockReturnValue('development'); 321 | mockQueryBuilderFromObject.mockReturnValue('maskPasswords=false&expand=value'); 322 | 323 | await client.searchSitePreferences(groupId, instanceType, searchRequest, options); 324 | 325 | // Verify all validations were called 326 | expect(Validator.validateRequired).toHaveBeenCalledWith( 327 | { groupId, instanceType }, 328 | ['groupId', 'instanceType'], 329 | ); 330 | expect(Validator.validateInstanceType).toHaveBeenCalledWith(instanceType); 331 | expect(Validator.validateSearchRequest).toHaveBeenCalledWith(searchRequest); 332 | 333 | // Verify query string building 334 | expect(QueryBuilder.fromObject).toHaveBeenCalledWith(options); 335 | 336 | // Verify the final API call 337 | expect((client as any).post).toHaveBeenCalledWith( 338 | '/site_preferences/preference_groups/CustomPreferences/development/preference_search?maskPasswords=false&expand=value', 339 | searchRequest, 340 | ); 341 | }); 342 | 343 | it('should handle minimal search request', async () => { 344 | const minimalRequest = { 345 | query: { match_all_query: {} }, 346 | }; 347 | 348 | mockValidateInstanceType.mockReturnValue('sandbox'); 349 | 350 | await client.searchSitePreferences('Basic', 'sandbox', minimalRequest); 351 | 352 | expect((client as any).post).toHaveBeenCalledWith( 353 | '/site_preferences/preference_groups/Basic/sandbox/preference_search', 354 | minimalRequest, 355 | ); 356 | }); 357 | }); 358 | }); 359 | ``` -------------------------------------------------------------------------------- /docs/dw_util/List.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.util 2 | 3 | # Class List 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.util.Collection 9 | - dw.util.List 10 | 11 | ## Description 12 | 13 | An ordered collection of objects. The user of a List has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list. Lists are zero based similar to arrays. Unlike sets, lists allow duplicate elements. 14 | 15 | ## Properties 16 | 17 | ## Constructor Summary 18 | 19 | ## Method Summary 20 | 21 | ### addAt 22 | 23 | **Signature:** `addAt(index : Number, value : Object) : void` 24 | 25 | Adds the specified object into the list at the specified index. 26 | 27 | ### concat 28 | 29 | **Signature:** `concat(values : Object...) : List` 30 | 31 | Creates and returns a new List that is the result of concatenating this list with each of the specified values. 32 | 33 | ### fill 34 | 35 | **Signature:** `fill(obj : Object) : void` 36 | 37 | Replaces all of the elements in the list with the given object. 38 | 39 | ### get 40 | 41 | **Signature:** `get(index : Number) : Object` 42 | 43 | Returns the object at the specified index. 44 | 45 | ### indexOf 46 | 47 | **Signature:** `indexOf(value : Object) : Number` 48 | 49 | Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. 50 | 51 | ### join 52 | 53 | **Signature:** `join() : String` 54 | 55 | Converts all elements of the list to a string by calling the toString() method and then concatenates them together, with a comma between elements. 56 | 57 | ### join 58 | 59 | **Signature:** `join(separator : String) : String` 60 | 61 | Converts all elements of the list to a string by calling the toString() method and then concatenates them together, with the separator string between elements. 62 | 63 | ### lastIndexOf 64 | 65 | **Signature:** `lastIndexOf(value : Object) : Number` 66 | 67 | Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element. 68 | 69 | ### pop 70 | 71 | **Signature:** `pop() : Object` 72 | 73 | Removes and returns the last element from the list. 74 | 75 | ### push 76 | 77 | **Signature:** `push(values : Object...) : Number` 78 | 79 | Appends the specified values to the end of the list in order. 80 | 81 | ### removeAt 82 | 83 | **Signature:** `removeAt(index : Number) : Object` 84 | 85 | Removes the object at the specified index. 86 | 87 | ### replaceAll 88 | 89 | **Signature:** `replaceAll(oldValue : Object, newValue : Object) : boolean` 90 | 91 | Replaces all occurrences of oldValue with newValue. 92 | 93 | ### reverse 94 | 95 | **Signature:** `reverse() : void` 96 | 97 | Reverses the order of the elements in the list. 98 | 99 | ### rotate 100 | 101 | **Signature:** `rotate(distance : Number) : void` 102 | 103 | Rotates the elements in the list by the specified distance. 104 | 105 | ### set 106 | 107 | **Signature:** `set(index : Number, value : Object) : Object` 108 | 109 | Replaces the object at the specified index in this list with the specified object. 110 | 111 | ### shift 112 | 113 | **Signature:** `shift() : Object` 114 | 115 | Removes and returns the first element of the list. 116 | 117 | ### shuffle 118 | 119 | **Signature:** `shuffle() : void` 120 | 121 | Randomly permutes the elements in the list. 122 | 123 | ### size 124 | 125 | **Signature:** `size() : Number` 126 | 127 | Returns the size of this list. 128 | 129 | ### slice 130 | 131 | **Signature:** `slice(from : Number) : List` 132 | 133 | Returns a slice, or sublist, of this list. 134 | 135 | ### slice 136 | 137 | **Signature:** `slice(from : Number, to : Number) : List` 138 | 139 | Returns a slice, or sublist, of this list. 140 | 141 | ### sort 142 | 143 | **Signature:** `sort() : void` 144 | 145 | Sorts the elements of the list based on their natural order. 146 | 147 | ### sort 148 | 149 | **Signature:** `sort(comparator : Object) : void` 150 | 151 | Sorts the elements of a list. 152 | 153 | ### subList 154 | 155 | **Signature:** `subList(from : Number, to : Number) : List` 156 | 157 | Returns a list containing the elements in this list identified by the specified arguments. 158 | 159 | ### swap 160 | 161 | **Signature:** `swap(i : Number, j : Number) : void` 162 | 163 | Swaps the elements at the specified positions in the list. 164 | 165 | ### unshift 166 | 167 | **Signature:** `unshift(values : Object...) : Number` 168 | 169 | Inserts values at the beginning of the list. 170 | 171 | ## Method Detail 172 | 173 | ## Method Details 174 | 175 | ### addAt 176 | 177 | **Signature:** `addAt(index : Number, value : Object) : void` 178 | 179 | **Description:** Adds the specified object into the list at the specified index. 180 | 181 | **Parameters:** 182 | 183 | - `index`: the index to use. 184 | - `value`: the object to insert. 185 | 186 | --- 187 | 188 | ### concat 189 | 190 | **Signature:** `concat(values : Object...) : List` 191 | 192 | **Description:** Creates and returns a new List that is the result of concatenating this list with each of the specified values. This list itself is unmodified. If any of the specified values is itself an array or a Collection, then the elements of that Collection or array are appended to the new list rather than the object itself. 193 | 194 | **Parameters:** 195 | 196 | - `values`: one or more objects to concatenate. 197 | 198 | **Returns:** 199 | 200 | a new List that is the result of concatenating this list with each of the specified values. 201 | 202 | --- 203 | 204 | ### fill 205 | 206 | **Signature:** `fill(obj : Object) : void` 207 | 208 | **Description:** Replaces all of the elements in the list with the given object. 209 | 210 | **Parameters:** 211 | 212 | - `obj`: the object to use during replacement. 213 | 214 | --- 215 | 216 | ### get 217 | 218 | **Signature:** `get(index : Number) : Object` 219 | 220 | **Description:** Returns the object at the specified index. 221 | 222 | **Parameters:** 223 | 224 | - `index`: the index to use. 225 | 226 | **Returns:** 227 | 228 | the object at the specified index. 229 | 230 | --- 231 | 232 | ### indexOf 233 | 234 | **Signature:** `indexOf(value : Object) : Number` 235 | 236 | **Description:** Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. 237 | 238 | **Parameters:** 239 | 240 | - `value`: the element to use. 241 | 242 | **Returns:** 243 | 244 | the index of the specified object or -1 if the passed object is not found in the list. 245 | 246 | --- 247 | 248 | ### join 249 | 250 | **Signature:** `join() : String` 251 | 252 | **Description:** Converts all elements of the list to a string by calling the toString() method and then concatenates them together, with a comma between elements. 253 | 254 | **Returns:** 255 | 256 | The string that results from converting each element of the list to a string and then concatenating them together, with a comma between elements. 257 | 258 | --- 259 | 260 | ### join 261 | 262 | **Signature:** `join(separator : String) : String` 263 | 264 | **Description:** Converts all elements of the list to a string by calling the toString() method and then concatenates them together, with the separator string between elements. If null is passed, then the comma character is used as a separator. 265 | 266 | **Parameters:** 267 | 268 | - `separator`: The separator string. May be null in which case the comma character is used. 269 | 270 | **Returns:** 271 | 272 | The string that results from converting each element of the list to a string and then concatenating them together, with the separator string between elements. 273 | 274 | --- 275 | 276 | ### lastIndexOf 277 | 278 | **Signature:** `lastIndexOf(value : Object) : Number` 279 | 280 | **Description:** Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element. 281 | 282 | **Parameters:** 283 | 284 | - `value`: the element to use. 285 | 286 | **Returns:** 287 | 288 | the last index of the specified object or -1 if the passed object is not found in the list. 289 | 290 | --- 291 | 292 | ### pop 293 | 294 | **Signature:** `pop() : Object` 295 | 296 | **Description:** Removes and returns the last element from the list. 297 | 298 | **Returns:** 299 | 300 | The last element of the list or null if the list is already empty. 301 | 302 | --- 303 | 304 | ### push 305 | 306 | **Signature:** `push(values : Object...) : Number` 307 | 308 | **Description:** Appends the specified values to the end of the list in order. 309 | 310 | **Parameters:** 311 | 312 | - `values`: One or more values to be appended to the end of the list. 313 | 314 | **Returns:** 315 | 316 | The new length of the list, after the specified values are appended to it. 317 | 318 | --- 319 | 320 | ### removeAt 321 | 322 | **Signature:** `removeAt(index : Number) : Object` 323 | 324 | **Description:** Removes the object at the specified index. 325 | 326 | **Parameters:** 327 | 328 | - `index`: the index to use. 329 | 330 | **Returns:** 331 | 332 | the object that was removed. 333 | 334 | --- 335 | 336 | ### replaceAll 337 | 338 | **Signature:** `replaceAll(oldValue : Object, newValue : Object) : boolean` 339 | 340 | **Description:** Replaces all occurrences of oldValue with newValue. 341 | 342 | **Parameters:** 343 | 344 | - `oldValue`: the old object. 345 | - `newValue`: the new object. 346 | 347 | **Returns:** 348 | 349 | true if one or more elements were replaced, false otherwise. 350 | 351 | --- 352 | 353 | ### reverse 354 | 355 | **Signature:** `reverse() : void` 356 | 357 | **Description:** Reverses the order of the elements in the list. 358 | 359 | --- 360 | 361 | ### rotate 362 | 363 | **Signature:** `rotate(distance : Number) : void` 364 | 365 | **Description:** Rotates the elements in the list by the specified distance. 366 | 367 | **Parameters:** 368 | 369 | - `distance`: the distance to use. 370 | 371 | --- 372 | 373 | ### set 374 | 375 | **Signature:** `set(index : Number, value : Object) : Object` 376 | 377 | **Description:** Replaces the object at the specified index in this list with the specified object. 378 | 379 | **Parameters:** 380 | 381 | - `index`: the index to use. 382 | - `value`: the object to use when replacing the existing object. 383 | 384 | **Returns:** 385 | 386 | the replaced object. 387 | 388 | --- 389 | 390 | ### shift 391 | 392 | **Signature:** `shift() : Object` 393 | 394 | **Description:** Removes and returns the first element of the list. If the list is already empty, this method simply returns null. 395 | 396 | **Returns:** 397 | 398 | The former first element of the list, or null is list is already empty. 399 | 400 | --- 401 | 402 | ### shuffle 403 | 404 | **Signature:** `shuffle() : void` 405 | 406 | **Description:** Randomly permutes the elements in the list. 407 | 408 | --- 409 | 410 | ### size 411 | 412 | **Signature:** `size() : Number` 413 | 414 | **Description:** Returns the size of this list. 415 | 416 | **Returns:** 417 | 418 | the size of this list. 419 | 420 | --- 421 | 422 | ### slice 423 | 424 | **Signature:** `slice(from : Number) : List` 425 | 426 | **Description:** Returns a slice, or sublist, of this list. The returned list contains the element specified by from and all subsequent elements up to the end of this list. 427 | 428 | **Parameters:** 429 | 430 | - `from`: The index at which the slice is to begin. If negative, this argument specifies a position measured from the end of this list. That, -1 indicates the last element, -2 indicates the next from the last element, and so on. 431 | 432 | **Returns:** 433 | 434 | A new List that contains the elements of this list from the element specified by from up to the end of this list. 435 | 436 | --- 437 | 438 | ### slice 439 | 440 | **Signature:** `slice(from : Number, to : Number) : List` 441 | 442 | **Description:** Returns a slice, or sublist, of this list. The returned list contains the element specified by from and all subsequent elements up to, but not including, the element specified by to. 443 | 444 | **Parameters:** 445 | 446 | - `from`: The index at which the slice is to begin. If negative, this argument specifies a position measured from the end of this list. That, -1 indicates the last element, -2 indicates the next from the last element, and so on. 447 | - `to`: The index immediately after the end of the slice. If this argument is negative, it specifies an element measured from the end of this list. 448 | 449 | **Returns:** 450 | 451 | A new List that contains the elements of this list from the element specified by from up to, but not including, the element specified by to. 452 | 453 | --- 454 | 455 | ### sort 456 | 457 | **Signature:** `sort() : void` 458 | 459 | **Description:** Sorts the elements of the list based on their natural order. This sort is guaranteed to be stable: equal elements will not be reordered as a result of the sort. 460 | 461 | --- 462 | 463 | ### sort 464 | 465 | **Signature:** `sort(comparator : Object) : void` 466 | 467 | **Description:** Sorts the elements of a list. The order of the elements is determined with a comparator (see PropertyComparator) or with the help of the given function. The function must take two parameters and return a value <0 if the first parameter is smaller than the second, a value of 0 if both are equal and a value if >0 if the first one is greater than the second parameter. This sort is guaranteed to be stable: equal elements will not be reordered as a result of the sort. 468 | 469 | **Parameters:** 470 | 471 | - `comparator`: an instance of a PropertyComparator or a comparison function 472 | 473 | --- 474 | 475 | ### subList 476 | 477 | **Signature:** `subList(from : Number, to : Number) : List` 478 | 479 | **Description:** Returns a list containing the elements in this list identified by the specified arguments. 480 | 481 | **Parameters:** 482 | 483 | - `from`: the beginning index of the elements to move to the new list. 484 | - `to`: the ending index of the elements to move to the new list. 485 | 486 | **Returns:** 487 | 488 | the new list containing the elements. 489 | 490 | --- 491 | 492 | ### swap 493 | 494 | **Signature:** `swap(i : Number, j : Number) : void` 495 | 496 | **Description:** Swaps the elements at the specified positions in the list. 497 | 498 | **Parameters:** 499 | 500 | - `i`: the first element to swap. 501 | - `j`: the second element to swap. 502 | 503 | --- 504 | 505 | ### unshift 506 | 507 | **Signature:** `unshift(values : Object...) : Number` 508 | 509 | **Description:** Inserts values at the beginning of the list. The first argument becomes the new element 0; the second argument becomes element 1; and so on. 510 | 511 | **Parameters:** 512 | 513 | - `values`: The values to insert into the list. 514 | 515 | **Returns:** 516 | 517 | The new length of the lest. 518 | 519 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-system-object-definitions.docs-only.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * MCP Aegis - Programmatic Tests for get_system_object_definitions Tool (Docs-Only Mode) 3 | * 4 | * Tests that system object tools are NOT available in docs-only mode. 5 | * This tool requires SFCC credentials and should not be available without them. 6 | * 7 | * Quick Test Commands: 8 | * node --test tests/mcp/node/get-system-object-definitions.docs-only.programmatic.test.js 9 | * npm test -- --grep "get_system_object_definitions.*Docs-Only" 10 | */ 11 | 12 | import { test, describe, before, after, beforeEach } from 'node:test'; 13 | import { strict as assert } from 'node:assert'; 14 | import { connect } from 'mcp-aegis'; 15 | 16 | describe('get_system_object_definitions Tool - Docs-Only Mode Programmatic Tests', () => { 17 | let client; 18 | 19 | before(async () => { 20 | client = await connect('./aegis.config.docs-only.json'); 21 | }); 22 | 23 | after(async () => { 24 | if (client?.connected) { 25 | await client.disconnect(); 26 | } 27 | }); 28 | 29 | beforeEach(() => { 30 | // CRITICAL: Clear all buffers to prevent leaking into next tests 31 | client.clearAllBuffers(); 32 | }); 33 | 34 | // ================================================================================== 35 | // TOOL UNAVAILABILITY TESTS 36 | // ================================================================================== 37 | 38 | describe('Tool Unavailability in Docs-Only Mode', () => { 39 | test('should NOT list get_system_object_definitions tool in docs-only mode', async () => { 40 | const tools = await client.listTools(); 41 | 42 | assert.ok(Array.isArray(tools), 'Tools should be an array'); 43 | 44 | const toolNames = tools.map(tool => tool.name); 45 | assert.ok(!toolNames.includes('get_system_object_definitions'), 46 | 'Should NOT include get_system_object_definitions tool in docs-only mode'); 47 | }); 48 | 49 | test('should NOT list any system object tools in docs-only mode', async () => { 50 | const tools = await client.listTools(); 51 | const toolNames = tools.map(tool => tool.name); 52 | 53 | const systemObjectTools = [ 54 | 'get_system_object_definitions', 55 | 'get_system_object_definition', 56 | 'search_system_object_attribute_definitions', 57 | 'search_site_preferences', 58 | 'search_system_object_attribute_groups', 59 | 'search_custom_object_attribute_definitions' 60 | ]; 61 | 62 | systemObjectTools.forEach(toolName => { 63 | assert.ok(!toolNames.includes(toolName), 64 | `Should NOT include ${toolName} tool in docs-only mode`); 65 | }); 66 | }); 67 | 68 | test('should have expected number of tools in docs-only mode', async () => { 69 | const tools = await client.listTools(); 70 | 71 | // Should have documentation, SFRA, best practices, and cartridge tools 72 | // but NOT system object, log, or code version tools 73 | assert.ok(tools.length >= 10, 'Should have at least 10 tools available'); 74 | assert.ok(tools.length <= 20, 'Should not have too many tools (likely around 15)'); 75 | }); 76 | }); 77 | 78 | 79 | 80 | // ================================================================================== 81 | // TOOL CALL BEHAVIOR - SHOULD RETURN ERROR 82 | // ================================================================================== 83 | 84 | describe('Tool Call Behavior - Configuration Error', () => { 85 | test('should return configuration error when calling unlisted tool', async () => { 86 | const result = await client.callTool('get_system_object_definitions', {}); 87 | 88 | assert.equal(result.isError, true, 'Should return error result'); 89 | assert.ok(result.content, 'Should have content'); 90 | assert.ok(Array.isArray(result.content), 'Content should be array'); 91 | assert.equal(result.content[0].type, 'text', 'Content should be text type'); 92 | 93 | const errorText = result.content[0].text; 94 | assert.ok(errorText.includes('OCAPI client not configured') || 95 | errorText.includes('not configured') || 96 | errorText.includes('credentials'), 97 | 'Error should mention configuration/credentials issue'); 98 | }); 99 | 100 | test('should return proper error result structure for unlisted tool', async () => { 101 | const result = await client.callTool('get_system_object_definitions', {}); 102 | 103 | assert.equal(result.isError, true, 'Should be error result'); 104 | assert.ok(Array.isArray(result.content), 'Content should be array'); 105 | assert.ok(result.content.length > 0, 'Should have error content'); 106 | assert.equal(result.content[0].type, 'text', 'Error content should be text'); 107 | }); 108 | 109 | test('should fail fast when calling unlisted tool with parameters', async () => { 110 | const startTime = Date.now(); 111 | 112 | const result = await client.callTool('get_system_object_definitions', { 113 | start: 0, 114 | count: 10, 115 | select: '(**)' 116 | }); 117 | 118 | const duration = Date.now() - startTime; 119 | 120 | assert.equal(result.isError, true, 'Should return error'); 121 | assert.ok(duration < 1000, 'Should fail quickly (under 1 second)'); 122 | 123 | const errorText = result.content[0].text; 124 | assert.ok(errorText.includes('OCAPI client not configured') || 125 | errorText.includes('not configured') || 126 | errorText.includes('credentials'), 127 | 'Error should mention configuration issue'); 128 | }); 129 | 130 | test('should handle invalid parameters gracefully even in error mode', async () => { 131 | const result = await client.callTool('get_system_object_definitions', { 132 | invalidParam: 'test', 133 | anotherInvalid: 123 134 | }); 135 | 136 | assert.equal(result.isError, true, 'Should return error'); 137 | 138 | const errorText = result.content[0].text; 139 | // Should mention configuration, not parameter validation 140 | assert.ok(errorText.includes('OCAPI client not configured') || 141 | errorText.includes('not configured') || 142 | errorText.includes('credentials'), 143 | 'Should prioritize configuration error over parameter validation'); 144 | }); 145 | }); 146 | 147 | // ================================================================================== 148 | // OTHER SYSTEM OBJECT TOOLS VERIFICATION 149 | // ================================================================================== 150 | 151 | describe('Other System Object Tools Unavailability', () => { 152 | test('should NOT have get_system_object_definition tool', async () => { 153 | const tools = await client.listTools(); 154 | const toolNames = tools.map(tool => tool.name); 155 | 156 | assert.ok(!toolNames.includes('get_system_object_definition'), 157 | 'Should NOT include get_system_object_definition tool'); 158 | 159 | // Verify calling it also returns error 160 | const result = await client.callTool('get_system_object_definition', { 161 | objectType: 'Product' 162 | }); 163 | 164 | assert.equal(result.isError, true, 'Should return configuration error'); 165 | }); 166 | 167 | test('should NOT have search_system_object_attribute_definitions tool', async () => { 168 | const tools = await client.listTools(); 169 | const toolNames = tools.map(tool => tool.name); 170 | 171 | assert.ok(!toolNames.includes('search_system_object_attribute_definitions'), 172 | 'Should NOT include search_system_object_attribute_definitions tool'); 173 | 174 | // Verify calling it also returns error 175 | const result = await client.callTool('search_system_object_attribute_definitions', { 176 | objectType: 'Product', 177 | searchRequest: { 178 | query: { match_all_query: {} } 179 | } 180 | }); 181 | 182 | assert.equal(result.isError, true, 'Should return configuration error'); 183 | }); 184 | 185 | test('should NOT have site preferences tools', async () => { 186 | const tools = await client.listTools(); 187 | const toolNames = tools.map(tool => tool.name); 188 | 189 | assert.ok(!toolNames.includes('search_site_preferences'), 190 | 'Should NOT include search_site_preferences tool'); 191 | 192 | // Verify calling it also returns error 193 | const result = await client.callTool('search_site_preferences', { 194 | groupId: 'SitePreferences', 195 | instanceType: 'sandbox', 196 | searchRequest: { 197 | query: { match_all_query: {} } 198 | } 199 | }); 200 | 201 | assert.equal(result.isError, true, 'Should return configuration error'); 202 | }); 203 | }); 204 | 205 | // ================================================================================== 206 | // CONSISTENCY TESTS 207 | // ================================================================================== 208 | 209 | describe('Consistency Tests', () => { 210 | test('should consistently exclude system object tools across multiple calls', async () => { 211 | const tools1 = await client.listTools(); 212 | const tools2 = await client.listTools(); 213 | 214 | const toolNames1 = tools1.map(tool => tool.name); 215 | const toolNames2 = tools2.map(tool => tool.name); 216 | 217 | assert.ok(!toolNames1.includes('get_system_object_definitions'), 218 | 'First call should exclude system object tools'); 219 | assert.ok(!toolNames2.includes('get_system_object_definitions'), 220 | 'Second call should exclude system object tools'); 221 | }); 222 | 223 | test('should maintain consistent tool exclusion on second call', async () => { 224 | const tools = await client.listTools(); 225 | const toolNames = tools.map(tool => tool.name); 226 | 227 | assert.ok(!toolNames.includes('get_system_object_definitions'), 228 | 'Should consistently exclude get_system_object_definitions'); 229 | }); 230 | 231 | test('should consistently return configuration error across multiple attempts', async () => { 232 | const result1 = await client.callTool('get_system_object_definitions', {}); 233 | const result2 = await client.callTool('get_system_object_definitions', {}); 234 | 235 | assert.equal(result1.isError, true, 'First call should return error'); 236 | assert.equal(result2.isError, true, 'Second call should return error'); 237 | 238 | const error1 = result1.content[0].text; 239 | const error2 = result2.content[0].text; 240 | 241 | assert.ok(error1.includes('OCAPI client not configured') || 242 | error1.includes('not configured'), 243 | 'First error should mention configuration'); 244 | assert.ok(error2.includes('OCAPI client not configured') || 245 | error2.includes('not configured'), 246 | 'Second error should mention configuration'); 247 | }); 248 | 249 | test('should have consistent tool count in docs-only mode', async () => { 250 | const tools1 = await client.listTools(); 251 | const tools2 = await client.listTools(); 252 | 253 | assert.equal(tools1.length, tools2.length, 254 | 'Tool count should be consistent between calls'); 255 | 256 | // Should have around 15 tools in docs-only mode 257 | assert.ok(tools1.length >= 10, 'Should have reasonable number of tools'); 258 | assert.ok(tools1.length <= 20, 'Should not have excessive tools'); 259 | }); 260 | }); 261 | 262 | 263 | 264 | // ================================================================================== 265 | // MODE IDENTIFICATION TESTS - FOCUSED ON SYSTEM OBJECT TOOLS 266 | // ================================================================================== 267 | 268 | describe('System Object Tools Mode Identification', () => { 269 | test('should clearly identify system object tools unavailability in docs-only mode', async () => { 270 | const tools = await client.listTools(); 271 | const toolNames = tools.map(tool => tool.name); 272 | 273 | const hasSystemObjectTools = toolNames.includes('get_system_object_definitions'); 274 | const hasOtherSystemObjectTools = toolNames.includes('search_site_preferences'); 275 | 276 | assert.ok(!hasSystemObjectTools, 'Should NOT have get_system_object_definitions tool'); 277 | assert.ok(!hasOtherSystemObjectTools, 'Should NOT have other system object tools'); 278 | }); 279 | 280 | test('should exclude all system object tools in docs-only mode', async () => { 281 | const tools = await client.listTools(); 282 | const toolNames = tools.map(tool => tool.name); 283 | 284 | // System object tools that should NOT be available in docs-only mode 285 | const systemObjectTools = [ 286 | 'get_system_object_definitions', 287 | 'get_system_object_definition', 288 | 'search_system_object_attribute_definitions', 289 | 'search_site_preferences', 290 | 'search_system_object_attribute_groups', 291 | 'search_custom_object_attribute_definitions' 292 | ]; 293 | 294 | systemObjectTools.forEach(toolName => { 295 | assert.ok(!toolNames.includes(toolName), 296 | `Docs-only mode should NOT include system object tool: ${toolName}`); 297 | }); 298 | }); 299 | }); 300 | }); ``` -------------------------------------------------------------------------------- /docs/TopLevel/global.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: TopLevel 2 | 3 | # Class global 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - global 9 | 10 | ## Description 11 | 12 | The global object is a pre-defined object that serves as a placeholder for the global properties and functions of JavaScript. All other predefined objects, functions, and properties are accessible through the global object. 13 | 14 | ## Constants 15 | 16 | ### PIPELET_ERROR 17 | 18 | **Type:** Number 19 | 20 | represents an error during pipelet execution 21 | 22 | ### PIPELET_NEXT 23 | 24 | **Type:** Number 25 | 26 | represents the next pipelet to fire 27 | 28 | ## Properties 29 | 30 | ## Constructor Summary 31 | 32 | ## Method Summary 33 | 34 | ### decodeURI 35 | 36 | **Signature:** `static decodeURI(uri : String) : String` 37 | 38 | Unescapes characters in a URI component. 39 | 40 | ### decodeURIComponent 41 | 42 | **Signature:** `static decodeURIComponent(uriComponent : String) : String` 43 | 44 | Unescapes characters in a URI component. 45 | 46 | ### empty 47 | 48 | **Signature:** `static empty(obj : Object) : boolean` 49 | 50 | The method tests, whether the given object is empty. 51 | 52 | ### encodeURI 53 | 54 | **Signature:** `static encodeURI(uri : String) : String` 55 | 56 | Escapes characters in a URI. 57 | 58 | ### encodeURIComponent 59 | 60 | **Signature:** `static encodeURIComponent(uriComponent : String) : String` 61 | 62 | Escapes characters in a URI component. 63 | 64 | ### escape 65 | 66 | **Signature:** `static escape(s : String) : String` 67 | 68 | Encodes a String. 69 | 70 | ### eval 71 | 72 | **Signature:** `static eval(code : String) : Object` 73 | 74 | Execute JavaScript code from a String. 75 | 76 | ### importClass 77 | 78 | **Signature:** `static importClass(classPath : Object) : void` 79 | 80 | Import the specified class and make it available at the top level. 81 | 82 | ### importPackage 83 | 84 | **Signature:** `static importPackage(packagePath : Object) : void` 85 | 86 | Import all the classes in the specified package available at the top level. 87 | 88 | ### importScript 89 | 90 | **Signature:** `static importScript(scriptPath : String) : void` 91 | 92 | Imports all functions from the specified script. 93 | 94 | ### isFinite 95 | 96 | **Signature:** `static isFinite(number : Number) : boolean` 97 | 98 | Returns true if the specified Number is finite. 99 | 100 | ### isNaN 101 | 102 | **Signature:** `static isNaN(object : Object) : boolean` 103 | 104 | Test the specified value to determine if it is not a Number. 105 | 106 | ### isXMLName 107 | 108 | **Signature:** `static isXMLName(name : String) : boolean` 109 | 110 | Determines whether the specified string is a valid name for an XML element or attribute. 111 | 112 | ### parseFloat 113 | 114 | **Signature:** `static parseFloat(s : String) : Number` 115 | 116 | Parses a String into an float Number. 117 | 118 | ### parseInt 119 | 120 | **Signature:** `static parseInt(s : String, radix : Number) : Number` 121 | 122 | Parses a String into an integer Number using the specified radix. 123 | 124 | ### parseInt 125 | 126 | **Signature:** `static parseInt(s : String) : Number` 127 | 128 | Parses a String into an integer Number. This function is a short form for the call to parseInt(String, Number) with automatic determination of the radix. 129 | 130 | ### parseInt 131 | 132 | **Signature:** `static parseInt(s : String) : Number` 133 | 134 | Parses a String into an integer Number. 135 | 136 | ### require 137 | 138 | **Signature:** `static require(path : String) : Module` 139 | 140 | The require() function supports loading of modules in JavaScript. 141 | 142 | ### trace 143 | 144 | **Signature:** `static trace(msg : String, params : Object...) : void` 145 | 146 | Formats and prints the message using the specified params and returns the formatted message. 147 | 148 | ### unescape 149 | 150 | **Signature:** `static unescape(string : String) : String` 151 | 152 | Decode an escaped String. 153 | 154 | ## Method Detail 155 | 156 | ## Method Details 157 | 158 | ### decodeURI 159 | 160 | **Signature:** `static decodeURI(uri : String) : String` 161 | 162 | **Description:** Unescapes characters in a URI component. 163 | 164 | **Parameters:** 165 | 166 | - `uri`: a string that contains an encoded URI or other text to be decoded. 167 | 168 | **Returns:** 169 | 170 | A copy of uri with any hexadecimal escape sequences replaced with the characters they represent 171 | 172 | --- 173 | 174 | ### decodeURIComponent 175 | 176 | **Signature:** `static decodeURIComponent(uriComponent : String) : String` 177 | 178 | **Description:** Unescapes characters in a URI component. 179 | 180 | **Parameters:** 181 | 182 | - `uriComponent`: a string that contains an encoded URI component or other text to be decoded. 183 | 184 | **Returns:** 185 | 186 | A copy of uriComponent with any hexadecimal escape sequences replaced with the characters they represent 187 | 188 | --- 189 | 190 | ### empty 191 | 192 | **Signature:** `static empty(obj : Object) : boolean` 193 | 194 | **Description:** The method tests, whether the given object is empty. The interpretation of empty is the following. - null is always empty - undefined is always empty - a string with zero length is empty - an array with no elements is empty - a collection with no elements is empty 195 | 196 | **Parameters:** 197 | 198 | - `obj`: the object to be thested 199 | 200 | **Returns:** 201 | 202 | true if the object is interpreted as being empty 203 | 204 | --- 205 | 206 | ### encodeURI 207 | 208 | **Signature:** `static encodeURI(uri : String) : String` 209 | 210 | **Description:** Escapes characters in a URI. 211 | 212 | **Parameters:** 213 | 214 | - `uri`: a String that contains the URI or other text to be encoded. 215 | 216 | **Returns:** 217 | 218 | a copy of uri with certain characters replaced by hexadecimal escape sequences. 219 | 220 | --- 221 | 222 | ### encodeURIComponent 223 | 224 | **Signature:** `static encodeURIComponent(uriComponent : String) : String` 225 | 226 | **Description:** Escapes characters in a URI component. 227 | 228 | **Parameters:** 229 | 230 | - `uriComponent`: a String that contains a portion of a URI or other text to be encoded. 231 | 232 | **Returns:** 233 | 234 | a copy of uriComponent with certain characters replaced by hexadecimal escape sequences. 235 | 236 | --- 237 | 238 | ### escape 239 | 240 | **Signature:** `static escape(s : String) : String` 241 | 242 | **Description:** Encodes a String. 243 | 244 | **Parameters:** 245 | 246 | - `s`: the String to be encoded. 247 | 248 | **Returns:** 249 | 250 | a copy of s where characters have been replace by hexadecimal escape sequences. 251 | 252 | --- 253 | 254 | ### eval 255 | 256 | **Signature:** `static eval(code : String) : Object` 257 | 258 | **Description:** Execute JavaScript code from a String. 259 | 260 | **Deprecated:** 261 | 262 | The eval() function is deprecated, because it's potential security risk for server side code injection. 263 | 264 | **Parameters:** 265 | 266 | - `code`: a String that contains the JavaScript expression to be evaluated or the statements to be executed. 267 | 268 | **Returns:** 269 | 270 | the value of the executed call or null. 271 | 272 | --- 273 | 274 | ### importClass 275 | 276 | **Signature:** `static importClass(classPath : Object) : void` 277 | 278 | **Description:** Import the specified class and make it available at the top level. It's equivalent in effect to the Java import declaration. 279 | 280 | **Parameters:** 281 | 282 | - `classPath`: the fully qualified class path. 283 | 284 | --- 285 | 286 | ### importPackage 287 | 288 | **Signature:** `static importPackage(packagePath : Object) : void` 289 | 290 | **Description:** Import all the classes in the specified package available at the top level. It's equivalent in effect to the Java import declaration. 291 | 292 | **Parameters:** 293 | 294 | - `packagePath`: the fully qualified package path. 295 | 296 | --- 297 | 298 | ### importScript 299 | 300 | **Signature:** `static importScript(scriptPath : String) : void` 301 | 302 | **Description:** Imports all functions from the specified script. Variables are not imported from the script and must be accessed through helper functions. The script path has the following syntax: [cartridgename:]scriptname, where cartridgename identifies a cartridge where the script file is located. If cartridgename is omitted the script file is loaded from the same cartridge in which the importing component is located. Examples: importScript( 'example.ds' ) imports the script file example.ds from the same cartridge importScript( 'abc:example.ds' ) imports the script file example.ds from the cartridge 'abc' 303 | 304 | **Parameters:** 305 | 306 | - `scriptPath`: the path to the script. 307 | 308 | --- 309 | 310 | ### isFinite 311 | 312 | **Signature:** `static isFinite(number : Number) : boolean` 313 | 314 | **Description:** Returns true if the specified Number is finite. 315 | 316 | **Parameters:** 317 | 318 | - `number`: the Number to test. 319 | 320 | **Returns:** 321 | 322 | true if the specified Number is finite, false otherwise. 323 | 324 | --- 325 | 326 | ### isNaN 327 | 328 | **Signature:** `static isNaN(object : Object) : boolean` 329 | 330 | **Description:** Test the specified value to determine if it is not a Number. 331 | 332 | **Parameters:** 333 | 334 | - `object`: the Object to be tested as a number 335 | 336 | **Returns:** 337 | 338 | True if the object is not a number 339 | 340 | --- 341 | 342 | ### isXMLName 343 | 344 | **Signature:** `static isXMLName(name : String) : boolean` 345 | 346 | **Description:** Determines whether the specified string is a valid name for an XML element or attribute. 347 | 348 | **Parameters:** 349 | 350 | - `name`: the String specified 351 | 352 | **Returns:** 353 | 354 | True if the string is a valid name 355 | 356 | --- 357 | 358 | ### parseFloat 359 | 360 | **Signature:** `static parseFloat(s : String) : Number` 361 | 362 | **Description:** Parses a String into an float Number. 363 | 364 | **Parameters:** 365 | 366 | - `s`: the String to parse. 367 | 368 | **Returns:** 369 | 370 | Returns the float as a Number. 371 | 372 | --- 373 | 374 | ### parseInt 375 | 376 | **Signature:** `static parseInt(s : String, radix : Number) : Number` 377 | 378 | **Description:** Parses a String into an integer Number using the specified radix. 379 | 380 | **Parameters:** 381 | 382 | - `s`: the String to parse. 383 | - `radix`: the radix to use. 384 | 385 | **Returns:** 386 | 387 | Returns the integer as a Number. 388 | 389 | --- 390 | 391 | ### parseInt 392 | 393 | **Signature:** `static parseInt(s : String) : Number` 394 | 395 | **Description:** Parses a String into an integer Number. This function is a short form for the call to parseInt(String, Number) with automatic determination of the radix. If the string starts with "0x" or "0X" then the radix is 16. If the string starts with "0" the the radix is 8. In all other cases the radix is 10. 396 | 397 | **API Versioned:** 398 | 399 | No longer available as of version 16.1. 400 | 401 | **Parameters:** 402 | 403 | - `s`: the String to parse. 404 | 405 | **Returns:** 406 | 407 | Returns the integer as a Number. 408 | 409 | --- 410 | 411 | ### parseInt 412 | 413 | **Signature:** `static parseInt(s : String) : Number` 414 | 415 | **Description:** Parses a String into an integer Number. This function is a short form for the call to parseInt(String, Number) with automatic determination of the radix. If the string starts with "0x" or "0X" then the radix is 16. In all other cases the radix is 10. 416 | 417 | **API Versioned:** 418 | 419 | From version 16.1. ECMAScript 5 compliance: removed support for octal numbers. 420 | 421 | **Parameters:** 422 | 423 | - `s`: the String to parse. 424 | 425 | **Returns:** 426 | 427 | Returns the integer as a Number. 428 | 429 | --- 430 | 431 | ### require 432 | 433 | **Signature:** `static require(path : String) : Module` 434 | 435 | **Description:** The require() function supports loading of modules in JavaScript. The function works similar to the require() function in CommonJS. The general module loading works the same way, but the exact path lookup is slightly different and better fits into Demandware concepts. Here are the details for the lookup: If the module name starts with "./" or "../" then load it relative to the current module. The module can be a file or a directory. A file extension is acknowledged, but not required. If it's a directory a 'package.json' or a 'main' file is expected. If the 'package.json' does not contain a main entry, then default to main file in the directory. Access to parent files can't go beyond the cartridges directory. Access to other cartridges is explicitly allowed. If the module name starts with "~/" it's a path relative to the current cartridge. If the module name starts with "*/" try to find the module in all cartridges that are assigned to the current site. A module with the name "dw" or which starts with "dw/" references Demandware built-in functions and classes. For example var u = require( 'dw/util' ); loads the classes in the util package, which can be then used like var h = new u.HashMap(); A module, which doesn't start with "./" or "../" is resolved as top level module. The module name is used to find a folder in the top cartridge directory, typically a cartridge itself, but it can also be a simple folder. If nothing is found, the module name is used to look into a special folder called "modules" in the top cartridge directory. That folder can be used by a developer to manage different modules. For example a developer could drop a module like "less.js" just into that folder. If the require function is used to reference a file then an optional file extension is used to determine the content of the file. Currently supported are the extensions ordered by priority: js - JavaScript file ds - Demandware Script file json - JSON file 436 | 437 | **Parameters:** 438 | 439 | - `path`: the path to the JavaScript module. 440 | 441 | **Returns:** 442 | 443 | an object with the exported functions and properties. 444 | 445 | --- 446 | 447 | ### trace 448 | 449 | **Signature:** `static trace(msg : String, params : Object...) : void` 450 | 451 | **Description:** Formats and prints the message using the specified params and returns the formatted message. The format message is a Java MessageFormat expression. Printing happens in the script log output. 452 | 453 | **Parameters:** 454 | 455 | - `msg`: the message to format. 456 | - `params`: one, or multiple parameters that are used to format the message. 457 | 458 | --- 459 | 460 | ### unescape 461 | 462 | **Signature:** `static unescape(string : String) : String` 463 | 464 | **Description:** Decode an escaped String. 465 | 466 | **Parameters:** 467 | 468 | - `string`: the String to decode. 469 | 470 | **Returns:** 471 | 472 | a copy of the String where hexadecimal character sequences are replace by Unicode characters. 473 | 474 | --- ``` -------------------------------------------------------------------------------- /docs/dw_system/Site.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.system 2 | 3 | # Class Site 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.system.Site 9 | 10 | ## Description 11 | 12 | This class represents a site in Commerce Cloud Digital and provides access to several site-level configuration values which are managed from within the Business Manager. It is only possible to get a reference to the current site as determined by the current request. The static method getCurrent() returns a reference to the current site. 13 | 14 | ## Constants 15 | 16 | ### SITE_STATUS_MAINTENANCE 17 | 18 | **Type:** Number = 3 19 | 20 | Constant that represents the Site under maintenance/offline 21 | 22 | ### SITE_STATUS_ONLINE 23 | 24 | **Type:** Number = 1 25 | 26 | Constant that represents the Site is Online 27 | 28 | ### SITE_STATUS_PROTECTED 29 | 30 | **Type:** Number = 5 31 | 32 | Constant that represents the Site is in preview mode or online/password (protected) 33 | 34 | ## Properties 35 | 36 | ### allowedCurrencies 37 | 38 | **Type:** List (Read Only) 39 | 40 | The allowed currencies of the current site as a collection of 41 | currency codes. 42 | 43 | ### allowedLocales 44 | 45 | **Type:** List (Read Only) 46 | 47 | The allowed locales of the current site as a collection of 48 | locale ID's. 49 | 50 | ### allSites 51 | 52 | **Type:** List (Read Only) 53 | 54 | All sites. 55 | 56 | ### calendar 57 | 58 | **Type:** Calendar (Read Only) 59 | 60 | A new Calendar object in the time zone of the 61 | current site. 62 | 63 | ### currencyCode 64 | 65 | **Type:** String (Read Only) 66 | 67 | The default currency code for the current site. 68 | 69 | ### current 70 | 71 | **Type:** Site (Read Only) 72 | 73 | The current site. 74 | 75 | ### defaultCurrency 76 | 77 | **Type:** String (Read Only) 78 | 79 | The default currency code for the current site. 80 | 81 | ### defaultLocale 82 | 83 | **Type:** String (Read Only) 84 | 85 | Return default locale for the site. 86 | 87 | ### einsteinSiteID 88 | 89 | **Type:** String (Read Only) 90 | 91 | The Einstein site Id. Typically this is a concatenation of the realm, underscore character and the site id. 92 | It can be overwritten by support users to help with realm moves to continue using the Einstein data from the old realm. 93 | Used when making calls to the Einstein APIs. 94 | 95 | ### httpHostName 96 | 97 | **Type:** String (Read Only) 98 | 99 | The configured HTTP host name. If no host name 100 | is configured the method returns the instance hostname. 101 | 102 | ### httpsHostName 103 | 104 | **Type:** String (Read Only) 105 | 106 | The configured HTTPS host name. If no host name 107 | is configured the method returns the HTTP host name or the instance hostname, if 108 | that is not configured as well. 109 | 110 | ### ID 111 | 112 | **Type:** String (Read Only) 113 | 114 | The ID of the site. 115 | 116 | ### name 117 | 118 | **Type:** String (Read Only) 119 | 120 | A descriptive name for the site. 121 | 122 | ### OMSEnabled 123 | 124 | **Type:** boolean (Read Only) 125 | 126 | Whether oms is active in the current site. This depends on a general 127 | property which states whether oms is active for the server, 128 | and a site-dependent preference whether oms is available for the current site. 129 | 130 | ### pageMetaTags 131 | 132 | **Type:** Array (Read Only) 133 | 134 | All page meta tags, defined for this instance for which content can be generated. 135 | 136 | The meta tag content is generated based on the home page meta tag context and rules. 137 | The rules are obtained from the current repository domain. 138 | 139 | ### preferences 140 | 141 | **Type:** SitePreferences (Read Only) 142 | 143 | This method returns a container of all site preferences of this site. 144 | 145 | ### status 146 | 147 | **Type:** Number (Read Only) 148 | 149 | The status of this site. 150 | 151 | Possible values are SITE_STATUS_ONLINE, SITE_STATUS_MAINTENANCE, SITE_STATUS_PROTECTED 152 | 153 | ### timezone 154 | 155 | **Type:** String (Read Only) 156 | 157 | The code for the time zone in which the storefront is 158 | running. 159 | 160 | ### timezoneOffset 161 | 162 | **Type:** Number (Read Only) 163 | 164 | Returns time zone offset in which the storefront is running. 165 | 166 | ## Constructor Summary 167 | 168 | ## Method Summary 169 | 170 | ### getAllowedCurrencies 171 | 172 | **Signature:** `getAllowedCurrencies() : List` 173 | 174 | Returns the allowed currencies of the current site as a collection of currency codes. 175 | 176 | ### getAllowedLocales 177 | 178 | **Signature:** `getAllowedLocales() : List` 179 | 180 | Returns the allowed locales of the current site as a collection of locale ID's. 181 | 182 | ### getAllSites 183 | 184 | **Signature:** `static getAllSites() : List` 185 | 186 | Returns all sites. 187 | 188 | ### getCalendar 189 | 190 | **Signature:** `static getCalendar() : Calendar` 191 | 192 | Returns a new Calendar object in the time zone of the current site. 193 | 194 | ### getCurrencyCode 195 | 196 | **Signature:** `getCurrencyCode() : String` 197 | 198 | Returns the default currency code for the current site. 199 | 200 | ### getCurrent 201 | 202 | **Signature:** `static getCurrent() : Site` 203 | 204 | Returns the current site. 205 | 206 | ### getCustomPreferenceValue 207 | 208 | **Signature:** `getCustomPreferenceValue(name : String) : Object` 209 | 210 | Returns a custom preference value. 211 | 212 | ### getDefaultCurrency 213 | 214 | **Signature:** `getDefaultCurrency() : String` 215 | 216 | Returns the default currency code for the current site. 217 | 218 | ### getDefaultLocale 219 | 220 | **Signature:** `getDefaultLocale() : String` 221 | 222 | Return default locale for the site. 223 | 224 | ### getEinsteinSiteID 225 | 226 | **Signature:** `getEinsteinSiteID() : String` 227 | 228 | Returns the Einstein site Id. 229 | 230 | ### getHttpHostName 231 | 232 | **Signature:** `getHttpHostName() : String` 233 | 234 | Returns the configured HTTP host name. 235 | 236 | ### getHttpsHostName 237 | 238 | **Signature:** `getHttpsHostName() : String` 239 | 240 | Returns the configured HTTPS host name. 241 | 242 | ### getID 243 | 244 | **Signature:** `getID() : String` 245 | 246 | Returns the ID of the site. 247 | 248 | ### getName 249 | 250 | **Signature:** `getName() : String` 251 | 252 | Returns a descriptive name for the site. 253 | 254 | ### getPageMetaTag 255 | 256 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag` 257 | 258 | Returns the page meta tag for the specified id. 259 | 260 | ### getPageMetaTags 261 | 262 | **Signature:** `getPageMetaTags() : Array` 263 | 264 | Returns all page meta tags, defined for this instance for which content can be generated. 265 | 266 | ### getPreferences 267 | 268 | **Signature:** `getPreferences() : SitePreferences` 269 | 270 | This method returns a container of all site preferences of this site. 271 | 272 | ### getStatus 273 | 274 | **Signature:** `getStatus() : Number` 275 | 276 | Returns the status of this site. 277 | 278 | ### getTimezone 279 | 280 | **Signature:** `getTimezone() : String` 281 | 282 | Returns the code for the time zone in which the storefront is running. 283 | 284 | ### getTimezoneOffset 285 | 286 | **Signature:** `getTimezoneOffset() : Number` 287 | 288 | Returns time zone offset in which the storefront is running. 289 | 290 | ### isOMSEnabled 291 | 292 | **Signature:** `isOMSEnabled() : boolean` 293 | 294 | Whether oms is active in the current site. 295 | 296 | ### setCustomPreferenceValue 297 | 298 | **Signature:** `setCustomPreferenceValue(name : String, value : Object) : void` 299 | 300 | The method sets a value for a custom preference. 301 | 302 | ## Method Detail 303 | 304 | ## Method Details 305 | 306 | ### getAllowedCurrencies 307 | 308 | **Signature:** `getAllowedCurrencies() : List` 309 | 310 | **Description:** Returns the allowed currencies of the current site as a collection of currency codes. 311 | 312 | **Returns:** 313 | 314 | Collection of allowed site currencies 315 | 316 | --- 317 | 318 | ### getAllowedLocales 319 | 320 | **Signature:** `getAllowedLocales() : List` 321 | 322 | **Description:** Returns the allowed locales of the current site as a collection of locale ID's. 323 | 324 | **Returns:** 325 | 326 | Collection if allowed site locales 327 | 328 | --- 329 | 330 | ### getAllSites 331 | 332 | **Signature:** `static getAllSites() : List` 333 | 334 | **Description:** Returns all sites. 335 | 336 | **Returns:** 337 | 338 | all sites for the current instance 339 | 340 | --- 341 | 342 | ### getCalendar 343 | 344 | **Signature:** `static getCalendar() : Calendar` 345 | 346 | **Description:** Returns a new Calendar object in the time zone of the current site. 347 | 348 | **Returns:** 349 | 350 | the Calendar object in the time zone of the current site. 351 | 352 | --- 353 | 354 | ### getCurrencyCode 355 | 356 | **Signature:** `getCurrencyCode() : String` 357 | 358 | **Description:** Returns the default currency code for the current site. 359 | 360 | **Deprecated:** 361 | 362 | Use getDefaultCurrency() method instead, 363 | 364 | **Returns:** 365 | 366 | the default currency code for the current site. 367 | 368 | --- 369 | 370 | ### getCurrent 371 | 372 | **Signature:** `static getCurrent() : Site` 373 | 374 | **Description:** Returns the current site. 375 | 376 | **Returns:** 377 | 378 | the current site. 379 | 380 | --- 381 | 382 | ### getCustomPreferenceValue 383 | 384 | **Signature:** `getCustomPreferenceValue(name : String) : Object` 385 | 386 | **Description:** Returns a custom preference value. If the preference does not exist the method returns null. This method is simply a shortcut method for accessing the value for a custom attribute defined on the SitePreferences object. // Method #1 var mySitePrefValue1 : String = dw.system.Site.getCurrent(). getCustomPreferenceValue("mySitePref"); // Method #2 var sitePrefs : SitePreferences = dw.system.Site.getCurrent().getPreferences(); var mySitePrefValue2 : String = sitePrefs.getCustom()["mySitePref"]; 387 | 388 | **Parameters:** 389 | 390 | - `name`: preference name. 391 | 392 | **Returns:** 393 | 394 | the preference value, or null if there is no preference with the given name. 395 | 396 | --- 397 | 398 | ### getDefaultCurrency 399 | 400 | **Signature:** `getDefaultCurrency() : String` 401 | 402 | **Description:** Returns the default currency code for the current site. 403 | 404 | **Returns:** 405 | 406 | the default currency code for the current site. 407 | 408 | --- 409 | 410 | ### getDefaultLocale 411 | 412 | **Signature:** `getDefaultLocale() : String` 413 | 414 | **Description:** Return default locale for the site. 415 | 416 | **Returns:** 417 | 418 | default locale for the site. 419 | 420 | --- 421 | 422 | ### getEinsteinSiteID 423 | 424 | **Signature:** `getEinsteinSiteID() : String` 425 | 426 | **Description:** Returns the Einstein site Id. Typically this is a concatenation of the realm, underscore character and the site id. It can be overwritten by support users to help with realm moves to continue using the Einstein data from the old realm. Used when making calls to the Einstein APIs. 427 | 428 | **Returns:** 429 | 430 | the Einstein site Id 431 | 432 | --- 433 | 434 | ### getHttpHostName 435 | 436 | **Signature:** `getHttpHostName() : String` 437 | 438 | **Description:** Returns the configured HTTP host name. If no host name is configured the method returns the instance hostname. 439 | 440 | **Returns:** 441 | 442 | the configured HTTP host name or if it is not set the instance hostname. 443 | 444 | --- 445 | 446 | ### getHttpsHostName 447 | 448 | **Signature:** `getHttpsHostName() : String` 449 | 450 | **Description:** Returns the configured HTTPS host name. If no host name is configured the method returns the HTTP host name or the instance hostname, if that is not configured as well. 451 | 452 | **Returns:** 453 | 454 | the configured HTTPS host name or HTTP host name or the instance hostname. 455 | 456 | --- 457 | 458 | ### getID 459 | 460 | **Signature:** `getID() : String` 461 | 462 | **Description:** Returns the ID of the site. 463 | 464 | **Returns:** 465 | 466 | the ID of the site. 467 | 468 | --- 469 | 470 | ### getName 471 | 472 | **Signature:** `getName() : String` 473 | 474 | **Description:** Returns a descriptive name for the site. 475 | 476 | **Returns:** 477 | 478 | a descriptive name for the site. 479 | 480 | --- 481 | 482 | ### getPageMetaTag 483 | 484 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag` 485 | 486 | **Description:** Returns the page meta tag for the specified id. The meta tag content is generated based on the home page meta tag context and rule. The rule is obtained from the current repository domain. Null will be returned if the meta tag is undefined on the current instance, or if no rule can be found for the current context, or if the rule resolves to an empty string. 487 | 488 | **Parameters:** 489 | 490 | - `id`: the ID to get the page meta tag for 491 | 492 | **Returns:** 493 | 494 | page meta tag containing content generated based on rules 495 | 496 | --- 497 | 498 | ### getPageMetaTags 499 | 500 | **Signature:** `getPageMetaTags() : Array` 501 | 502 | **Description:** Returns all page meta tags, defined for this instance for which content can be generated. The meta tag content is generated based on the home page meta tag context and rules. The rules are obtained from the current repository domain. 503 | 504 | **Returns:** 505 | 506 | page meta tags defined for this instance, containing content generated based on rules 507 | 508 | --- 509 | 510 | ### getPreferences 511 | 512 | **Signature:** `getPreferences() : SitePreferences` 513 | 514 | **Description:** This method returns a container of all site preferences of this site. 515 | 516 | **Returns:** 517 | 518 | a preferences object containing all system and custom site preferences of this site 519 | 520 | --- 521 | 522 | ### getStatus 523 | 524 | **Signature:** `getStatus() : Number` 525 | 526 | **Description:** Returns the status of this site. Possible values are SITE_STATUS_ONLINE, SITE_STATUS_MAINTENANCE, SITE_STATUS_PROTECTED 527 | 528 | **Returns:** 529 | 530 | Status of the this site. 531 | 532 | --- 533 | 534 | ### getTimezone 535 | 536 | **Signature:** `getTimezone() : String` 537 | 538 | **Description:** Returns the code for the time zone in which the storefront is running. 539 | 540 | **Returns:** 541 | 542 | time zone code in which the storefront is running. 543 | 544 | --- 545 | 546 | ### getTimezoneOffset 547 | 548 | **Signature:** `getTimezoneOffset() : Number` 549 | 550 | **Description:** Returns time zone offset in which the storefront is running. 551 | 552 | **Returns:** 553 | 554 | time zone offset in which the storefront is running. 555 | 556 | --- 557 | 558 | ### isOMSEnabled 559 | 560 | **Signature:** `isOMSEnabled() : boolean` 561 | 562 | **Description:** Whether oms is active in the current site. This depends on a general property which states whether oms is active for the server, and a site-dependent preference whether oms is available for the current site. 563 | 564 | **Deprecated:** 565 | 566 | This item is deprecated. 567 | 568 | **Returns:** 569 | 570 | whether oms is active in the site 571 | 572 | --- 573 | 574 | ### setCustomPreferenceValue 575 | 576 | **Signature:** `setCustomPreferenceValue(name : String, value : Object) : void` 577 | 578 | **Description:** The method sets a value for a custom preference. The type of the value must match with the declared type of the preference definition. 579 | 580 | **Parameters:** 581 | 582 | - `name`: name of the preference 583 | - `value`: new value for the preference 584 | 585 | --- ```