This is page 16 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/yaml/get-code-versions.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | # ================================================================================== 2 | # SFCC MCP Server - get_code_versions Tool YAML Tests (Full Mode) 3 | # Tests code version retrieval functionality with SFCC credentials 4 | # This tool provides code version management for deployment troubleshooting 5 | # 6 | # Quick Test Commands: 7 | # aegis "tests/mcp/yaml/get-code-versions.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose 8 | # aegis query get_code_versions '{}' --config "aegis.config.with-dw.json" 9 | # ================================================================================== 10 | 11 | description: "get_code_versions tool full mode tests - Core functionality validation" 12 | 13 | tests: 14 | # ================================================================================== 15 | # TOOL AVAILABILITY TESTS 16 | # ================================================================================== 17 | - it: "should list get_code_versions tool in full mode" 18 | request: 19 | jsonrpc: "2.0" 20 | id: "tool-availability-full" 21 | method: "tools/list" 22 | params: {} 23 | expect: 24 | response: 25 | jsonrpc: "2.0" 26 | id: "tool-availability-full" 27 | result: 28 | tools: 29 | match:arrayElements: 30 | match:partial: 31 | name: "match:type:string" 32 | description: "match:type:string" 33 | match:extractField: "tools.*.name" 34 | value: "match:arrayContains:get_code_versions" 35 | stderr: "toBeEmpty" 36 | 37 | - it: "should have correct tool definition in full mode" 38 | request: 39 | jsonrpc: "2.0" 40 | id: "tool-definition-full" 41 | method: "tools/list" 42 | params: {} 43 | expect: 44 | response: 45 | jsonrpc: "2.0" 46 | id: "tool-definition-full" 47 | result: 48 | tools: "match:arrayContains:name:get_code_versions" 49 | stderr: "toBeEmpty" 50 | 51 | # ================================================================================== 52 | # BASIC FUNCTIONALITY TESTS 53 | # ================================================================================== 54 | - it: "should retrieve code versions with default parameters" 55 | request: 56 | jsonrpc: "2.0" 57 | id: "code-versions-default" 58 | method: "tools/call" 59 | params: 60 | name: "get_code_versions" 61 | arguments: {} 62 | expect: 63 | response: 64 | jsonrpc: "2.0" 65 | id: "code-versions-default" 66 | result: 67 | content: 68 | match:arrayElements: 69 | type: "text" 70 | text: "match:regex:[\\s\\S]*code_version_result[\\s\\S]*" 71 | isError: false 72 | stderr: "toBeEmpty" 73 | performance: 74 | maxResponseTime: "2000ms" 75 | 76 | # ================================================================================== 77 | # PARAMETER VALIDATION TESTS 78 | # ================================================================================== 79 | - it: "should accept empty arguments object" 80 | request: 81 | jsonrpc: "2.0" 82 | id: "empty-args" 83 | method: "tools/call" 84 | params: 85 | name: "get_code_versions" 86 | arguments: {} 87 | expect: 88 | response: 89 | jsonrpc: "2.0" 90 | id: "empty-args" 91 | result: 92 | content: 93 | match:arrayElements: 94 | match:partial: 95 | type: "text" 96 | isError: false 97 | stderr: "toBeEmpty" 98 | performance: 99 | maxResponseTime: "2000ms" 100 | 101 | - it: "should ignore extra parameters gracefully" 102 | request: 103 | jsonrpc: "2.0" 104 | id: "extra-params" 105 | method: "tools/call" 106 | params: 107 | name: "get_code_versions" 108 | arguments: 109 | extraParam: "should be ignored" 110 | anotherParam: 123 111 | expect: 112 | response: 113 | jsonrpc: "2.0" 114 | id: "extra-params" 115 | result: 116 | content: 117 | match:arrayElements: 118 | match:partial: 119 | type: "text" 120 | isError: false 121 | stderr: "toBeEmpty" 122 | performance: 123 | maxResponseTime: "2000ms" 124 | 125 | # ================================================================================== 126 | # RESPONSE CONTENT VALIDATION 127 | # ================================================================================== 128 | - it: "should return properly formatted JSON content" 129 | request: 130 | jsonrpc: "2.0" 131 | id: "json-format" 132 | method: "tools/call" 133 | params: 134 | name: "get_code_versions" 135 | arguments: {} 136 | expect: 137 | response: 138 | jsonrpc: "2.0" 139 | id: "json-format" 140 | result: 141 | content: 142 | match:arrayElements: 143 | type: "text" 144 | text: "match:regex:[\\s\\S]*\\{[\\s\\S]*\\}[\\s\\S]*" 145 | isError: false 146 | stderr: "toBeEmpty" 147 | 148 | - it: "should include essential code version fields" 149 | request: 150 | jsonrpc: "2.0" 151 | id: "essential-fields" 152 | method: "tools/call" 153 | params: 154 | name: "get_code_versions" 155 | arguments: {} 156 | expect: 157 | response: 158 | jsonrpc: "2.0" 159 | id: "essential-fields" 160 | result: 161 | content: 162 | match:arrayElements: 163 | type: "text" 164 | text: "match:regex:[\\s\\S]*_type[\\s\\S]*code_version_result[\\s\\S]*" 165 | isError: false 166 | stderr: "toBeEmpty" 167 | 168 | - it: "should include data array with code version objects" 169 | request: 170 | jsonrpc: "2.0" 171 | id: "data-array" 172 | method: "tools/call" 173 | params: 174 | name: "get_code_versions" 175 | arguments: {} 176 | expect: 177 | response: 178 | jsonrpc: "2.0" 179 | id: "data-array" 180 | result: 181 | content: 182 | match:arrayElements: 183 | type: "text" 184 | text: "match:regex:[\\s\\S]*data[\\s\\S]*\\[[\\s\\S]*\\][\\s\\S]*" 185 | isError: false 186 | stderr: "toBeEmpty" 187 | 188 | - it: "should include code version metadata" 189 | request: 190 | jsonrpc: "2.0" 191 | id: "version-metadata" 192 | method: "tools/call" 193 | params: 194 | name: "get_code_versions" 195 | arguments: {} 196 | expect: 197 | response: 198 | jsonrpc: "2.0" 199 | id: "version-metadata" 200 | result: 201 | content: 202 | match:arrayElements: 203 | type: "text" 204 | text: "match:regex:[\\s\\S]*(?:id)[\\s\\S]*(?:active)[\\s\\S]*" 205 | isError: false 206 | stderr: "toBeEmpty" 207 | 208 | # ================================================================================== 209 | # PERFORMANCE TESTS 210 | # ================================================================================== 211 | - it: "should respond within acceptable time limit" 212 | request: 213 | jsonrpc: "2.0" 214 | id: "performance-test" 215 | method: "tools/call" 216 | params: 217 | name: "get_code_versions" 218 | arguments: {} 219 | expect: 220 | response: 221 | jsonrpc: "2.0" 222 | id: "performance-test" 223 | result: 224 | content: 225 | match:arrayElements: 226 | match:partial: 227 | type: "text" 228 | isError: false 229 | stderr: "toBeEmpty" 230 | performance: 231 | maxResponseTime: "2000ms" 232 | 233 | - it: "should maintain consistent performance on repeat calls" 234 | request: 235 | jsonrpc: "2.0" 236 | id: "performance-consistency" 237 | method: "tools/call" 238 | params: 239 | name: "get_code_versions" 240 | arguments: {} 241 | expect: 242 | response: 243 | jsonrpc: "2.0" 244 | id: "performance-consistency" 245 | result: 246 | content: 247 | match:arrayElements: 248 | match:partial: 249 | type: "text" 250 | isError: false 251 | stderr: "toBeEmpty" 252 | performance: 253 | maxResponseTime: "2000ms" 254 | 255 | # ================================================================================== 256 | # ERROR HANDLING TESTS (Edge Cases) 257 | # ================================================================================== 258 | - it: "should return validation error for null arguments" 259 | request: 260 | jsonrpc: "2.0" 261 | id: "null-args" 262 | method: "tools/call" 263 | params: 264 | name: "get_code_versions" 265 | arguments: null 266 | expect: 267 | response: 268 | jsonrpc: "2.0" 269 | id: "null-args" 270 | error: 271 | code: "match:type:number" 272 | message: "match:type:string" 273 | stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/src/routes/webdav.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * WebDAV Route Handler 3 | * 4 | * Handles WebDAV requests including PROPFIND for directory listings 5 | * and GET requests for file content with range support. 6 | * Modular implementation following single responsibility principle. 7 | */ 8 | 9 | const express = require('express'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const { URL } = require('url'); 13 | const { 14 | generateFileXmlResponse, 15 | generateDirectoryXmlResponse, 16 | generateErrorXmlResponse, 17 | parseDepthHeader 18 | } = require('../utils/webdav-xml'); 19 | 20 | class WebDAVRouteHandler { 21 | constructor(config) { 22 | this.config = config; 23 | this.webdavConfig = config.getWebdavConfig(); 24 | this.router = express.Router(); 25 | this.setupRoutes(); 26 | } 27 | 28 | setupRoutes() { 29 | // Handle PROPFIND method (custom method support) 30 | this.router.use((req, res, next) => { 31 | if (req.method === 'PROPFIND') { 32 | this.handlePropfind(req, res); 33 | } else { 34 | next(); 35 | } 36 | }); 37 | 38 | // WebDAV base path routes 39 | this.router.get(`${this.webdavConfig.basePath}/Logs/*`, (req, res) => { 40 | this.handleGet(req, res); 41 | }); 42 | 43 | // Direct logs path routes (for backward compatibility) 44 | this.router.get('/Logs/*', (req, res) => { 45 | this.handleGet(req, res); 46 | }); 47 | 48 | // Root logs directory 49 | this.router.get('/Logs', (req, res) => { 50 | this.handleGet(req, res); 51 | }); 52 | } 53 | 54 | /** 55 | * Map URL path to file system path 56 | */ 57 | mapUrlToFilePath(urlPath) { 58 | // Handle SFCC WebDAV path structure 59 | if (urlPath.startsWith(this.webdavConfig.basePath)) { 60 | // Remove the WebDAV base path and map to our mock structure 61 | let relativePath = urlPath.replace(this.webdavConfig.basePath, ''); 62 | // Convert uppercase Logs to lowercase logs for file system, but remove the leading /Logs since it's already in logsPath 63 | relativePath = relativePath.replace(/^\/Logs/, ''); 64 | return path.join(this.webdavConfig.logsPath, relativePath); 65 | } 66 | 67 | // Fallback: treat as direct path to logs (for backward compatibility) 68 | let relativePath = urlPath.replace(/^\/+/, ''); 69 | // Convert uppercase Logs to lowercase logs for file system, but remove the leading Logs since it's already in logsPath 70 | relativePath = relativePath.replace(/^Logs/, ''); 71 | return path.join(this.webdavConfig.logsPath, relativePath); 72 | } 73 | 74 | /** 75 | * Handle GET requests for file content 76 | */ 77 | handleGet(req, res) { 78 | try { 79 | const urlPath = decodeURIComponent(req.path); 80 | const fsPath = this.mapUrlToFilePath(urlPath); 81 | 82 | if (this.config.isDevMode) { 83 | console.log(`[WebDAV] GET ${urlPath} -> ${fsPath}`); 84 | } 85 | 86 | if (!fs.existsSync(fsPath)) { 87 | return res.status(404).send('Not Found'); 88 | } 89 | 90 | const stats = fs.statSync(fsPath); 91 | if (stats.isDirectory()) { 92 | return res.status(404).send('Not Found'); 93 | } 94 | 95 | // Handle range requests for partial content 96 | const rangeHeader = req.headers.range; 97 | if (rangeHeader) { 98 | return this.handleRangeRequest(req, res, fsPath, stats); 99 | } 100 | 101 | // Normal full file request 102 | res.set({ 103 | 'Content-Type': 'text/plain', 104 | 'Content-Length': stats.size, 105 | 'Accept-Ranges': 'bytes' 106 | }); 107 | 108 | const stream = fs.createReadStream(fsPath); 109 | stream.pipe(res); 110 | 111 | } catch (error) { 112 | console.error('[WebDAV] Error handling GET request:', error); 113 | res.status(500).send('Internal Server Error'); 114 | } 115 | } 116 | 117 | /** 118 | * Handle range requests for partial content 119 | */ 120 | handleRangeRequest(req, res, fsPath, stats) { 121 | try { 122 | const rangeHeader = req.headers.range; 123 | const fileSize = stats.size; 124 | 125 | if (this.config.isDevMode) { 126 | console.log(`[WebDAV] Range request: ${rangeHeader} for file ${fsPath} (size: ${fileSize})`); 127 | } 128 | 129 | // Parse range header (e.g., "bytes=0-499" or "bytes=500-999") 130 | const rangeMatch = rangeHeader.match(/bytes=(\d+)-(\d*)/); 131 | if (!rangeMatch) { 132 | res.set('Content-Range', `bytes */${fileSize}`); 133 | return res.status(416).send('Range Not Satisfiable'); 134 | } 135 | 136 | const start = parseInt(rangeMatch[1], 10); 137 | const end = rangeMatch[2] ? parseInt(rangeMatch[2], 10) : fileSize - 1; 138 | 139 | // Validate range 140 | if (start >= fileSize || end >= fileSize || start > end) { 141 | res.set('Content-Range', `bytes */${fileSize}`); 142 | return res.status(416).send('Range Not Satisfiable'); 143 | } 144 | 145 | const contentLength = end - start + 1; 146 | 147 | if (this.config.isDevMode) { 148 | console.log(`[WebDAV] Range: ${start}-${end}, Content-Length: ${contentLength}`); 149 | } 150 | 151 | // Send partial content response 152 | res.set({ 153 | 'Content-Type': 'text/plain', 154 | 'Content-Length': contentLength, 155 | 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 156 | 'Accept-Ranges': 'bytes' 157 | }); 158 | 159 | res.status(206); // Partial Content 160 | const stream = fs.createReadStream(fsPath, { start, end }); 161 | stream.pipe(res); 162 | 163 | } catch (error) { 164 | console.error('[WebDAV] Error handling range request:', error); 165 | res.status(500).send('Internal Server Error'); 166 | } 167 | } 168 | 169 | /** 170 | * Handle PROPFIND requests for directory listings 171 | */ 172 | handlePropfind(req, res) { 173 | try { 174 | const urlPath = decodeURIComponent(req.path); 175 | const fsPath = this.mapUrlToFilePath(urlPath); 176 | 177 | if (this.config.isDevMode) { 178 | console.log(`[WebDAV] PROPFIND ${urlPath} -> ${fsPath}`); 179 | } 180 | 181 | if (!fs.existsSync(fsPath)) { 182 | if (this.config.isDevMode) { 183 | console.log(`[WebDAV] File not found: ${fsPath}`); 184 | } 185 | return res.status(404).send('Not Found'); 186 | } 187 | 188 | const stats = fs.statSync(fsPath); 189 | 190 | if (this.config.isDevMode) { 191 | console.log(`[WebDAV] File stats: size=${stats.size}, isFile=${stats.isFile()}, isDirectory=${stats.isDirectory()}`); 192 | } 193 | 194 | // Handle PROPFIND for individual files 195 | if (stats.isFile()) { 196 | const xml = generateFileXmlResponse(urlPath, stats); 197 | 198 | if (this.config.isDevMode) { 199 | console.log(`[WebDAV] Returning file PROPFIND response for ${urlPath}`); 200 | } 201 | 202 | res.set('Content-Type', 'application/xml'); 203 | return res.status(207).send(xml); 204 | } 205 | 206 | // Handle PROPFIND for directories 207 | if (!stats.isDirectory()) { 208 | return res.status(404).send('Not Found'); 209 | } 210 | 211 | // Read directory contents 212 | const itemNames = fs.readdirSync(fsPath); 213 | const items = itemNames.map(name => { 214 | const itemPath = path.join(fsPath, name); 215 | const itemStats = fs.statSync(itemPath); 216 | return { name, stats: itemStats }; 217 | }); 218 | 219 | const xml = generateDirectoryXmlResponse(urlPath, items, stats); 220 | 221 | res.set('Content-Type', 'application/xml'); 222 | res.status(207).send(xml); 223 | 224 | } catch (error) { 225 | console.error(`[WebDAV] Error handling PROPFIND request for ${req.path}:`, error); 226 | res.status(500).send('Internal Server Error'); 227 | } 228 | } 229 | 230 | /** 231 | * Get the configured router 232 | */ 233 | getRouter() { 234 | return this.router; 235 | } 236 | } 237 | 238 | module.exports = WebDAVRouteHandler; ``` -------------------------------------------------------------------------------- /src/clients/logs/log-file-reader.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Log file reading operations with range request support 3 | */ 4 | 5 | import type { WebDAVClient } from 'webdav'; 6 | import { Logger } from '../../utils/logger.js'; 7 | import { LOG_CONSTANTS } from './log-constants.js'; 8 | import type { FileReadOptions } from './log-types.js'; 9 | 10 | export class LogFileReader { 11 | private logger: Logger; 12 | private webdavClient: WebDAVClient; 13 | 14 | constructor(webdavClient: WebDAVClient, logger: Logger) { 15 | this.webdavClient = webdavClient; 16 | this.logger = logger; 17 | } 18 | 19 | /** 20 | * Get the last portion of a file using range requests to avoid loading huge files 21 | * 22 | * @param filename - The file path to read 23 | * @param options - Read options including maxBytes 24 | * @returns Promise<string> - The file content as a string 25 | */ 26 | async getFileContentsTail( 27 | filename: string, 28 | options: FileReadOptions = {}, 29 | ): Promise<string> { 30 | const { maxBytes = LOG_CONSTANTS.DEFAULT_TAIL_BYTES } = options; 31 | 32 | this.logger.debug(`Reading file tail: ${filename} (maxBytes: ${maxBytes})`); 33 | 34 | try { 35 | // First, try to get file info to determine the file size 36 | this.logger.debug(`Attempting stat for file: ${filename}`); 37 | const stat = await this.webdavClient.stat(filename); 38 | this.logger.debug(`Stat successful for ${filename}:`, stat); 39 | const fileSize = (stat as any).size; 40 | 41 | if (!fileSize || fileSize <= maxBytes) { 42 | // File is small enough or size unknown, just get the whole file 43 | this.logger.debug(`File ${filename} is small (${fileSize} bytes), reading full content`); 44 | return await this.getFullFileContents(filename); 45 | } 46 | 47 | // File is large, get only the last portion using range request 48 | this.logger.debug(`File ${filename} is large (${fileSize} bytes), using range request for last ${maxBytes} bytes`); 49 | const startByte = fileSize - maxBytes; 50 | return await this.getRangeFileContents(filename, startByte, fileSize - 1); 51 | 52 | } catch (statError) { 53 | const error = statError as Error; 54 | this.logger.warn(`Failed to get file stats for ${filename}, falling back to full file. Error:`, { 55 | message: error.message, 56 | name: error.name, 57 | code: (error as any).code, 58 | status: (error as any).status, 59 | }); 60 | return await this.getFullFileContents(filename); 61 | } 62 | } 63 | 64 | /** 65 | * Read the full contents of a file 66 | */ 67 | private async getFullFileContents(filename: string): Promise<string> { 68 | const content = await this.webdavClient.getFileContents(filename, { format: 'text' }); 69 | return content as string; 70 | } 71 | 72 | /** 73 | * Read from the beginning of a file with optional size limit 74 | * 75 | * @param filename - The file path to read 76 | * @param maxBytes - Maximum number of bytes to read from the beginning 77 | * @returns Promise<string> - The file content as a string (truncated if needed) 78 | */ 79 | async getFileContentsHead( 80 | filename: string, 81 | maxBytes?: number, 82 | ): Promise<string> { 83 | this.logger.debug(`Reading file head: ${filename} (maxBytes: ${maxBytes ?? 'unlimited'})`); 84 | 85 | if (!maxBytes) { 86 | // No size limit, read full file 87 | return await this.getFullFileContents(filename); 88 | } 89 | 90 | try { 91 | // First, try to get file info to determine the file size 92 | this.logger.debug(`Attempting stat for file: ${filename}`); 93 | const stat = await this.webdavClient.stat(filename); 94 | this.logger.debug(`Stat successful for ${filename}:`, stat); 95 | const fileSize = (stat as any).size; 96 | 97 | if (!fileSize || fileSize <= maxBytes) { 98 | // File is small enough or size unknown, just get the whole file 99 | this.logger.debug(`File ${filename} is small (${fileSize} bytes), reading full content`); 100 | return await this.getFullFileContents(filename); 101 | } 102 | 103 | // File is large, get only the first portion using range request 104 | this.logger.debug(`File ${filename} is large (${fileSize} bytes), using range request for first ${maxBytes} bytes`); 105 | return await this.getRangeFileContents(filename, 0, maxBytes - 1); 106 | 107 | } catch (statError) { 108 | const error = statError as Error; 109 | this.logger.warn(`Failed to get file stats for ${filename}, falling back to full file with truncation. Error:`, { 110 | message: error.message, 111 | name: error.name, 112 | code: (error as any).code, 113 | status: (error as any).status, 114 | }); 115 | // Fallback to reading full file and truncating 116 | const content = await this.getFullFileContents(filename); 117 | if (content.length > maxBytes) { 118 | this.logger.debug(`Truncating full file content from ${content.length} to ${maxBytes} characters`); 119 | return content.substring(0, maxBytes); 120 | } 121 | return content; 122 | } 123 | } 124 | 125 | /** 126 | * Read a range of bytes from a file using streaming 127 | * 128 | * @param filename - The file path to read 129 | * @param start - Start byte position (0-based) 130 | * @param end - End byte position (inclusive) 131 | * @returns Promise<string> - The file content as a string 132 | */ 133 | private async getRangeFileContents( 134 | filename: string, 135 | start: number, 136 | end: number, 137 | ): Promise<string> { 138 | return new Promise((resolve, reject) => { 139 | try { 140 | const stream = this.webdavClient.createReadStream(filename, { 141 | range: { start, end }, 142 | }); 143 | 144 | const chunks: Buffer[] = []; 145 | 146 | stream.on('data', (chunk: Buffer) => { 147 | chunks.push(chunk); 148 | }); 149 | 150 | stream.on('end', () => { 151 | const content = Buffer.concat(chunks).toString('utf-8'); 152 | this.logger.debug(`Successfully read ${content.length} characters from range request (${start}-${end})`); 153 | resolve(content); 154 | }); 155 | 156 | stream.on('error', (error: any) => { 157 | this.logger.warn(`Failed to read range ${start}-${end} for ${filename}, falling back to full file:`, error); 158 | // Fallback to getting the full file 159 | this.getFullFileContents(filename) 160 | .then((content: string) => { 161 | // Truncate to the requested range if possible 162 | const requestedLength = end - start + 1; 163 | if (start === 0 && content.length > requestedLength) { 164 | // Reading from start, truncate 165 | resolve(content.substring(0, requestedLength)); 166 | } else { 167 | // Reading from end or full file is smaller, return what we have 168 | resolve(content); 169 | } 170 | }) 171 | .catch((fallbackError: any) => reject(fallbackError)); 172 | }); 173 | } catch (error) { 174 | // If createReadStream fails, fall back to full file 175 | this.logger.warn(`Failed to create read stream for ${filename}, falling back to full file:`, error); 176 | this.getFullFileContents(filename) 177 | .then((content: string) => { 178 | const requestedLength = end - start + 1; 179 | if (start === 0 && content.length > requestedLength) { 180 | resolve(content.substring(0, requestedLength)); 181 | } else { 182 | resolve(content); 183 | } 184 | }) 185 | .catch((fallbackError: any) => reject(fallbackError)); 186 | } 187 | }); 188 | } 189 | 190 | /** 191 | * Read multiple files with tail optimization 192 | */ 193 | async readMultipleFiles( 194 | filenames: string[], 195 | options: FileReadOptions = {}, 196 | ): Promise<Map<string, string>> { 197 | const results = new Map<string, string>(); 198 | 199 | for (const filename of filenames) { 200 | try { 201 | const content = await this.getFileContentsTail(filename, options); 202 | results.set(filename, content); 203 | } catch (error) { 204 | this.logger.error(`Error reading file ${filename}:`, error); 205 | // Continue processing other files even if one fails 206 | } 207 | } 208 | 209 | return results; 210 | } 211 | 212 | /** 213 | * Check if a file exists and get its basic metadata 214 | */ 215 | async getFileInfo(filename: string): Promise<{ exists: boolean; size?: number; lastmod?: string }> { 216 | try { 217 | const stat = await this.webdavClient.stat(filename); 218 | return { 219 | exists: true, 220 | size: (stat as any).size, 221 | lastmod: (stat as any).lastmod, 222 | }; 223 | } catch { 224 | return { exists: false }; 225 | } 226 | } 227 | } 228 | ``` -------------------------------------------------------------------------------- /docs/best-practices/cartridge_creation.md: -------------------------------------------------------------------------------- ```markdown 1 | # Instructions for Creating a Salesforce B2C Commerce (SFRA) Cartridge 2 | 3 | This document provides instructions to create, configure, and deploy a new custom cartridge for Salesforce B2C Commerce using the Storefront Reference Architecture (SFRA). 4 | 5 | **NOTE**: When doing this, also request best practices for controller creation from this MCP server. Additionally, consult the **Performance and Stability Best Practices** guide from this MCP server to ensure your cartridge follows performance optimization strategies and coding standards. 6 | 7 | ## 1. Core Principles 8 | 9 | **Cartridge:** A cartridge is a self-contained module for code and data. It is the fundamental unit for extending functionality. 10 | 11 | **Cartridge Path:** A colon-separated list of cartridge names that dictates the order of code execution. The path is searched from left to right, and the first resource found is used. 12 | 13 | **Override Mechanism:** To customize functionality, create a new cartridge (e.g., app_custom_mybrand) and place it at the beginning of the cartridge path. This allows your custom files to override the base functionality without modifying the core app_storefront_base cartridge. 14 | 15 | **Example Path:** `app_custom_mybrand:app_storefront_base` 16 | 17 | ## 2. Prerequisites 18 | 19 | - **Git Client:** Installed and configured. 20 | - **Node.js:** Version 18 is recommended for compatibility. 21 | - **SFCC Sandbox:** Access to a sandbox instance, including Business Manager credentials. 22 | - **GitHub Access:** Ability to clone SalesforceCommerceCloud repositories. 23 | 24 | ## 3. Environment Setup 25 | 26 | Create a parent project directory. 27 | 28 | Clone the following repositories as siblings inside the parent directory: 29 | 30 | Note: This step is not necessary unless specifically asked for. You should only clone these if the user requests it, for most 31 | projects, there is only a need for the new cartridge that is being built - but not the entire storefront reference architecture. 32 | 33 | ```bash 34 | # Contains the base cartridge (app_storefront_base) 35 | git clone [email protected]:SalesforceCommerceCloud/storefront-reference-architecture.git 36 | 37 | # Contains build and deployment scripts 38 | git clone [email protected]:SalesforceCommerceCloud/sgmf-scripts.git 39 | ``` 40 | 41 | Install dependencies: 42 | 43 | ```bash 44 | cd storefront-reference-architecture 45 | npm install 46 | ``` 47 | 48 | ## 4. Cartridge File Structure 49 | 50 | A new cartridge should be created using the provided scaffolding tool. The core structure is as follows: 51 | 52 | ``` 53 | package.json 54 | .eslintrc.json 55 | .eslintignore 56 | .stylelintrc.json 57 | .gitignore 58 | README.md 59 | dw.json 60 | webpack.config.js 61 | cartridges/ 62 | └── plugin_my_custom_cartridge/ 63 | └── cartridge/ 64 | ├── client/ 65 | │ └── default/ 66 | │ ├── js/ 67 | │ └── scss/ 68 | ├── controllers/ 69 | ├── models/ 70 | ├── scripts/ 71 | └── templates/ 72 | └── default/ 73 | ``` 74 | 75 | Optional but common directories: 76 | 77 | - `cartridge/forms/default/`: XML form definitions. 78 | - `cartridge/services/`: Definitions for external web service integrations. 79 | - `cartridge/scripts/jobs/`: Scripts for automated tasks scheduled in Business Manager. 80 | - `cartridge/properties/`: Localization string files (.properties). 81 | 82 | ## 5. Creating a New Cartridge 83 | 84 | ### Step 1: Generate the Cartridge Structure 85 | 86 | !IMPORTANT!: Always do this step, don't attempt to create the cartridge structure manually. The MCP server ensures all necessary files and configurations are created correctly. 87 | 88 | **Using MCP Server (Recommended)** 89 | Use the `generate_cartridge_structure` tool from this MCP server to automatically create the cartridge structure: 90 | 91 | ```json 92 | { 93 | "cartridgeName": "plugin_my_custom_cartridge", 94 | "targetPath": "/path/to/your/project", 95 | "fullProjectSetup": true 96 | } 97 | ``` 98 | 99 | This tool will: 100 | - Create all necessary configuration files (package.json, webpack.config.js, etc.) 101 | - Set up the complete cartridge structure with proper organization 102 | - Ensure the cartridge is created exactly where needed in your project structure 103 | - Generate all required directories and files for a fully functional cartridge 104 | 105 | ## 6. "Hello, World" Example 106 | 107 | ### Step 1: Navigate to Your Project 108 | 109 | After creating the cartridge structure, navigate to your project directory: 110 | 111 | ```bash 112 | # If you kept the subdirectory structure: 113 | cd plugin_my_custom_cartridge 114 | 115 | # If you moved files to root level, you're already in the right place 116 | ``` 117 | 118 | ### Step 2: Create the Controller 119 | 120 | **File:** `cartridges/plugin_my_custom_cartridge/cartridge/controllers/Hello.js` 121 | 122 | ```javascript 123 | 'use strict'; 124 | 125 | var server = require('server'); 126 | 127 | // URL: /Hello-Show 128 | server.get('Show', function (req, res, next) { 129 | res.render('hello/helloTemplate', { 130 | message: 'Hello from a custom cartridge!' 131 | }); 132 | next(); 133 | }); 134 | 135 | module.exports = server.exports(); 136 | ``` 137 | 138 | ### Step 3: Create the ISML Template 139 | 140 | **File:** `cartridges/plugin_my_custom_cartridge/cartridge/templates/default/hello/helloTemplate.isml` 141 | 142 | ```html 143 | <iscontent type="text/html" charset="UTF-8" compact="true"/> 144 | <isdecorate template="common/layout/page"> 145 | <isreplace name="main"> 146 | <div class="container"> 147 | <h1>Custom Page</h1> 148 | <p>${pdict.message}</p> 149 | </div> 150 | </isreplace> 151 | </isdecorate> 152 | ``` 153 | 154 | ### Step 4: Update Deployment Configuration 155 | 156 | Ensure your `dw.json` file has the correct sandbox credentials (this file is automatically generated but needs your specific sandbox details). 157 | 158 | ## 7. Deployment and Registration 159 | 160 | ### Step 1: Upload the Cartridge 161 | 162 | From the project root, run: 163 | 164 | ```bash 165 | npm run uploadCartridge plugin_my_custom_cartridge 166 | ``` 167 | 168 | For continuous development, use `npm run watch`. 169 | 170 | ## 8. Naming Conventions & Best Practices 171 | 172 | **Cartridge Naming:** Use standard prefixes. 173 | 174 | - `app_custom_*`: Site-specific customizations. 175 | - `int_*`: Third-party integrations. 176 | - `bm_*`: Business Manager extensions. 177 | - `plugin_*`: Reusable SFRA feature extensions. 178 | 179 | **File Naming:** Controllers use PascalCase.js. Other JS files use camelCase.js. 180 | 181 | **NEVER modify app_storefront_base or other base/plugin cartridges directly.** 182 | 183 | **Extend, Don't Replace:** Use server.append() or server.prepend() to extend controllers. Avoid server.replace(). 184 | 185 | **Logic-less Templates:** Keep business logic out of ISML files. Use models to prepare data. 186 | 187 | **Security:** Protect all state-changing POST requests with CSRF tokens. Properly encode all user-provided output to prevent XSS. 188 | 189 | **Localization:** Use Resource.msg() in templates to fetch text from .properties files. 190 | 191 | ## 9. MCP Integration Workflow 192 | 193 | When using this MCP server for cartridge development, follow this enhanced workflow: 194 | 195 | 1. **Generate Cartridge Structure**: Use the `generate_cartridge_structure` tool to create the initial cartridge with all necessary files and configurations 196 | 2. **Get Best Practices**: Use `get_best_practice_guide` with "sfra_controllers" for controller development patterns 197 | 3. **Search Documentation**: Use `search_sfcc_classes` and `get_sfcc_class_info` for API reference 198 | 4. **Validate Implementation**: Use `search_best_practices` to ensure your code follows security and performance guidelines 199 | 5. **Debug Issues**: Use log analysis tools to troubleshoot deployment or runtime issues 200 | 6. **Handle Deployment Issues**: If new cartridge features (jobs, SCAPI endpoints, hooks) don't appear after upload: 201 | - **Check Code Versions**: Use `get_code_versions` tool to see available versions 202 | - **Activate Version**: Use `activate_code_version` tool to ensure proper registration 203 | - **Alternative**: Manually switch code versions in Business Manager (Administration > Site Development > Code Deployment) 204 | 205 | ### Common Deployment Troubleshooting 206 | 207 | **Issue**: Custom jobs or SCAPI endpoints not visible after cartridge deployment 208 | **Solution**: 209 | 1. Use MCP `get_code_versions` tool to check available code versions 210 | 2. Use MCP `activate_code_version` tool to switch to the uploaded version 211 | 3. Verify registration in logs using log analysis tools 212 | 213 | **Issue**: Hooks not taking effect after deployment 214 | **Solution**: Follow the same code version activation process above 215 | 216 | This integrated approach ensures your cartridge follows all best practices and leverages the full power of the SFCC development ecosystem with direct file generation capabilities. 217 | ``` -------------------------------------------------------------------------------- /docs/TopLevel/Number.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: TopLevel 2 | 3 | # Class Number 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - Number 9 | 10 | ## Description 11 | 12 | A Number object represents any numerical value, whether it is an integer or floating-point number. Generally, you do not need to worry about a Number object because a numerical value automatically becomes a Number object instance when you use a numerical value or assign it to a variable. 13 | 14 | ## Constants 15 | 16 | ### EPSILON 17 | 18 | **Type:** Number 19 | 20 | EPSILON is the Number value for the magnitude of the difference between 1 and the smallest value greater than 1 that is representable as a Number value, which is approximately 2.2204460492503130808472633361816 × 10-16. 21 | 22 | ### MAX_SAFE_INTEGER 23 | 24 | **Type:** Number 25 | 26 | The maximum safe integer in JavaScript. 27 | 28 | ### MAX_VALUE 29 | 30 | **Type:** Number 31 | 32 | The largest representable Number. 33 | 34 | ### MIN_SAFE_INTEGER 35 | 36 | **Type:** Number 37 | 38 | The minimum safe integer in JavaScript. 39 | 40 | ### MIN_VALUE 41 | 42 | **Type:** Number 43 | 44 | The smallest representable Number. 45 | 46 | ### NEGATIVE_INFINITY 47 | 48 | **Type:** Number 49 | 50 | Negative infinite value; returned on overflow; 51 | 52 | ### POSITIVE_INFINITY 53 | 54 | **Type:** Number 55 | 56 | Negative infinite value; returned on overflow; 57 | 58 | ## Properties 59 | 60 | ## Constructor Summary 61 | 62 | Number() Constructs a Number with value 0 63 | 64 | Number(num : Number) Constructs a new Number using the specified Number. 65 | 66 | Number(value : String) Constructs a Number using the specified value. 67 | 68 | ## Method Summary 69 | 70 | ### isFinite 71 | 72 | **Signature:** `static isFinite(value : Object) : boolean` 73 | 74 | Determines whether the passed value is a finite number. 75 | 76 | ### isInteger 77 | 78 | **Signature:** `static isInteger(value : Object) : boolean` 79 | 80 | Determines whether the passed value is an integer number. 81 | 82 | ### isNaN 83 | 84 | **Signature:** `static isNaN(value : Object) : boolean` 85 | 86 | Determines whether the passed value is NaN. 87 | 88 | ### isSafeInteger 89 | 90 | **Signature:** `static isSafeInteger(value : Object) : boolean` 91 | 92 | Determines whether the passed value is a safe integer number. 93 | 94 | ### parseFloat 95 | 96 | **Signature:** `static parseFloat(s : String) : Number` 97 | 98 | Parses a String into an float Number. 99 | 100 | ### parseInt 101 | 102 | **Signature:** `static parseInt(s : String) : Number` 103 | 104 | Parses a String into an integer Number. 105 | 106 | ### parseInt 107 | 108 | **Signature:** `static parseInt(s : String, radix : Number) : Number` 109 | 110 | Parses a String into an integer Number using the specified radix. 111 | 112 | ### toExponential 113 | 114 | **Signature:** `toExponential() : String` 115 | 116 | Converts this Number to a String using exponential notation. 117 | 118 | ### toExponential 119 | 120 | **Signature:** `toExponential(digits : Number) : String` 121 | 122 | Converts this Number to a String using exponential notation with the specified number of digits after the decimal place. 123 | 124 | ### toFixed 125 | 126 | **Signature:** `toFixed() : String` 127 | 128 | Converts a Number to a String that contains a no fractional part. 129 | 130 | ### toFixed 131 | 132 | **Signature:** `toFixed(digits : Number) : String` 133 | 134 | Converts a Number to a String that contains a specified number of digits after the decimal place. 135 | 136 | ### toLocaleString 137 | 138 | **Signature:** `toLocaleString() : String` 139 | 140 | Converts this Number to a String using local number formatting conventions. 141 | 142 | ### toPrecision 143 | 144 | **Signature:** `toPrecision(precision : Number) : String` 145 | 146 | Converts a Number to a String using the specified number of significant digits. 147 | 148 | ### toString 149 | 150 | **Signature:** `toString() : String` 151 | 152 | A String representation of this Number. 153 | 154 | ### toString 155 | 156 | **Signature:** `toString(radix : Number) : String` 157 | 158 | Converts the number into a string using the specified radix (base). 159 | 160 | ## Constructor Detail 161 | 162 | ## Method Detail 163 | 164 | ## Method Details 165 | 166 | ### isFinite 167 | 168 | **Signature:** `static isFinite(value : Object) : boolean` 169 | 170 | **Description:** Determines whether the passed value is a finite number. 171 | 172 | **API Versioned:** 173 | 174 | From version 21.2. 175 | 176 | **Parameters:** 177 | 178 | - `value`: The value to check. 179 | 180 | **Returns:** 181 | 182 | true if the passed value is a finite number, else false. 183 | 184 | --- 185 | 186 | ### isInteger 187 | 188 | **Signature:** `static isInteger(value : Object) : boolean` 189 | 190 | **Description:** Determines whether the passed value is an integer number. 191 | 192 | **API Versioned:** 193 | 194 | From version 21.2. 195 | 196 | **Parameters:** 197 | 198 | - `value`: The value to check. 199 | 200 | **Returns:** 201 | 202 | true if the passed value is a finite integer number, else false. 203 | 204 | --- 205 | 206 | ### isNaN 207 | 208 | **Signature:** `static isNaN(value : Object) : boolean` 209 | 210 | **Description:** Determines whether the passed value is NaN. Unlike the global function, the passed parameter is not converted to number before doing the check. 211 | 212 | **API Versioned:** 213 | 214 | From version 21.2. 215 | 216 | **Parameters:** 217 | 218 | - `value`: The value to check. 219 | 220 | **Returns:** 221 | 222 | true if the passed value is the NaN number value, else false. 223 | 224 | --- 225 | 226 | ### isSafeInteger 227 | 228 | **Signature:** `static isSafeInteger(value : Object) : boolean` 229 | 230 | **Description:** Determines whether the passed value is a safe integer number. 231 | 232 | **API Versioned:** 233 | 234 | From version 21.2. 235 | 236 | **Parameters:** 237 | 238 | - `value`: The value to check. 239 | 240 | **Returns:** 241 | 242 | true if the passed value is a safe integer number, else false. 243 | 244 | --- 245 | 246 | ### parseFloat 247 | 248 | **Signature:** `static parseFloat(s : String) : Number` 249 | 250 | **Description:** Parses a String into an float Number. 251 | 252 | **API Versioned:** 253 | 254 | From version 21.2. 255 | 256 | **Parameters:** 257 | 258 | - `s`: the String to parse. 259 | 260 | **Returns:** 261 | 262 | Returns the float as a Number. 263 | 264 | --- 265 | 266 | ### parseInt 267 | 268 | **Signature:** `static parseInt(s : String) : Number` 269 | 270 | **Description:** Parses a String into an integer Number. This function is a short form for the call to parseInt(String, Number) with automatic determination of the radix. If the string starts with "0x" or "0X" then the radix is 16. In all other cases the radix is 10. 271 | 272 | **API Versioned:** 273 | 274 | From version 21.2. 275 | 276 | **Parameters:** 277 | 278 | - `s`: the String to parse. 279 | 280 | **Returns:** 281 | 282 | Returns the integer as a Number. 283 | 284 | --- 285 | 286 | ### parseInt 287 | 288 | **Signature:** `static parseInt(s : String, radix : Number) : Number` 289 | 290 | **Description:** Parses a String into an integer Number using the specified radix. 291 | 292 | **API Versioned:** 293 | 294 | From version 21.2. 295 | 296 | **Parameters:** 297 | 298 | - `s`: the String to parse. 299 | - `radix`: the radix to use. 300 | 301 | **Returns:** 302 | 303 | Returns the integer as a Number. 304 | 305 | --- 306 | 307 | ### toExponential 308 | 309 | **Signature:** `toExponential() : String` 310 | 311 | **Description:** Converts this Number to a String using exponential notation. 312 | 313 | **Returns:** 314 | 315 | a String using exponential notation. 316 | 317 | --- 318 | 319 | ### toExponential 320 | 321 | **Signature:** `toExponential(digits : Number) : String` 322 | 323 | **Description:** Converts this Number to a String using exponential notation with the specified number of digits after the decimal place. 324 | 325 | **Parameters:** 326 | 327 | - `digits`: the number of digits after the decimal place. 328 | 329 | **Returns:** 330 | 331 | a String using exponential notation with the specified number of digits after the decimal place. 332 | 333 | --- 334 | 335 | ### toFixed 336 | 337 | **Signature:** `toFixed() : String` 338 | 339 | **Description:** Converts a Number to a String that contains a no fractional part. 340 | 341 | **Returns:** 342 | 343 | a String representation of the number 344 | 345 | --- 346 | 347 | ### toFixed 348 | 349 | **Signature:** `toFixed(digits : Number) : String` 350 | 351 | **Description:** Converts a Number to a String that contains a specified number of digits after the decimal place. 352 | 353 | **Parameters:** 354 | 355 | - `digits`: the number of digits after the decimal place. 356 | 357 | **Returns:** 358 | 359 | a String that contains a specified number of digits after the decimal place. 360 | 361 | --- 362 | 363 | ### toLocaleString 364 | 365 | **Signature:** `toLocaleString() : String` 366 | 367 | **Description:** Converts this Number to a String using local number formatting conventions. The current implementation actually only returns the same as toString(). 368 | 369 | **Returns:** 370 | 371 | a String using local number formatting conventions. 372 | 373 | --- 374 | 375 | ### toPrecision 376 | 377 | **Signature:** `toPrecision(precision : Number) : String` 378 | 379 | **Description:** Converts a Number to a String using the specified number of significant digits. Uses exponential or fixed point notation depending on the size of the number and the number of significant digits specified. 380 | 381 | **Parameters:** 382 | 383 | - `precision`: the precision to use when converting the Number to a String. 384 | 385 | **Returns:** 386 | 387 | a String using the specified number of significant digits. 388 | 389 | --- 390 | 391 | ### toString 392 | 393 | **Signature:** `toString() : String` 394 | 395 | **Description:** A String representation of this Number. 396 | 397 | **Returns:** 398 | 399 | a String representation of this Number. 400 | 401 | --- 402 | 403 | ### toString 404 | 405 | **Signature:** `toString(radix : Number) : String` 406 | 407 | **Description:** Converts the number into a string using the specified radix (base). 408 | 409 | **Parameters:** 410 | 411 | - `radix`: the radix to use. 412 | 413 | **Returns:** 414 | 415 | a String representation of this Number. 416 | 417 | --- ``` -------------------------------------------------------------------------------- /docs/dw_svc/HTTPService.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.svc 2 | 3 | # Class HTTPService 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.svc.Service 9 | - dw.svc.HTTPService 10 | 11 | ## Description 12 | 13 | Represents an HTTP Service. The HTTP Service will use the return value of the createRequest callback as the request body (if supported by the HTTP method). If this is an array of non-null HTTPRequestPart objects, then a multi-part request will be formed. Otherwise the object is converted to a String and used. See also XML.toXMLString() and JSON.stringify(Object), which must be explicitly called if needed. 14 | 15 | ## Properties 16 | 17 | ### authentication 18 | 19 | **Type:** String 20 | 21 | The authentication type. 22 | 23 | ### cachingTTL 24 | 25 | **Type:** Number 26 | 27 | The caching time to live value. 28 | 29 | ### client 30 | 31 | **Type:** HTTPClient (Read Only) 32 | 33 | The underlying HTTP client object. 34 | 35 | ### encoding 36 | 37 | **Type:** String 38 | 39 | The request body encoding to declare. 40 | 41 | ### hostNameVerification 42 | 43 | **Type:** boolean 44 | 45 | Determines whether host name verification is enabled. 46 | 47 | ### identity 48 | 49 | **Type:** KeyRef 50 | 51 | Gets the identity used for mutual TLS (mTLS). 52 | 53 | ### outFile 54 | 55 | **Type:** File 56 | 57 | The output file, or null if there is none. 58 | 59 | ### requestMethod 60 | 61 | **Type:** String 62 | 63 | The request method. 64 | 65 | ## Constructor Summary 66 | 67 | ## Method Summary 68 | 69 | ### addHeader 70 | 71 | **Signature:** `addHeader(name : String, val : String) : HTTPService` 72 | 73 | Adds an HTTP Header. 74 | 75 | ### addParam 76 | 77 | **Signature:** `addParam(name : String, val : String) : HTTPService` 78 | 79 | Adds a query parameter that will be appended to the URL. 80 | 81 | ### getAuthentication 82 | 83 | **Signature:** `getAuthentication() : String` 84 | 85 | Returns the authentication type. 86 | 87 | ### getCachingTTL 88 | 89 | **Signature:** `getCachingTTL() : Number` 90 | 91 | Returns the caching time to live value. 92 | 93 | ### getClient 94 | 95 | **Signature:** `getClient() : HTTPClient` 96 | 97 | Returns the underlying HTTP client object. 98 | 99 | ### getEncoding 100 | 101 | **Signature:** `getEncoding() : String` 102 | 103 | Returns the request body encoding to declare. 104 | 105 | ### getHostNameVerification 106 | 107 | **Signature:** `getHostNameVerification() : boolean` 108 | 109 | Determines whether host name verification is enabled. 110 | 111 | ### getIdentity 112 | 113 | **Signature:** `getIdentity() : KeyRef` 114 | 115 | Gets the identity used for mutual TLS (mTLS). 116 | 117 | ### getOutFile 118 | 119 | **Signature:** `getOutFile() : File` 120 | 121 | Returns the output file, or null if there is none. 122 | 123 | ### getRequestMethod 124 | 125 | **Signature:** `getRequestMethod() : String` 126 | 127 | Returns the request method. 128 | 129 | ### setAuthentication 130 | 131 | **Signature:** `setAuthentication(authentication : String) : HTTPService` 132 | 133 | Sets the type of authentication. 134 | 135 | ### setCachingTTL 136 | 137 | **Signature:** `setCachingTTL(ttl : Number) : HTTPService` 138 | 139 | Enables caching for GET requests. 140 | 141 | ### setEncoding 142 | 143 | **Signature:** `setEncoding(encoding : String) : HTTPService` 144 | 145 | Sets the encoding of the request body (if any). 146 | 147 | ### setHostNameVerification 148 | 149 | **Signature:** `setHostNameVerification(enable : boolean) : HTTPService` 150 | 151 | Sets whether certificate host name verification is enabled. 152 | 153 | ### setIdentity 154 | 155 | **Signature:** `setIdentity(keyRef : KeyRef) : HTTPService` 156 | 157 | Sets the identity (private key) to use when mutual TLS (mTLS) is configured. 158 | 159 | ### setOutFile 160 | 161 | **Signature:** `setOutFile(outFile : File) : HTTPService` 162 | 163 | Sets the output file in which to write the HTTP response body. 164 | 165 | ### setRequestMethod 166 | 167 | **Signature:** `setRequestMethod(requestMethod : String) : HTTPService` 168 | 169 | Sets the HTTP request method. 170 | 171 | ## Method Detail 172 | 173 | ## Method Details 174 | 175 | ### addHeader 176 | 177 | **Signature:** `addHeader(name : String, val : String) : HTTPService` 178 | 179 | **Description:** Adds an HTTP Header. 180 | 181 | **Parameters:** 182 | 183 | - `name`: Header name. 184 | - `val`: Header value. 185 | 186 | **Returns:** 187 | 188 | this HTTP Service. 189 | 190 | --- 191 | 192 | ### addParam 193 | 194 | **Signature:** `addParam(name : String, val : String) : HTTPService` 195 | 196 | **Description:** Adds a query parameter that will be appended to the URL. 197 | 198 | **Parameters:** 199 | 200 | - `name`: Parameter name. 201 | - `val`: Parameter value. 202 | 203 | **Returns:** 204 | 205 | this HTTP Service. 206 | 207 | --- 208 | 209 | ### getAuthentication 210 | 211 | **Signature:** `getAuthentication() : String` 212 | 213 | **Description:** Returns the authentication type. 214 | 215 | **Returns:** 216 | 217 | Authentication type. 218 | 219 | --- 220 | 221 | ### getCachingTTL 222 | 223 | **Signature:** `getCachingTTL() : Number` 224 | 225 | **Description:** Returns the caching time to live value. 226 | 227 | **Returns:** 228 | 229 | The caching time to live value in seconds. 230 | 231 | **See Also:** 232 | 233 | setCachingTTL(Number) 234 | 235 | --- 236 | 237 | ### getClient 238 | 239 | **Signature:** `getClient() : HTTPClient` 240 | 241 | **Description:** Returns the underlying HTTP client object. 242 | 243 | **Returns:** 244 | 245 | HTTP client object. 246 | 247 | --- 248 | 249 | ### getEncoding 250 | 251 | **Signature:** `getEncoding() : String` 252 | 253 | **Description:** Returns the request body encoding to declare. 254 | 255 | **Returns:** 256 | 257 | Request encoding. 258 | 259 | --- 260 | 261 | ### getHostNameVerification 262 | 263 | **Signature:** `getHostNameVerification() : boolean` 264 | 265 | **Description:** Determines whether host name verification is enabled. 266 | 267 | **Returns:** 268 | 269 | true if verification is enabled, false otherwise 270 | 271 | --- 272 | 273 | ### getIdentity 274 | 275 | **Signature:** `getIdentity() : KeyRef` 276 | 277 | **Description:** Gets the identity used for mutual TLS (mTLS). 278 | 279 | **Returns:** 280 | 281 | Reference to the private key, or null if not configured 282 | 283 | --- 284 | 285 | ### getOutFile 286 | 287 | **Signature:** `getOutFile() : File` 288 | 289 | **Description:** Returns the output file, or null if there is none. 290 | 291 | **Returns:** 292 | 293 | Output file or null. 294 | 295 | --- 296 | 297 | ### getRequestMethod 298 | 299 | **Signature:** `getRequestMethod() : String` 300 | 301 | **Description:** Returns the request method. 302 | 303 | **Returns:** 304 | 305 | HTTP Request method. 306 | 307 | --- 308 | 309 | ### setAuthentication 310 | 311 | **Signature:** `setAuthentication(authentication : String) : HTTPService` 312 | 313 | **Description:** Sets the type of authentication. Valid values include "BASIC" and "NONE". The default value is BASIC. 314 | 315 | **Parameters:** 316 | 317 | - `authentication`: Type of authentication. 318 | 319 | **Returns:** 320 | 321 | this HTTP Service. 322 | 323 | --- 324 | 325 | ### setCachingTTL 326 | 327 | **Signature:** `setCachingTTL(ttl : Number) : HTTPService` 328 | 329 | **Description:** Enables caching for GET requests. This only caches status codes 2xx with a content length and size of less than 50k that are not immediately written to file. The URL and the user name are used as cache keys. The total size of cacheable content and the number of cached items is limited and automatically managed by the system. Cache control information sent by the remote server is ignored. Caching HTTP responses should be done very carefully. It is important to ensure that the response really depends only on the URL and doesn't contain any remote state information or time information which is independent of the URL. It is also important to verify that the application sends exactly the same URL multiple times. 330 | 331 | **Parameters:** 332 | 333 | - `ttl`: The time to live for the cached content in seconds. A value of 0 disables caching. 334 | 335 | **See Also:** 336 | 337 | HTTPClient.enableCaching(Number) 338 | 339 | --- 340 | 341 | ### setEncoding 342 | 343 | **Signature:** `setEncoding(encoding : String) : HTTPService` 344 | 345 | **Description:** Sets the encoding of the request body (if any). The default value is UTF-8. 346 | 347 | **Parameters:** 348 | 349 | - `encoding`: Encoding of the request body. 350 | 351 | **Returns:** 352 | 353 | this HTTP Service. 354 | 355 | --- 356 | 357 | ### setHostNameVerification 358 | 359 | **Signature:** `setHostNameVerification(enable : boolean) : HTTPService` 360 | 361 | **Description:** Sets whether certificate host name verification is enabled. The default value is true. Set it to false to disable host name verification. 362 | 363 | **Parameters:** 364 | 365 | - `enable`: true to enable host name verification or false to disable it. 366 | 367 | **Returns:** 368 | 369 | this HTTP Service. 370 | 371 | --- 372 | 373 | ### setIdentity 374 | 375 | **Signature:** `setIdentity(keyRef : KeyRef) : HTTPService` 376 | 377 | **Description:** Sets the identity (private key) to use when mutual TLS (mTLS) is configured. If this is not set and mTLS is used then the private key will be chosen from the key store based on the host name. If this is set to a reference named "__NONE__" then no private key will be used even if one is requested by the remote server. 378 | 379 | **Parameters:** 380 | 381 | - `keyRef`: Reference to the private key 382 | 383 | --- 384 | 385 | ### setOutFile 386 | 387 | **Signature:** `setOutFile(outFile : File) : HTTPService` 388 | 389 | **Description:** Sets the output file in which to write the HTTP response body. The default behavior is to not write a file. 390 | 391 | **Parameters:** 392 | 393 | - `outFile`: Output file, or null to disable. 394 | 395 | **Returns:** 396 | 397 | this HTTP Service. 398 | 399 | --- 400 | 401 | ### setRequestMethod 402 | 403 | **Signature:** `setRequestMethod(requestMethod : String) : HTTPService` 404 | 405 | **Description:** Sets the HTTP request method. Valid values include GET, PUT, POST, and DELETE. The default value is POST. 406 | 407 | **Parameters:** 408 | 409 | - `requestMethod`: HTTP request method. 410 | 411 | **Returns:** 412 | 413 | this HTTP Service. 414 | 415 | --- ``` -------------------------------------------------------------------------------- /docs/dw_order/Return.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class Return 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.Extensible 9 | - dw.order.AbstractItemCtnr 10 | - dw.order.Return 11 | 12 | ## Description 13 | 14 | The Return represents a physical customer return, and contains 1..n ReturnItems. The Return is associated with one ReturnCase, and each ReturnItem is associated with one ReturnCaseItem and (via the ReturnCaseItem) a single OrderItem usually representing an Order ProductLineItem. The ReturnItem records the quantity returned. The Return can have one of these status values: NEW - the return is new, i.e. needs to undergo a check before it can be marked as COMPLETED COMPLETED - the return is complete, this is a precondition for refunding the customer for a return. 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 | ### ORDERBY_ITEMID 19 | 20 | **Type:** Object 21 | 22 | Sorting by item id. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 23 | 24 | ### ORDERBY_ITEMPOSITION 25 | 26 | **Type:** Object 27 | 28 | Sorting by the position of the related oder item. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 29 | 30 | ### ORDERBY_UNSORTED 31 | 32 | **Type:** Object 33 | 34 | Unsorted , as it is. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 35 | 36 | ### QUALIFIER_PRODUCTITEMS 37 | 38 | **Type:** Object 39 | 40 | Selects the product items. Use with method getItems() as an argument to method FilteringCollection.select(Object). 41 | 42 | ### QUALIFIER_SERVICEITEMS 43 | 44 | **Type:** Object 45 | 46 | Selects for the service items. Use with method getItems() as an argument to method FilteringCollection.select(Object). 47 | 48 | ### STATUS_COMPLETED 49 | 50 | **Type:** String = "COMPLETED" 51 | 52 | Constant for Return Status COMPLETED 53 | 54 | ### STATUS_NEW 55 | 56 | **Type:** String = "NEW" 57 | 58 | Constant for Return Status NEW 59 | 60 | ## Properties 61 | 62 | ### invoice 63 | 64 | **Type:** Invoice (Read Only) 65 | 66 | Returns null or the previously created Invoice. 67 | 68 | ### invoiceNumber 69 | 70 | **Type:** String (Read Only) 71 | 72 | Returns null or the invoice-number. 73 | 74 | ### items 75 | 76 | **Type:** FilteringCollection (Read Only) 77 | 78 | The ReturnItems contained in the Return, created with method 79 | createItem(String). 80 | 81 | 82 | This FilteringCollection can be sorted / filtered using: 83 | 84 | FilteringCollection.sort(Object) with ORDERBY_ITEMID 85 | FilteringCollection.sort(Object) with 86 | ORDERBY_ITEMPOSITION 87 | FilteringCollection.sort(Object) with ORDERBY_UNSORTED 88 | FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS 89 | FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS 90 | 91 | ### note 92 | 93 | **Type:** String 94 | 95 | A note for the return. 96 | 97 | ### returnCase 98 | 99 | **Type:** ReturnCase (Read Only) 100 | 101 | The ReturnCase with which this Return is associated. The ReturnCase 102 | may represent an RMA (return merchandise authorization). 103 | 104 | ### returnNumber 105 | 106 | **Type:** String (Read Only) 107 | 108 | The return number identifying this return. 109 | 110 | ### status 111 | 112 | **Type:** EnumValue 113 | 114 | Gets the return status. 115 | 116 | Possible values are STATUS_NEW, STATUS_COMPLETED. 117 | 118 | ## Constructor Summary 119 | 120 | ## Method Summary 121 | 122 | ### createInvoice 123 | 124 | **Signature:** `createInvoice() : Invoice` 125 | 126 | Creates a new Invoice based on this Return. 127 | 128 | ### createInvoice 129 | 130 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice` 131 | 132 | Creates a new Invoice based on this Return. 133 | 134 | ### createItem 135 | 136 | **Signature:** `createItem(returnCaseItemID : String) : ReturnItem` 137 | 138 | Create a ReturnItem based on a ReturnCaseItem. 139 | 140 | ### getInvoice 141 | 142 | **Signature:** `getInvoice() : Invoice` 143 | 144 | Returns null or the previously created Invoice. 145 | 146 | ### getInvoiceNumber 147 | 148 | **Signature:** `getInvoiceNumber() : String` 149 | 150 | Returns null or the invoice-number. 151 | 152 | ### getItems 153 | 154 | **Signature:** `getItems() : FilteringCollection` 155 | 156 | Returns the ReturnItems contained in the Return, created with method createItem(String). 157 | 158 | ### getNote 159 | 160 | **Signature:** `getNote() : String` 161 | 162 | A note for the return. 163 | 164 | ### getReturnCase 165 | 166 | **Signature:** `getReturnCase() : ReturnCase` 167 | 168 | Returns the ReturnCase with which this Return is associated. 169 | 170 | ### getReturnNumber 171 | 172 | **Signature:** `getReturnNumber() : String` 173 | 174 | The return number identifying this return. 175 | 176 | ### getStatus 177 | 178 | **Signature:** `getStatus() : EnumValue` 179 | 180 | Gets the return status. 181 | 182 | ### setNote 183 | 184 | **Signature:** `setNote(note : String) : void` 185 | 186 | Sets a note for the return. 187 | 188 | ### setStatus 189 | 190 | **Signature:** `setStatus(statusName : String) : void` 191 | 192 | Sets the return status. 193 | 194 | ## Method Detail 195 | 196 | ## Method Details 197 | 198 | ### createInvoice 199 | 200 | **Signature:** `createInvoice() : Invoice` 201 | 202 | **Description:** Creates a new Invoice based on this Return. The return-number will be used as the invoice-number. The Invoice can then be accessed using getInvoice() or its number using getInvoiceNumber(). The method must not be called more than once for a Return, nor may 2 Invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and will be passed to the refund payment-hook in a separate database transaction for processing. 203 | 204 | **Returns:** 205 | 206 | new invoice 207 | 208 | --- 209 | 210 | ### createInvoice 211 | 212 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice` 213 | 214 | **Description:** Creates a new Invoice based on this Return. The invoice-number must be specified as an argument. The Invoice can then be accessed using getInvoice() or its number using getInvoiceNumber(). The method must not be called more than once for a Return, nor may 2 Invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and will be passed to the refund payment-hook in a separate database transaction for processing. 215 | 216 | **Parameters:** 217 | 218 | - `invoiceNumber`: the invoice-number to use 219 | 220 | **Returns:** 221 | 222 | the new invoice 223 | 224 | --- 225 | 226 | ### createItem 227 | 228 | **Signature:** `createItem(returnCaseItemID : String) : ReturnItem` 229 | 230 | **Description:** Create a ReturnItem based on a ReturnCaseItem. 231 | 232 | **Parameters:** 233 | 234 | - `returnCaseItemID`: the id of the return case item 235 | 236 | **Returns:** 237 | 238 | the created return item 239 | 240 | --- 241 | 242 | ### getInvoice 243 | 244 | **Signature:** `getInvoice() : Invoice` 245 | 246 | **Description:** Returns null or the previously created Invoice. 247 | 248 | **Returns:** 249 | 250 | null or the previously created invoice. 251 | 252 | **See Also:** 253 | 254 | createInvoice(String) 255 | 256 | --- 257 | 258 | ### getInvoiceNumber 259 | 260 | **Signature:** `getInvoiceNumber() : String` 261 | 262 | **Description:** Returns null or the invoice-number. 263 | 264 | **Returns:** 265 | 266 | null or the previously created invoice. 267 | 268 | **See Also:** 269 | 270 | createInvoice(String) 271 | 272 | --- 273 | 274 | ### getItems 275 | 276 | **Signature:** `getItems() : FilteringCollection` 277 | 278 | **Description:** Returns the ReturnItems contained in the Return, created with method createItem(String). This FilteringCollection can be sorted / filtered using: FilteringCollection.sort(Object) with ORDERBY_ITEMID FilteringCollection.sort(Object) with ORDERBY_ITEMPOSITION FilteringCollection.sort(Object) with ORDERBY_UNSORTED FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS 279 | 280 | **Returns:** 281 | 282 | the return items 283 | 284 | **See Also:** 285 | 286 | ReturnItem 287 | 288 | --- 289 | 290 | ### getNote 291 | 292 | **Signature:** `getNote() : String` 293 | 294 | **Description:** A note for the return. 295 | 296 | **Returns:** 297 | 298 | the note or null 299 | 300 | --- 301 | 302 | ### getReturnCase 303 | 304 | **Signature:** `getReturnCase() : ReturnCase` 305 | 306 | **Description:** Returns the ReturnCase with which this Return is associated. The ReturnCase may represent an RMA (return merchandise authorization). 307 | 308 | **Returns:** 309 | 310 | the return case 311 | 312 | --- 313 | 314 | ### getReturnNumber 315 | 316 | **Signature:** `getReturnNumber() : String` 317 | 318 | **Description:** The return number identifying this return. 319 | 320 | **Returns:** 321 | 322 | the return number 323 | 324 | --- 325 | 326 | ### getStatus 327 | 328 | **Signature:** `getStatus() : EnumValue` 329 | 330 | **Description:** Gets the return status. Possible values are STATUS_NEW, STATUS_COMPLETED. 331 | 332 | **Returns:** 333 | 334 | the status 335 | 336 | --- 337 | 338 | ### setNote 339 | 340 | **Signature:** `setNote(note : String) : void` 341 | 342 | **Description:** Sets a note for the return. 343 | 344 | **Parameters:** 345 | 346 | - `note`: the note 347 | 348 | --- 349 | 350 | ### setStatus 351 | 352 | **Signature:** `setStatus(statusName : String) : void` 353 | 354 | **Description:** Sets the return status. Possible values are STATUS_NEW, STATUS_COMPLETED When set to status COMPLETED, only the the custom attributes of the return itself and its return items can be changed. 355 | 356 | **Parameters:** 357 | 358 | - `statusName`: the status 359 | 360 | --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/mock-data/ocapi/custom-object-attributes-versionhistory.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "_v": "23.2", 3 | "_type": "object_attribute_definition_search_result", 4 | "count": 8, 5 | "hits": [ 6 | { 7 | "_type": "object_attribute_definition", 8 | "_resource_state": "896b31874723fb22d8e7dca243b9b1c5c2de3a2a73645562d9af221a91318612", 9 | "creation_date": "2024-02-26T19:35:57.000Z", 10 | "effective_id": "c_ID", 11 | "externally_defined": false, 12 | "externally_managed": false, 13 | "id": "ID", 14 | "key": true, 15 | "last_modified": "2024-02-26T19:35:57.000Z", 16 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/ID", 17 | "localizable": false, 18 | "mandatory": true, 19 | "min_length": 0, 20 | "multi_value_type": false, 21 | "order_required": false, 22 | "queryable": true, 23 | "read_only": false, 24 | "requires_encoding": false, 25 | "searchable": false, 26 | "set_value_type": false, 27 | "site_specific": false, 28 | "system": false, 29 | "value_type": "string", 30 | "visible": false 31 | }, 32 | { 33 | "_type": "object_attribute_definition", 34 | "_resource_state": "69bff66e9e1774188891baa67e28861a73c716636b7a84ae2582c7b4b649c0bd", 35 | "creation_date": "2024-02-26T19:35:57.000Z", 36 | "display_name": { 37 | "default": "UUID" 38 | }, 39 | "effective_id": "UUID", 40 | "externally_defined": false, 41 | "externally_managed": false, 42 | "field_length": 28, 43 | "id": "UUID", 44 | "key": false, 45 | "last_modified": "2024-02-26T19:35:57.000Z", 46 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/UUID", 47 | "localizable": false, 48 | "mandatory": true, 49 | "min_length": 0, 50 | "multi_value_type": false, 51 | "order_required": false, 52 | "queryable": true, 53 | "read_only": true, 54 | "requires_encoding": false, 55 | "searchable": false, 56 | "set_value_type": false, 57 | "site_specific": false, 58 | "system": true, 59 | "value_type": "string", 60 | "visible": false 61 | }, 62 | { 63 | "_type": "object_attribute_definition", 64 | "_resource_state": "7606845698773be81fe494069711f52bfb6092e0909e95948c944611fb259572", 65 | "creation_date": "2024-02-26T19:35:57.000Z", 66 | "description": { 67 | "default": "The ID of the component the history is of." 68 | }, 69 | "display_name": { 70 | "default": "Component ID" 71 | }, 72 | "effective_id": "c_componentId", 73 | "externally_defined": false, 74 | "externally_managed": false, 75 | "id": "componentId", 76 | "key": false, 77 | "last_modified": "2024-02-26T19:35:57.000Z", 78 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/componentId", 79 | "localizable": false, 80 | "mandatory": false, 81 | "min_length": 0, 82 | "multi_value_type": false, 83 | "order_required": false, 84 | "queryable": true, 85 | "read_only": false, 86 | "requires_encoding": false, 87 | "searchable": false, 88 | "set_value_type": false, 89 | "site_specific": false, 90 | "system": false, 91 | "value_type": "string", 92 | "visible": false 93 | }, 94 | { 95 | "_type": "object_attribute_definition", 96 | "_resource_state": "66393d94b5177d0c307d20e948fd3e18687767d72bdcb9913ecde08a312c6275", 97 | "creation_date": "2024-02-26T19:35:57.000Z", 98 | "display_name": { 99 | "default": "Creation Date" 100 | }, 101 | "effective_id": "creationDate", 102 | "externally_defined": false, 103 | "externally_managed": false, 104 | "id": "creationDate", 105 | "key": false, 106 | "last_modified": "2024-02-26T19:35:57.000Z", 107 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/creationDate", 108 | "localizable": false, 109 | "mandatory": true, 110 | "min_length": 0, 111 | "multi_value_type": false, 112 | "order_required": false, 113 | "queryable": true, 114 | "read_only": true, 115 | "requires_encoding": false, 116 | "searchable": false, 117 | "set_value_type": false, 118 | "site_specific": false, 119 | "system": true, 120 | "value_type": "datetime", 121 | "visible": false 122 | }, 123 | { 124 | "_type": "object_attribute_definition", 125 | "_resource_state": "8476a3b0562d6aa0900126bab96ccc90e27d0508d604ed432d6757515594a098", 126 | "creation_date": "2024-02-26T19:35:57.000Z", 127 | "display_name": { 128 | "default": "Last Modified" 129 | }, 130 | "effective_id": "lastModified", 131 | "externally_defined": false, 132 | "externally_managed": false, 133 | "id": "lastModified", 134 | "key": false, 135 | "last_modified": "2024-02-26T19:35:57.000Z", 136 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/lastModified", 137 | "localizable": false, 138 | "mandatory": true, 139 | "min_length": 0, 140 | "multi_value_type": false, 141 | "order_required": false, 142 | "queryable": true, 143 | "read_only": true, 144 | "requires_encoding": false, 145 | "searchable": false, 146 | "set_value_type": false, 147 | "site_specific": false, 148 | "system": true, 149 | "value_type": "datetime", 150 | "visible": false 151 | }, 152 | { 153 | "_type": "object_attribute_definition", 154 | "_resource_state": "3081b438888efbc03c8bbcddfc033b9097ff6644a59023c047ab88ad6d46a1c3", 155 | "creation_date": "2024-02-26T19:35:57.000Z", 156 | "description": { 157 | "default": "The locale of the value." 158 | }, 159 | "display_name": { 160 | "default": "Locale" 161 | }, 162 | "effective_id": "c_locale", 163 | "externally_defined": false, 164 | "externally_managed": false, 165 | "id": "locale", 166 | "key": false, 167 | "last_modified": "2024-02-26T19:35:57.000Z", 168 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/locale", 169 | "localizable": false, 170 | "mandatory": false, 171 | "min_length": 0, 172 | "multi_value_type": false, 173 | "order_required": false, 174 | "queryable": true, 175 | "read_only": false, 176 | "requires_encoding": false, 177 | "searchable": false, 178 | "set_value_type": false, 179 | "site_specific": false, 180 | "system": false, 181 | "value_type": "string", 182 | "visible": false 183 | }, 184 | { 185 | "_type": "object_attribute_definition", 186 | "_resource_state": "383d2b5957098f09e256dfc26926790784b382c1d87a14426e1ef7a7e81271f4", 187 | "creation_date": "2024-02-26T19:35:57.000Z", 188 | "description": { 189 | "default": "The user who has made the modification" 190 | }, 191 | "display_name": { 192 | "default": "User" 193 | }, 194 | "effective_id": "c_user", 195 | "externally_defined": false, 196 | "externally_managed": false, 197 | "id": "user", 198 | "key": false, 199 | "last_modified": "2024-02-26T19:35:57.000Z", 200 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/user", 201 | "localizable": false, 202 | "mandatory": false, 203 | "min_length": 0, 204 | "multi_value_type": false, 205 | "order_required": false, 206 | "queryable": true, 207 | "read_only": false, 208 | "requires_encoding": false, 209 | "searchable": false, 210 | "set_value_type": false, 211 | "site_specific": false, 212 | "system": false, 213 | "value_type": "string", 214 | "visible": false 215 | }, 216 | { 217 | "_type": "object_attribute_definition", 218 | "_resource_state": "ee9469bc639a2cb37bb6d2419f8123397853f0ff40e69d211b665088ae322957", 219 | "creation_date": "2024-02-26T19:35:57.000Z", 220 | "description": { 221 | "default": "The value" 222 | }, 223 | "display_name": { 224 | "default": "Value" 225 | }, 226 | "effective_id": "c_value", 227 | "externally_defined": false, 228 | "externally_managed": false, 229 | "id": "value", 230 | "key": false, 231 | "last_modified": "2024-02-26T19:35:57.000Z", 232 | "link": "https://localhost:3000/s/-/dw/data/v23_2/custom_object_definitions/VersionHistory/attribute_definitions/value", 233 | "localizable": false, 234 | "mandatory": false, 235 | "min_length": 0, 236 | "multi_value_type": false, 237 | "order_required": false, 238 | "queryable": true, 239 | "read_only": false, 240 | "requires_encoding": false, 241 | "searchable": false, 242 | "set_value_type": false, 243 | "site_specific": false, 244 | "system": false, 245 | "value_type": "string", 246 | "visible": false 247 | } 248 | ], 249 | "query": { 250 | "match_all_query": { 251 | "_type": "match_all_query" 252 | } 253 | }, 254 | "start": 0, 255 | "total": 8 256 | } ``` -------------------------------------------------------------------------------- /tests/configuration-factory.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ConfigurationFactory } from '../src/config/configuration-factory'; 2 | import { SFCCConfig } from '../src/types/types'; 3 | import { writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs'; 4 | import { join } from 'path'; 5 | import { tmpdir } from 'os'; 6 | 7 | describe('ConfigurationFactory', () => { 8 | const testDir = join(tmpdir(), 'sfcc-config-factory-tests'); 9 | 10 | beforeAll(() => { 11 | if (!existsSync(testDir)) { 12 | mkdirSync(testDir, { recursive: true }); 13 | } 14 | }); 15 | 16 | afterEach(() => { 17 | // Clean up test files 18 | try { 19 | const testFiles = ['valid-dw.json', 'invalid-dw.json', 'missing-dw.json']; 20 | testFiles.forEach(file => { 21 | const filePath = join(testDir, file); 22 | if (existsSync(filePath)) { 23 | unlinkSync(filePath); 24 | } 25 | }); 26 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 27 | } catch (error) { 28 | // Ignore cleanup errors 29 | } 30 | }); 31 | 32 | describe('create', () => { 33 | it('should create configuration from dw.json file', () => { 34 | const dwJsonContent = { 35 | hostname: 'test-instance.demandware.net', 36 | username: 'testuser', 37 | password: 'testpass', 38 | 'client-id': 'test-client-id', 39 | 'client-secret': 'test-client-secret', 40 | }; 41 | 42 | const testFile = join(testDir, 'valid-dw.json'); 43 | writeFileSync(testFile, JSON.stringify(dwJsonContent, null, 2)); 44 | 45 | const config = ConfigurationFactory.create({ dwJsonPath: testFile }); 46 | 47 | expect(config.hostname).toBe('test-instance.demandware.net'); 48 | expect(config.username).toBe('testuser'); 49 | expect(config.password).toBe('testpass'); 50 | expect(config.clientId).toBe('test-client-id'); 51 | expect(config.clientSecret).toBe('test-client-secret'); 52 | }); 53 | 54 | it('should throw an error if dw.json file is invalid', () => { 55 | const testFile = join(testDir, 'invalid-dw.json'); 56 | writeFileSync(testFile, '{ invalid json }'); 57 | 58 | expect(() => { 59 | ConfigurationFactory.create({ dwJsonPath: testFile }); 60 | }).toThrow(/Invalid JSON in configuration file:/); 61 | }); 62 | 63 | it('should throw an error if dw.json file is missing', () => { 64 | const testFile = join(testDir, 'missing-dw.json'); 65 | 66 | expect(() => { 67 | ConfigurationFactory.create({ dwJsonPath: testFile }); 68 | }).toThrow(/dw\.json file not found at:/); 69 | }); 70 | 71 | it('should create configuration from provided options', () => { 72 | const config = ConfigurationFactory.create({ 73 | hostname: 'test-instance.demandware.net', 74 | username: 'testuser', 75 | password: 'testpass', 76 | clientId: 'test-client-id', 77 | clientSecret: 'test-client-secret', 78 | siteId: 'test-site', 79 | }); 80 | 81 | expect(config.hostname).toBe('test-instance.demandware.net'); 82 | expect(config.username).toBe('testuser'); 83 | expect(config.password).toBe('testpass'); 84 | expect(config.clientId).toBe('test-client-id'); 85 | expect(config.clientSecret).toBe('test-client-secret'); 86 | expect(config.siteId).toBe('test-site'); 87 | }); 88 | 89 | it('should allow configuration without credentials for local mode', () => { 90 | const config = ConfigurationFactory.create({}); 91 | expect(config.hostname).toBe(''); 92 | expect(config.username).toBeUndefined(); 93 | expect(config.password).toBeUndefined(); 94 | expect(config.clientId).toBeUndefined(); 95 | expect(config.clientSecret).toBeUndefined(); 96 | }); 97 | 98 | it('should validate configuration and throw error for hostname without credentials', () => { 99 | expect(() => { 100 | ConfigurationFactory.create({ hostname: 'test-instance.demandware.net' }); 101 | }).toThrow('When hostname is provided, either username/password or OAuth credentials (clientId/clientSecret) must be provided'); 102 | }); 103 | 104 | it('should validate configuration and throw error for invalid hostname format', () => { 105 | expect(() => { 106 | ConfigurationFactory.create({ 107 | hostname: 'invalid@hostname!', 108 | username: 'testuser', 109 | password: 'testpass', 110 | }); 111 | }).toThrow('Invalid hostname format in configuration'); 112 | }); 113 | 114 | it('should validate configuration and allow hostname with basic auth', () => { 115 | const config = ConfigurationFactory.create({ 116 | hostname: 'test-instance.demandware.net', 117 | username: 'testuser', 118 | password: 'testpass', 119 | }); 120 | expect(config.hostname).toBe('test-instance.demandware.net'); 121 | expect(config.username).toBe('testuser'); 122 | expect(config.password).toBe('testpass'); 123 | }); 124 | 125 | it('should validate configuration and allow hostname with OAuth', () => { 126 | const config = ConfigurationFactory.create({ 127 | hostname: 'test-instance.demandware.net', 128 | clientId: 'test-client-id', 129 | clientSecret: 'test-client-secret', 130 | }); 131 | expect(config.hostname).toBe('test-instance.demandware.net'); 132 | expect(config.clientId).toBe('test-client-id'); 133 | expect(config.clientSecret).toBe('test-client-secret'); 134 | }); 135 | 136 | it('should validate configuration and allow localhost with port', () => { 137 | const config = ConfigurationFactory.create({ 138 | hostname: 'localhost:3000', 139 | username: 'testuser', 140 | password: 'testpass', 141 | }); 142 | expect(config.hostname).toBe('localhost:3000'); 143 | expect(config.username).toBe('testuser'); 144 | expect(config.password).toBe('testpass'); 145 | }); 146 | 147 | it('should validate configuration and allow hostname with custom port', () => { 148 | const config = ConfigurationFactory.create({ 149 | hostname: 'test-instance.demandware.net:8080', 150 | clientId: 'test-client-id', 151 | clientSecret: 'test-client-secret', 152 | }); 153 | expect(config.hostname).toBe('test-instance.demandware.net:8080'); 154 | expect(config.clientId).toBe('test-client-id'); 155 | expect(config.clientSecret).toBe('test-client-secret'); 156 | }); 157 | }); 158 | 159 | describe('getCapabilities', () => { 160 | it('should return correct capabilities for basic auth', () => { 161 | const config: SFCCConfig = { 162 | hostname: 'test-instance.demandware.net', 163 | username: 'testuser', 164 | password: 'testpass', 165 | }; 166 | 167 | const capabilities = ConfigurationFactory.getCapabilities(config); 168 | 169 | expect(capabilities.canAccessLogs).toBe(true); 170 | expect(capabilities.canAccessOCAPI).toBe(false); 171 | expect(capabilities.canAccessWebDAV).toBe(true); 172 | expect(capabilities.isLocalMode).toBe(false); 173 | }); 174 | 175 | it('should return correct capabilities for OAuth', () => { 176 | const config: SFCCConfig = { 177 | hostname: 'test-instance.demandware.net', 178 | clientId: 'test-client-id', 179 | clientSecret: 'test-client-secret', 180 | }; 181 | 182 | const capabilities = ConfigurationFactory.getCapabilities(config); 183 | 184 | expect(capabilities.canAccessLogs).toBe(true); 185 | expect(capabilities.canAccessOCAPI).toBe(true); 186 | expect(capabilities.canAccessWebDAV).toBe(true); 187 | expect(capabilities.isLocalMode).toBe(false); 188 | }); 189 | 190 | it('should return correct capabilities for both basic auth and OAuth', () => { 191 | const config: SFCCConfig = { 192 | hostname: 'test-instance.demandware.net', 193 | username: 'testuser', 194 | password: 'testpass', 195 | clientId: 'test-client-id', 196 | clientSecret: 'test-client-secret', 197 | }; 198 | 199 | const capabilities = ConfigurationFactory.getCapabilities(config); 200 | 201 | expect(capabilities.canAccessLogs).toBe(true); 202 | expect(capabilities.canAccessOCAPI).toBe(true); 203 | expect(capabilities.canAccessWebDAV).toBe(true); 204 | expect(capabilities.isLocalMode).toBe(false); 205 | }); 206 | 207 | it('should return local mode capabilities for empty config', () => { 208 | const config: SFCCConfig = {}; 209 | 210 | const capabilities = ConfigurationFactory.getCapabilities(config); 211 | 212 | expect(capabilities.canAccessLogs).toBe(false); 213 | expect(capabilities.canAccessOCAPI).toBe(false); 214 | expect(capabilities.canAccessWebDAV).toBe(false); 215 | expect(capabilities.isLocalMode).toBe(true); 216 | }); 217 | 218 | it('should return local mode capabilities for config without hostname', () => { 219 | const config: SFCCConfig = { 220 | hostname: '', 221 | username: undefined, 222 | password: undefined, 223 | }; 224 | 225 | const capabilities = ConfigurationFactory.getCapabilities(config); 226 | 227 | expect(capabilities.canAccessLogs).toBe(false); 228 | expect(capabilities.canAccessOCAPI).toBe(false); 229 | expect(capabilities.canAccessWebDAV).toBe(false); 230 | expect(capabilities.isLocalMode).toBe(true); 231 | }); 232 | }); 233 | }); 234 | ``` -------------------------------------------------------------------------------- /docs/dw_order/ReturnItem.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class ReturnItem 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.Extensible 9 | - dw.order.AbstractItem 10 | - dw.order.ReturnItem 11 | 12 | ## Description 13 | 14 | An item of a Return, created using Return.createItem(String). Represents a physically returned order line item. Please refer to the documentation of ReturnHooks for further information. When the related Return were set to status COMPLETED, only the the custom attributes of the return 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 | ## Properties 17 | 18 | ### basePrice 19 | 20 | **Type:** Money (Read Only) 21 | 22 | Price of a single unit before discount application. 23 | 24 | ### note 25 | 26 | **Type:** String 27 | 28 | Return the note for this return item. 29 | 30 | ### parentItem 31 | 32 | **Type:** ReturnItem 33 | 34 | Returns null or the parent item. 35 | 36 | ### reasonCode 37 | 38 | **Type:** EnumValue 39 | 40 | The reason code for return item. The list of reason codes can be updated 41 | by updating meta-data for ReturnItem. 42 | 43 | ### returnCaseItem 44 | 45 | **Type:** ReturnCaseItem (Read Only) 46 | 47 | The return case item related to this item. Should never return null. 48 | 49 | ### returnedQuantity 50 | 51 | **Type:** Quantity 52 | 53 | The Quantity returned. This may return an N/A quantity. 54 | 55 | ### returnNumber 56 | 57 | **Type:** String (Read Only) 58 | 59 | The mandatory returnNumber of the Return to which this item belongs. 60 | 61 | ## Constructor Summary 62 | 63 | ## Method Summary 64 | 65 | ### addTaxItem 66 | 67 | **Signature:** `addTaxItem(amount : Decimal, taxGroup : TaxGroup) : TaxItem` 68 | 69 | Create a new tax-item and add to this item. 70 | 71 | ### applyPriceRate 72 | 73 | **Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void` 74 | 75 | Apply a rate of (factor / divisor) to the prices in this item, with the option to half round up or half round down to the nearest cent if necessary. 76 | 77 | ### getBasePrice 78 | 79 | **Signature:** `getBasePrice() : Money` 80 | 81 | Price of a single unit before discount application. 82 | 83 | ### getNote 84 | 85 | **Signature:** `getNote() : String` 86 | 87 | Return the note for this return item. 88 | 89 | ### getParentItem 90 | 91 | **Signature:** `getParentItem() : ReturnItem` 92 | 93 | Returns null or the parent item. 94 | 95 | ### getReasonCode 96 | 97 | **Signature:** `getReasonCode() : EnumValue` 98 | 99 | Returns the reason code for return item. 100 | 101 | ### getReturnCaseItem 102 | 103 | **Signature:** `getReturnCaseItem() : ReturnCaseItem` 104 | 105 | Returns the return case item related to this item. 106 | 107 | ### getReturnedQuantity 108 | 109 | **Signature:** `getReturnedQuantity() : Quantity` 110 | 111 | The Quantity returned. 112 | 113 | ### getReturnNumber 114 | 115 | **Signature:** `getReturnNumber() : String` 116 | 117 | The mandatory returnNumber of the Return to which this item belongs. 118 | 119 | ### setNote 120 | 121 | **Signature:** `setNote(note : String) : void` 122 | 123 | Sets a note for this return item. 124 | 125 | ### setParentItem 126 | 127 | **Signature:** `setParentItem(parentItem : ReturnItem) : void` 128 | 129 | Set a parent item. 130 | 131 | ### setReasonCode 132 | 133 | **Signature:** `setReasonCode(reasonCode : String) : void` 134 | 135 | Set the reason code. 136 | 137 | ### setReturnedQuantity 138 | 139 | **Signature:** `setReturnedQuantity(quantity : Quantity) : void` 140 | 141 | Set the Quantity returned. 142 | 143 | ### setTaxBasis 144 | 145 | **Signature:** `setTaxBasis(taxBasis : Money) : void` 146 | 147 | Set the tax-basis price for this item. 148 | 149 | ### setTaxItems 150 | 151 | **Signature:** `setTaxItems(taxItems : Collection) : void` 152 | 153 | Set the tax-items for this item. 154 | 155 | ## Method Detail 156 | 157 | ## Method Details 158 | 159 | ### addTaxItem 160 | 161 | **Signature:** `addTaxItem(amount : Decimal, taxGroup : TaxGroup) : TaxItem` 162 | 163 | **Description:** Create a new tax-item and add to this item. 164 | 165 | **Parameters:** 166 | 167 | - `amount`: amount to assign to the tax-item 168 | - `taxGroup`: the TaxGroup to which the item belongs 169 | 170 | **Returns:** 171 | 172 | the new tax-item 173 | 174 | --- 175 | 176 | ### applyPriceRate 177 | 178 | **Signature:** `applyPriceRate(factor : Decimal, divisor : Decimal, roundUp : boolean) : void` 179 | 180 | **Description:** Apply a rate of (factor / divisor) to the prices in this item, with the option to half round up or half round down to the nearest cent if necessary. Examples: TaxBasis beforefactordivisorroundupCalculationTaxBasis after $10.0012true10*1/2=$5.00 $10.00910true10*9/10=$9.00 $10.0013true10*1/3=3.3333=$3.33 $2.4712true2.47*1/2=1.235=$1.24 $2.4712false2.47*1/2=1.235=$1.23 Which prices are updated?: The rate described above is applied to tax-basis and tax then the net-price and gross-price are recalculated by adding / subtracting depending on whether the order is based on net price. Example (order based on net price) New TaxBasis:$10.00, Tax:$1.00, NetPrice=TaxBasis=$10.00, GrossPrice=TaxBasis+Tax=$11.00 Example (order based on gross price) New TaxBasis:$10.00, Tax:$1.00, NetPrice=TaxBasis-tax=$9.00, GrossPrice=TaxBasis=$10.00 181 | 182 | **Parameters:** 183 | 184 | - `factor`: factor used to calculate rate 185 | - `divisor`: divisor used to calculate rate 186 | - `roundUp`: whether to round up or down on 0.5 187 | 188 | **See Also:** 189 | 190 | AbstractItem.getTaxBasis() 191 | AbstractItem.getTax() 192 | AbstractItem.getNetPrice() 193 | AbstractItem.getGrossPrice() 194 | TaxMgr.getTaxationPolicy() 195 | 196 | --- 197 | 198 | ### getBasePrice 199 | 200 | **Signature:** `getBasePrice() : Money` 201 | 202 | **Description:** Price of a single unit before discount application. 203 | 204 | **Returns:** 205 | 206 | Price of a single unit before discount application. 207 | 208 | --- 209 | 210 | ### getNote 211 | 212 | **Signature:** `getNote() : String` 213 | 214 | **Description:** Return the note for this return item. 215 | 216 | **Returns:** 217 | 218 | the note or null 219 | 220 | --- 221 | 222 | ### getParentItem 223 | 224 | **Signature:** `getParentItem() : ReturnItem` 225 | 226 | **Description:** Returns null or the parent item. 227 | 228 | **Returns:** 229 | 230 | null or the parent item. 231 | 232 | --- 233 | 234 | ### getReasonCode 235 | 236 | **Signature:** `getReasonCode() : EnumValue` 237 | 238 | **Description:** Returns the reason code for return item. The list of reason codes can be updated by updating meta-data for ReturnItem. 239 | 240 | **Returns:** 241 | 242 | the return reason code 243 | 244 | --- 245 | 246 | ### getReturnCaseItem 247 | 248 | **Signature:** `getReturnCaseItem() : ReturnCaseItem` 249 | 250 | **Description:** Returns the return case item related to this item. Should never return null. 251 | 252 | **Returns:** 253 | 254 | the return case item related to this item 255 | 256 | --- 257 | 258 | ### getReturnedQuantity 259 | 260 | **Signature:** `getReturnedQuantity() : Quantity` 261 | 262 | **Description:** The Quantity returned. This may return an N/A quantity. 263 | 264 | **Returns:** 265 | 266 | the quantity returned, may be N/A 267 | 268 | --- 269 | 270 | ### getReturnNumber 271 | 272 | **Signature:** `getReturnNumber() : String` 273 | 274 | **Description:** The mandatory returnNumber of the Return to which this item belongs. 275 | 276 | **Returns:** 277 | 278 | the returnNumber of the Return to which this item belongs 279 | 280 | --- 281 | 282 | ### setNote 283 | 284 | **Signature:** `setNote(note : String) : void` 285 | 286 | **Description:** Sets a note for this return item. 287 | 288 | **Parameters:** 289 | 290 | - `note`: the note for this return item to set 291 | 292 | --- 293 | 294 | ### setParentItem 295 | 296 | **Signature:** `setParentItem(parentItem : ReturnItem) : void` 297 | 298 | **Description:** Set a parent item. The parent item must belong to the same Return. 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(). 299 | 300 | **Parameters:** 301 | 302 | - `parentItem`: The parent item, null is allowed 303 | 304 | --- 305 | 306 | ### setReasonCode 307 | 308 | **Signature:** `setReasonCode(reasonCode : String) : void` 309 | 310 | **Description:** Set the reason code. The list of reason codes can be updated by updating meta-data for ReturnItem. 311 | 312 | **Parameters:** 313 | 314 | - `reasonCode`: the reason code to set 315 | 316 | --- 317 | 318 | ### setReturnedQuantity 319 | 320 | **Signature:** `setReturnedQuantity(quantity : Quantity) : void` 321 | 322 | **Description:** Set the Quantity returned. Passing null results in an exception being thrown. The quantity must be higher than zero and not be higher than the remaining quantity to return. The item prices are recalculated in this method as described in applyPriceRate(Decimal, Decimal, Boolean) with the quantity argument as the factor, and ordered quantity as divisor and true as the roundup parameter. 323 | 324 | **Parameters:** 325 | 326 | - `quantity`: the quantity returned, null not allowed 327 | 328 | **See Also:** 329 | 330 | OrderItem.getReturnedQuantity() 331 | ProductLineItem.getQuantity() 332 | 333 | --- 334 | 335 | ### setTaxBasis 336 | 337 | **Signature:** `setTaxBasis(taxBasis : Money) : void` 338 | 339 | **Description:** Set the tax-basis price for this item. 340 | 341 | **Parameters:** 342 | 343 | - `taxBasis`: the tax basis value. 344 | 345 | --- 346 | 347 | ### setTaxItems 348 | 349 | **Signature:** `setTaxItems(taxItems : Collection) : void` 350 | 351 | **Description:** Set the tax-items for this item. 352 | 353 | **Parameters:** 354 | 355 | - `taxItems`: items 356 | 357 | **See Also:** 358 | 359 | addTaxItem(Decimal, TaxGroup) 360 | TaxGroup.create(String, String, String, Decimal) 361 | 362 | --- ``` -------------------------------------------------------------------------------- /docs/dw_order/ShippingLineItem.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class ShippingLineItem 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.order.LineItem 11 | - dw.order.ShippingLineItem 12 | 13 | ## Description 14 | 15 | Represents a specific line item in a shipment. The ShippingLineItem defines the general shipping costs of a shipment. 16 | 17 | ## Constants 18 | 19 | ### STANDARD_SHIPPING_ID 20 | 21 | **Type:** String = "STANDARD_SHIPPING" 22 | 23 | Constant used to get the standard shipping line item. 24 | 25 | ## Properties 26 | 27 | ### adjustedGrossPrice 28 | 29 | **Type:** Money (Read Only) 30 | 31 | The price of this shipping line item including tax after 32 | shipping adjustments have been applied. 33 | 34 | ### adjustedNetPrice 35 | 36 | **Type:** Money (Read Only) 37 | 38 | The price of this shipping line item, excluding tax after 39 | shipping adjustments have been applied. 40 | 41 | ### adjustedPrice 42 | 43 | **Type:** Money (Read Only) 44 | 45 | The adjusted price of this shipping line item. If the line item 46 | container is based on net pricing, the adjusted net price is returned. If 47 | the line item container is based on gross pricing, the adjusted gross 48 | price is returned. 49 | 50 | ### adjustedTax 51 | 52 | **Type:** Money (Read Only) 53 | 54 | The tax of this shipping line item after shipping adjustments 55 | have been applied. 56 | 57 | ### ID 58 | 59 | **Type:** String (Read Only) 60 | 61 | The ID of this ShippingLineItem. 62 | 63 | ### orderItem 64 | 65 | **Type:** OrderItem (Read Only) 66 | 67 | The order-item extension for this item, or null. 68 | An order-item extension will only exist for a ShippingLineItem which 69 | belongs to an Order. 70 | 71 | Order post-processing APIs (gillian) are now inactive by default and will throw 72 | an exception if accessed. Activation needs preliminary approval by Product Management. 73 | Please contact support in this case. Existing customers using these APIs are not 74 | affected by this change and can use the APIs until further notice. 75 | 76 | ### shippingPriceAdjustments 77 | 78 | **Type:** Collection (Read Only) 79 | 80 | The collection of shipping price adjustments that have been 81 | applied to this shipping line item. 82 | 83 | ## Constructor Summary 84 | 85 | ## Method Summary 86 | 87 | ### createShippingPriceAdjustment 88 | 89 | **Signature:** `createShippingPriceAdjustment(promotionID : String) : PriceAdjustment` 90 | 91 | Creates a shipping price adjustment to be applied to the shipping line item. The promotion ID is mandatory and must not be the ID of any actual promotion defined in B2C Commerce. If there already exists a shipping price adjustment on this shipping line item referring to the specified promotion ID, an exception is thrown. 92 | 93 | ### createShippingPriceAdjustment 94 | 95 | **Signature:** `createShippingPriceAdjustment(promotionID : String, discount : Discount) : PriceAdjustment` 96 | 97 | Creates a shipping price adjustment to be applied to the shipping line item. The promotion ID is mandatory and must not be the ID of any actual promotion defined in B2C Commerce. 98 | 99 | ### getAdjustedGrossPrice 100 | 101 | **Signature:** `getAdjustedGrossPrice() : Money` 102 | 103 | Returns the price of this shipping line item including tax after shipping adjustments have been applied. 104 | 105 | ### getAdjustedNetPrice 106 | 107 | **Signature:** `getAdjustedNetPrice() : Money` 108 | 109 | Returns the price of this shipping line item, excluding tax after shipping adjustments have been applied. 110 | 111 | ### getAdjustedPrice 112 | 113 | **Signature:** `getAdjustedPrice() : Money` 114 | 115 | Returns the adjusted price of this shipping line item. 116 | 117 | ### getAdjustedTax 118 | 119 | **Signature:** `getAdjustedTax() : Money` 120 | 121 | Returns the tax of this shipping line item after shipping adjustments have been applied. 122 | 123 | ### getID 124 | 125 | **Signature:** `getID() : String` 126 | 127 | Returns the ID of this ShippingLineItem. 128 | 129 | ### getOrderItem 130 | 131 | **Signature:** `getOrderItem() : OrderItem` 132 | 133 | Returns the order-item extension for this item, or null. 134 | 135 | ### getShippingPriceAdjustments 136 | 137 | **Signature:** `getShippingPriceAdjustments() : Collection` 138 | 139 | Returns the collection of shipping price adjustments that have been applied to this shipping line item. 140 | 141 | ### removeShippingPriceAdjustment 142 | 143 | **Signature:** `removeShippingPriceAdjustment(priceAdjustment : PriceAdjustment) : void` 144 | 145 | Removes the specified shipping price adjustment from this shipping line item. 146 | 147 | ## Method Detail 148 | 149 | ## Method Details 150 | 151 | ### createShippingPriceAdjustment 152 | 153 | **Signature:** `createShippingPriceAdjustment(promotionID : String) : PriceAdjustment` 154 | 155 | **Description:** Creates a shipping price adjustment to be applied to the shipping line item. The promotion ID is mandatory and must not be the ID of any actual promotion defined in B2C Commerce. If there already exists a shipping price adjustment on this shipping line item referring to the specified promotion ID, an exception is thrown. 156 | 157 | **Parameters:** 158 | 159 | - `promotionID`: Promotion ID 160 | 161 | **Returns:** 162 | 163 | The new price adjustment line item. 164 | 165 | --- 166 | 167 | ### createShippingPriceAdjustment 168 | 169 | **Signature:** `createShippingPriceAdjustment(promotionID : String, discount : Discount) : PriceAdjustment` 170 | 171 | **Description:** Creates a shipping price adjustment to be applied to the shipping line item. The promotion ID is mandatory and must not be the ID of any actual promotion defined in B2C Commerce. If a shipping price adjustment on this shipping line item referring to the specified promotion ID already exists, an exception is thrown. The possible values for discount are PercentageDiscount, AmountDiscount, FixedPriceShippingDiscount. Examples: var myShippingItem : dw.order.ShippingLineItem; // assume known var paFixedShippingPrice12 : dw.order.PriceAdjustment = myShippingItem.createPriceAdjustment("myPromotionID1", new FixedPriceShippingDiscount(12)); var paTenPercent : dw.order.PriceAdjustment = myShippingItem.createPriceAdjustment("myPromotionID2", new PercentageDiscount(10)); var paReduceBy2 : dw.order.PriceAdjustment = myShippingItem.createPriceAdjustment("myPromotionID3", new AmountDiscount(2)); 172 | 173 | **Parameters:** 174 | 175 | - `promotionID`: Promotion ID 176 | - `discount`: The desired discount, not null 177 | 178 | **Returns:** 179 | 180 | The new price adjustment line item. 181 | 182 | --- 183 | 184 | ### getAdjustedGrossPrice 185 | 186 | **Signature:** `getAdjustedGrossPrice() : Money` 187 | 188 | **Description:** Returns the price of this shipping line item including tax after shipping adjustments have been applied. 189 | 190 | **Returns:** 191 | 192 | the price of this shipping line item, including tax after shipping adjustments have been applied. 193 | 194 | --- 195 | 196 | ### getAdjustedNetPrice 197 | 198 | **Signature:** `getAdjustedNetPrice() : Money` 199 | 200 | **Description:** Returns the price of this shipping line item, excluding tax after shipping adjustments have been applied. 201 | 202 | **Returns:** 203 | 204 | the price of this shipping line item, excluding tax after shipping adjustments have been applied. 205 | 206 | --- 207 | 208 | ### getAdjustedPrice 209 | 210 | **Signature:** `getAdjustedPrice() : Money` 211 | 212 | **Description:** Returns the adjusted price of this shipping line item. If the line item container is based on net pricing, the adjusted net price is returned. If the line item container is based on gross pricing, the adjusted gross price is returned. 213 | 214 | **Returns:** 215 | 216 | either the adjusted net or gross price of this shipping line item. 217 | 218 | --- 219 | 220 | ### getAdjustedTax 221 | 222 | **Signature:** `getAdjustedTax() : Money` 223 | 224 | **Description:** Returns the tax of this shipping line item after shipping adjustments have been applied. 225 | 226 | **Returns:** 227 | 228 | the tax of this shipping line item after shipping adjustments have been applied. 229 | 230 | --- 231 | 232 | ### getID 233 | 234 | **Signature:** `getID() : String` 235 | 236 | **Description:** Returns the ID of this ShippingLineItem. 237 | 238 | **Returns:** 239 | 240 | ID of this ShippingLineItem 241 | 242 | --- 243 | 244 | ### getOrderItem 245 | 246 | **Signature:** `getOrderItem() : OrderItem` 247 | 248 | **Description:** Returns the order-item extension for this item, or null. An order-item extension will only exist for a ShippingLineItem which belongs to an Order. 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. 249 | 250 | **Returns:** 251 | 252 | null or the order-item 253 | 254 | --- 255 | 256 | ### getShippingPriceAdjustments 257 | 258 | **Signature:** `getShippingPriceAdjustments() : Collection` 259 | 260 | **Description:** Returns the collection of shipping price adjustments that have been applied to this shipping line item. 261 | 262 | **Returns:** 263 | 264 | the collection of shipping price adjustments that have been applied to this shipping line item. 265 | 266 | --- 267 | 268 | ### removeShippingPriceAdjustment 269 | 270 | **Signature:** `removeShippingPriceAdjustment(priceAdjustment : PriceAdjustment) : void` 271 | 272 | **Description:** Removes the specified shipping price adjustment from this shipping line item. 273 | 274 | **Parameters:** 275 | 276 | - `priceAdjustment`: The price adjustment line item to remove 277 | 278 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/get-system-object-definitions.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | # ================================================================================== 2 | # SFCC MCP Server - get_system_object_definitions Tool YAML Tests (Docs-Only Mode) 3 | # Tests that system object tools are NOT available in docs-only mode 4 | # This tool requires SFCC credentials and should not be available without them 5 | # 6 | # Quick Test Commands: 7 | # aegis "tests/mcp/yaml/get-system-object-definitions.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose 8 | # aegis "tests/mcp/yaml/get-system-object-definitions.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --debug --timing 9 | # aegis query --config "aegis.config.docs-only.json" 10 | # ================================================================================== 11 | 12 | description: "get_system_object_definitions tool tests - Docs-only mode tool availability" 13 | 14 | # ================================================================================== 15 | # TOOL UNAVAILABILITY IN DOCS-ONLY MODE 16 | # ================================================================================== 17 | tests: 18 | - it: "should NOT list get_system_object_definitions tool in docs-only mode" 19 | request: 20 | jsonrpc: "2.0" 21 | id: "tool-not-available-docs" 22 | method: "tools/list" 23 | params: {} 24 | expect: 25 | response: 26 | jsonrpc: "2.0" 27 | id: "tool-not-available-docs" 28 | result: 29 | match:extractField: "tools.*.name" 30 | value: "match:not:arrayContains:get_system_object_definitions" 31 | stderr: "toBeEmpty" 32 | 33 | - it: "should NOT list any system object tools in docs-only mode" 34 | request: 35 | jsonrpc: "2.0" 36 | id: "no-system-object-tools" 37 | method: "tools/list" 38 | params: {} 39 | expect: 40 | response: 41 | jsonrpc: "2.0" 42 | id: "no-system-object-tools" 43 | result: 44 | match:extractField: "tools.*.name" 45 | value: "match:not:arrayContains:get_system_object_definition" 46 | stderr: "toBeEmpty" 47 | 48 | - it: "should NOT list site preferences tools in docs-only mode" 49 | request: 50 | jsonrpc: "2.0" 51 | id: "no-site-prefs-tools" 52 | method: "tools/list" 53 | params: {} 54 | expect: 55 | response: 56 | jsonrpc: "2.0" 57 | id: "no-site-prefs-tools" 58 | result: 59 | match:extractField: "tools.*.name" 60 | value: "match:not:arrayContains:search_site_preferences" 61 | stderr: "toBeEmpty" 62 | 63 | - it: "should NOT list custom object attribute tools in docs-only mode" 64 | request: 65 | jsonrpc: "2.0" 66 | id: "no-custom-object-tools" 67 | method: "tools/list" 68 | params: {} 69 | expect: 70 | response: 71 | jsonrpc: "2.0" 72 | id: "no-custom-object-tools" 73 | result: 74 | match:extractField: "tools.*.name" 75 | value: "match:not:arrayContains:search_custom_object_attribute_definitions" 76 | stderr: "toBeEmpty" 77 | 78 | # ================================================================================== 79 | # TOOL CALL BEHAVIOR - AVAILABLE BUT RETURNS ERROR 80 | # ================================================================================== 81 | 82 | - it: "should return configuration error when calling unlisted tool" 83 | request: 84 | jsonrpc: "2.0" 85 | id: "config-error" 86 | method: "tools/call" 87 | params: 88 | name: "get_system_object_definitions" 89 | arguments: {} 90 | expect: 91 | response: 92 | jsonrpc: "2.0" 93 | id: "config-error" 94 | result: 95 | content: 96 | match:arrayElements: 97 | match:partial: 98 | type: "text" 99 | text: "match:contains:OCAPI client not configured" 100 | isError: true 101 | performance: 102 | maxResponseTime: "500ms" # Error should be fast 103 | 104 | - it: "should return proper error result structure for unlisted tool" 105 | request: 106 | jsonrpc: "2.0" 107 | id: "error-result-structure" 108 | method: "tools/call" 109 | params: 110 | name: "get_system_object_definitions" 111 | arguments: {} 112 | expect: 113 | response: 114 | jsonrpc: "2.0" 115 | id: "error-result-structure" 116 | result: 117 | content: "match:type:array" 118 | isError: true 119 | 120 | - it: "should fail fast when calling unlisted tool with parameters" 121 | request: 122 | jsonrpc: "2.0" 123 | id: "fail-fast-with-params" 124 | method: "tools/call" 125 | params: 126 | name: "get_system_object_definitions" 127 | arguments: 128 | start: 0 129 | count: 10 130 | expect: 131 | response: 132 | jsonrpc: "2.0" 133 | id: "fail-fast-with-params" 134 | result: 135 | content: 136 | match:arrayElements: 137 | match:partial: 138 | type: "text" 139 | text: "match:contains:OCAPI client not configured" 140 | isError: true 141 | performance: 142 | maxResponseTime: "500ms" # Should fail quickly 143 | 144 | # ================================================================================== 145 | # DOCS-ONLY MODE TOOL AVAILABILITY VERIFICATION 146 | # ================================================================================== 147 | 148 | - it: "should have SFCC documentation tools available" 149 | request: 150 | jsonrpc: "2.0" 151 | id: "docs-tools-available" 152 | method: "tools/list" 153 | params: {} 154 | expect: 155 | response: 156 | jsonrpc: "2.0" 157 | id: "docs-tools-available" 158 | result: 159 | match:extractField: "tools.*.name" 160 | value: "match:arrayContains:get_sfcc_class_info" 161 | stderr: "toBeEmpty" 162 | 163 | - it: "should have SFRA documentation tools available" 164 | request: 165 | jsonrpc: "2.0" 166 | id: "sfra-tools-available" 167 | method: "tools/list" 168 | params: {} 169 | expect: 170 | response: 171 | jsonrpc: "2.0" 172 | id: "sfra-tools-available" 173 | result: 174 | match:extractField: "tools.*.name" 175 | value: "match:arrayContains:get_sfra_document" 176 | stderr: "toBeEmpty" 177 | 178 | - it: "should have best practices tools available" 179 | request: 180 | jsonrpc: "2.0" 181 | id: "best-practices-available" 182 | method: "tools/list" 183 | params: {} 184 | expect: 185 | response: 186 | jsonrpc: "2.0" 187 | id: "best-practices-available" 188 | result: 189 | match:extractField: "tools.*.name" 190 | value: "match:arrayContains:get_best_practice_guide" 191 | stderr: "toBeEmpty" 192 | 193 | - it: "should have cartridge generation tools available" 194 | request: 195 | jsonrpc: "2.0" 196 | id: "cartridge-tools-available" 197 | method: "tools/list" 198 | params: {} 199 | expect: 200 | response: 201 | jsonrpc: "2.0" 202 | id: "cartridge-tools-available" 203 | result: 204 | match:extractField: "tools.*.name" 205 | value: "match:arrayContains:generate_cartridge_structure" 206 | stderr: "toBeEmpty" 207 | 208 | - it: "should have expected number of tools in docs-only mode" 209 | request: 210 | jsonrpc: "2.0" 211 | id: "expected-tool-count" 212 | method: "tools/list" 213 | params: {} 214 | expect: 215 | response: 216 | jsonrpc: "2.0" 217 | id: "expected-tool-count" 218 | result: 219 | tools: "match:arrayLength:15" # Based on actual count from aegis query 220 | stderr: "toBeEmpty" 221 | 222 | # ================================================================================== 223 | # CONSISTENCY TESTING 224 | # ================================================================================== 225 | 226 | - it: "should consistently exclude system object tools across multiple calls" 227 | request: 228 | jsonrpc: "2.0" 229 | id: "consistent-exclusion-1" 230 | method: "tools/list" 231 | params: {} 232 | expect: 233 | response: 234 | jsonrpc: "2.0" 235 | id: "consistent-exclusion-1" 236 | result: 237 | match:extractField: "tools.*.name" 238 | value: "match:not:arrayContains:get_system_object_definitions" 239 | 240 | - it: "should maintain consistent tool exclusion on second call" 241 | request: 242 | jsonrpc: "2.0" 243 | id: "consistent-exclusion-2" 244 | method: "tools/list" 245 | params: {} 246 | expect: 247 | response: 248 | jsonrpc: "2.0" 249 | id: "consistent-exclusion-2" 250 | result: 251 | match:extractField: "tools.*.name" 252 | value: "match:not:arrayContains:get_system_object_definitions" 253 | 254 | - it: "should consistently return configuration error across multiple attempts" 255 | request: 256 | jsonrpc: "2.0" 257 | id: "consistent-config-error" 258 | method: "tools/call" 259 | params: 260 | name: "get_system_object_definitions" 261 | arguments: {} 262 | expect: 263 | response: 264 | jsonrpc: "2.0" 265 | id: "consistent-config-error" 266 | result: 267 | content: 268 | match:arrayElements: 269 | match:partial: 270 | type: "text" 271 | text: "match:contains:OCAPI client not configured" 272 | isError: true ``` -------------------------------------------------------------------------------- /docs/dw_catalog/Store.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.catalog 2 | 3 | # Class Store 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.catalog.Store 11 | 12 | ## Description 13 | 14 | Represents a store in Commerce Cloud Digital. 15 | 16 | ## Properties 17 | 18 | ### address1 19 | 20 | **Type:** String (Read Only) 21 | 22 | The address1 of the store. 23 | 24 | ### address2 25 | 26 | **Type:** String (Read Only) 27 | 28 | The address2 of the store. 29 | 30 | ### city 31 | 32 | **Type:** String (Read Only) 33 | 34 | The city of the store. 35 | 36 | ### countryCode 37 | 38 | **Type:** EnumValue (Read Only) 39 | 40 | The countryCode of the store. 41 | 42 | ### demandwarePosEnabled 43 | 44 | **Type:** boolean (Read Only) 45 | 46 | The demandwarePosEnabled flag for the store. 47 | Indicates that this store uses Commerce Cloud Store for point-of-sale. 48 | 49 | ### email 50 | 51 | **Type:** String (Read Only) 52 | 53 | The email of the store. 54 | 55 | ### fax 56 | 57 | **Type:** String (Read Only) 58 | 59 | The fax of the store. 60 | 61 | ### ID 62 | 63 | **Type:** String (Read Only) 64 | 65 | The ID of the store. 66 | 67 | ### image 68 | 69 | **Type:** MediaFile (Read Only) 70 | 71 | The store image. 72 | 73 | ### inventoryList 74 | 75 | **Type:** ProductInventoryList (Read Only) 76 | 77 | The inventory list the store is associated with. If the 78 | store is not associated with a inventory list, or the inventory list does not 79 | exist, the method returns null. 80 | 81 | ### inventoryListID 82 | 83 | **Type:** String (Read Only) 84 | 85 | The inventory list id the store is associated with. If the 86 | store is not associated with a inventory list, or the inventory list does not 87 | exist, the method returns null. 88 | 89 | ### latitude 90 | 91 | **Type:** Number (Read Only) 92 | 93 | The latitude of the store. 94 | 95 | ### longitude 96 | 97 | **Type:** Number (Read Only) 98 | 99 | The longitude of the store. 100 | 101 | ### name 102 | 103 | **Type:** String (Read Only) 104 | 105 | The name of the store. 106 | 107 | ### phone 108 | 109 | **Type:** String (Read Only) 110 | 111 | The phone of the store. 112 | 113 | ### posEnabled 114 | 115 | **Type:** boolean (Read Only) 116 | 117 | The posEnabled flag for the Store. 118 | Indicates that this store uses Commerce Cloud Store for point-of-sale. 119 | 120 | ### postalCode 121 | 122 | **Type:** String (Read Only) 123 | 124 | The postalCode of the store. 125 | 126 | ### stateCode 127 | 128 | **Type:** String (Read Only) 129 | 130 | The stateCode of the store. 131 | 132 | ### storeEvents 133 | 134 | **Type:** MarkupText (Read Only) 135 | 136 | The storeEvents of the store. 137 | 138 | ### storeGroups 139 | 140 | **Type:** Collection (Read Only) 141 | 142 | All the store groups this store belongs to. 143 | 144 | ### storeHours 145 | 146 | **Type:** MarkupText (Read Only) 147 | 148 | The storeHours of the store. 149 | 150 | ### storeLocatorEnabled 151 | 152 | **Type:** boolean (Read Only) 153 | 154 | The storeLocatorEnabled flag for the store. 155 | 156 | ## Constructor Summary 157 | 158 | ## Method Summary 159 | 160 | ### getAddress1 161 | 162 | **Signature:** `getAddress1() : String` 163 | 164 | Returns the address1 of the store. 165 | 166 | ### getAddress2 167 | 168 | **Signature:** `getAddress2() : String` 169 | 170 | Returns the address2 of the store. 171 | 172 | ### getCity 173 | 174 | **Signature:** `getCity() : String` 175 | 176 | Returns the city of the store. 177 | 178 | ### getCountryCode 179 | 180 | **Signature:** `getCountryCode() : EnumValue` 181 | 182 | Returns the countryCode of the store. 183 | 184 | ### getEmail 185 | 186 | **Signature:** `getEmail() : String` 187 | 188 | Returns the email of the store. 189 | 190 | ### getFax 191 | 192 | **Signature:** `getFax() : String` 193 | 194 | Returns the fax of the store. 195 | 196 | ### getID 197 | 198 | **Signature:** `getID() : String` 199 | 200 | Returns the ID of the store. 201 | 202 | ### getImage 203 | 204 | **Signature:** `getImage() : MediaFile` 205 | 206 | Returns the store image. 207 | 208 | ### getInventoryList 209 | 210 | **Signature:** `getInventoryList() : ProductInventoryList` 211 | 212 | Returns the inventory list the store is associated with. 213 | 214 | ### getInventoryListID 215 | 216 | **Signature:** `getInventoryListID() : String` 217 | 218 | Returns the inventory list id the store is associated with. 219 | 220 | ### getLatitude 221 | 222 | **Signature:** `getLatitude() : Number` 223 | 224 | Returns the latitude of the store. 225 | 226 | ### getLongitude 227 | 228 | **Signature:** `getLongitude() : Number` 229 | 230 | Returns the longitude of the store. 231 | 232 | ### getName 233 | 234 | **Signature:** `getName() : String` 235 | 236 | Returns the name of the store. 237 | 238 | ### getPhone 239 | 240 | **Signature:** `getPhone() : String` 241 | 242 | Returns the phone of the store. 243 | 244 | ### getPostalCode 245 | 246 | **Signature:** `getPostalCode() : String` 247 | 248 | Returns the postalCode of the store. 249 | 250 | ### getStateCode 251 | 252 | **Signature:** `getStateCode() : String` 253 | 254 | Returns the stateCode of the store. 255 | 256 | ### getStoreEvents 257 | 258 | **Signature:** `getStoreEvents() : MarkupText` 259 | 260 | Returns the storeEvents of the store. 261 | 262 | ### getStoreGroups 263 | 264 | **Signature:** `getStoreGroups() : Collection` 265 | 266 | Returns all the store groups this store belongs to. 267 | 268 | ### getStoreHours 269 | 270 | **Signature:** `getStoreHours() : MarkupText` 271 | 272 | Returns the storeHours of the store. 273 | 274 | ### isDemandwarePosEnabled 275 | 276 | **Signature:** `isDemandwarePosEnabled() : boolean` 277 | 278 | Returns the demandwarePosEnabled flag for the store. 279 | 280 | ### isPosEnabled 281 | 282 | **Signature:** `isPosEnabled() : boolean` 283 | 284 | Returns the posEnabled flag for the Store. 285 | 286 | ### isStoreLocatorEnabled 287 | 288 | **Signature:** `isStoreLocatorEnabled() : boolean` 289 | 290 | Returns the storeLocatorEnabled flag for the store. 291 | 292 | ## Method Detail 293 | 294 | ## Method Details 295 | 296 | ### getAddress1 297 | 298 | **Signature:** `getAddress1() : String` 299 | 300 | **Description:** Returns the address1 of the store. 301 | 302 | **Returns:** 303 | 304 | address1 of the store 305 | 306 | --- 307 | 308 | ### getAddress2 309 | 310 | **Signature:** `getAddress2() : String` 311 | 312 | **Description:** Returns the address2 of the store. 313 | 314 | **Returns:** 315 | 316 | address2 of the store 317 | 318 | --- 319 | 320 | ### getCity 321 | 322 | **Signature:** `getCity() : String` 323 | 324 | **Description:** Returns the city of the store. 325 | 326 | **Returns:** 327 | 328 | city of the store 329 | 330 | --- 331 | 332 | ### getCountryCode 333 | 334 | **Signature:** `getCountryCode() : EnumValue` 335 | 336 | **Description:** Returns the countryCode of the store. 337 | 338 | **Returns:** 339 | 340 | countryCode of the store 341 | 342 | --- 343 | 344 | ### getEmail 345 | 346 | **Signature:** `getEmail() : String` 347 | 348 | **Description:** Returns the email of the store. 349 | 350 | **Returns:** 351 | 352 | email of the store 353 | 354 | --- 355 | 356 | ### getFax 357 | 358 | **Signature:** `getFax() : String` 359 | 360 | **Description:** Returns the fax of the store. 361 | 362 | **Returns:** 363 | 364 | fax of the store 365 | 366 | --- 367 | 368 | ### getID 369 | 370 | **Signature:** `getID() : String` 371 | 372 | **Description:** Returns the ID of the store. 373 | 374 | **Returns:** 375 | 376 | ID of the store 377 | 378 | --- 379 | 380 | ### getImage 381 | 382 | **Signature:** `getImage() : MediaFile` 383 | 384 | **Description:** Returns the store image. 385 | 386 | **Returns:** 387 | 388 | the store image. 389 | 390 | --- 391 | 392 | ### getInventoryList 393 | 394 | **Signature:** `getInventoryList() : ProductInventoryList` 395 | 396 | **Description:** Returns the inventory list the store is associated with. If the store is not associated with a inventory list, or the inventory list does not exist, the method returns null. 397 | 398 | **Returns:** 399 | 400 | ProductInventoryList or null 401 | 402 | --- 403 | 404 | ### getInventoryListID 405 | 406 | **Signature:** `getInventoryListID() : String` 407 | 408 | **Description:** Returns the inventory list id the store is associated with. If the store is not associated with a inventory list, or the inventory list does not exist, the method returns null. 409 | 410 | **Returns:** 411 | 412 | the inventory list id 413 | 414 | --- 415 | 416 | ### getLatitude 417 | 418 | **Signature:** `getLatitude() : Number` 419 | 420 | **Description:** Returns the latitude of the store. 421 | 422 | **Returns:** 423 | 424 | latitude of the store 425 | 426 | --- 427 | 428 | ### getLongitude 429 | 430 | **Signature:** `getLongitude() : Number` 431 | 432 | **Description:** Returns the longitude of the store. 433 | 434 | **Returns:** 435 | 436 | longitude of the store 437 | 438 | --- 439 | 440 | ### getName 441 | 442 | **Signature:** `getName() : String` 443 | 444 | **Description:** Returns the name of the store. 445 | 446 | **Returns:** 447 | 448 | name of the store 449 | 450 | --- 451 | 452 | ### getPhone 453 | 454 | **Signature:** `getPhone() : String` 455 | 456 | **Description:** Returns the phone of the store. 457 | 458 | **Returns:** 459 | 460 | phone of the store 461 | 462 | --- 463 | 464 | ### getPostalCode 465 | 466 | **Signature:** `getPostalCode() : String` 467 | 468 | **Description:** Returns the postalCode of the store. 469 | 470 | **Returns:** 471 | 472 | postalCode of the store 473 | 474 | --- 475 | 476 | ### getStateCode 477 | 478 | **Signature:** `getStateCode() : String` 479 | 480 | **Description:** Returns the stateCode of the store. 481 | 482 | **Returns:** 483 | 484 | stateCode of the store 485 | 486 | --- 487 | 488 | ### getStoreEvents 489 | 490 | **Signature:** `getStoreEvents() : MarkupText` 491 | 492 | **Description:** Returns the storeEvents of the store. 493 | 494 | **Returns:** 495 | 496 | storeEvents of the store 497 | 498 | --- 499 | 500 | ### getStoreGroups 501 | 502 | **Signature:** `getStoreGroups() : Collection` 503 | 504 | **Description:** Returns all the store groups this store belongs to. 505 | 506 | **Returns:** 507 | 508 | collection of store groups 509 | 510 | --- 511 | 512 | ### getStoreHours 513 | 514 | **Signature:** `getStoreHours() : MarkupText` 515 | 516 | **Description:** Returns the storeHours of the store. 517 | 518 | **Returns:** 519 | 520 | storeHours of the store 521 | 522 | --- 523 | 524 | ### isDemandwarePosEnabled 525 | 526 | **Signature:** `isDemandwarePosEnabled() : boolean` 527 | 528 | **Description:** Returns the demandwarePosEnabled flag for the store. Indicates that this store uses Commerce Cloud Store for point-of-sale. 529 | 530 | **Deprecated:** 531 | 532 | Use isPosEnabled() instead 533 | 534 | **Returns:** 535 | 536 | the demandwarePosEnabled flag 537 | 538 | --- 539 | 540 | ### isPosEnabled 541 | 542 | **Signature:** `isPosEnabled() : boolean` 543 | 544 | **Description:** Returns the posEnabled flag for the Store. Indicates that this store uses Commerce Cloud Store for point-of-sale. 545 | 546 | **Returns:** 547 | 548 | the posEnabled flag 549 | 550 | --- 551 | 552 | ### isStoreLocatorEnabled 553 | 554 | **Signature:** `isStoreLocatorEnabled() : boolean` 555 | 556 | **Description:** Returns the storeLocatorEnabled flag for the store. 557 | 558 | **Returns:** 559 | 560 | the storeLocatorEnabled flag 561 | 562 | --- ``` -------------------------------------------------------------------------------- /tests/query-builder.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Tests for QueryBuilder utility 3 | * Tests URL parameter construction and array handling 4 | */ 5 | 6 | import { QueryBuilder } from '../src/utils/query-builder.js'; 7 | 8 | describe('QueryBuilder', () => { 9 | let builder: QueryBuilder; 10 | 11 | beforeEach(() => { 12 | builder = new QueryBuilder(); 13 | }); 14 | 15 | describe('constructor', () => { 16 | it('should initialize with empty parameters', () => { 17 | expect(builder).toBeInstanceOf(QueryBuilder); 18 | }); 19 | }); 20 | 21 | describe('add method', () => { 22 | it('should add string parameter', () => { 23 | const result = builder.add('key', 'value').build(); 24 | expect(result).toBe('key=value'); 25 | }); 26 | 27 | it('should add number parameter', () => { 28 | const result = builder.add('count', 10).build(); 29 | expect(result).toBe('count=10'); 30 | }); 31 | 32 | it('should add boolean parameter', () => { 33 | const result = builder.add('active', true).build(); 34 | expect(result).toBe('active=true'); 35 | }); 36 | 37 | it('should skip undefined values', () => { 38 | const result = builder.add('key', undefined as any).build(); 39 | expect(result).toBe(''); 40 | }); 41 | 42 | it('should skip null values', () => { 43 | const result = builder.add('key', null as any).build(); 44 | expect(result).toBe(''); 45 | }); 46 | 47 | it('should chain multiple adds', () => { 48 | const result = builder 49 | .add('first', 'value1') 50 | .add('second', 'value2') 51 | .build(); 52 | expect(result).toBe('first=value1&second=value2'); 53 | }); 54 | }); 55 | 56 | describe('addArray method', () => { 57 | it('should handle regular arrays with comma separation', () => { 58 | const result = builder.addArray('ids', ['1', '2', '3']).build(); 59 | expect(result).toBe('ids=1%2C2%2C3'); 60 | }); 61 | 62 | it('should handle refine parameters with multiple entries', () => { 63 | const result = builder.addArray('refine', ['category=shirts', 'color=blue']).build(); 64 | expect(result).toBe('refine=category%3Dshirts&refine=color%3Dblue'); 65 | }); 66 | 67 | it('should handle mixed string and number arrays', () => { 68 | const result = builder.addArray('values', ['string', 123]).build(); 69 | expect(result).toBe('values=string%2C123'); 70 | }); 71 | 72 | it('should skip empty arrays', () => { 73 | const result = builder.addArray('empty', []).build(); 74 | expect(result).toBe(''); 75 | }); 76 | 77 | it('should skip non-arrays', () => { 78 | const result = builder.addArray('invalid', null as any).build(); 79 | expect(result).toBe(''); 80 | }); 81 | 82 | it('should chain with other methods', () => { 83 | const result = builder 84 | .add('single', 'value') 85 | .addArray('multiple', ['a', 'b']) 86 | .build(); 87 | expect(result).toBe('single=value&multiple=a%2Cb'); 88 | }); 89 | }); 90 | 91 | describe('addFromObject method', () => { 92 | it('should add simple object properties', () => { 93 | const params = { 94 | name: 'test', 95 | count: 5, 96 | active: true, 97 | }; 98 | const result = builder.addFromObject(params).build(); 99 | expect(result).toBe('name=test&count=5&active=true'); 100 | }); 101 | 102 | it('should handle arrays in object', () => { 103 | const params = { 104 | ids: ['1', '2'], 105 | expand: ['details', 'variations'], 106 | }; 107 | const result = builder.addFromObject(params).build(); 108 | expect(result).toBe('ids=1%2C2&expand=details%2Cvariations'); 109 | }); 110 | 111 | it('should handle refine arrays specially', () => { 112 | const params = { 113 | q: 'shirt', 114 | refine: ['category=clothing', 'size=large'], 115 | }; 116 | const result = builder.addFromObject(params).build(); 117 | expect(result).toBe('q=shirt&refine=category%3Dclothing&refine=size%3Dlarge'); 118 | }); 119 | 120 | it('should skip undefined and null values', () => { 121 | const params = { 122 | defined: 'value', 123 | undefined, 124 | null: null, 125 | empty: '', 126 | zero: 0, 127 | }; 128 | const result = builder.addFromObject(params).build(); 129 | expect(result).toBe('defined=value&empty=&zero=0'); 130 | }); 131 | 132 | it('should handle complex object with mixed types', () => { 133 | const params = { 134 | q: 'search term', 135 | count: 20, 136 | start: 0, 137 | expand: ['images', 'prices'], 138 | refine: ['brand=nike', 'color=red'], 139 | active: true, 140 | skip: undefined, 141 | }; 142 | const result = builder.addFromObject(params).build(); 143 | expect(result).toBe( 144 | 'q=search+term&count=20&start=0&expand=images%2Cprices&refine=brand%3Dnike&refine=color%3Dred&active=true', 145 | ); 146 | }); 147 | 148 | it('should chain with other methods', () => { 149 | const result = builder 150 | .add('manual', 'value') 151 | .addFromObject({ auto: 'generated' }) 152 | .add('final', 'last') 153 | .build(); 154 | expect(result).toBe('manual=value&auto=generated&final=last'); 155 | }); 156 | }); 157 | 158 | describe('build method', () => { 159 | it('should return empty string for no parameters', () => { 160 | const result = builder.build(); 161 | expect(result).toBe(''); 162 | }); 163 | 164 | it('should properly encode special characters', () => { 165 | const result = builder.add('special', 'value with spaces & symbols!').build(); 166 | expect(result).toBe('special=value+with+spaces+%26+symbols%21'); 167 | }); 168 | 169 | it('should handle multiple calls to build', () => { 170 | builder.add('key', 'value'); 171 | const first = builder.build(); 172 | const second = builder.build(); 173 | expect(first).toBe(second); 174 | expect(first).toBe('key=value'); 175 | }); 176 | }); 177 | 178 | describe('reset method', () => { 179 | it('should clear all parameters', () => { 180 | const result = builder 181 | .add('first', 'value') 182 | .add('second', 'value') 183 | .reset() 184 | .build(); 185 | expect(result).toBe(''); 186 | }); 187 | 188 | it('should return QueryBuilder instance for chaining', () => { 189 | const result = builder.reset(); 190 | expect(result).toBeInstanceOf(QueryBuilder); 191 | expect(result).toBe(builder); 192 | }); 193 | 194 | it('should allow rebuilding after reset', () => { 195 | const result = builder 196 | .add('old', 'value') 197 | .reset() 198 | .add('new', 'value') 199 | .build(); 200 | expect(result).toBe('new=value'); 201 | }); 202 | }); 203 | 204 | describe('static fromObject method', () => { 205 | it('should create query string from object', () => { 206 | const params = { 207 | search: 'test', 208 | count: 10, 209 | active: true, 210 | }; 211 | const result = QueryBuilder.fromObject(params); 212 | expect(result).toBe('search=test&count=10&active=true'); 213 | }); 214 | 215 | it('should handle arrays correctly', () => { 216 | const params = { 217 | ids: ['1', '2', '3'], 218 | refine: ['category=shirts', 'size=large'], 219 | }; 220 | const result = QueryBuilder.fromObject(params); 221 | expect(result).toBe('ids=1%2C2%2C3&refine=category%3Dshirts&refine=size%3Dlarge'); 222 | }); 223 | 224 | it('should handle empty object', () => { 225 | const result = QueryBuilder.fromObject({}); 226 | expect(result).toBe(''); 227 | }); 228 | 229 | it('should handle complex OCAPI-style parameters', () => { 230 | const params = { 231 | q: 'mens shoes', 232 | count: 25, 233 | start: 50, 234 | expand: ['images', 'variations', 'prices'], 235 | refine: ['category=footwear', 'brand=nike', 'size=10'], 236 | sort: 'price-asc', 237 | currency: 'USD', 238 | locale: 'en_US', 239 | }; 240 | const result = QueryBuilder.fromObject(params); 241 | 242 | expect(result).toContain('q=mens+shoes'); 243 | expect(result).toContain('count=25'); 244 | expect(result).toContain('start=50'); 245 | expect(result).toContain('expand=images%2Cvariations%2Cprices'); 246 | expect(result).toContain('refine=category%3Dfootwear'); 247 | expect(result).toContain('refine=brand%3Dnike'); 248 | expect(result).toContain('refine=size%3D10'); 249 | expect(result).toContain('sort=price-asc'); 250 | expect(result).toContain('currency=USD'); 251 | expect(result).toContain('locale=en_US'); 252 | }); 253 | }); 254 | 255 | describe('edge cases', () => { 256 | it('should handle empty strings', () => { 257 | const result = builder.add('empty', '').build(); 258 | expect(result).toBe('empty='); 259 | }); 260 | 261 | it('should handle zero values', () => { 262 | const result = builder.add('zero', 0).build(); 263 | expect(result).toBe('zero=0'); 264 | }); 265 | 266 | it('should handle false values', () => { 267 | const result = builder.add('false', false).build(); 268 | expect(result).toBe('false=false'); 269 | }); 270 | 271 | it('should handle arrays with empty strings', () => { 272 | const result = builder.addArray('mixed', ['value', '', 'another']).build(); 273 | expect(result).toBe('mixed=value%2C%2Canother'); 274 | }); 275 | 276 | it('should handle Unicode characters', () => { 277 | const result = builder.add('unicode', 'café ñoño 中文').build(); 278 | expect(result).toBe('unicode=caf%C3%A9+%C3%B1o%C3%B1o+%E4%B8%AD%E6%96%87'); 279 | }); 280 | }); 281 | }); 282 | ```