This is page 15 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 -------------------------------------------------------------------------------- /src/utils/cache.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * In-Memory Caching Module 3 | * 4 | * Provides efficient caching with TTL (Time-To-Live) and LRU (Least Recently Used) eviction 5 | * to reduce IO operations and improve response times for SFCC documentation queries. 6 | */ 7 | 8 | export interface CacheEntry<T> { 9 | value: T; 10 | timestamp: number; 11 | accessCount: number; 12 | lastAccessed: number; 13 | } 14 | 15 | export interface CacheOptions { 16 | maxSize?: number; 17 | ttlMs?: number; 18 | cleanupIntervalMs?: number; 19 | } 20 | 21 | export class InMemoryCache<T> { 22 | private cache = new Map<string, CacheEntry<T>>(); 23 | private readonly maxSize: number; 24 | private readonly ttlMs: number; 25 | private cleanupTimer?: NodeJS.Timeout; 26 | 27 | constructor(options: CacheOptions = {}) { 28 | this.maxSize = options.maxSize ?? 1000; 29 | this.ttlMs = options.ttlMs ?? 60 * 60 * 1000; // 1 hour default for static data 30 | 31 | // Setup automatic cleanup - less frequent for static data 32 | const cleanupInterval = options.cleanupIntervalMs ?? 10 * 60 * 1000; // 10 minutes 33 | this.cleanupTimer = setInterval(() => this.cleanup(), cleanupInterval); 34 | } 35 | 36 | /** 37 | * Store a value in the cache 38 | */ 39 | set(key: string, value: T): void { 40 | const now = Date.now(); 41 | 42 | // Handle zero max size - don't store anything 43 | if (this.maxSize === 0) { 44 | return; 45 | } 46 | 47 | // If at max capacity and adding a new key, remove LRU item first 48 | if (this.cache.size >= this.maxSize && !this.cache.has(key)) { 49 | this.evictLRU(); 50 | } 51 | 52 | this.cache.set(key, { 53 | value, 54 | timestamp: now, 55 | accessCount: 0, 56 | lastAccessed: now, 57 | }); 58 | } 59 | 60 | /** 61 | * Retrieve a value from the cache 62 | */ 63 | get(key: string): T | undefined { 64 | const entry = this.cache.get(key); 65 | 66 | if (!entry) { 67 | return undefined; 68 | } 69 | 70 | const now = Date.now(); 71 | 72 | // Check if entry has expired 73 | if (now - entry.timestamp > this.ttlMs) { 74 | this.cache.delete(key); 75 | return undefined; 76 | } 77 | 78 | // Update access statistics 79 | entry.accessCount++; 80 | entry.lastAccessed = now; 81 | 82 | return entry.value; 83 | } 84 | 85 | /** 86 | * Check if a key exists in the cache (without updating access stats) 87 | */ 88 | has(key: string): boolean { 89 | const entry = this.cache.get(key); 90 | 91 | if (!entry) { 92 | return false; 93 | } 94 | 95 | // Check if entry has expired 96 | if (Date.now() - entry.timestamp > this.ttlMs) { 97 | this.cache.delete(key); 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | /** 105 | * Remove a specific key from the cache 106 | */ 107 | delete(key: string): boolean { 108 | return this.cache.delete(key); 109 | } 110 | 111 | /** 112 | * Clear all entries from the cache 113 | */ 114 | clear(): void { 115 | this.cache.clear(); 116 | } 117 | 118 | /** 119 | * Get cache statistics 120 | */ 121 | getStats(): { 122 | size: number; 123 | maxSize: number; 124 | hitRate: number; 125 | entries: Array<{ key: string; accessCount: number; age: number }>; 126 | } { 127 | const now = Date.now(); 128 | const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({ 129 | key, 130 | accessCount: entry.accessCount, 131 | age: now - entry.timestamp, 132 | })); 133 | 134 | const totalAccesses = entries.reduce((sum, entry) => sum + entry.accessCount, 0); 135 | const totalHits = entries.filter(entry => entry.accessCount > 0).length; 136 | 137 | return { 138 | size: this.cache.size, 139 | maxSize: this.maxSize, 140 | hitRate: totalAccesses > 0 ? totalHits / totalAccesses : 0, 141 | entries, 142 | }; 143 | } 144 | 145 | /** 146 | * Remove expired entries from the cache 147 | */ 148 | private cleanup(): void { 149 | const now = Date.now(); 150 | const expiredKeys: string[] = []; 151 | 152 | for (const [key, entry] of this.cache.entries()) { 153 | if (now - entry.timestamp > this.ttlMs) { 154 | expiredKeys.push(key); 155 | } 156 | } 157 | 158 | expiredKeys.forEach(key => this.cache.delete(key)); 159 | } 160 | 161 | /** 162 | * Evict the least recently used item 163 | */ 164 | private evictLRU(): void { 165 | let oldestKey: string | undefined; 166 | let oldestTime = Date.now(); 167 | 168 | for (const [key, entry] of this.cache.entries()) { 169 | if (entry.lastAccessed < oldestTime) { 170 | oldestTime = entry.lastAccessed; 171 | oldestKey = key; 172 | } 173 | } 174 | 175 | if (oldestKey) { 176 | this.cache.delete(oldestKey); 177 | } 178 | } 179 | 180 | /** 181 | * Cleanup resources 182 | */ 183 | destroy(): void { 184 | if (this.cleanupTimer) { 185 | clearInterval(this.cleanupTimer); 186 | this.cleanupTimer = undefined; 187 | } 188 | this.clear(); 189 | } 190 | } 191 | 192 | /** 193 | * Multi-layer cache manager for different types of data 194 | */ 195 | export class CacheManager { 196 | private fileContentCache: InMemoryCache<string>; 197 | private classDetailsCache: InMemoryCache<any>; 198 | private searchResultsCache: InMemoryCache<any>; 199 | private methodSearchCache: InMemoryCache<any>; 200 | 201 | constructor() { 202 | // Much longer TTL for static documentation data that doesn't change during server runtime 203 | this.fileContentCache = new InMemoryCache<string>({ 204 | maxSize: 500, 205 | ttlMs: 4 * 60 * 60 * 1000, // 4 hours - raw file content is completely static 206 | cleanupIntervalMs: 30 * 60 * 1000, // 30 minutes cleanup interval 207 | }); 208 | 209 | this.classDetailsCache = new InMemoryCache({ 210 | maxSize: 300, 211 | ttlMs: 2 * 60 * 60 * 1000, // 2 hours - parsed data is static 212 | cleanupIntervalMs: 20 * 60 * 1000, // 20 minutes cleanup interval 213 | }); 214 | 215 | this.searchResultsCache = new InMemoryCache({ 216 | maxSize: 200, 217 | ttlMs: 60 * 60 * 1000, // 1 hour - search results are static 218 | cleanupIntervalMs: 15 * 60 * 1000, // 15 minutes cleanup interval 219 | }); 220 | 221 | this.methodSearchCache = new InMemoryCache({ 222 | maxSize: 100, 223 | ttlMs: 60 * 60 * 1000, // 1 hour - method search results are static 224 | cleanupIntervalMs: 15 * 60 * 1000, // 15 minutes cleanup interval 225 | }); 226 | } 227 | 228 | getFileContent(key: string): string | undefined { 229 | return this.fileContentCache.get(key); 230 | } 231 | 232 | setFileContent(key: string, content: string): void { 233 | this.fileContentCache.set(key, content); 234 | } 235 | 236 | getClassDetails(key: string): any { 237 | return this.classDetailsCache.get(key); 238 | } 239 | 240 | setClassDetails(key: string, details: any): void { 241 | this.classDetailsCache.set(key, details); 242 | } 243 | 244 | getSearchResults(key: string): any { 245 | return this.searchResultsCache.get(key); 246 | } 247 | 248 | setSearchResults(key: string, results: any): void { 249 | this.searchResultsCache.set(key, results); 250 | } 251 | 252 | getMethodSearch(key: string): any { 253 | return this.methodSearchCache.get(key); 254 | } 255 | 256 | setMethodSearch(key: string, results: any): void { 257 | this.methodSearchCache.set(key, results); 258 | } 259 | 260 | /** 261 | * Get comprehensive cache statistics 262 | */ 263 | getAllStats(): { 264 | fileContent: ReturnType<InMemoryCache<any>['getStats']>; 265 | classDetails: ReturnType<InMemoryCache<any>['getStats']>; 266 | searchResults: ReturnType<InMemoryCache<any>['getStats']>; 267 | methodSearch: ReturnType<InMemoryCache<any>['getStats']>; 268 | } { 269 | return { 270 | fileContent: this.fileContentCache.getStats(), 271 | classDetails: this.classDetailsCache.getStats(), 272 | searchResults: this.searchResultsCache.getStats(), 273 | methodSearch: this.methodSearchCache.getStats(), 274 | }; 275 | } 276 | 277 | /** 278 | * Clear all caches 279 | */ 280 | clearAll(): void { 281 | this.fileContentCache.clear(); 282 | this.classDetailsCache.clear(); 283 | this.searchResultsCache.clear(); 284 | this.methodSearchCache.clear(); 285 | } 286 | 287 | /** 288 | * Cleanup all resources 289 | */ 290 | destroy(): void { 291 | this.fileContentCache.destroy(); 292 | this.classDetailsCache.destroy(); 293 | this.searchResultsCache.destroy(); 294 | this.methodSearchCache.destroy(); 295 | } 296 | } 297 | ``` -------------------------------------------------------------------------------- /src/clients/logs/log-formatter.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Output formatting and presentation logic for logs 3 | */ 4 | 5 | import { formatBytes } from '../../utils/utils.js'; 6 | import { LOG_CONSTANTS, LOG_MESSAGES } from './log-constants.js'; 7 | import type { LogSummary, LogFileInfo, LogLevel, JobLogInfo } from './log-types.js'; 8 | 9 | export class LogFormatter { 10 | /** 11 | * Format latest log entries response 12 | */ 13 | static formatLatestLogs( 14 | entries: string[], 15 | level: LogLevel, 16 | limit: number, 17 | files: string[], 18 | ): string { 19 | const fileList = files.join(', '); 20 | return `Latest ${limit} ${level} messages from files: ${fileList}\n\n${entries.join('\n\n---\n\n')}`; 21 | } 22 | 23 | /** 24 | * Format search results 25 | */ 26 | static formatSearchResults( 27 | matches: string[], 28 | pattern: string, 29 | date: string, 30 | ): string { 31 | if (matches.length === 0) { 32 | return LOG_MESSAGES.NO_SEARCH_MATCHES(pattern, date); 33 | } 34 | 35 | return `${LOG_MESSAGES.SEARCH_RESULTS(matches.length, pattern)}\n\n${matches.join('\n\n')}`; 36 | } 37 | 38 | /** 39 | * Format "no files found" message 40 | */ 41 | static formatNoFilesFound( 42 | level: LogLevel, 43 | date: string, 44 | availableFiles: string[], 45 | ): string { 46 | const available = availableFiles.join(', '); 47 | return LOG_MESSAGES.NO_FILES_FOUND(level, date, available); 48 | } 49 | 50 | /** 51 | * Format log summary into a readable string 52 | */ 53 | static formatLogSummary(summary: LogSummary): string { 54 | const keyIssuesSection = summary.keyIssues.length > 0 55 | ? summary.keyIssues.map((issue: string) => `- ${issue}`).join('\n') 56 | : 'No major issues detected'; 57 | 58 | return [ 59 | `Log Summary for ${summary.date}:`, 60 | '', 61 | '📊 Counts:', 62 | `- Errors: ${summary.errorCount}`, 63 | `- Warnings: ${summary.warningCount}`, 64 | `- Info: ${summary.infoCount}`, 65 | `- Debug: ${summary.debugCount}`, 66 | '', 67 | `📁 Log Files (${summary.files.length}):`, 68 | summary.files.map((f: string) => `- ${f}`).join('\n'), 69 | '', 70 | '🔥 Key Issues:', 71 | keyIssuesSection, 72 | ].join('\n'); 73 | } 74 | 75 | /** 76 | * Format log files list with metadata 77 | */ 78 | static formatLogFilesList(logFiles: LogFileInfo[]): string { 79 | const totalFiles = (logFiles as any).totalCount ?? logFiles.length; 80 | const showingText = totalFiles > LOG_CONSTANTS.MAX_LOG_FILES_DISPLAY 81 | ? ` (showing latest ${LOG_CONSTANTS.MAX_LOG_FILES_DISPLAY} of ${totalFiles} total)` 82 | : ''; 83 | 84 | return `Available log files${showingText}:\n\n${logFiles.map((file: LogFileInfo) => 85 | `📄 ${file.name}\n Size: ${formatBytes(file.size)}\n Modified: ${file.lastModified}`, 86 | ).join('\n\n')}`; 87 | } 88 | 89 | /** 90 | * Format file processing summary 91 | */ 92 | static formatProcessingSummary( 93 | entriesReturned: number, 94 | filesProcessed: number, 95 | totalEntries: number, 96 | ): string { 97 | return `Parsed ${totalEntries} total entries from ${filesProcessed} files, returning latest ${entriesReturned}`; 98 | } 99 | 100 | /** 101 | * Format log level statistics 102 | */ 103 | static formatLogLevelStats(stats: Record<LogLevel, number>): string { 104 | const entries = Object.entries(stats) as Array<[LogLevel, number]>; 105 | return entries 106 | .map(([level, count]) => `${level}: ${count}`) 107 | .join(', '); 108 | } 109 | 110 | /** 111 | * Format error message with context 112 | */ 113 | static formatError(operation: string, error: unknown): string { 114 | const message = error instanceof Error ? error.message : String(error); 115 | return `Failed to ${operation}: ${message}`; 116 | } 117 | 118 | /** 119 | * Format file list for debugging 120 | */ 121 | static formatFileList(files: string[], prefix = ''): string { 122 | return files.map(f => `${prefix}${f}`).join(', '); 123 | } 124 | 125 | /** 126 | * Format timestamp for display 127 | */ 128 | static formatTimestamp(date: Date): string { 129 | return date.toISOString().replace('T', ' ').substring(0, 19); 130 | } 131 | 132 | /** 133 | * Format log entry with timestamp and level highlighting 134 | */ 135 | static formatLogEntry(entry: string, highlightLevel = false): string { 136 | if (!highlightLevel) { 137 | return entry; 138 | } 139 | 140 | return entry 141 | .replace(/ ERROR /g, ' 🔴 ERROR ') 142 | .replace(/ WARN /g, ' 🟡 WARN ') 143 | .replace(/ INFO /g, ' 🔵 INFO ') 144 | .replace(/ DEBUG /g, ' 🟢 DEBUG '); 145 | } 146 | 147 | /** 148 | * Format progress indicator 149 | */ 150 | static formatProgress(current: number, total: number, operation: string): string { 151 | const percentage = Math.round((current / total) * 100); 152 | return `${operation}: ${current}/${total} (${percentage}%)`; 153 | } 154 | 155 | /** 156 | * Format file size summary 157 | */ 158 | static formatFileSizes(files: Array<{ name: string; size: number }>): string { 159 | const totalSize = files.reduce((sum, file) => sum + file.size, 0); 160 | const avgSize = totalSize / files.length; 161 | 162 | return `Total: ${formatBytes(totalSize)}, Average: ${formatBytes(avgSize)}, Files: ${files.length}`; 163 | } 164 | 165 | /** 166 | * Truncate long text with ellipsis 167 | */ 168 | static truncateText(text: string, maxLength: number): string { 169 | if (text.length <= maxLength) { 170 | return text; 171 | } 172 | return `${text.substring(0, maxLength - 3)}...`; 173 | } 174 | 175 | /** 176 | * Format job log list 177 | */ 178 | static formatJobLogList(jobLogs: JobLogInfo[]): string { 179 | if (jobLogs.length === 0) { 180 | return 'No job logs found.'; 181 | } 182 | 183 | return `Found ${jobLogs.length} job logs:\n\n${jobLogs.map((jobLog: JobLogInfo) => { 184 | const baseInfo = `🔧 Job: ${jobLog.jobName}\n ID: ${jobLog.jobId}\n File: ${jobLog.logFile.split('/').pop()}\n Modified: ${jobLog.lastModified}`; 185 | const sizeInfo = jobLog.size ? `\n Size: ${formatBytes(jobLog.size)}` : ''; 186 | return baseInfo + sizeInfo; 187 | }).join('\n\n')}`; 188 | } 189 | 190 | /** 191 | * Format job log entries with job context 192 | */ 193 | static formatJobLogEntries( 194 | entries: string[], 195 | level: LogLevel | 'all', 196 | limit: number, 197 | jobContext?: string, 198 | ): string { 199 | const levelDisplay = level === 'all' ? 'all levels' : level; 200 | const contextText = jobContext ? ` from ${jobContext}` : ''; 201 | 202 | return `Latest ${limit} ${levelDisplay} messages${contextText}:\n\n${entries.join('\n\n---\n\n')}`; 203 | } 204 | 205 | /** 206 | * Format job execution summary 207 | */ 208 | static formatJobExecutionSummary(summary: { 209 | startTime?: string; 210 | endTime?: string; 211 | status?: string; 212 | duration?: string; 213 | errorCount: number; 214 | warningCount: number; 215 | steps: string[]; 216 | }, jobName: string): string { 217 | const sections = [ 218 | `Job Execution Summary: ${jobName}`, 219 | '', 220 | '⏱️ Timing:', 221 | `- Start: ${summary.startTime ?? 'Unknown'}`, 222 | `- End: ${summary.endTime ?? 'Unknown'}`, 223 | `- Duration: ${summary.duration ?? 'Unknown'}`, 224 | '', 225 | '📊 Status:', 226 | `- Status: ${summary.status ?? 'Unknown'}`, 227 | `- Errors: ${summary.errorCount}`, 228 | `- Warnings: ${summary.warningCount}`, 229 | ]; 230 | 231 | if (summary.steps.length > 0) { 232 | sections.push( 233 | '', 234 | '🔄 Steps:', 235 | ...summary.steps.map(step => `- ${step}`), 236 | ); 237 | } 238 | 239 | return sections.join('\n'); 240 | } 241 | 242 | /** 243 | * Format job search results 244 | */ 245 | static formatJobSearchResults( 246 | matches: string[], 247 | pattern: string, 248 | jobContext?: string, 249 | ): string { 250 | if (matches.length === 0) { 251 | const contextText = jobContext ? ` in ${jobContext} logs` : ' in job logs'; 252 | return `No matches found for "${pattern}"${contextText}`; 253 | } 254 | 255 | const contextText = jobContext ? ` in ${jobContext} logs` : ' in job logs'; 256 | return `Found ${matches.length} matches for "${pattern}"${contextText}:\n\n${matches.join('\n\n')}`; 257 | } 258 | } 259 | ``` -------------------------------------------------------------------------------- /docs/dw_extensions.payments/SalesforcePaymentMethod.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.extensions.payments 2 | 3 | # Class SalesforcePaymentMethod 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.extensions.payments.SalesforcePaymentMethod 9 | 10 | ## Description 11 | 12 | Salesforce Payments representation of a payment method object. See Salesforce Payments documentation for how to gain access and configure it for use on your sites. A payment method contains information about a credential used by a shopper to attempt payment, such as a payment card or bank account. The available information differs for each type of payment method. It includes only limited information that can be safely presented to a shopper to remind them what credential they used, and specifically not complete card, account, or other numbers that could be used to make future payments. 13 | 14 | ## Constants 15 | 16 | ### TYPE_AFTERPAY_CLEARPAY 17 | 18 | **Type:** String = "afterpay_clearpay" 19 | 20 | Represents the Afterpay Clearpay payment method. 21 | 22 | ### TYPE_BANCONTACT 23 | 24 | **Type:** String = "bancontact" 25 | 26 | Represents the Bancontact payment method. 27 | 28 | ### TYPE_CARD 29 | 30 | **Type:** String = "card" 31 | 32 | Represents a credit card type of payment method. 33 | 34 | ### TYPE_EPS 35 | 36 | **Type:** String = "eps" 37 | 38 | Represents the EPS (Electronic Payment Standard) payment method. 39 | 40 | ### TYPE_IDEAL 41 | 42 | **Type:** String = "ideal" 43 | 44 | Represents the iDEAL payment method. 45 | 46 | ### TYPE_KLARNA 47 | 48 | **Type:** String = "klarna" 49 | 50 | Represents the Klarna payment method. 51 | 52 | ### TYPE_SEPA_DEBIT 53 | 54 | **Type:** String = "sepa_debit" 55 | 56 | Represents the SEPA Debit payment method. 57 | 58 | ## Properties 59 | 60 | ### bank 61 | 62 | **Type:** String (Read Only) 63 | 64 | The bank of this payment method, or null if none is available. Available on 65 | TYPE_IDEAL and TYPE_EPS type methods. 66 | 67 | ### bankCode 68 | 69 | **Type:** String (Read Only) 70 | 71 | The bank code of this payment method, or null if none is available. Available on 72 | TYPE_SEPA_DEBIT and TYPE_BANCONTACT type methods. 73 | 74 | ### bankName 75 | 76 | **Type:** String (Read Only) 77 | 78 | The bank name of this payment method, or null if none is available. Available on 79 | TYPE_BANCONTACT type methods. 80 | 81 | ### branchCode 82 | 83 | **Type:** String (Read Only) 84 | 85 | The bank branch code of this payment method, or null if none is available. Available on 86 | TYPE_SEPA_DEBIT type methods. 87 | 88 | ### brand 89 | 90 | **Type:** String (Read Only) 91 | 92 | The brand of this payment method, or null if none is available. Available on 93 | TYPE_CARD type methods. 94 | 95 | ### country 96 | 97 | **Type:** String (Read Only) 98 | 99 | The country of this payment method, or null if none is available. Available on 100 | TYPE_SEPA_DEBIT type methods. 101 | 102 | ### ID 103 | 104 | **Type:** String (Read Only) 105 | 106 | The identifier of this payment method. 107 | 108 | ### last4 109 | 110 | **Type:** String (Read Only) 111 | 112 | The last 4 digits of the credential for this payment method, or null if none is available. 113 | Available on TYPE_CARD, TYPE_SEPA_DEBIT, and 114 | TYPE_BANCONTACT type methods. 115 | 116 | ### paymentMethodCategory 117 | 118 | **Type:** String (Read Only) 119 | 120 | The payment method category of this payment method, or null if none is available. Available 121 | on TYPE_KLARNA type methods. 122 | 123 | ### type 124 | 125 | **Type:** String (Read Only) 126 | 127 | The type of this payment method. 128 | 129 | ## Constructor Summary 130 | 131 | ## Method Summary 132 | 133 | ### getBank 134 | 135 | **Signature:** `getBank() : String` 136 | 137 | Returns the bank of this payment method, or null if none is available. 138 | 139 | ### getBankCode 140 | 141 | **Signature:** `getBankCode() : String` 142 | 143 | Returns the bank code of this payment method, or null if none is available. 144 | 145 | ### getBankName 146 | 147 | **Signature:** `getBankName() : String` 148 | 149 | Returns the bank name of this payment method, or null if none is available. 150 | 151 | ### getBranchCode 152 | 153 | **Signature:** `getBranchCode() : String` 154 | 155 | Returns the bank branch code of this payment method, or null if none is available. 156 | 157 | ### getBrand 158 | 159 | **Signature:** `getBrand() : String` 160 | 161 | Returns the brand of this payment method, or null if none is available. 162 | 163 | ### getCountry 164 | 165 | **Signature:** `getCountry() : String` 166 | 167 | Returns the country of this payment method, or null if none is available. 168 | 169 | ### getID 170 | 171 | **Signature:** `getID() : String` 172 | 173 | Returns the identifier of this payment method. 174 | 175 | ### getLast4 176 | 177 | **Signature:** `getLast4() : String` 178 | 179 | Returns the last 4 digits of the credential for this payment method, or null if none is available. 180 | 181 | ### getPaymentDetails 182 | 183 | **Signature:** `getPaymentDetails(paymentInstrument : OrderPaymentInstrument) : SalesforcePaymentDetails` 184 | 185 | Returns the details to the Salesforce Payments payment for this payment method, using the given payment instrument. 186 | 187 | ### getPaymentMethodCategory 188 | 189 | **Signature:** `getPaymentMethodCategory() : String` 190 | 191 | Returns the payment method category of this payment method, or null if none is available. 192 | 193 | ### getType 194 | 195 | **Signature:** `getType() : String` 196 | 197 | Returns the type of this payment method. 198 | 199 | ## Method Detail 200 | 201 | ## Method Details 202 | 203 | ### getBank 204 | 205 | **Signature:** `getBank() : String` 206 | 207 | **Description:** Returns the bank of this payment method, or null if none is available. Available on TYPE_IDEAL and TYPE_EPS type methods. 208 | 209 | **Returns:** 210 | 211 | payment method bank 212 | 213 | --- 214 | 215 | ### getBankCode 216 | 217 | **Signature:** `getBankCode() : String` 218 | 219 | **Description:** Returns the bank code of this payment method, or null if none is available. Available on TYPE_SEPA_DEBIT and TYPE_BANCONTACT type methods. 220 | 221 | **Returns:** 222 | 223 | payment method bank code 224 | 225 | --- 226 | 227 | ### getBankName 228 | 229 | **Signature:** `getBankName() : String` 230 | 231 | **Description:** Returns the bank name of this payment method, or null if none is available. Available on TYPE_BANCONTACT type methods. 232 | 233 | **Returns:** 234 | 235 | payment method bank name 236 | 237 | --- 238 | 239 | ### getBranchCode 240 | 241 | **Signature:** `getBranchCode() : String` 242 | 243 | **Description:** Returns the bank branch code of this payment method, or null if none is available. Available on TYPE_SEPA_DEBIT type methods. 244 | 245 | **Returns:** 246 | 247 | payment method bank branch code 248 | 249 | --- 250 | 251 | ### getBrand 252 | 253 | **Signature:** `getBrand() : String` 254 | 255 | **Description:** Returns the brand of this payment method, or null if none is available. Available on TYPE_CARD type methods. 256 | 257 | **Returns:** 258 | 259 | payment method brand 260 | 261 | --- 262 | 263 | ### getCountry 264 | 265 | **Signature:** `getCountry() : String` 266 | 267 | **Description:** Returns the country of this payment method, or null if none is available. Available on TYPE_SEPA_DEBIT type methods. 268 | 269 | **Returns:** 270 | 271 | payment method country 272 | 273 | --- 274 | 275 | ### getID 276 | 277 | **Signature:** `getID() : String` 278 | 279 | **Description:** Returns the identifier of this payment method. 280 | 281 | **Returns:** 282 | 283 | payment method identifier 284 | 285 | --- 286 | 287 | ### getLast4 288 | 289 | **Signature:** `getLast4() : String` 290 | 291 | **Description:** Returns the last 4 digits of the credential for this payment method, or null if none is available. Available on TYPE_CARD, TYPE_SEPA_DEBIT, and TYPE_BANCONTACT type methods. 292 | 293 | **Returns:** 294 | 295 | payment method credential last 4 digits 296 | 297 | --- 298 | 299 | ### getPaymentDetails 300 | 301 | **Signature:** `getPaymentDetails(paymentInstrument : OrderPaymentInstrument) : SalesforcePaymentDetails` 302 | 303 | **Description:** Returns the details to the Salesforce Payments payment for this payment method, using the given payment instrument. 304 | 305 | **Parameters:** 306 | 307 | - `paymentInstrument`: payment instrument 308 | 309 | **Returns:** 310 | 311 | The payment details 312 | 313 | --- 314 | 315 | ### getPaymentMethodCategory 316 | 317 | **Signature:** `getPaymentMethodCategory() : String` 318 | 319 | **Description:** Returns the payment method category of this payment method, or null if none is available. Available on TYPE_KLARNA type methods. 320 | 321 | **Returns:** 322 | 323 | payment method category 324 | 325 | --- 326 | 327 | ### getType 328 | 329 | **Signature:** `getType() : String` 330 | 331 | **Description:** Returns the type of this payment method. 332 | 333 | **Returns:** 334 | 335 | payment method type 336 | 337 | **See Also:** 338 | 339 | TYPE_BANCONTACT 340 | TYPE_CARD 341 | TYPE_EPS 342 | TYPE_AFTERPAY_CLEARPAY 343 | TYPE_IDEAL 344 | TYPE_SEPA_DEBIT 345 | 346 | --- ``` -------------------------------------------------------------------------------- /docs/dw_web/Cookie.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.web 2 | 3 | # Class Cookie 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.web.Cookie 9 | 10 | ## Description 11 | 12 | Represents an HTTP cookie used for storing information on a client browser. Cookies are passed along in the HTTP request and can be retrieved by calling dw.system.Request.getHttpCookies(). Cookies must comply with RFC6265. We recommend you use only printable ASCII characters without separators, such as a comma or equal sign. If JSON is used as a cookie value, it must be encoded. Note: this class allows access to sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. See Request.getHttpCookies(). 13 | 14 | ## Constants 15 | 16 | ### EMPTYNAME 17 | 18 | **Type:** String = "dw_emptyname__" 19 | 20 | Default name for cookies with empty strings. 21 | 22 | ## Properties 23 | 24 | ### comment 25 | 26 | **Type:** String 27 | 28 | The comment associated with the cookie. 29 | 30 | ### domain 31 | 32 | **Type:** String 33 | 34 | The domain associated with the cookie. 35 | 36 | ### httpOnly 37 | 38 | **Type:** boolean 39 | 40 | Identifies if the cookie is http-only. 41 | 42 | ### maxAge 43 | 44 | **Type:** Number 45 | 46 | The maximum age of the cookie, specified in seconds. 47 | By default, -1 indicating the cookie will persist until client shutdown. 48 | 49 | ### name 50 | 51 | **Type:** String (Read Only) 52 | 53 | The cookie's name. 54 | 55 | ### path 56 | 57 | **Type:** String 58 | 59 | The path for the cookie. 60 | 61 | ### secure 62 | 63 | **Type:** boolean 64 | 65 | Identifies if the cookie is secure. 66 | 67 | ### value 68 | 69 | **Type:** String 70 | 71 | The cookie's value. 72 | 73 | ### version 74 | 75 | **Type:** Number 76 | 77 | The version for the cookie. 0 means original Netscape cookie and 78 | 1 means RFC 2109 compliant cookie. 79 | 80 | ## Constructor Summary 81 | 82 | Cookie(name : String, value : String) Constructs a new cookie using the specified name and value. 83 | 84 | ## Method Summary 85 | 86 | ### getComment 87 | 88 | **Signature:** `getComment() : String` 89 | 90 | Returns the comment associated with the cookie. 91 | 92 | ### getDomain 93 | 94 | **Signature:** `getDomain() : String` 95 | 96 | Returns the domain associated with the cookie. 97 | 98 | ### getMaxAge 99 | 100 | **Signature:** `getMaxAge() : Number` 101 | 102 | Returns the maximum age of the cookie, specified in seconds. 103 | 104 | ### getName 105 | 106 | **Signature:** `getName() : String` 107 | 108 | Returns the cookie's name. 109 | 110 | ### getPath 111 | 112 | **Signature:** `getPath() : String` 113 | 114 | Returns the path for the cookie. 115 | 116 | ### getSecure 117 | 118 | **Signature:** `getSecure() : boolean` 119 | 120 | Identifies if the cookie is secure. 121 | 122 | ### getValue 123 | 124 | **Signature:** `getValue() : String` 125 | 126 | Returns the cookie's value. 127 | 128 | ### getVersion 129 | 130 | **Signature:** `getVersion() : Number` 131 | 132 | Returns the version for the cookie. 133 | 134 | ### isHttpOnly 135 | 136 | **Signature:** `isHttpOnly() : boolean` 137 | 138 | Identifies if the cookie is http-only. 139 | 140 | ### setComment 141 | 142 | **Signature:** `setComment(comment : String) : void` 143 | 144 | Sets the comment associated with the cookie. 145 | 146 | ### setDomain 147 | 148 | **Signature:** `setDomain(domain : String) : void` 149 | 150 | Sets the domain associated with the cookie. 151 | 152 | ### setHttpOnly 153 | 154 | **Signature:** `setHttpOnly(httpOnly : boolean) : void` 155 | 156 | Sets the http-only state for the cookie. 157 | 158 | ### setMaxAge 159 | 160 | **Signature:** `setMaxAge(age : Number) : void` 161 | 162 | Sets the maximum age of the cookie in seconds. 163 | 164 | ### setPath 165 | 166 | **Signature:** `setPath(path : String) : void` 167 | 168 | Sets the path for the cookie. 169 | 170 | ### setSecure 171 | 172 | **Signature:** `setSecure(secure : boolean) : void` 173 | 174 | Sets the secure state for the cookie. 175 | 176 | ### setValue 177 | 178 | **Signature:** `setValue(value : String) : void` 179 | 180 | Sets the cookie's value. 181 | 182 | ### setVersion 183 | 184 | **Signature:** `setVersion(version : Number) : void` 185 | 186 | Sets the version for the cookie. 187 | 188 | ## Constructor Detail 189 | 190 | ## Method Detail 191 | 192 | ## Method Details 193 | 194 | ### getComment 195 | 196 | **Signature:** `getComment() : String` 197 | 198 | **Description:** Returns the comment associated with the cookie. 199 | 200 | **Returns:** 201 | 202 | the comment associated with the cookie. 203 | 204 | --- 205 | 206 | ### getDomain 207 | 208 | **Signature:** `getDomain() : String` 209 | 210 | **Description:** Returns the domain associated with the cookie. 211 | 212 | **Returns:** 213 | 214 | the domain associated with the cookie. 215 | 216 | --- 217 | 218 | ### getMaxAge 219 | 220 | **Signature:** `getMaxAge() : Number` 221 | 222 | **Description:** Returns the maximum age of the cookie, specified in seconds. By default, -1 indicating the cookie will persist until client shutdown. 223 | 224 | **Returns:** 225 | 226 | an integer specifying the maximum age of the cookie in seconds; if negative, means the cookie persists until client shutdown 227 | 228 | --- 229 | 230 | ### getName 231 | 232 | **Signature:** `getName() : String` 233 | 234 | **Description:** Returns the cookie's name. 235 | 236 | **Returns:** 237 | 238 | the cookie's name. 239 | 240 | --- 241 | 242 | ### getPath 243 | 244 | **Signature:** `getPath() : String` 245 | 246 | **Description:** Returns the path for the cookie. 247 | 248 | **Returns:** 249 | 250 | the path for the cookie. 251 | 252 | --- 253 | 254 | ### getSecure 255 | 256 | **Signature:** `getSecure() : boolean` 257 | 258 | **Description:** Identifies if the cookie is secure. 259 | 260 | **Returns:** 261 | 262 | true if the cookie is secure, false otherwise. 263 | 264 | --- 265 | 266 | ### getValue 267 | 268 | **Signature:** `getValue() : String` 269 | 270 | **Description:** Returns the cookie's value. 271 | 272 | **Returns:** 273 | 274 | the cookie's value. 275 | 276 | --- 277 | 278 | ### getVersion 279 | 280 | **Signature:** `getVersion() : Number` 281 | 282 | **Description:** Returns the version for the cookie. 0 means original Netscape cookie and 1 means RFC 2109 compliant cookie. 283 | 284 | **Returns:** 285 | 286 | the version for the cookie. 287 | 288 | --- 289 | 290 | ### isHttpOnly 291 | 292 | **Signature:** `isHttpOnly() : boolean` 293 | 294 | **Description:** Identifies if the cookie is http-only. 295 | 296 | **Returns:** 297 | 298 | true if the cookie is http-only, false otherwise. 299 | 300 | --- 301 | 302 | ### setComment 303 | 304 | **Signature:** `setComment(comment : String) : void` 305 | 306 | **Description:** Sets the comment associated with the cookie. Setting a comment automatically changes the cookie to be a RFC 2109 (set-cookie2) compliant cookie, because comments are only supported with RFC cookies and not with Netscapes original cookie. 307 | 308 | **Parameters:** 309 | 310 | - `comment`: the comment associated with the cookie. 311 | 312 | --- 313 | 314 | ### setDomain 315 | 316 | **Signature:** `setDomain(domain : String) : void` 317 | 318 | **Description:** Sets the domain associated with the cookie. 319 | 320 | **Parameters:** 321 | 322 | - `domain`: the comment associated with the cookie. 323 | 324 | --- 325 | 326 | ### setHttpOnly 327 | 328 | **Signature:** `setHttpOnly(httpOnly : boolean) : void` 329 | 330 | **Description:** Sets the http-only state for the cookie. 331 | 332 | **Parameters:** 333 | 334 | - `httpOnly`: sets http-only state for the cookie. 335 | 336 | --- 337 | 338 | ### setMaxAge 339 | 340 | **Signature:** `setMaxAge(age : Number) : void` 341 | 342 | **Description:** Sets the maximum age of the cookie in seconds. A positive value indicates that the cookie will expire after that many seconds have passed. Note that the value is the maximum age when the cookie will expire, not the cookie's current age. A negative value means that the cookie is not stored persistently and will be deleted when the client exits. A zero value causes the cookie to be deleted. 343 | 344 | **Parameters:** 345 | 346 | - `age`: an integer specifying the maximum age of the cookie in seconds; if negative, means the cookie is not stored; if zero, deletes the cookie 347 | 348 | --- 349 | 350 | ### setPath 351 | 352 | **Signature:** `setPath(path : String) : void` 353 | 354 | **Description:** Sets the path for the cookie. 355 | 356 | **Parameters:** 357 | 358 | - `path`: the path for the cookie. 359 | 360 | --- 361 | 362 | ### setSecure 363 | 364 | **Signature:** `setSecure(secure : boolean) : void` 365 | 366 | **Description:** Sets the secure state for the cookie. 367 | 368 | **Parameters:** 369 | 370 | - `secure`: sets secure state for the cookie. 371 | 372 | --- 373 | 374 | ### setValue 375 | 376 | **Signature:** `setValue(value : String) : void` 377 | 378 | **Description:** Sets the cookie's value. 379 | 380 | **Parameters:** 381 | 382 | - `value`: the value to set in the cookie. 383 | 384 | --- 385 | 386 | ### setVersion 387 | 388 | **Signature:** `setVersion(version : Number) : void` 389 | 390 | **Description:** Sets the version for the cookie. 0 means original Netscape cookie and 1 means RFC 2109 compliant cookie. The default is 0. 391 | 392 | **Parameters:** 393 | 394 | - `version`: the version for the cookie. 395 | 396 | --- ``` -------------------------------------------------------------------------------- /docs/dw_campaign/Coupon.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.campaign 2 | 3 | # Class Coupon 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.campaign.Coupon 10 | 11 | ## Description 12 | 13 | Represents a coupon in Commerce Cloud Digital. 14 | 15 | ## Constants 16 | 17 | ### TYPE_MULTIPLE_CODES 18 | 19 | **Type:** String = "MULTIPLE_CODES" 20 | 21 | Constant representing coupon type multiple-codes. 22 | 23 | ### TYPE_SINGLE_CODE 24 | 25 | **Type:** String = "SINGLE_CODE" 26 | 27 | Constant representing coupon type single-code. 28 | 29 | ### TYPE_SYSTEM_CODES 30 | 31 | **Type:** String = "SYSTEM_CODES" 32 | 33 | Constant representing coupon type system-codes. 34 | 35 | ## Properties 36 | 37 | ### codePrefix 38 | 39 | **Type:** String (Read Only) 40 | 41 | The prefix defined for coupons of type TYPE_SYSTEM_CODES 42 | If no prefix is defined, or coupon is of type TYPE_SINGLE_CODE 43 | or TYPE_MULTIPLE_CODES, null is returned. 44 | 45 | ### enabled 46 | 47 | **Type:** boolean (Read Only) 48 | 49 | Returns true if coupon is enabled, else false. 50 | 51 | ### ID 52 | 53 | **Type:** String (Read Only) 54 | 55 | The ID of the coupon. 56 | 57 | ### nextCouponCode 58 | 59 | **Type:** String (Read Only) 60 | 61 | The next unissued code of this coupon. 62 | For single-code coupons, the single fixed coupon code is returned. 63 | For all multi-code coupons, the next available, unissued coupon code is returned. 64 | If all codes of the coupon have been issued, then there is no next code, and null is returned. 65 | 66 | A transaction is required when calling this method. This needs to be ensured by the calling script. 67 | 68 | ### promotions 69 | 70 | **Type:** Collection (Read Only) 71 | 72 | The coupon-based promotions directly or indirectly (through 73 | campaigns) assigned to this coupon. 74 | 75 | ### redemptionLimitPerCode 76 | 77 | **Type:** Number (Read Only) 78 | 79 | The defined limit on redemption per coupon code. Null is 80 | returned if no limit is defined, which means that each code can be 81 | redeemed an unlimited number of times. 82 | 83 | ### redemptionLimitPerCustomer 84 | 85 | **Type:** Number (Read Only) 86 | 87 | The defined limit on redemption of this coupon per customer. 88 | Null is returned if no limit is defined, which means that customers can 89 | redeem this coupon an unlimited number of times. 90 | 91 | ### redemptionLimitPerTimeFrame 92 | 93 | **Type:** Number (Read Only) 94 | 95 | The defined limit on redemption per customer per time-frame (see 96 | getRedemptionLimitTimeFrame(). Null is returned if no limit is 97 | defined, which means that there is no time-specific redemption limit for 98 | customers. 99 | 100 | ### redemptionLimitTimeFrame 101 | 102 | **Type:** Number (Read Only) 103 | 104 | The time-frame (in days) of the defined limit on redemption per 105 | customer per time-frame. Null is returned if no limit is defined, which 106 | means that there is no time-specific redemption limit for customers. 107 | 108 | ### type 109 | 110 | **Type:** String (Read Only) 111 | 112 | The coupon type. 113 | Possible values are TYPE_SINGLE_CODE, TYPE_MULTIPLE_CODES 114 | and TYPE_SYSTEM_CODES. 115 | 116 | ## Constructor Summary 117 | 118 | ## Method Summary 119 | 120 | ### getCodePrefix 121 | 122 | **Signature:** `getCodePrefix() : String` 123 | 124 | Returns the prefix defined for coupons of type TYPE_SYSTEM_CODES If no prefix is defined, or coupon is of type TYPE_SINGLE_CODE or TYPE_MULTIPLE_CODES, null is returned. 125 | 126 | ### getID 127 | 128 | **Signature:** `getID() : String` 129 | 130 | Returns the ID of the coupon. 131 | 132 | ### getNextCouponCode 133 | 134 | **Signature:** `getNextCouponCode() : String` 135 | 136 | Returns the next unissued code of this coupon. 137 | 138 | ### getPromotions 139 | 140 | **Signature:** `getPromotions() : Collection` 141 | 142 | Returns the coupon-based promotions directly or indirectly (through campaigns) assigned to this coupon. 143 | 144 | ### getRedemptionLimitPerCode 145 | 146 | **Signature:** `getRedemptionLimitPerCode() : Number` 147 | 148 | Returns the defined limit on redemption per coupon code. 149 | 150 | ### getRedemptionLimitPerCustomer 151 | 152 | **Signature:** `getRedemptionLimitPerCustomer() : Number` 153 | 154 | Returns the defined limit on redemption of this coupon per customer. 155 | 156 | ### getRedemptionLimitPerTimeFrame 157 | 158 | **Signature:** `getRedemptionLimitPerTimeFrame() : Number` 159 | 160 | Returns the defined limit on redemption per customer per time-frame (see getRedemptionLimitTimeFrame(). 161 | 162 | ### getRedemptionLimitTimeFrame 163 | 164 | **Signature:** `getRedemptionLimitTimeFrame() : Number` 165 | 166 | Returns the time-frame (in days) of the defined limit on redemption per customer per time-frame. 167 | 168 | ### getType 169 | 170 | **Signature:** `getType() : String` 171 | 172 | Returns the coupon type. 173 | 174 | ### isEnabled 175 | 176 | **Signature:** `isEnabled() : boolean` 177 | 178 | Returns true if coupon is enabled, else false. 179 | 180 | ## Method Detail 181 | 182 | ## Method Details 183 | 184 | ### getCodePrefix 185 | 186 | **Signature:** `getCodePrefix() : String` 187 | 188 | **Description:** Returns the prefix defined for coupons of type TYPE_SYSTEM_CODES If no prefix is defined, or coupon is of type TYPE_SINGLE_CODE or TYPE_MULTIPLE_CODES, null is returned. 189 | 190 | **Returns:** 191 | 192 | Coupon code prefix or null 193 | 194 | --- 195 | 196 | ### getID 197 | 198 | **Signature:** `getID() : String` 199 | 200 | **Description:** Returns the ID of the coupon. 201 | 202 | **Returns:** 203 | 204 | Coupon ID 205 | 206 | --- 207 | 208 | ### getNextCouponCode 209 | 210 | **Signature:** `getNextCouponCode() : String` 211 | 212 | **Description:** Returns the next unissued code of this coupon. For single-code coupons, the single fixed coupon code is returned. For all multi-code coupons, the next available, unissued coupon code is returned. If all codes of the coupon have been issued, then there is no next code, and null is returned. A transaction is required when calling this method. This needs to be ensured by the calling script. 213 | 214 | **Returns:** 215 | 216 | Next available code of this coupon, or null if there are no available codes. 217 | 218 | --- 219 | 220 | ### getPromotions 221 | 222 | **Signature:** `getPromotions() : Collection` 223 | 224 | **Description:** Returns the coupon-based promotions directly or indirectly (through campaigns) assigned to this coupon. 225 | 226 | **Returns:** 227 | 228 | Promotions assigned to the coupon in no particular order. 229 | 230 | --- 231 | 232 | ### getRedemptionLimitPerCode 233 | 234 | **Signature:** `getRedemptionLimitPerCode() : Number` 235 | 236 | **Description:** Returns the defined limit on redemption per coupon code. Null is returned if no limit is defined, which means that each code can be redeemed an unlimited number of times. 237 | 238 | **Returns:** 239 | 240 | The maximum number of redemption per coupon code 241 | 242 | --- 243 | 244 | ### getRedemptionLimitPerCustomer 245 | 246 | **Signature:** `getRedemptionLimitPerCustomer() : Number` 247 | 248 | **Description:** Returns the defined limit on redemption of this coupon per customer. Null is returned if no limit is defined, which means that customers can redeem this coupon an unlimited number of times. 249 | 250 | **Returns:** 251 | 252 | The maximum number of redemption per customer 253 | 254 | --- 255 | 256 | ### getRedemptionLimitPerTimeFrame 257 | 258 | **Signature:** `getRedemptionLimitPerTimeFrame() : Number` 259 | 260 | **Description:** Returns the defined limit on redemption per customer per time-frame (see getRedemptionLimitTimeFrame(). Null is returned if no limit is defined, which means that there is no time-specific redemption limit for customers. 261 | 262 | **Returns:** 263 | 264 | The maximum number of redemption per customer within time-frame 265 | 266 | **See Also:** 267 | 268 | getRedemptionLimitTimeFrame() 269 | 270 | --- 271 | 272 | ### getRedemptionLimitTimeFrame 273 | 274 | **Signature:** `getRedemptionLimitTimeFrame() : Number` 275 | 276 | **Description:** Returns the time-frame (in days) of the defined limit on redemption per customer per time-frame. Null is returned if no limit is defined, which means that there is no time-specific redemption limit for customers. 277 | 278 | **Returns:** 279 | 280 | Timeframe (days) of redemption per time 281 | 282 | **See Also:** 283 | 284 | getRedemptionLimitPerTimeFrame() 285 | 286 | --- 287 | 288 | ### getType 289 | 290 | **Signature:** `getType() : String` 291 | 292 | **Description:** Returns the coupon type. Possible values are TYPE_SINGLE_CODE, TYPE_MULTIPLE_CODES and TYPE_SYSTEM_CODES. 293 | 294 | **Returns:** 295 | 296 | Coupon type 297 | 298 | --- 299 | 300 | ### isEnabled 301 | 302 | **Signature:** `isEnabled() : boolean` 303 | 304 | **Description:** Returns true if coupon is enabled, else false. 305 | 306 | **Returns:** 307 | 308 | true if coupon is enabled. 309 | 310 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/get-job-log-entries.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | --- 2 | description: "Test get_job_log_entries tool in full mode - Aegis framework validation and core MCP functionality" 3 | tests: 4 | # Core functionality tests - essential MCP protocol validation 5 | - it: "should retrieve job log entries with default parameters" 6 | request: 7 | jsonrpc: "2.0" 8 | id: "job-entries-default" 9 | method: "tools/call" 10 | params: 11 | name: "get_job_log_entries" 12 | arguments: {} 13 | expect: 14 | response: 15 | jsonrpc: "2.0" 16 | id: "job-entries-default" 17 | result: 18 | content: 19 | match:arrayElements: 20 | match:partial: 21 | type: "text" 22 | text: "match:contains:Latest" 23 | isError: false 24 | stderr: "toBeEmpty" 25 | performance: 26 | maxResponseTime: "2000ms" 27 | 28 | - it: "should respect limit parameter" 29 | request: 30 | jsonrpc: "2.0" 31 | id: "job-entries-limit" 32 | method: "tools/call" 33 | params: 34 | name: "get_job_log_entries" 35 | arguments: 36 | limit: 5 37 | expect: 38 | response: 39 | jsonrpc: "2.0" 40 | id: "job-entries-limit" 41 | result: 42 | content: 43 | match:arrayElements: 44 | match:partial: 45 | type: "text" 46 | text: "match:contains:Latest 5" 47 | isError: false 48 | stderr: "toBeEmpty" 49 | performance: 50 | maxResponseTime: "2000ms" 51 | 52 | # Parameter validation tests - level filtering (sample representative cases) 53 | - it: "should filter by error level" 54 | request: 55 | jsonrpc: "2.0" 56 | id: "job-entries-error" 57 | method: "tools/call" 58 | params: 59 | name: "get_job_log_entries" 60 | arguments: 61 | level: "error" 62 | limit: 3 63 | expect: 64 | response: 65 | jsonrpc: "2.0" 66 | id: "job-entries-error" 67 | result: 68 | content: 69 | match:arrayElements: 70 | match:partial: 71 | type: "text" 72 | text: "match:contains:error messages" 73 | isError: false 74 | stderr: "toBeEmpty" 75 | 76 | - it: "should filter by info level" 77 | request: 78 | jsonrpc: "2.0" 79 | id: "job-entries-info" 80 | method: "tools/call" 81 | params: 82 | name: "get_job_log_entries" 83 | arguments: 84 | level: "info" 85 | limit: 3 86 | expect: 87 | response: 88 | jsonrpc: "2.0" 89 | id: "job-entries-info" 90 | result: 91 | content: 92 | match:arrayElements: 93 | match:partial: 94 | type: "text" 95 | text: "match:contains:info messages" 96 | isError: false 97 | stderr: "toBeEmpty" 98 | 99 | # Job name filtering tests 100 | - it: "should filter by job name" 101 | request: 102 | jsonrpc: "2.0" 103 | id: "job-entries-by-name" 104 | method: "tools/call" 105 | params: 106 | name: "get_job_log_entries" 107 | arguments: 108 | jobName: "ProcessOrders" 109 | limit: 3 110 | expect: 111 | response: 112 | jsonrpc: "2.0" 113 | id: "job-entries-by-name" 114 | result: 115 | content: 116 | match:arrayElements: 117 | match:partial: 118 | type: "text" 119 | text: "match:contains:from job: ProcessOrders" 120 | isError: false 121 | stderr: "toBeEmpty" 122 | 123 | - it: "should combine job name and level parameters" 124 | request: 125 | jsonrpc: "2.0" 126 | id: "job-entries-name-level" 127 | method: "tools/call" 128 | params: 129 | name: "get_job_log_entries" 130 | arguments: 131 | jobName: "ImportCatalog" 132 | level: "info" 133 | limit: 3 134 | expect: 135 | response: 136 | jsonrpc: "2.0" 137 | id: "job-entries-name-level" 138 | result: 139 | content: 140 | match:arrayElements: 141 | match:partial: 142 | type: "text" 143 | text: "match:regex:.*info messages from job: ImportCatalog" 144 | isError: false 145 | stderr: "toBeEmpty" 146 | 147 | # Content structure validation - Aegis pattern matching capabilities 148 | - it: "should contain SystemJobThread pattern in job logs" 149 | request: 150 | jsonrpc: "2.0" 151 | id: "job-entries-content" 152 | method: "tools/call" 153 | params: 154 | name: "get_job_log_entries" 155 | arguments: 156 | limit: 5 157 | expect: 158 | response: 159 | jsonrpc: "2.0" 160 | id: "job-entries-content" 161 | result: 162 | content: 163 | match:arrayElements: 164 | match:partial: 165 | type: "text" 166 | text: "match:contains:SystemJobThread" 167 | isError: false 168 | stderr: "toBeEmpty" 169 | 170 | - it: "should include proper GMT timestamps" 171 | request: 172 | jsonrpc: "2.0" 173 | id: "job-entries-timestamps" 174 | method: "tools/call" 175 | params: 176 | name: "get_job_log_entries" 177 | arguments: 178 | limit: 3 179 | expect: 180 | response: 181 | jsonrpc: "2.0" 182 | id: "job-entries-timestamps" 183 | result: 184 | content: 185 | match:arrayElements: 186 | match:partial: 187 | type: "text" 188 | text: "match:regex:\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3} GMT" 189 | isError: false 190 | stderr: "toBeEmpty" 191 | 192 | # Error handling tests - essential MCP error response validation 193 | - it: "should handle zero limit with proper error response" 194 | request: 195 | jsonrpc: "2.0" 196 | id: "job-entries-zero-limit" 197 | method: "tools/call" 198 | params: 199 | name: "get_job_log_entries" 200 | arguments: 201 | limit: 0 202 | expect: 203 | response: 204 | jsonrpc: "2.0" 205 | id: "job-entries-zero-limit" 206 | result: 207 | content: 208 | match:arrayElements: 209 | match:partial: 210 | type: "text" 211 | text: "match:contains:Invalid limit" 212 | isError: true 213 | stderr: "toBeEmpty" 214 | 215 | - it: "should handle invalid log level gracefully" 216 | request: 217 | jsonrpc: "2.0" 218 | id: "job-entries-invalid-level" 219 | method: "tools/call" 220 | params: 221 | name: "get_job_log_entries" 222 | arguments: 223 | level: "invalid" 224 | limit: 5 225 | expect: 226 | response: 227 | jsonrpc: "2.0" 228 | id: "job-entries-invalid-level" 229 | result: 230 | content: 231 | match:arrayElements: 232 | match:partial: 233 | type: "text" 234 | text: "match:contains:Error" 235 | isError: true 236 | stderr: "toBeEmpty" 237 | 238 | - it: "should handle nonexistent job name gracefully" 239 | request: 240 | jsonrpc: "2.0" 241 | id: "job-entries-nonexistent-job" 242 | method: "tools/call" 243 | params: 244 | name: "get_job_log_entries" 245 | arguments: 246 | jobName: "NonExistentJob123" 247 | limit: 3 248 | expect: 249 | response: 250 | jsonrpc: "2.0" 251 | id: "job-entries-nonexistent-job" 252 | result: 253 | content: 254 | match:arrayElements: 255 | match:partial: 256 | type: "text" 257 | text: "match:contains:No job logs found" 258 | isError: false 259 | stderr: "toBeEmpty" 260 | 261 | # Performance test - single representative case for aegis framework validation 262 | - it: "should respond within reasonable time for standard request" 263 | request: 264 | jsonrpc: "2.0" 265 | id: "job-entries-performance" 266 | method: "tools/call" 267 | params: 268 | name: "get_job_log_entries" 269 | arguments: 270 | limit: 10 271 | expect: 272 | response: 273 | jsonrpc: "2.0" 274 | id: "job-entries-performance" 275 | result: 276 | content: 277 | match:arrayElements: 278 | match:partial: 279 | type: "text" 280 | text: "match:type:string" 281 | isError: false 282 | stderr: "toBeEmpty" 283 | performance: 284 | maxResponseTime: "2500ms" 285 | ``` -------------------------------------------------------------------------------- /docs/dw_order/PaymentMethod.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class PaymentMethod 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.order.PaymentMethod 11 | 12 | ## Description 13 | 14 | The PaymentMethod class represents a logical type of payment a customer can make in the storefront. This class provides methods to access the payment method attributes, status, and (for card-based payment methods) the related payment cards. A typical storefront presents the customer a list of payment methods that a customer can choose from after he has entered his billing address during the checkout. PaymentMgr.getApplicablePaymentMethods(Customer, String, Number) is used to determine the PaymentMethods that are relevant for the customer based on the amount of his order, his customer groups, and his shipping address. 15 | 16 | ## Properties 17 | 18 | ### active 19 | 20 | **Type:** boolean (Read Only) 21 | 22 | Returns 'true' if payment method is active (enabled), otherwise 'false' is returned. 23 | 24 | ### activePaymentCards 25 | 26 | **Type:** List (Read Only) 27 | 28 | Returns enabled payment cards that are assigned to this payment method, regardless 29 | of current customer, country or payment amount restrictions. 30 | The payment cards are sorted as defined in the Business Manager. 31 | 32 | ### description 33 | 34 | **Type:** MarkupText (Read Only) 35 | 36 | The description of the payment method. 37 | 38 | ### ID 39 | 40 | **Type:** String (Read Only) 41 | 42 | The unique ID of the payment method. 43 | 44 | ### image 45 | 46 | **Type:** MediaFile (Read Only) 47 | 48 | The reference to the payment method image. 49 | 50 | ### name 51 | 52 | **Type:** String (Read Only) 53 | 54 | The name of the payment method. 55 | 56 | ### paymentProcessor 57 | 58 | **Type:** PaymentProcessor (Read Only) 59 | 60 | The payment processor associated to this payment method. 61 | 62 | ## Constructor Summary 63 | 64 | ## Method Summary 65 | 66 | ### getActivePaymentCards 67 | 68 | **Signature:** `getActivePaymentCards() : List` 69 | 70 | Returns enabled payment cards that are assigned to this payment method, regardless of current customer, country or payment amount restrictions. 71 | 72 | ### getApplicablePaymentCards 73 | 74 | **Signature:** `getApplicablePaymentCards(customer : Customer, countryCode : String, paymentAmount : Number) : List` 75 | 76 | Returns the sorted list of all enabled payment cards of this payment method applicable for the specified customer, country, payment amount and the session currency The payment cards are sorted as defined in the Business Manager. 77 | 78 | ### getDescription 79 | 80 | **Signature:** `getDescription() : MarkupText` 81 | 82 | Returns the description of the payment method. 83 | 84 | ### getID 85 | 86 | **Signature:** `getID() : String` 87 | 88 | Returns the unique ID of the payment method. 89 | 90 | ### getImage 91 | 92 | **Signature:** `getImage() : MediaFile` 93 | 94 | Returns the reference to the payment method image. 95 | 96 | ### getName 97 | 98 | **Signature:** `getName() : String` 99 | 100 | Returns the name of the payment method. 101 | 102 | ### getPaymentProcessor 103 | 104 | **Signature:** `getPaymentProcessor() : PaymentProcessor` 105 | 106 | Returns the payment processor associated to this payment method. 107 | 108 | ### isActive 109 | 110 | **Signature:** `isActive() : boolean` 111 | 112 | Returns 'true' if payment method is active (enabled), otherwise 'false' is returned. 113 | 114 | ### isApplicable 115 | 116 | **Signature:** `isApplicable(customer : Customer, countryCode : String, paymentAmount : Number) : boolean` 117 | 118 | Returns 'true' if this payment method is applicable for the specified customer, country and payment amount and the session currency. 119 | 120 | ## Method Detail 121 | 122 | ## Method Details 123 | 124 | ### getActivePaymentCards 125 | 126 | **Signature:** `getActivePaymentCards() : List` 127 | 128 | **Description:** Returns enabled payment cards that are assigned to this payment method, regardless of current customer, country or payment amount restrictions. The payment cards are sorted as defined in the Business Manager. 129 | 130 | **Returns:** 131 | 132 | List of enabled payment cards of current site 133 | 134 | --- 135 | 136 | ### getApplicablePaymentCards 137 | 138 | **Signature:** `getApplicablePaymentCards(customer : Customer, countryCode : String, paymentAmount : Number) : List` 139 | 140 | **Description:** Returns the sorted list of all enabled payment cards of this payment method applicable for the specified customer, country, payment amount and the session currency The payment cards are sorted as defined in the Business Manager. A payment card is applicable if the card is restricted by customer group, and at least one of the groups of the specified customer is assigned to the card the card is restricted by billing country, and the specified country code is assigned to the card the card is restricted by payment amount for the session currency, and the specified payment amount is within the limits of the min/max payment amount defined for the method and the session currency the card is restricted by currency code, and the specified currency code matches session currency. All parameters are optional, and if not specified, the respective restriction won't be validated. For example, if a card is restricted by billing country, but no country code is specified, this card will be returned, unless it is filtered out by customer group or payment amount. 141 | 142 | **Parameters:** 143 | 144 | - `customer`: Customer or null 145 | - `countryCode`: Billing country code or null 146 | - `paymentAmount`: Payment amount or null 147 | 148 | **Returns:** 149 | 150 | List of applicable payment cards of this payment method 151 | 152 | --- 153 | 154 | ### getDescription 155 | 156 | **Signature:** `getDescription() : MarkupText` 157 | 158 | **Description:** Returns the description of the payment method. 159 | 160 | **Returns:** 161 | 162 | Description of the payment method. 163 | 164 | --- 165 | 166 | ### getID 167 | 168 | **Signature:** `getID() : String` 169 | 170 | **Description:** Returns the unique ID of the payment method. 171 | 172 | **Returns:** 173 | 174 | ID of the payment method. 175 | 176 | --- 177 | 178 | ### getImage 179 | 180 | **Signature:** `getImage() : MediaFile` 181 | 182 | **Description:** Returns the reference to the payment method image. 183 | 184 | **Returns:** 185 | 186 | Image of the payment method. 187 | 188 | --- 189 | 190 | ### getName 191 | 192 | **Signature:** `getName() : String` 193 | 194 | **Description:** Returns the name of the payment method. 195 | 196 | **Returns:** 197 | 198 | Name of the payment method. 199 | 200 | --- 201 | 202 | ### getPaymentProcessor 203 | 204 | **Signature:** `getPaymentProcessor() : PaymentProcessor` 205 | 206 | **Description:** Returns the payment processor associated to this payment method. 207 | 208 | **Returns:** 209 | 210 | the payment processor associated to this payment method. 211 | 212 | --- 213 | 214 | ### isActive 215 | 216 | **Signature:** `isActive() : boolean` 217 | 218 | **Description:** Returns 'true' if payment method is active (enabled), otherwise 'false' is returned. 219 | 220 | **Returns:** 221 | 222 | true if payment method is active, otherwise false. 223 | 224 | --- 225 | 226 | ### isApplicable 227 | 228 | **Signature:** `isApplicable(customer : Customer, countryCode : String, paymentAmount : Number) : boolean` 229 | 230 | **Description:** Returns 'true' if this payment method is applicable for the specified customer, country and payment amount and the session currency. The payment method is applicable if the method is restricted by customer group, and at least one of the groups of the specified customer is assigned to the method the method is restricted by billing country, and the specified country code is assigned to the method the method is restricted by payment amount for the session currency, and the specified payment amount is within the limits of the min/max payment amount defined for the method and the session currency the method is restricted by currency code, and the specified currency code matches session currency. All parameters are optional, and if not specified, the respective restriction won't be validated. For example, if a method is restricted by billing country, but no country code is specified, this method will be returned, unless it is filtered out by customer group or payment amount. 231 | 232 | **Parameters:** 233 | 234 | - `customer`: Customer or null 235 | - `countryCode`: Billing country code or null 236 | - `paymentAmount`: Payment amount or null 237 | 238 | **Returns:** 239 | 240 | true if payment method is applicable, false otherwise 241 | 242 | --- ``` -------------------------------------------------------------------------------- /src/core/handlers/base-handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Logger } from '../../utils/logger.js'; 2 | import { SFCCConfig } from '../../types/types.js'; 3 | 4 | export interface HandlerContext { 5 | logger: Logger; 6 | config: SFCCConfig; 7 | capabilities: { 8 | canAccessLogs: boolean; 9 | canAccessOCAPI: boolean; 10 | }; 11 | } 12 | 13 | export interface ToolExecutionResult { 14 | content: Array<{ type: 'text'; text: string }>; 15 | isError?: boolean; 16 | } 17 | 18 | export interface ToolArguments { 19 | [key: string]: any; 20 | } 21 | 22 | /** 23 | * Generic tool specification interface 24 | * Defines the contract for declarative tool configuration 25 | */ 26 | export interface GenericToolSpec<TArgs = ToolArguments, TResult = any> { 27 | /** Optional validation function for tool arguments */ 28 | validate?: (args: TArgs, toolName: string) => void; 29 | /** Optional function to apply default values to arguments */ 30 | defaults?: (args: TArgs) => Partial<TArgs>; 31 | /** Main execution function for the tool */ 32 | exec: (args: TArgs, context: ToolExecutionContext) => Promise<TResult>; 33 | /** Function to generate log message for the tool execution */ 34 | logMessage: (args: TArgs) => string; 35 | } 36 | 37 | /** 38 | * Context provided to tool execution functions 39 | * Allows tools to access clients and other resources 40 | */ 41 | export interface ToolExecutionContext { 42 | /** Handler context with configuration and capabilities */ 43 | handlerContext: HandlerContext; 44 | /** Logger instance for the handler */ 45 | logger: any; 46 | /** Additional context data that can be provided by concrete handlers */ 47 | [key: string]: any; 48 | } 49 | 50 | export class HandlerError extends Error { 51 | constructor( 52 | message: string, 53 | public readonly toolName: string, 54 | public readonly code: string = 'HANDLER_ERROR', 55 | public readonly details?: any, 56 | ) { 57 | super(message); 58 | this.name = 'HandlerError'; 59 | } 60 | } 61 | 62 | export abstract class BaseToolHandler<TToolName extends string = string> { 63 | protected context: HandlerContext; 64 | protected logger: Logger; 65 | private _isInitialized = false; 66 | 67 | constructor(context: HandlerContext, subLoggerName: string) { 68 | this.context = context; 69 | this.logger = Logger.getChildLogger(`Handler:${subLoggerName}`); 70 | } 71 | 72 | /** 73 | * Abstract method to get tool configuration 74 | * Each concrete handler implements this with their specific config 75 | */ 76 | protected abstract getToolConfig(): Record<TToolName, GenericToolSpec>; 77 | 78 | /** 79 | * Abstract method to get tool name set for O(1) lookup 80 | * Each concrete handler implements this with their specific tool set 81 | */ 82 | protected abstract getToolNameSet(): Set<string>; 83 | 84 | /** 85 | * Abstract method to create execution context 86 | * Each concrete handler can provide specialized context 87 | */ 88 | protected abstract createExecutionContext(): Promise<ToolExecutionContext>; 89 | 90 | /** 91 | * Check if this handler can handle the given tool 92 | */ 93 | canHandle(toolName: string): boolean { 94 | return this.getToolNameSet().has(toolName); 95 | } 96 | 97 | /** 98 | * Config-driven tool execution 99 | * Handles validation, defaults, execution, and logging uniformly 100 | */ 101 | async handle(toolName: string, args: ToolArguments, startTime: number): Promise<ToolExecutionResult> { 102 | if (!this.canHandle(toolName)) { 103 | throw new Error(`Unsupported tool: ${toolName}`); 104 | } 105 | 106 | const toolConfig = this.getToolConfig(); 107 | const spec = toolConfig[toolName as TToolName]; 108 | 109 | if (!spec) { 110 | throw new Error(`No configuration found for tool: ${toolName}`); 111 | } 112 | 113 | return this.executeWithLogging( 114 | toolName, 115 | startTime, 116 | () => this.dispatchTool(spec, args), 117 | spec.logMessage(this.applyDefaults(spec, args)), 118 | ); 119 | } 120 | 121 | /** 122 | * Generic tool dispatch using configuration 123 | * Handles validation, defaults, and execution 124 | */ 125 | private async dispatchTool(spec: GenericToolSpec, args: ToolArguments): Promise<any> { 126 | const context = await this.createExecutionContext(); 127 | const processedArgs = this.createValidatedArgs(spec, args, 'tool'); 128 | 129 | return spec.exec(processedArgs, context); 130 | } 131 | 132 | /** 133 | * Apply default values to arguments 134 | */ 135 | private applyDefaults(spec: GenericToolSpec, args: ToolArguments): ToolArguments { 136 | if (!spec.defaults) { 137 | return args; 138 | } 139 | 140 | const defaults = spec.defaults(args); 141 | return { ...args, ...defaults }; 142 | } 143 | 144 | /** 145 | * Create validated arguments with defaults applied 146 | */ 147 | private createValidatedArgs(spec: GenericToolSpec, args: ToolArguments, toolName: string): ToolArguments { 148 | // Apply defaults first 149 | const processedArgs = this.applyDefaults(spec, args); 150 | 151 | // Validate if validator exists 152 | if (spec.validate) { 153 | spec.validate(processedArgs, toolName); 154 | } 155 | 156 | return processedArgs; 157 | } 158 | 159 | /** 160 | * Initialize the handler (lazy initialization) 161 | */ 162 | protected async initialize(): Promise<void> { 163 | if (this._isInitialized) { 164 | return; 165 | } 166 | await this.onInitialize(); 167 | this._isInitialized = true; 168 | } 169 | 170 | /** 171 | * Override this method for custom initialization logic 172 | */ 173 | protected async onInitialize(): Promise<void> { 174 | // Default: no-op 175 | } 176 | 177 | /** 178 | * Clean up resources when handler is destroyed 179 | */ 180 | async dispose(): Promise<void> { 181 | await this.onDispose(); 182 | this._isInitialized = false; 183 | } 184 | 185 | /** 186 | * Override this method for custom cleanup logic 187 | */ 188 | protected async onDispose(): Promise<void> { 189 | // Default: no-op 190 | } 191 | 192 | /** 193 | * Validate required arguments 194 | */ 195 | protected validateArgs(args: ToolArguments, required: string[], toolName: string): void { 196 | for (const field of required) { 197 | if (!args?.[field]) { 198 | throw new HandlerError( 199 | `${field} is required`, 200 | toolName, 201 | 'MISSING_ARGUMENT', 202 | { required, provided: Object.keys(args || {}) }, 203 | ); 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Create a standardized response 210 | */ 211 | protected createResponse(data: any, stringify: boolean = true): ToolExecutionResult { 212 | return { 213 | content: [ 214 | { type: 'text', text: stringify ? JSON.stringify(data, null, 2) : data }, 215 | ], 216 | isError: false, 217 | }; 218 | } 219 | 220 | /** 221 | * Create an error response 222 | */ 223 | protected createErrorResponse(error: Error, toolName: string): ToolExecutionResult { 224 | this.logger.error(`Error in ${toolName}:`, error); 225 | return { 226 | content: [ 227 | { 228 | type: 'text', 229 | text: error instanceof HandlerError 230 | ? `Error: ${error.message}` 231 | : `Error: ${error.message}`, 232 | }, 233 | ], 234 | isError: true, 235 | }; 236 | } 237 | 238 | /** 239 | * Execute a tool operation with standardized logging and error handling 240 | */ 241 | protected async executeWithLogging( 242 | toolName: string, 243 | startTime: number, 244 | operation: () => Promise<any>, 245 | logMessage?: string, 246 | ): Promise<ToolExecutionResult> { 247 | try { 248 | await this.initialize(); 249 | 250 | if (logMessage) { 251 | this.logger.debug(logMessage); 252 | } 253 | 254 | const result = await operation(); 255 | this.logger.timing(toolName, startTime); 256 | 257 | // Log result metadata for debugging 258 | this.logger.debug(`${toolName} completed successfully`, { 259 | resultType: typeof result, 260 | resultLength: Array.isArray(result) ? result.length : undefined, 261 | hasData: result != null, 262 | }); 263 | 264 | return this.createResponse(result); 265 | } catch (error) { 266 | this.logger.timing(`${toolName}_error`, startTime); 267 | return this.createErrorResponse(error as Error, toolName); 268 | } 269 | } 270 | 271 | /** 272 | * @deprecated Use executeWithLogging instead 273 | */ 274 | protected async wrap( 275 | toolName: string, 276 | startTime: number, 277 | fn: () => Promise<any>, 278 | logMessage?: string, 279 | ): Promise<ToolExecutionResult> { 280 | return this.executeWithLogging(toolName, startTime, fn, logMessage); 281 | } 282 | } 283 | ``` -------------------------------------------------------------------------------- /docs/dw_order/OrderPaymentInstrument.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class OrderPaymentInstrument 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.customer.EncryptedObject 11 | - dw.order.PaymentInstrument 12 | - dw.order.OrderPaymentInstrument 13 | 14 | ## Description 15 | 16 | Represents any payment instrument used to pay orders, such as credit card or bank transfer. The object defines standard methods for credit card payment, and can be extended by attributes appropriate for other payment methods. 17 | 18 | ## Properties 19 | 20 | ### bankAccountDriversLicense 21 | 22 | **Type:** String (Read Only) 23 | 24 | The driver's license associated with a bank account if the calling 25 | context meets the following criteria: 26 | 27 | 28 | If the method call happens in the context of a storefront request and 29 | the current customer is identical to the customer related to the basket 30 | or order, and the current protocol is HTTPS. 31 | 32 | 33 | If the method call happens in the context of the business manager and the 34 | current user has permission to the Orders module. 35 | 36 | 37 | Otherwise, the method throws an exception. 38 | 39 | ### bankAccountNumber 40 | 41 | **Type:** String (Read Only) 42 | 43 | The account number if the calling context meets 44 | the following criteria: 45 | 46 | 47 | If the method call happens in the context of a storefront request and 48 | the current customer is identical to the customer related to the basket 49 | or order, and the current protocol is HTTPS. 50 | 51 | 52 | If the method call happens in the context of the business manager and the 53 | current user has permissions to the Orders module. 54 | 55 | 56 | Otherwise, the method throws an exception. 57 | 58 | ### capturedAmount 59 | 60 | **Type:** Money (Read Only) 61 | 62 | The sum of the captured amounts. The captured amounts 63 | are calculated on the fly. Associate a payment capture for an Payment Instrument with an Invoice 64 | using Invoice method addCaptureTransaction. 65 | 66 | ### creditCardNumber 67 | 68 | **Type:** String (Read Only) 69 | 70 | The de-crypted creditcard number if the calling context meets 71 | the following criteria: 72 | 73 | 74 | If the method call happens in the context of a storefront request and 75 | the current authenticated customer is referenced by the basket or order, and the current protocol is HTTPS. 76 | 77 | 78 | If the customer is anonymous, and the order references this customer, and the protocol is secure and 79 | the order status is CREATED. 80 | 81 | 82 | If the method call happens in the context of the business manager and the 83 | current user has the permission to manage orders. 84 | 85 | 86 | If the payment information has not been masked as a result of the data retention security policy 87 | for the site. 88 | 89 | 90 | Otherwise, the method returns the masked credit card number. 91 | 92 | ### paymentTransaction 93 | 94 | **Type:** PaymentTransaction (Read Only) 95 | 96 | The Payment Transaction for this Payment Instrument or null. 97 | 98 | ### refundedAmount 99 | 100 | **Type:** Money (Read Only) 101 | 102 | The sum of the refunded amounts. The refunded amounts 103 | are calculated on the fly. Associate a payment refund for an Payment Instrument with an Invoice 104 | using Invoice method addRefundTransaction. 105 | 106 | ## Constructor Summary 107 | 108 | ## Method Summary 109 | 110 | ### getBankAccountDriversLicense 111 | 112 | **Signature:** `getBankAccountDriversLicense() : String` 113 | 114 | Returns the driver's license associated with a bank account if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current customer is identical to the customer related to the basket or order, and the current protocol is HTTPS. 115 | 116 | ### getBankAccountNumber 117 | 118 | **Signature:** `getBankAccountNumber() : String` 119 | 120 | Returns the account number if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current customer is identical to the customer related to the basket or order, and the current protocol is HTTPS. 121 | 122 | ### getCapturedAmount 123 | 124 | **Signature:** `getCapturedAmount() : Money` 125 | 126 | Returns the sum of the captured amounts. 127 | 128 | ### getCreditCardNumber 129 | 130 | **Signature:** `getCreditCardNumber() : String` 131 | 132 | Returns the de-crypted creditcard number if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current authenticated customer is referenced by the basket or order, and the current protocol is HTTPS. 133 | 134 | ### getPaymentTransaction 135 | 136 | **Signature:** `getPaymentTransaction() : PaymentTransaction` 137 | 138 | Returns the Payment Transaction for this Payment Instrument or null. 139 | 140 | ### getRefundedAmount 141 | 142 | **Signature:** `getRefundedAmount() : Money` 143 | 144 | Returns the sum of the refunded amounts. 145 | 146 | ## Method Detail 147 | 148 | ## Method Details 149 | 150 | ### getBankAccountDriversLicense 151 | 152 | **Signature:** `getBankAccountDriversLicense() : String` 153 | 154 | **Description:** Returns the driver's license associated with a bank account if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current customer is identical to the customer related to the basket or order, and the current protocol is HTTPS. If the method call happens in the context of the business manager and the current user has permission to the Orders module. Otherwise, the method throws an exception. 155 | 156 | **Returns:** 157 | 158 | the driver's license number if the calling context meets the necessary criteria. 159 | 160 | --- 161 | 162 | ### getBankAccountNumber 163 | 164 | **Signature:** `getBankAccountNumber() : String` 165 | 166 | **Description:** Returns the account number if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current customer is identical to the customer related to the basket or order, and the current protocol is HTTPS. If the method call happens in the context of the business manager and the current user has permissions to the Orders module. Otherwise, the method throws an exception. 167 | 168 | **Returns:** 169 | 170 | the account number if the calling context meets the necessary criteria. 171 | 172 | --- 173 | 174 | ### getCapturedAmount 175 | 176 | **Signature:** `getCapturedAmount() : Money` 177 | 178 | **Description:** Returns the sum of the captured amounts. The captured amounts are calculated on the fly. Associate a payment capture for an Payment Instrument with an Invoice using Invoice method addCaptureTransaction. 179 | 180 | **Returns:** 181 | 182 | sum of captured amounts 183 | 184 | --- 185 | 186 | ### getCreditCardNumber 187 | 188 | **Signature:** `getCreditCardNumber() : String` 189 | 190 | **Description:** Returns the de-crypted creditcard number if the calling context meets the following criteria: If the method call happens in the context of a storefront request and the current authenticated customer is referenced by the basket or order, and the current protocol is HTTPS. If the customer is anonymous, and the order references this customer, and the protocol is secure and the order status is CREATED. If the method call happens in the context of the business manager and the current user has the permission to manage orders. If the payment information has not been masked as a result of the data retention security policy for the site. Otherwise, the method returns the masked credit card number. 191 | 192 | **Returns:** 193 | 194 | the de-crypted creditcard number if the calling context meets the necessary criteria. 195 | 196 | --- 197 | 198 | ### getPaymentTransaction 199 | 200 | **Signature:** `getPaymentTransaction() : PaymentTransaction` 201 | 202 | **Description:** Returns the Payment Transaction for this Payment Instrument or null. 203 | 204 | **Returns:** 205 | 206 | the Payment Transaction for this Payment Instrument or null. 207 | 208 | --- 209 | 210 | ### getRefundedAmount 211 | 212 | **Signature:** `getRefundedAmount() : Money` 213 | 214 | **Description:** Returns the sum of the refunded amounts. The refunded amounts are calculated on the fly. Associate a payment refund for an Payment Instrument with an Invoice using Invoice method addRefundTransaction. 215 | 216 | **Returns:** 217 | 218 | sum of refunded amounts 219 | 220 | --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/mock-data/ocapi/system-object-attributes-product-expanded.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "_v": "23.2", 3 | "_type": "object_attribute_definition_search_result", 4 | "count": 20, 5 | "hits": [ 6 | { 7 | "_type": "object_attribute_definition", 8 | "_resource_state": "47de03c012d48eee975077f45131088fe24f34660ecf4dc69de5737257836fda", 9 | "id": "EAN", 10 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/EAN" 11 | }, 12 | { 13 | "_type": "object_attribute_definition", 14 | "_resource_state": "d9215260b911efb2f40c78cb1b9a7134feca85bf429a9462371db6a16d22e456", 15 | "id": "ID", 16 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/ID" 17 | }, 18 | { 19 | "_type": "object_attribute_definition", 20 | "_resource_state": "5d41eb23dc9228d397af90560aaa1b8b0ff825e6d8699a56977366d3534c5cc6", 21 | "id": "UPC", 22 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/UPC" 23 | }, 24 | { 25 | "_type": "object_attribute_definition", 26 | "_resource_state": "461201495377a8c0e50340bbce3ce929cecfbb74022d1ba87ff3bfeb2501b295", 27 | "id": "UUID", 28 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/UUID" 29 | }, 30 | { 31 | "_type": "object_attribute_definition", 32 | "_resource_state": "94d3911611a738b687448c2742b6b61216925f45e60627906e871da0d752f35b", 33 | "id": "Wool", 34 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/Wool" 35 | }, 36 | { 37 | "_type": "object_attribute_definition", 38 | "_resource_state": "f3714641f6cd57c36db45c8976a743e21dafdb52d753ea67013ef9a9209488f3", 39 | "id": "available", 40 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/available" 41 | }, 42 | { 43 | "_type": "object_attribute_definition", 44 | "_resource_state": "5945247ee819586780982084e8099326c6cc04499492ff82f8603ecb224172d0", 45 | "id": "availableForInStorePickup", 46 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/availableForInStorePickup" 47 | }, 48 | { 49 | "_type": "object_attribute_definition", 50 | "_resource_state": "835703bec9a082f3525872afaa1d371c985c70359812e1e4a751fb2d9b200df6", 51 | "id": "baseImageGroup", 52 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/baseImageGroup" 53 | }, 54 | { 55 | "_type": "object_attribute_definition", 56 | "_resource_state": "67a18a0bbc59a1e4dd7e22d3fb6e1b624b9aacb68c4bb1cd18fe5dbe99d3e21b", 57 | "id": "bottomType", 58 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/bottomType" 59 | }, 60 | { 61 | "_type": "object_attribute_definition", 62 | "_resource_state": "aa8ad7704c3818641e179362b17cdca0b56a411e0b0c2bbcd99689bc54193554", 63 | "id": "brand", 64 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/brand" 65 | }, 66 | { 67 | "_type": "object_attribute_definition", 68 | "_resource_state": "503a227e5ed4449c29e36df3d093f000f5da55a8c3f15d50136e3a6371a0aa0a", 69 | "id": "bundledProducts", 70 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/bundledProducts" 71 | }, 72 | { 73 | "_type": "object_attribute_definition", 74 | "_resource_state": "89b49b5c86e3e1b9b7bd6b84e8ea93f5a56e4e2e8f5b0d1b1a3b2c3d4e5f6789", 75 | "id": "catalog", 76 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/catalog" 77 | }, 78 | { 79 | "_type": "object_attribute_definition", 80 | "_resource_state": "12345678901234567890123456789012345678901234567890123456789012345", 81 | "id": "categories", 82 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/categories" 83 | }, 84 | { 85 | "_type": "object_attribute_definition", 86 | "_resource_state": "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef", 87 | "id": "classificationCategory", 88 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/classificationCategory" 89 | }, 90 | { 91 | "_type": "object_attribute_definition", 92 | "_resource_state": "fedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcba", 93 | "id": "color", 94 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/color" 95 | }, 96 | { 97 | "_type": "object_attribute_definition", 98 | "_resource_state": "1111111111111111111111111111111111111111111111111111111111111111", 99 | "id": "creationDate", 100 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/creationDate" 101 | }, 102 | { 103 | "_type": "object_attribute_definition", 104 | "_resource_state": "2222222222222222222222222222222222222222222222222222222222222222", 105 | "id": "custom", 106 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/custom" 107 | }, 108 | { 109 | "_type": "object_attribute_definition", 110 | "_resource_state": "3333333333333333333333333333333333333333333333333333333333333333", 111 | "id": "description", 112 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/description" 113 | }, 114 | { 115 | "_type": "object_attribute_definition", 116 | "_resource_state": "4444444444444444444444444444444444444444444444444444444444444444", 117 | "id": "imageGroups", 118 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/imageGroups" 119 | }, 120 | { 121 | "_type": "object_attribute_definition", 122 | "_resource_state": "5555555555555555555555555555555555555555555555555555555555555555", 123 | "id": "lastModified", 124 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/lastModified" 125 | } 126 | ], 127 | "next": { 128 | "_type": "result_page", 129 | "count": 5, 130 | "start": 5 131 | }, 132 | "query": { 133 | "match_all_query": { 134 | "_type": "match_all_query" 135 | } 136 | }, 137 | "start": 0, 138 | "total": 113, 139 | "expandedData": { 140 | "brand": { 141 | "_type": "object_attribute_definition", 142 | "_resource_state": "aa8ad7704c3818641e179362b17cdca0b56a411e0b0c2bbcd99689bc54193554", 143 | "creation_date": "2024-02-19T10:18:31.000Z", 144 | "display_name": { 145 | "de": "Marke", 146 | "de-DE": "Marke", 147 | "it": "Marca", 148 | "fr": "Marque", 149 | "zh-CN": "品牌", 150 | "es": "Marca", 151 | "fr-CA": "Marque", 152 | "it-IT": "Marca", 153 | "default": "Brand", 154 | "ja": "ブランド", 155 | "fr-FR": "Marque", 156 | "ja-JP": "ブランド", 157 | "nl": "Merk" 158 | }, 159 | "effective_id": "brand", 160 | "externally_defined": false, 161 | "externally_managed": false, 162 | "field_length": 0, 163 | "id": "brand", 164 | "key": false, 165 | "last_modified": "2024-02-19T10:22:33.000Z", 166 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product/attribute_definitions/brand", 167 | "localizable": false, 168 | "mandatory": false, 169 | "min_length": 0, 170 | "multi_value_type": false, 171 | "order_required": false, 172 | "queryable": true, 173 | "read_only": false, 174 | "requires_encoding": false, 175 | "searchable": false, 176 | "set_value_type": false, 177 | "site_specific": false, 178 | "system": true, 179 | "value_type": "string", 180 | "visible": true 181 | } 182 | } 183 | } ``` -------------------------------------------------------------------------------- /docs/dw_content/ContentSearchRefinements.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.content 2 | 3 | # Class ContentSearchRefinements 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.catalog.SearchRefinements 9 | - dw.content.ContentSearchRefinements 10 | 11 | ## Description 12 | 13 | This class provides an interface to refinement options for the content asset search. In a typical usage, the client application UI displays the search refinements along with the search results and allows customers to "refine" the results (i.e. limit the results that are shown) by specifying additional criteria, or "relax" (i.e. broaden) the results after previously refining. The two types of content search refinements are: Refine By Folder: Limit the content assets to those assigned to specific child/ancestor folder of the search folder. Refine By Attribute: Limit the content assets to those with specific values for a given attribute. Values may be grouped into "buckets" so that a given set of values are represented as a single refinement option. Rendering a content search refinement UI typically begins with iterating the refinement definitions for the search result. Call SearchRefinements.getRefinementDefinitions() or SearchRefinements.getAllRefinementDefinitions() to retrieve the appropriate collection of refinement definitions. For each definition, display the available refinement values by calling getAllRefinementValues(ContentSearchRefinementDefinition). Depending on the type of the refinement definition, the application must use slightly different logic to display the refinement widgets. For all 2 types, methods in ContentSearchModel are used to generate URLs to render hyperlinks in the UI. When clicked, these links trigger a call to the Search pipelet which in turn applies the appropriate filters to the native search result. 14 | 15 | ## Properties 16 | 17 | ### folderRefinementDefinition 18 | 19 | **Type:** ContentSearchRefinementDefinition (Read Only) 20 | 21 | The appropriate folder refinement definition based on the search 22 | result. The folder refinement definition returned will be the first that 23 | can be found traversing the folder tree upward starting at the deepest 24 | common folder of the search result. 25 | 26 | ### matchingFolders 27 | 28 | **Type:** Collection (Read Only) 29 | 30 | A collection of matching folders. 31 | 32 | ## Constructor Summary 33 | 34 | ## Method Summary 35 | 36 | ### getAllRefinementValues 37 | 38 | **Signature:** `getAllRefinementValues(definition : ContentSearchRefinementDefinition) : Collection` 39 | 40 | Returns a sorted collection of refinement values for the given refinement definition. 41 | 42 | ### getFolderHits 43 | 44 | **Signature:** `getFolderHits(folder : Folder) : Number` 45 | 46 | Returns the number of search hits for the passed folder object. 47 | 48 | ### getFolderRefinementDefinition 49 | 50 | **Signature:** `getFolderRefinementDefinition() : ContentSearchRefinementDefinition` 51 | 52 | Returns the appropriate folder refinement definition based on the search result. 53 | 54 | ### getMatchingFolders 55 | 56 | **Signature:** `getMatchingFolders() : Collection` 57 | 58 | Returns a collection of matching folders. 59 | 60 | ### getNextLevelFolderRefinementValues 61 | 62 | **Signature:** `getNextLevelFolderRefinementValues(folder : Folder) : Collection` 63 | 64 | Returns folder refinement values based on the current search result filtered such that only folder refinements representing children of the given folder are present. 65 | 66 | ### getRefinementValue 67 | 68 | **Signature:** `getRefinementValue(definition : ContentSearchRefinementDefinition, value : String) : ContentSearchRefinementValue` 69 | 70 | Returns the refinement value (incl. 71 | 72 | ### getRefinementValue 73 | 74 | **Signature:** `getRefinementValue(name : String, value : String) : ContentSearchRefinementValue` 75 | 76 | Returns the refinement value (incl. 77 | 78 | ### getRefinementValues 79 | 80 | **Signature:** `getRefinementValues(definition : ContentSearchRefinementDefinition) : Collection` 81 | 82 | Returns a collection of refinement values for the given refinement definition. 83 | 84 | ## Method Detail 85 | 86 | ## Method Details 87 | 88 | ### getAllRefinementValues 89 | 90 | **Signature:** `getAllRefinementValues(definition : ContentSearchRefinementDefinition) : Collection` 91 | 92 | **Description:** Returns a sorted collection of refinement values for the given refinement definition. The returned collection includes all refinement values for which the hit count is greater than 0 within the search result when the passed refinement definitions is excluded from filtering the search hits but all other refinement filters are still applied. This is useful for rendering broadening options for the refinement definitions that the search is already refined by. It is important to note that this method does NOT return refinement values independent of the search result. 93 | 94 | **Parameters:** 95 | 96 | - `definition`: The refinement definition to return refinement values for. 97 | 98 | **Returns:** 99 | 100 | The collection of ContentSearchRefinementValue instances sorted according to the settings of the definition. 101 | 102 | --- 103 | 104 | ### getFolderHits 105 | 106 | **Signature:** `getFolderHits(folder : Folder) : Number` 107 | 108 | **Description:** Returns the number of search hits for the passed folder object. 109 | 110 | **Parameters:** 111 | 112 | - `folder`: Folder object. 113 | 114 | **Returns:** 115 | 116 | Number of search hits. 117 | 118 | --- 119 | 120 | ### getFolderRefinementDefinition 121 | 122 | **Signature:** `getFolderRefinementDefinition() : ContentSearchRefinementDefinition` 123 | 124 | **Description:** Returns the appropriate folder refinement definition based on the search result. The folder refinement definition returned will be the first that can be found traversing the folder tree upward starting at the deepest common folder of the search result. 125 | 126 | **Returns:** 127 | 128 | The folder refinement definition or null if none can be found. 129 | 130 | --- 131 | 132 | ### getMatchingFolders 133 | 134 | **Signature:** `getMatchingFolders() : Collection` 135 | 136 | **Description:** Returns a collection of matching folders. 137 | 138 | **Returns:** 139 | 140 | Collection of matching folders. 141 | 142 | --- 143 | 144 | ### getNextLevelFolderRefinementValues 145 | 146 | **Signature:** `getNextLevelFolderRefinementValues(folder : Folder) : Collection` 147 | 148 | **Description:** Returns folder refinement values based on the current search result filtered such that only folder refinements representing children of the given folder are present. If no folder is given, the method uses the library's root folder. The refinement value content counts represent all hits contained in the library tree starting at the corresponding child folder. 149 | 150 | **Parameters:** 151 | 152 | - `folder`: The folder to return child folder refinement values for. 153 | 154 | **Returns:** 155 | 156 | The refinement values for all child folders of the given folder. 157 | 158 | --- 159 | 160 | ### getRefinementValue 161 | 162 | **Signature:** `getRefinementValue(definition : ContentSearchRefinementDefinition, value : String) : ContentSearchRefinementValue` 163 | 164 | **Description:** Returns the refinement value (incl. content hit count) for the given refinement definition and the given (selected) value. 165 | 166 | **Parameters:** 167 | 168 | - `definition`: The definition to return the refinement for. 169 | - `value`: The value to return the refinement value for. 170 | 171 | **Returns:** 172 | 173 | The refinement value. 174 | 175 | --- 176 | 177 | ### getRefinementValue 178 | 179 | **Signature:** `getRefinementValue(name : String, value : String) : ContentSearchRefinementValue` 180 | 181 | **Description:** Returns the refinement value (incl. content hit count) for the given attribute refinement and the given (selected) value. 182 | 183 | **Parameters:** 184 | 185 | - `name`: The name of the refinement attribute. 186 | - `value`: The value to return the refinement value for. 187 | 188 | **Returns:** 189 | 190 | The refinement value. 191 | 192 | --- 193 | 194 | ### getRefinementValues 195 | 196 | **Signature:** `getRefinementValues(definition : ContentSearchRefinementDefinition) : Collection` 197 | 198 | **Description:** Returns a collection of refinement values for the given refinement definition. The returned refinement values only include those that are part of the actual search result (i.e. hit count will always be > 0). 199 | 200 | **Parameters:** 201 | 202 | - `definition`: The refinement definition to return refinement values for. 203 | 204 | **Returns:** 205 | 206 | The collection of refinement values sorted according to the settings of the definition. 207 | 208 | --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/mock-data/ocapi/system-object-definitions.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "_v": "23.2", 3 | "_type": "object_type_definitions", 4 | "count": 27, 5 | "data": [ 6 | { 7 | "_type": "object_type_definition", 8 | "_resource_state": "50ddc22db56ece8a16a546bb710d894b8a3d7a90d453c7d3e68c18f1957528b4", 9 | "object_type": "Appeasement", 10 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Appeasement" 11 | }, 12 | { 13 | "_type": "object_type_definition", 14 | "_resource_state": "690b6ebf6a292ebabff1bceee5700e1d23756cf795295590a6d676d80e76752c", 15 | "object_type": "AppeasementItem", 16 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/AppeasementItem" 17 | }, 18 | { 19 | "_type": "object_type_definition", 20 | "_resource_state": "8428af5200166cf3149f948ea843f38832a2f77214699a5b0e26605c6098c93b", 21 | "object_type": "Basket", 22 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Basket" 23 | }, 24 | { 25 | "_type": "object_type_definition", 26 | "_resource_state": "5c583fb95335bb7c08a021f15cec35d972040fd1259d1497fe70e9b0861f0b39", 27 | "object_type": "BonusDiscountLineItem", 28 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/BonusDiscountLineItem" 29 | }, 30 | { 31 | "_type": "object_type_definition", 32 | "_resource_state": "509b6af7dc05ecc7c74beef901e37d6c48803b02922b782a5f13fd67f2855f61", 33 | "object_type": "Campaign", 34 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Campaign" 35 | }, 36 | { 37 | "_type": "object_type_definition", 38 | "_resource_state": "3d5d0265c55926e15bcc829ded6977db8bcf0cd276a02a10823adc0e57a1e795", 39 | "object_type": "Catalog", 40 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Catalog" 41 | }, 42 | { 43 | "_type": "object_type_definition", 44 | "_resource_state": "c02990e0c2dd64cecf2dd7f89b857718ddbf19c1607a30b2a964945f7d653343", 45 | "object_type": "Category", 46 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Category" 47 | }, 48 | { 49 | "_type": "object_type_definition", 50 | "_resource_state": "fddeadc4d54ecd507c9eeb74388b6e51147d42f45108bb56e569316e06c0c900", 51 | "object_type": "CategoryAssignment", 52 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CategoryAssignment" 53 | }, 54 | { 55 | "_type": "object_type_definition", 56 | "_resource_state": "bbd46a905ab7393d2ccbc0ffd111eee82c3c331302161308950f40c6eb23396f", 57 | "object_type": "Content", 58 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Content" 59 | }, 60 | { 61 | "_type": "object_type_definition", 62 | "_resource_state": "c41b2f924e0cf0cb793bf0bd2d50e53e66308a4137d1fed613df584d7ef08a46", 63 | "object_type": "Coupon", 64 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Coupon" 65 | }, 66 | { 67 | "_type": "object_type_definition", 68 | "_resource_state": "861081f499e48eb616129cd71eb4edad71914636b4f722ec5fd035139c4025ab", 69 | "object_type": "CouponLineItem", 70 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CouponLineItem" 71 | }, 72 | { 73 | "_type": "object_type_definition", 74 | "_resource_state": "969b2870f867b14ec145ae39ddc0370ea64951112cde3275ff2bf063f045bf96", 75 | "object_type": "CustomObject", 76 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 77 | }, 78 | { 79 | "_type": "object_type_definition", 80 | "_resource_state": "f6b5e0f6be3e55d750b49d7ebda7ce7e60548ab426deb9c38f847437b83ebc05", 81 | "object_type": "CustomObject", 82 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 83 | }, 84 | { 85 | "_type": "object_type_definition", 86 | "_resource_state": "0f001293cee7e2041cb11c9ac75df963e00e87992c3fae1294f68a520de26a66", 87 | "object_type": "CustomObject", 88 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 89 | }, 90 | { 91 | "_type": "object_type_definition", 92 | "_resource_state": "c551d79ad5d03e43f3818b62deb9bc022bf63ab65a25d20a5baace4814479b93", 93 | "object_type": "CustomObject", 94 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 95 | }, 96 | { 97 | "_type": "object_type_definition", 98 | "_resource_state": "c791af0e68594272ed567adb2a0cd6f317649d268d4a130eaea156453ef93191", 99 | "object_type": "CustomObject", 100 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 101 | }, 102 | { 103 | "_type": "object_type_definition", 104 | "_resource_state": "e0ee4be30313bff780fafbe624a7b5fa12dcd1fe612e07c8253cabeb89673eef", 105 | "object_type": "CustomObject", 106 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 107 | }, 108 | { 109 | "_type": "object_type_definition", 110 | "_resource_state": "37dae93b96f46692eefa0198ebb0343f39b9087a01dc0866ac9f68d607fee347", 111 | "object_type": "CustomObject", 112 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 113 | }, 114 | { 115 | "_type": "object_type_definition", 116 | "_resource_state": "3f00a07beb99dff8bd23a260e39706aef06a972abee152acbf99cb23f393a874", 117 | "object_type": "CustomObject", 118 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 119 | }, 120 | { 121 | "_type": "object_type_definition", 122 | "_resource_state": "ca9c5999b32a237368365b3a6d9282112b318e83a87fa44196887ee5e27ad722", 123 | "object_type": "CustomObject", 124 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomObject" 125 | }, 126 | { 127 | "_type": "object_type_definition", 128 | "_resource_state": "eefce3a725c68b0396b5fe04bfcfdd3308b7e6c2b959cdf1dc4cf6585aefb40e", 129 | "object_type": "CustomerActiveData", 130 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomerActiveData" 131 | }, 132 | { 133 | "_type": "object_type_definition", 134 | "_resource_state": "41677a8c9dc8a3d7bff48395880f3c80eac4d5627d82d03581f7e6ae8e36c0dd", 135 | "object_type": "CustomerAddress", 136 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomerAddress" 137 | }, 138 | { 139 | "_type": "object_type_definition", 140 | "_resource_state": "725f55adb3c725776cc9041735b49bad2c4986f6992fdd36c4c7e730df024acf", 141 | "object_type": "CustomerCDPData", 142 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomerCDPData" 143 | }, 144 | { 145 | "_type": "object_type_definition", 146 | "_resource_state": "fdcad9f0ccbe08c5620d33558d88872465bc4899fa2e1b1d5d662cf2a20e2333", 147 | "object_type": "CustomerGroup", 148 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomerGroup" 149 | }, 150 | { 151 | "_type": "object_type_definition", 152 | "_resource_state": "e81f19ba694f61c8c9773a14d2e1ccde997f33033948e5840ff4fa4ef44381a8", 153 | "object_type": "CustomerPaymentInstrument", 154 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/CustomerPaymentInstrument" 155 | }, 156 | { 157 | "_type": "object_type_definition", 158 | "_resource_state": "f92c6870f867b14ec145ae39ddc0370ea64951112cde3275ff2bf063f045bf97", 159 | "object_type": "Product", 160 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Product" 161 | }, 162 | { 163 | "_type": "object_type_definition", 164 | "_resource_state": "a93d7870f867b14ec145ae39ddc0370ea64951112cde3275ff2bf063f045bf98", 165 | "object_type": "Order", 166 | "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/Order" 167 | } 168 | ], 169 | "next": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions?start=27&count=25", 170 | "start": 0, 171 | "total": 27 172 | } ``` -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Logger class for standardized logging across the SFCC MCP application. 3 | * Provides consistent logging with timestamps and log levels. 4 | * Always logs to files for consistent debugging and to avoid interfering with stdio. 5 | * 6 | * ## Log Directory Location 7 | * 8 | * The logger uses the operating system's temporary directory via Node.js `os.tmpdir()`: 9 | * - **macOS**: `/var/folders/{user-specific-path}/T/sfcc-mcp-logs/` 10 | * - **Linux**: `/tmp/sfcc-mcp-logs/` (typically) 11 | * - **Windows**: `%TEMP%\sfcc-mcp-logs\` (typically `C:\Users\{user}\AppData\Local\Temp\`) 12 | * 13 | * This approach provides: 14 | * - User-specific isolation (more secure than system-wide `/tmp`) 15 | * - Automatic cleanup by the OS 16 | * - Platform-appropriate temporary storage 17 | * - Proper permissions handling 18 | * 19 | * To find your log directory, use `Logger.getInstance().getLogDirectory()` or check 20 | * the debug logs which show the directory path during initialization. 21 | */ 22 | 23 | import { appendFileSync, existsSync, mkdirSync } from 'fs'; 24 | import { join } from 'path'; 25 | import { tmpdir } from 'os'; 26 | 27 | export class Logger { 28 | private context: string; 29 | private enableTimestamp: boolean; 30 | private debugEnabled: boolean; 31 | private logDir: string; 32 | private static instance: Logger | null = null; 33 | 34 | /** 35 | * Create a new Logger instance 36 | * @param context The context/component name for this logger 37 | * @param enableTimestamp Whether to include timestamps in log messages (default: true) 38 | * @param debugEnabled Whether to enable debug logging (default: false) 39 | * @param customLogDir Custom log directory for testing purposes 40 | */ 41 | constructor(context: string = 'SFCC-MCP', enableTimestamp: boolean = true, debugEnabled: boolean = false, customLogDir?: string) { 42 | this.context = context; 43 | this.enableTimestamp = enableTimestamp; 44 | this.debugEnabled = debugEnabled; 45 | 46 | // Set up log directory - use custom directory for testing or default for production 47 | this.logDir = customLogDir ?? join(tmpdir(), 'sfcc-mcp-logs'); 48 | if (!existsSync(this.logDir)) { 49 | mkdirSync(this.logDir, { recursive: true }); 50 | } 51 | } 52 | 53 | /** 54 | * Initialize the global logger instance with specific settings 55 | * This should be called once at application startup 56 | */ 57 | public static initialize(context: string = 'SFCC-MCP', enableTimestamp: boolean = true, debugEnabled: boolean = false, customLogDir?: string): void { 58 | Logger.instance = new Logger(context, enableTimestamp, debugEnabled, customLogDir); 59 | } 60 | 61 | /** 62 | * Get the global logger instance 63 | * If not initialized, creates a default instance 64 | */ 65 | public static getInstance(): Logger { 66 | Logger.instance ??= new Logger(); 67 | return Logger.instance; 68 | } 69 | 70 | /** 71 | * Create a child logger with a new context but inheriting other settings from the global instance 72 | * @param subContext The sub-context to append to the current context 73 | * @returns A new Logger instance with the combined context 74 | */ 75 | public static getChildLogger(subContext: string): Logger { 76 | const globalLogger = Logger.getInstance(); 77 | return new Logger(`${globalLogger.context}:${subContext}`, globalLogger.enableTimestamp, globalLogger.debugEnabled, globalLogger.logDir); 78 | } 79 | 80 | /** 81 | * Format a log message with optional timestamp and context 82 | * @param message The message to format 83 | * @returns Formatted message string 84 | */ 85 | private formatMessage(message: string): string { 86 | const timestamp = this.enableTimestamp ? `[${new Date().toISOString()}] ` : ''; 87 | return `${timestamp}[${this.context}] ${message}`; 88 | } 89 | 90 | /** 91 | * Write log message to appropriate log file 92 | */ 93 | private writeLog(level: 'info' | 'warn' | 'error' | 'debug', message: string, ...args: any[]): void { 94 | const formattedMessage = this.formatMessage(message); 95 | const fullMessage = args.length > 0 ? `${formattedMessage} ${args.map(arg => 96 | typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg), 97 | ).join(' ')}` : formattedMessage; 98 | 99 | // Always write to log files 100 | const logFile = join(this.logDir, `sfcc-mcp-${level}.log`); 101 | const logEntry = `${fullMessage}\n`; 102 | 103 | try { 104 | appendFileSync(logFile, logEntry, 'utf8'); 105 | } catch (error) { 106 | // Fallback: if file logging fails, try stderr for critical errors only 107 | if (level === 'error') { 108 | process.stderr.write(`[LOGGER ERROR] Could not write to log file: ${error}\n`); 109 | process.stderr.write(`${logEntry}`); 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Log an informational message 116 | * @param message The message to log 117 | * @param args Optional arguments to include 118 | */ 119 | public log(message: string, ...args: any[]): void { 120 | this.writeLog('info', message, ...args); 121 | } 122 | 123 | /** 124 | * Log an informational message (alias for log) 125 | * @param message The message to log 126 | * @param args Optional arguments to include 127 | */ 128 | public info(message: string, ...args: any[]): void { 129 | this.writeLog('info', message, ...args); 130 | } 131 | 132 | /** 133 | * Log a warning message 134 | * @param message The warning message to log 135 | * @param args Optional arguments to include 136 | */ 137 | public warn(message: string, ...args: any[]): void { 138 | this.writeLog('warn', message, ...args); 139 | } 140 | 141 | /** 142 | * Log an error message 143 | * @param message The error message to log 144 | * @param args Optional arguments to include 145 | */ 146 | public error(message: string, ...args: any[]): void { 147 | this.writeLog('error', message, ...args); 148 | } 149 | 150 | /** 151 | * Log a debug message (only if debug is enabled) 152 | * @param message The debug message to log 153 | * @param args Optional arguments to include 154 | */ 155 | public debug(message: string, ...args: any[]): void { 156 | if (this.debugEnabled) { 157 | this.writeLog('debug', `[DEBUG] ${message}`, ...args); 158 | } 159 | } 160 | 161 | /** 162 | * Log method entry with parameters 163 | * @param methodName The name of the method being entered 164 | * @param params Optional parameters being passed to the method 165 | */ 166 | public methodEntry(methodName: string, params?: any): void { 167 | if (this.debugEnabled) { 168 | const paramStr = params ? ` with params: ${JSON.stringify(params)}` : ''; 169 | this.debug(`Entering method: ${methodName}${paramStr}`); 170 | } 171 | } 172 | 173 | /** 174 | * Log method exit with optional result 175 | * @param methodName The name of the method being exited 176 | * @param result Optional result being returned from the method 177 | */ 178 | public methodExit(methodName: string, result?: any): void { 179 | if (this.debugEnabled) { 180 | const resultStr = result !== undefined ? ` with result: ${typeof result === 'object' ? JSON.stringify(result) : result}` : ''; 181 | this.debug(`Exiting method: ${methodName}${resultStr}`); 182 | } 183 | } 184 | 185 | /** 186 | * Log performance timing information 187 | * @param operation The operation being timed 188 | * @param startTime The start time (from performance.now() or Date.now()) 189 | */ 190 | public timing(operation: string, startTime: number): void { 191 | if (this.debugEnabled) { 192 | const duration = Date.now() - startTime; 193 | this.debug(`Performance: ${operation} took ${duration}ms`); 194 | } 195 | } 196 | 197 | /** 198 | * Create a child logger with a new context but inheriting other settings 199 | * @param subContext The sub-context to append to the current context 200 | * @returns A new Logger instance with the combined context 201 | */ 202 | public createChildLogger(subContext: string): Logger { 203 | return new Logger(`${this.context}:${subContext}`, this.enableTimestamp, this.debugEnabled, this.logDir); 204 | } 205 | 206 | /** 207 | * Enable or disable debug logging 208 | * @param enabled Whether debug logging should be enabled 209 | */ 210 | public setDebugEnabled(enabled: boolean): void { 211 | this.debugEnabled = enabled; 212 | } 213 | 214 | /** 215 | * Get the current log directory 216 | */ 217 | public getLogDirectory(): string { 218 | return this.logDir; 219 | } 220 | } 221 | 222 | // Export the singleton instance getter for convenience 223 | export const getLogger = Logger.getInstance; 224 | ``` -------------------------------------------------------------------------------- /tests/mcp/node/tools.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { test, describe, before, after, beforeEach } from 'node:test'; 2 | import { strict as assert } from 'node:assert'; 3 | import { connect } from 'mcp-aegis'; 4 | 5 | describe('SFCC Development MCP Server - Full Mode with Credentials Tests (Tool Presence Only)', () => { 6 | let client; 7 | let toolsCache; 8 | 9 | before(async () => { 10 | client = await connect('./aegis.config.with-dw.json'); 11 | }); 12 | 13 | after(async () => { 14 | if (client?.connected) { 15 | await client.disconnect(); 16 | } 17 | }); 18 | 19 | beforeEach(() => { 20 | // CRITICAL: Clear all buffers to prevent leaking into next tests 21 | client.clearAllBuffers(); // Recommended - comprehensive protection 22 | }); 23 | 24 | test('should successfully connect to server with credentials', async () => { 25 | assert.ok(client.connected, 'Client should be connected'); 26 | }); 27 | 28 | test('should list all available tools in full mode', async () => { 29 | const tools = await client.listTools(); 30 | toolsCache = tools; // Cache for other tests 31 | 32 | assert.ok(Array.isArray(tools), 'Tools should be an array'); 33 | assert.ok(tools.length > 15, 'Should have more tools than documentation-only mode'); 34 | }); 35 | 36 | test('should have all documentation tools available', async () => { 37 | const tools = toolsCache || await client.listTools(); 38 | const toolNames = tools.map(tool => tool.name); 39 | 40 | // SFCC Documentation Tools 41 | assert.ok(toolNames.includes('get_sfcc_class_info'), 'Should have SFCC class info tool'); 42 | assert.ok(toolNames.includes('search_sfcc_classes'), 'Should have SFCC class search tool'); 43 | assert.ok(toolNames.includes('list_sfcc_classes'), 'Should have SFCC class list tool'); 44 | 45 | // Best Practices Tools 46 | assert.ok(toolNames.includes('get_available_best_practice_guides'), 'Should have best practices list tool'); 47 | assert.ok(toolNames.includes('get_best_practice_guide'), 'Should have best practice guide tool'); 48 | 49 | // SFRA Documentation Tools 50 | assert.ok(toolNames.includes('get_available_sfra_documents'), 'Should have SFRA documents list tool'); 51 | assert.ok(toolNames.includes('get_sfra_document'), 'Should have SFRA document tool'); 52 | 53 | // Cartridge Generation Tools 54 | assert.ok(toolNames.includes('generate_cartridge_structure'), 'Should have cartridge generation tool'); 55 | }); 56 | 57 | test('should have log analysis tools in full mode', async () => { 58 | const tools = toolsCache || await client.listTools(); 59 | const toolNames = tools.map(tool => tool.name); 60 | 61 | // Log analysis tools should be available with credentials 62 | assert.ok(toolNames.includes('get_latest_error'), 'Should have log error tool'); 63 | assert.ok(toolNames.includes('get_latest_info'), 'Should have log info tool'); 64 | assert.ok(toolNames.includes('get_latest_warn'), 'Should have log warn tool'); 65 | assert.ok(toolNames.includes('get_latest_debug'), 'Should have log debug tool'); 66 | assert.ok(toolNames.includes('summarize_logs'), 'Should have log summary tool'); 67 | assert.ok(toolNames.includes('search_logs'), 'Should have log search tool'); 68 | assert.ok(toolNames.includes('list_log_files'), 'Should have log file listing tool'); 69 | }); 70 | 71 | test('should have OCAPI system object tools in full mode', async () => { 72 | const tools = toolsCache || await client.listTools(); 73 | const toolNames = tools.map(tool => tool.name); 74 | 75 | // System object tools should be available with credentials 76 | assert.ok(toolNames.includes('get_system_object_definitions'), 'Should have system object definitions tool'); 77 | assert.ok(toolNames.includes('get_system_object_definition'), 'Should have system object definition tool'); 78 | assert.ok(toolNames.includes('search_system_object_attribute_definitions'), 'Should have system object attribute search tool'); 79 | assert.ok(toolNames.includes('search_system_object_attribute_groups'), 'Should have system object attribute groups search tool'); 80 | assert.ok(toolNames.includes('search_site_preferences'), 'Should have site preferences search tool'); 81 | }); 82 | 83 | test('should have code version tools in full mode', async () => { 84 | const tools = toolsCache || await client.listTools(); 85 | const toolNames = tools.map(tool => tool.name); 86 | 87 | // Code version tools should be available with credentials 88 | assert.ok(toolNames.includes('get_code_versions'), 'Should have code versions tool'); 89 | assert.ok(toolNames.includes('activate_code_version'), 'Should have code version activation tool'); 90 | }); 91 | 92 | test('should have job log tools in full mode', async () => { 93 | const tools = toolsCache || await client.listTools(); 94 | const toolNames = tools.map(tool => tool.name); 95 | 96 | // Job log tools should be available with credentials 97 | assert.ok(toolNames.includes('get_latest_job_log_files'), 'Should have job log files tool'); 98 | assert.ok(toolNames.includes('get_job_log_entries'), 'Should have job log entries tool'); 99 | assert.ok(toolNames.includes('search_job_logs'), 'Should have job log search tool'); 100 | assert.ok(toolNames.includes('search_job_logs_by_name'), 'Should have job log search by name tool'); 101 | assert.ok(toolNames.includes('get_job_execution_summary'), 'Should have job execution summary tool'); 102 | }); 103 | 104 | // NOTE: We intentionally do NOT test tool execution in full mode because: 105 | // 1. The test credentials are not real SFCC instances 106 | // 2. Tools would fail with authentication/connection errors 107 | // 3. This test suite is designed to verify tool PRESENCE, not functionality 108 | // 4. Tool functionality testing should be done against real SFCC development instances 109 | 110 | test('should have exactly 36 tools available in full mode', async () => { 111 | const tools = toolsCache || await client.listTools(); 112 | 113 | // Full mode should have exactly 36 tools (same count as YAML test) 114 | assert.equal(tools.length, 36, `Should have exactly 36 tools, got ${tools.length}`); 115 | }); 116 | 117 | test('should have WebDAV-dependent tools (log analysis)', async () => { 118 | const tools = toolsCache || await client.listTools(); 119 | const toolNames = tools.map(tool => tool.name); 120 | 121 | // WebDAV-dependent tools should be available 122 | assert.ok(toolNames.includes('summarize_logs'), 'Should have summarize_logs tool'); 123 | assert.ok(toolNames.includes('get_log_file_contents'), 'Should have get_log_file_contents tool'); 124 | }); 125 | 126 | test('should have OCAPI-dependent tools (system objects)', async () => { 127 | const tools = toolsCache || await client.listTools(); 128 | const toolNames = tools.map(tool => tool.name); 129 | 130 | // OCAPI-dependent tools should be available 131 | assert.ok(toolNames.includes('search_site_preferences'), 'Should have search_site_preferences tool'); 132 | assert.ok(toolNames.includes('search_custom_object_attribute_definitions'), 'Should have search_custom_object_attribute_definitions tool'); 133 | }); 134 | 135 | test('should include all docs-only tools in full mode', async () => { 136 | const tools = toolsCache || await client.listTools(); 137 | const toolNames = tools.map(tool => tool.name); 138 | 139 | // Verify that docs-only tools are NOT missing in full mode 140 | assert.ok(toolNames.includes('search_sfra_documentation'), 'Should have search_sfra_documentation tool'); 141 | assert.ok(toolNames.includes('get_best_practice_guide'), 'Should have get_best_practice_guide tool'); 142 | assert.ok(toolNames.includes('get_sfcc_class_info'), 'Should have get_sfcc_class_info tool'); 143 | }); 144 | 145 | test('should validate all tool schemas in full mode', async () => { 146 | const tools = toolsCache || await client.listTools(); 147 | 148 | for (const tool of tools) { 149 | assert.ok(tool.name, `Tool should have a name: ${JSON.stringify(tool, null, 2)}`); 150 | assert.ok(tool.description, `Tool ${tool.name} should have a description`); 151 | assert.ok(tool.inputSchema, `Tool ${tool.name} should have an input schema`); 152 | assert.equal(tool.inputSchema.type, 'object', `Tool ${tool.name} input schema should be object type`); 153 | } 154 | }); 155 | }); 156 | ``` -------------------------------------------------------------------------------- /docs/dw_order/ReturnCaseItem.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class ReturnCaseItem 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.Extensible 9 | - dw.order.AbstractItem 10 | - dw.order.ReturnCaseItem 11 | 12 | ## Description 13 | 14 | An item of a ReturnCase, created using method ReturnCase.createItem(String). Initially the ReturnCaseItem is NEW. No Return can be created at this point. From NEW the item transitions in CONFIRMED state. Now Return can be created. Next transition is either to PARTIAL_RETURNED or to CANCELLED. At the end the item can be RETURNED (no other Returns can be created. The custom code implementing the ReturnHooks is responsible to provide the logic for the transitions. Please refer to the documentation of ReturnHooks for further information. When the related ReturnCase were confirmed, only the the custom attributes of the return case item can be changed. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice. 15 | 16 | ## Constants 17 | 18 | ### STATUS_CANCELLED 19 | 20 | **Type:** String = "CANCELLED" 21 | 22 | constant for ReturnCase Status CANCELLED 23 | 24 | ### STATUS_CONFIRMED 25 | 26 | **Type:** String = "CONFIRMED" 27 | 28 | constant for ReturnCase Status CONFIRMED 29 | 30 | ### STATUS_NEW 31 | 32 | **Type:** String = "NEW" 33 | 34 | constant for ReturnCase Status NEW 35 | 36 | ### STATUS_PARTIAL_RETURNED 37 | 38 | **Type:** String = "PARTIAL_RETURNED" 39 | 40 | constant for ReturnCase Status PARTIAL RETURNED 41 | 42 | ### STATUS_RETURNED 43 | 44 | **Type:** String = "RETURNED" 45 | 46 | constant for ReturnCase Status RETURNED 47 | 48 | ## Properties 49 | 50 | ### authorizedQuantity 51 | 52 | **Type:** Quantity 53 | 54 | Return the Quantity authorized for this ReturnCaseItem, may be N/A. 55 | 56 | ### basePrice 57 | 58 | **Type:** Money (Read Only) 59 | 60 | Price of a single unit before discount application. 61 | 62 | ### note 63 | 64 | **Type:** String 65 | 66 | Return the note for this return case item. 67 | 68 | ### parentItem 69 | 70 | **Type:** ReturnCaseItem 71 | 72 | Returns null or the parent item. 73 | 74 | ### reasonCode 75 | 76 | **Type:** EnumValue 77 | 78 | The reason code for return case item. 79 | 80 | ### returnCaseNumber 81 | 82 | **Type:** String (Read Only) 83 | 84 | Mandatory number of ReturnCase to which this item belongs 85 | 86 | ### returnItems 87 | 88 | **Type:** Collection (Read Only) 89 | 90 | Unsorted collection of ReturnItems associated with this ReturnCaseItem. 91 | 92 | ### status 93 | 94 | **Type:** EnumValue 95 | 96 | Gets the return case item status. 97 | 98 | The possible values are STATUS_NEW,STATUS_CONFIRMED, 99 | STATUS_PARTIAL_RETURNED, STATUS_RETURNED, 100 | STATUS_CANCELLED. 101 | 102 | ## Constructor Summary 103 | 104 | ## Method Summary 105 | 106 | ### createReturnItem 107 | 108 | **Signature:** `createReturnItem(returnNumber : String) : ReturnItem` 109 | 110 | Create a new ReturnItem for this ReturnCaseItem and assign it to the given Return. 111 | 112 | ### getAuthorizedQuantity 113 | 114 | **Signature:** `getAuthorizedQuantity() : Quantity` 115 | 116 | Return the Quantity authorized for this ReturnCaseItem, may be N/A. 117 | 118 | ### getBasePrice 119 | 120 | **Signature:** `getBasePrice() : Money` 121 | 122 | Price of a single unit before discount application. 123 | 124 | ### getNote 125 | 126 | **Signature:** `getNote() : String` 127 | 128 | Return the note for this return case item. 129 | 130 | ### getParentItem 131 | 132 | **Signature:** `getParentItem() : ReturnCaseItem` 133 | 134 | Returns null or the parent item. 135 | 136 | ### getReasonCode 137 | 138 | **Signature:** `getReasonCode() : EnumValue` 139 | 140 | Returns the reason code for return case item. 141 | 142 | ### getReturnCaseNumber 143 | 144 | **Signature:** `getReturnCaseNumber() : String` 145 | 146 | Mandatory number of ReturnCase to which this item belongs 147 | 148 | ### getReturnItems 149 | 150 | **Signature:** `getReturnItems() : Collection` 151 | 152 | Unsorted collection of ReturnItems associated with this ReturnCaseItem. 153 | 154 | ### getStatus 155 | 156 | **Signature:** `getStatus() : EnumValue` 157 | 158 | Gets the return case item status. 159 | 160 | ### setAuthorizedQuantity 161 | 162 | **Signature:** `setAuthorizedQuantity(authorizedQuantity : Quantity) : void` 163 | 164 | Set the optional authorized Quantity for this item. 165 | 166 | ### setNote 167 | 168 | **Signature:** `setNote(note : String) : void` 169 | 170 | Sets a note for this return case item. 171 | 172 | ### setParentItem 173 | 174 | **Signature:** `setParentItem(parentItem : ReturnCaseItem) : void` 175 | 176 | Set a parent item. 177 | 178 | ### setReasonCode 179 | 180 | **Signature:** `setReasonCode(reasonCode : String) : void` 181 | 182 | Changes the reason code. 183 | 184 | ### setStatus 185 | 186 | **Signature:** `setStatus(statusString : String) : void` 187 | 188 | Sets the status. 189 | 190 | ## Method Detail 191 | 192 | ## Method Details 193 | 194 | ### createReturnItem 195 | 196 | **Signature:** `createReturnItem(returnNumber : String) : ReturnItem` 197 | 198 | **Description:** Create a new ReturnItem for this ReturnCaseItem and assign it to the given Return. 199 | 200 | **Parameters:** 201 | 202 | - `returnNumber`: number of Return to which new item is assigned. 203 | 204 | **Returns:** 205 | 206 | new ReturnItem 207 | 208 | --- 209 | 210 | ### getAuthorizedQuantity 211 | 212 | **Signature:** `getAuthorizedQuantity() : Quantity` 213 | 214 | **Description:** Return the Quantity authorized for this ReturnCaseItem, may be N/A. 215 | 216 | **Returns:** 217 | 218 | the authorized quantity or N/A 219 | 220 | --- 221 | 222 | ### getBasePrice 223 | 224 | **Signature:** `getBasePrice() : Money` 225 | 226 | **Description:** Price of a single unit before discount application. 227 | 228 | **Returns:** 229 | 230 | Price of a single unit before discount application. 231 | 232 | --- 233 | 234 | ### getNote 235 | 236 | **Signature:** `getNote() : String` 237 | 238 | **Description:** Return the note for this return case item. 239 | 240 | **Returns:** 241 | 242 | the note or null 243 | 244 | --- 245 | 246 | ### getParentItem 247 | 248 | **Signature:** `getParentItem() : ReturnCaseItem` 249 | 250 | **Description:** Returns null or the parent item. 251 | 252 | **Returns:** 253 | 254 | null or the parent item. 255 | 256 | --- 257 | 258 | ### getReasonCode 259 | 260 | **Signature:** `getReasonCode() : EnumValue` 261 | 262 | **Description:** Returns the reason code for return case item. 263 | 264 | **Returns:** 265 | 266 | the return reason code 267 | 268 | --- 269 | 270 | ### getReturnCaseNumber 271 | 272 | **Signature:** `getReturnCaseNumber() : String` 273 | 274 | **Description:** Mandatory number of ReturnCase to which this item belongs 275 | 276 | **Returns:** 277 | 278 | number of ReturnCase to which this item belongs 279 | 280 | --- 281 | 282 | ### getReturnItems 283 | 284 | **Signature:** `getReturnItems() : Collection` 285 | 286 | **Description:** Unsorted collection of ReturnItems associated with this ReturnCaseItem. 287 | 288 | **Returns:** 289 | 290 | unsorted collection of ReturnItems associated with this ReturnCaseItem 291 | 292 | **See Also:** 293 | 294 | createReturnItem(String) 295 | 296 | --- 297 | 298 | ### getStatus 299 | 300 | **Signature:** `getStatus() : EnumValue` 301 | 302 | **Description:** Gets the return case item status. The possible values are STATUS_NEW,STATUS_CONFIRMED, STATUS_PARTIAL_RETURNED, STATUS_RETURNED, STATUS_CANCELLED. 303 | 304 | **Returns:** 305 | 306 | the status 307 | 308 | --- 309 | 310 | ### setAuthorizedQuantity 311 | 312 | **Signature:** `setAuthorizedQuantity(authorizedQuantity : Quantity) : void` 313 | 314 | **Description:** Set the optional authorized Quantity for this item. Passing null will result in an N/A Quantity being set. 315 | 316 | **Parameters:** 317 | 318 | - `authorizedQuantity`: null or the quantity 319 | 320 | --- 321 | 322 | ### setNote 323 | 324 | **Signature:** `setNote(note : String) : void` 325 | 326 | **Description:** Sets a note for this return case item. 327 | 328 | **Parameters:** 329 | 330 | - `note`: the note for this return case item to set 331 | 332 | --- 333 | 334 | ### setParentItem 335 | 336 | **Signature:** `setParentItem(parentItem : ReturnCaseItem) : void` 337 | 338 | **Description:** Set a parent item. The parent item must belong to the same ReturnCase. An infinite parent-child loop is disallowed as is a parent-child depth greater than 10. Setting a parent item indicates a dependency of the child item on the parent item, and can be used to form a parallel structure to that accessed using ProductLineItem.getParent(). 339 | 340 | **Parameters:** 341 | 342 | - `parentItem`: The parent item, null is allowed 343 | 344 | --- 345 | 346 | ### setReasonCode 347 | 348 | **Signature:** `setReasonCode(reasonCode : String) : void` 349 | 350 | **Description:** Changes the reason code. Initially the reason code is set on return case item creation. 351 | 352 | **Parameters:** 353 | 354 | - `reasonCode`: the reason code to set 355 | 356 | --- 357 | 358 | ### setStatus 359 | 360 | **Signature:** `setStatus(statusString : String) : void` 361 | 362 | **Description:** Sets the status. The possible values are STATUS_NEW,STATUS_CONFIRMED, STATUS_PARTIAL_RETURNED, STATUS_RETURNED, STATUS_CANCELLED. 363 | 364 | **Parameters:** 365 | 366 | - `statusString`: the status 367 | 368 | **Throws:** 369 | 370 | NullPointerException - if status is null 371 | IllegalArgumentException - if the status transition to the status is not allowed 372 | 373 | --- ``` -------------------------------------------------------------------------------- /tests/ocapi-auth-client.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for OCAPIAuthClient 3 | * Tests OAuth authentication functionality for OCAPI 4 | */ 5 | 6 | import { OCAPIAuthClient } from '../src/clients/base/ocapi-auth-client.js'; 7 | import { TokenManager } from '../src/clients/base/oauth-token.js'; 8 | import { OCAPIConfig, OAuthTokenResponse } from '../src/types/types.js'; 9 | 10 | // Mock fetch globally 11 | global.fetch = jest.fn(); 12 | 13 | // Mock TokenManager 14 | jest.mock('../src/clients/base/oauth-token.js'); 15 | 16 | // Mock Logger 17 | jest.mock('../src/utils/logger.js', () => ({ 18 | Logger: { 19 | initialize: jest.fn(), 20 | getInstance: jest.fn(() => ({ 21 | methodEntry: jest.fn(), 22 | methodExit: jest.fn(), 23 | debug: jest.fn(), 24 | warn: jest.fn(), 25 | error: jest.fn(), 26 | timing: jest.fn(), 27 | log: jest.fn(), 28 | info: jest.fn(), 29 | })), 30 | getChildLogger: jest.fn(() => ({ 31 | methodEntry: jest.fn(), 32 | methodExit: jest.fn(), 33 | debug: jest.fn(), 34 | warn: jest.fn(), 35 | error: jest.fn(), 36 | timing: jest.fn(), 37 | log: jest.fn(), 38 | info: jest.fn(), 39 | })), 40 | }, 41 | })); 42 | 43 | // Mock BaseHttpClient 44 | jest.mock('../src/clients/base/http-client.js'); 45 | 46 | describe('OCAPIAuthClient', () => { 47 | let client: OCAPIAuthClient; 48 | let mockTokenManager: jest.Mocked<TokenManager>; 49 | let mockFetch: jest.MockedFunction<typeof fetch>; 50 | 51 | const mockConfig: OCAPIConfig = { 52 | hostname: 'test-instance.demandware.net', 53 | clientId: 'test-client-id', 54 | clientSecret: 'test-client-secret', 55 | version: 'v21_3', 56 | }; 57 | 58 | beforeEach(() => { 59 | jest.clearAllMocks(); 60 | 61 | mockFetch = fetch as jest.MockedFunction<typeof fetch>; 62 | 63 | // Setup TokenManager mock 64 | mockTokenManager = { 65 | getValidToken: jest.fn(), 66 | storeToken: jest.fn(), 67 | clearToken: jest.fn(), 68 | getTokenExpiration: jest.fn(), 69 | isTokenValid: jest.fn(), 70 | clearAllTokens: jest.fn(), 71 | } as any; 72 | 73 | (TokenManager.getInstance as jest.Mock).mockReturnValue(mockTokenManager); 74 | 75 | client = new OCAPIAuthClient(mockConfig); 76 | 77 | // Manually set up the logger mock since BaseHttpClient mock isn't working as expected 78 | (client as any).logger = { 79 | debug: jest.fn(), 80 | info: jest.fn(), 81 | warn: jest.fn(), 82 | error: jest.fn(), 83 | }; 84 | }); 85 | 86 | describe('constructor', () => { 87 | it('should initialize with config', () => { 88 | expect(client).toBeInstanceOf(OCAPIAuthClient); 89 | expect(TokenManager.getInstance).toHaveBeenCalled(); 90 | }); 91 | }); 92 | 93 | describe('getAuthHeaders', () => { 94 | it('should return Bearer token in auth headers', async () => { 95 | const mockToken = 'mock-access-token'; 96 | mockTokenManager.getValidToken.mockReturnValue(mockToken); 97 | 98 | const headers = await (client as any).getAuthHeaders(); 99 | 100 | expect(headers).toEqual({ 101 | 'Authorization': 'Bearer mock-access-token', 102 | }); 103 | expect(mockTokenManager.getValidToken).toHaveBeenCalledWith( 104 | mockConfig.hostname, 105 | mockConfig.clientId, 106 | ); 107 | }); 108 | 109 | it('should request new token when no valid token exists', async () => { 110 | const newToken = 'new-access-token'; 111 | const tokenResponse: OAuthTokenResponse = { 112 | access_token: newToken, 113 | token_type: 'bearer', 114 | expires_in: 3600, 115 | }; 116 | 117 | mockTokenManager.getValidToken.mockReturnValue(null); 118 | mockFetch.mockResolvedValue({ 119 | ok: true, 120 | json: async () => tokenResponse, 121 | } as Response); 122 | 123 | const headers = await (client as any).getAuthHeaders(); 124 | 125 | expect(headers).toEqual({ 126 | 'Authorization': 'Bearer new-access-token', 127 | }); 128 | expect(mockFetch).toHaveBeenCalledWith( 129 | 'https://account.demandware.com/dwsso/oauth2/access_token', 130 | { 131 | method: 'POST', 132 | headers: { 133 | 'Authorization': `Basic ${Buffer.from(`${mockConfig.clientId}:${mockConfig.clientSecret}`).toString('base64')}`, 134 | 'Content-Type': 'application/x-www-form-urlencoded', 135 | }, 136 | body: 'grant_type=client_credentials', 137 | }, 138 | ); 139 | expect(mockTokenManager.storeToken).toHaveBeenCalledWith( 140 | mockConfig.hostname, 141 | mockConfig.clientId, 142 | tokenResponse, 143 | ); 144 | }); 145 | 146 | it('should handle OAuth request failure', async () => { 147 | mockTokenManager.getValidToken.mockReturnValue(null); 148 | mockFetch.mockResolvedValue({ 149 | ok: false, 150 | status: 401, 151 | statusText: 'Unauthorized', 152 | text: async () => 'Invalid credentials', 153 | } as Response); 154 | 155 | await expect((client as any).getAuthHeaders()).rejects.toThrow( 156 | 'Failed to get access token: Error: OAuth authentication failed: 401 Unauthorized - Invalid credentials', 157 | ); 158 | }); 159 | 160 | it('should handle network errors during token request', async () => { 161 | mockTokenManager.getValidToken.mockReturnValue(null); 162 | mockFetch.mockRejectedValue(new Error('Network error')); 163 | 164 | await expect((client as any).getAuthHeaders()).rejects.toThrow( 165 | 'Failed to get access token: Error: Network error', 166 | ); 167 | }); 168 | }); 169 | 170 | describe('handleAuthError', () => { 171 | it('should clear token when handling auth error', async () => { 172 | await (client as any).handleAuthError(); 173 | 174 | expect(mockTokenManager.clearToken).toHaveBeenCalledWith( 175 | mockConfig.hostname, 176 | mockConfig.clientId, 177 | ); 178 | }); 179 | }); 180 | 181 | describe('getTokenExpiration', () => { 182 | it('should return token expiration from TokenManager', () => { 183 | const mockExpiration = new Date('2025-12-31T23:59:59Z'); 184 | mockTokenManager.getTokenExpiration.mockReturnValue(mockExpiration); 185 | 186 | const result = client.getTokenExpiration(); 187 | 188 | expect(result).toBe(mockExpiration); 189 | expect(mockTokenManager.getTokenExpiration).toHaveBeenCalledWith( 190 | mockConfig.hostname, 191 | mockConfig.clientId, 192 | ); 193 | }); 194 | 195 | it('should return null when no token exists', () => { 196 | mockTokenManager.getTokenExpiration.mockReturnValue(null); 197 | 198 | const result = client.getTokenExpiration(); 199 | 200 | expect(result).toBeNull(); 201 | }); 202 | }); 203 | 204 | describe('refreshToken', () => { 205 | it('should clear token and request new one', async () => { 206 | const newToken = 'refreshed-token'; 207 | const tokenResponse: OAuthTokenResponse = { 208 | access_token: newToken, 209 | token_type: 'bearer', 210 | expires_in: 3600, 211 | }; 212 | 213 | mockFetch.mockResolvedValue({ 214 | ok: true, 215 | json: async () => tokenResponse, 216 | } as Response); 217 | 218 | await client.refreshToken(); 219 | 220 | expect(mockTokenManager.clearToken).toHaveBeenCalledWith( 221 | mockConfig.hostname, 222 | mockConfig.clientId, 223 | ); 224 | expect(mockFetch).toHaveBeenCalled(); 225 | }); 226 | 227 | it('should handle refresh token failure', async () => { 228 | mockFetch.mockResolvedValue({ 229 | ok: false, 230 | status: 401, 231 | statusText: 'Unauthorized', 232 | text: async () => 'Invalid refresh', 233 | } as Response); 234 | 235 | await expect(client.refreshToken()).rejects.toThrow( 236 | 'Failed to get access token', 237 | ); 238 | }); 239 | }); 240 | 241 | describe('token management flow', () => { 242 | it('should use existing token when valid', async () => { 243 | const existingToken = 'existing-valid-token'; 244 | mockTokenManager.getValidToken.mockReturnValue(existingToken); 245 | 246 | const headers = await (client as any).getAuthHeaders(); 247 | 248 | expect(headers).toEqual({ 249 | 'Authorization': 'Bearer existing-valid-token', 250 | }); 251 | expect(mockFetch).not.toHaveBeenCalled(); 252 | }); 253 | 254 | it('should store new token after successful request', async () => { 255 | const tokenResponse: OAuthTokenResponse = { 256 | access_token: 'new-token', 257 | token_type: 'bearer', 258 | expires_in: 7200, 259 | }; 260 | 261 | mockTokenManager.getValidToken.mockReturnValue(null); 262 | mockFetch.mockResolvedValue({ 263 | ok: true, 264 | json: async () => tokenResponse, 265 | } as Response); 266 | 267 | await (client as any).getAuthHeaders(); 268 | 269 | expect(mockTokenManager.storeToken).toHaveBeenCalledWith( 270 | mockConfig.hostname, 271 | mockConfig.clientId, 272 | tokenResponse, 273 | ); 274 | }); 275 | }); 276 | }); 277 | ```