#
tokens: 45376/50000 7/825 files (page 31/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 31 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

--------------------------------------------------------------------------------
/docs/dw_web/FormField.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.web
  2 | 
  3 | # Class FormField
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.web.FormElement
  9 |   - dw.web.FormField
 10 | 
 11 | ## Description
 12 | 
 13 | Represents a field in a form.
 14 | 
 15 | ## Properties
 16 | 
 17 | ### checked
 18 | 
 19 | **Type:** boolean (Read Only)
 20 | 
 21 | Identifies if the current selected state of this field is checked. In case of
 22 |  a boolean field the method directly represent the boolean value. In case
 23 |  of a string or int field, the method returns true if the current value
 24 |  matched with the value specified as "selected-value". In this way a
 25 |  selected status can be as determined for non-boolean fields.
 26 | 
 27 | ### description
 28 | 
 29 | **Type:** String (Read Only)
 30 | 
 31 | An optinal description for the field.
 32 | 
 33 | ### error
 34 | 
 35 | **Type:** String (Read Only)
 36 | 
 37 | The error text that will be shown to the user when the field is
 38 |  invalid. The error messages that may be returned by this method are
 39 |  defined in the form field definition under the following attribute names:
 40 | 
 41 |  
 42 |  missing-error
 43 |  parse-error
 44 |  range-error
 45 |  value-error
 46 |  
 47 | 
 48 |  The framework performs error checks in a specific order, and so if there
 49 |  are multiple errors for a single FormField, the following sequence
 50 |  defines which error is returned:
 51 | 
 52 |  
 53 |  When submitting a form entry, whitespace is first trimmed from user
 54 |  entry and the entry is parsed into native data type (boolean, date,
 55 |  integer, number, or string). A regex, if defined, is also matched against
 56 |  the input. If there is an error while parsing or matching with regex,
 57 |  "parse-error" is set as error.
 58 |  If field was marked as "mandatory" but there is no entry,
 59 |  "missing-error" is returned
 60 |  The min/max and minlength/maxlength checks are performed. If test
 61 |  failed, "range-error" is returned.
 62 |  value-error or form-error are returned when "invalidate()" was called
 63 |  programatically (or pipelet InvalidateFormElement is used)
 64 |  
 65 | 
 66 |  If the field is valid, this method returns null. If no error message was
 67 |  specified in the form field definition, this method also returns null.
 68 | 
 69 | ### htmlValue
 70 | 
 71 | **Type:** String
 72 | 
 73 | The current external string representation of the
 74 |  value in this field.
 75 | 
 76 | ### label
 77 | 
 78 | **Type:** String (Read Only)
 79 | 
 80 | An optional label text for the field.
 81 | 
 82 | ### mandatory
 83 | 
 84 | **Type:** boolean (Read Only)
 85 | 
 86 | Indicates if the field is mandatory.
 87 | 
 88 | ### maxLength
 89 | 
 90 | **Type:** Number (Read Only)
 91 | 
 92 | The maximum length for the form field. A maximum length can
 93 |  be specified for all form data types, but is only used to validate fields
 94 |  of type "string". For other data types the value is just provided as an
 95 |  easy way to dynamically format the user interface. If not specified in
 96 |  the form definition the default minimum length is Integer.MAX_VALUE.
 97 | 
 98 | ### maxValue
 99 | 
100 | **Type:** Object (Read Only)
101 | 
102 | The maximum value for a form field. A maximum value is only
103 |  applicable for fields with the data type "int", "number" and "date".
104 |  If a maximum value was not specified in the form definition the method
105 |  returns null.
106 | 
107 | ### minLength
108 | 
109 | **Type:** Number (Read Only)
110 | 
111 | The minimum length for the form field. A minimum length can
112 |  be specified for all form data types, but is only used to validate fields
113 |  of type "string". For other data types the value is just provided as an
114 |  easy way to dynamically format the user interface. If not specified in
115 |  the form definition the default minimum length is 0.
116 | 
117 | ### minValue
118 | 
119 | **Type:** Object (Read Only)
120 | 
121 | The minimum value for a form field. A minimum value is only
122 |  applicable for fields with the data type "int", "number" and "date".
123 |  If a minimum value was not specified in the form definition the method
124 |  returns null.
125 | 
126 | ### options
127 | 
128 | **Type:** FormFieldOptions
129 | 
130 | A list of possible values for this field. The method
131 |  is typically used to render a selection list or to render radio buttons.
132 | 
133 | ### regEx
134 | 
135 | **Type:** String (Read Only)
136 | 
137 | An optional regular expression pattern, which was set in the form
138 |  definition. A pattern is only used for validation only for string fields.
139 |  If no pattern was set, the method returns null.
140 | 
141 | ### selected
142 | 
143 | **Type:** boolean (Read Only)
144 | 
145 | Identifies if the current selected state of this field is selected. In case of
146 |  a boolean field the method directly represent the boolean value. In case
147 |  of a string or int field, the method returns true if the current value
148 |  matched with the value specified as "selected-value". In this way a
149 |  selected status can be as determined for non-boolean fields.
150 | 
151 | ### selectedOption
152 | 
153 | **Type:** FormFieldOption (Read Only)
154 | 
155 | The selected options or null if the field has no option
156 |  or non is selected.
157 | 
158 | ### selectedOptionObject
159 | 
160 | **Type:** Object (Read Only)
161 | 
162 | The object that was optionally associated with the
163 |  currently selected option.
164 | 
165 | ### type
166 | 
167 | **Type:** Number (Read Only)
168 | 
169 | The method returns the type of the field. The type is one of the
170 |  FIELD_TYPE constants defined in this class.
171 | 
172 | ### value
173 | 
174 | **Type:** Object
175 | 
176 | The internal value representation, which can be a string, a
177 |  number, a boolean or a date.
178 | 
179 | ## Constructor Summary
180 | 
181 | ## Method Summary
182 | 
183 | ### getDescription
184 | 
185 | **Signature:** `getDescription() : String`
186 | 
187 | Returns an optinal description for the field.
188 | 
189 | ### getError
190 | 
191 | **Signature:** `getError() : String`
192 | 
193 | Returns the error text that will be shown to the user when the field is invalid.
194 | 
195 | ### getHtmlValue
196 | 
197 | **Signature:** `getHtmlValue() : String`
198 | 
199 | Returns the current external string representation of the value in this field.
200 | 
201 | ### getLabel
202 | 
203 | **Signature:** `getLabel() : String`
204 | 
205 | Returns an optional label text for the field.
206 | 
207 | ### getMaxLength
208 | 
209 | **Signature:** `getMaxLength() : Number`
210 | 
211 | Returns the maximum length for the form field.
212 | 
213 | ### getMaxValue
214 | 
215 | **Signature:** `getMaxValue() : Object`
216 | 
217 | Returns the maximum value for a form field.
218 | 
219 | ### getMinLength
220 | 
221 | **Signature:** `getMinLength() : Number`
222 | 
223 | Returns the minimum length for the form field.
224 | 
225 | ### getMinValue
226 | 
227 | **Signature:** `getMinValue() : Object`
228 | 
229 | Returns the minimum value for a form field.
230 | 
231 | ### getOptions
232 | 
233 | **Signature:** `getOptions() : FormFieldOptions`
234 | 
235 | Returns a list of possible values for this field.
236 | 
237 | ### getRegEx
238 | 
239 | **Signature:** `getRegEx() : String`
240 | 
241 | Returns an optional regular expression pattern, which was set in the form definition.
242 | 
243 | ### getSelectedOption
244 | 
245 | **Signature:** `getSelectedOption() : FormFieldOption`
246 | 
247 | Returns the selected options or null if the field has no option or non is selected.
248 | 
249 | ### getSelectedOptionObject
250 | 
251 | **Signature:** `getSelectedOptionObject() : Object`
252 | 
253 | Returns the object that was optionally associated with the currently selected option.
254 | 
255 | ### getType
256 | 
257 | **Signature:** `getType() : Number`
258 | 
259 | The method returns the type of the field.
260 | 
261 | ### getValue
262 | 
263 | **Signature:** `getValue() : Object`
264 | 
265 | Returns the internal value representation, which can be a string, a number, a boolean or a date.
266 | 
267 | ### isChecked
268 | 
269 | **Signature:** `isChecked() : boolean`
270 | 
271 | Identifies if the current selected state of this field is checked.
272 | 
273 | ### isMandatory
274 | 
275 | **Signature:** `isMandatory() : boolean`
276 | 
277 | Indicates if the field is mandatory.
278 | 
279 | ### isSelected
280 | 
281 | **Signature:** `isSelected() : boolean`
282 | 
283 | Identifies if the current selected state of this field is selected.
284 | 
285 | ### setHtmlValue
286 | 
287 | **Signature:** `setHtmlValue(htmlValue : String) : void`
288 | 
289 | A form field has two value representations, the HTML value and the plain value.
290 | 
291 | ### setOptions
292 | 
293 | **Signature:** `setOptions(optionValues : Map) : void`
294 | 
295 | The method can be called to update an option list based on the given key and values in the given map.
296 | 
297 | ### setOptions
298 | 
299 | **Signature:** `setOptions(optionValues : Map, begin : Number, end : Number) : void`
300 | 
301 | The method can be called to update an option list based on the given key and values in the given map.
302 | 
303 | ### setOptions
304 | 
305 | **Signature:** `setOptions(optionValues : Iterator, begin : Number, end : Number) : void`
306 | 
307 | The method can be called to update an option list based on the given iterator with objects.
308 | 
309 | ### setOptions
310 | 
311 | **Signature:** `setOptions(optionValues : Iterator) : void`
312 | 
313 | The method can be called to update an option list based on the given iterator with objects.
314 | 
315 | ### setValue
316 | 
317 | **Signature:** `setValue(value : Object) : void`
318 | 
319 | Sets the typed value of the field.
320 | 
321 | ## Method Detail
322 | 
323 | ## Method Details
324 | 
325 | ### getDescription
326 | 
327 | **Signature:** `getDescription() : String`
328 | 
329 | **Description:** Returns an optinal description for the field.
330 | 
331 | **Returns:**
332 | 
333 | an optional description for the field.
334 | 
335 | ---
336 | 
337 | ### getError
338 | 
339 | **Signature:** `getError() : String`
340 | 
341 | **Description:** Returns the error text that will be shown to the user when the field is invalid. The error messages that may be returned by this method are defined in the form field definition under the following attribute names: missing-error parse-error range-error value-error The framework performs error checks in a specific order, and so if there are multiple errors for a single FormField, the following sequence defines which error is returned: When submitting a form entry, whitespace is first trimmed from user entry and the entry is parsed into native data type (boolean, date, integer, number, or string). A regex, if defined, is also matched against the input. If there is an error while parsing or matching with regex, "parse-error" is set as error. If field was marked as "mandatory" but there is no entry, "missing-error" is returned The min/max and minlength/maxlength checks are performed. If test failed, "range-error" is returned. value-error or form-error are returned when "invalidate()" was called programatically (or pipelet InvalidateFormElement is used) If the field is valid, this method returns null. If no error message was specified in the form field definition, this method also returns null.
342 | 
343 | **Returns:**
344 | 
345 | the error text that will be shown to the user when the field is invalid.
346 | 
347 | ---
348 | 
349 | ### getHtmlValue
350 | 
351 | **Signature:** `getHtmlValue() : String`
352 | 
353 | **Description:** Returns the current external string representation of the value in this field.
354 | 
355 | **Returns:**
356 | 
357 | the current external string representation of the value in this field.
358 | 
359 | ---
360 | 
361 | ### getLabel
362 | 
363 | **Signature:** `getLabel() : String`
364 | 
365 | **Description:** Returns an optional label text for the field.
366 | 
367 | **Returns:**
368 | 
369 | an optional label text for the field.
370 | 
371 | ---
372 | 
373 | ### getMaxLength
374 | 
375 | **Signature:** `getMaxLength() : Number`
376 | 
377 | **Description:** Returns the maximum length for the form field. A maximum length can be specified for all form data types, but is only used to validate fields of type "string". For other data types the value is just provided as an easy way to dynamically format the user interface. If not specified in the form definition the default minimum length is Integer.MAX_VALUE.
378 | 
379 | **Returns:**
380 | 
381 | maximum length or MAX_VALUE
382 | 
383 | ---
384 | 
385 | ### getMaxValue
386 | 
387 | **Signature:** `getMaxValue() : Object`
388 | 
389 | **Description:** Returns the maximum value for a form field. A maximum value is only applicable for fields with the data type "int", "number" and "date". If a maximum value was not specified in the form definition the method returns null.
390 | 
391 | **Returns:**
392 | 
393 | maximum value or null
394 | 
395 | ---
396 | 
397 | ### getMinLength
398 | 
399 | **Signature:** `getMinLength() : Number`
400 | 
401 | **Description:** Returns the minimum length for the form field. A minimum length can be specified for all form data types, but is only used to validate fields of type "string". For other data types the value is just provided as an easy way to dynamically format the user interface. If not specified in the form definition the default minimum length is 0.
402 | 
403 | **Returns:**
404 | 
405 | minimum length or 0
406 | 
407 | ---
408 | 
409 | ### getMinValue
410 | 
411 | **Signature:** `getMinValue() : Object`
412 | 
413 | **Description:** Returns the minimum value for a form field. A minimum value is only applicable for fields with the data type "int", "number" and "date". If a minimum value was not specified in the form definition the method returns null.
414 | 
415 | **Returns:**
416 | 
417 | minimum value or null
418 | 
419 | ---
420 | 
421 | ### getOptions
422 | 
423 | **Signature:** `getOptions() : FormFieldOptions`
424 | 
425 | **Description:** Returns a list of possible values for this field. The method is typically used to render a selection list or to render radio buttons.
426 | 
427 | **Returns:**
428 | 
429 | a list of possible values for this field.
430 | 
431 | ---
432 | 
433 | ### getRegEx
434 | 
435 | **Signature:** `getRegEx() : String`
436 | 
437 | **Description:** Returns an optional regular expression pattern, which was set in the form definition. A pattern is only used for validation only for string fields. If no pattern was set, the method returns null.
438 | 
439 | **Returns:**
440 | 
441 | the regular expression used for validation or null
442 | 
443 | ---
444 | 
445 | ### getSelectedOption
446 | 
447 | **Signature:** `getSelectedOption() : FormFieldOption`
448 | 
449 | **Description:** Returns the selected options or null if the field has no option or non is selected.
450 | 
451 | **Returns:**
452 | 
453 | the selected options or null if the field has no option or non is selected.
454 | 
455 | ---
456 | 
457 | ### getSelectedOptionObject
458 | 
459 | **Signature:** `getSelectedOptionObject() : Object`
460 | 
461 | **Description:** Returns the object that was optionally associated with the currently selected option.
462 | 
463 | **Returns:**
464 | 
465 | the object that was optionally associated with the currently selected option.
466 | 
467 | ---
468 | 
469 | ### getType
470 | 
471 | **Signature:** `getType() : Number`
472 | 
473 | **Description:** The method returns the type of the field. The type is one of the FIELD_TYPE constants defined in this class.
474 | 
475 | **Returns:**
476 | 
477 | the type of the form field
478 | 
479 | ---
480 | 
481 | ### getValue
482 | 
483 | **Signature:** `getValue() : Object`
484 | 
485 | **Description:** Returns the internal value representation, which can be a string, a number, a boolean or a date.
486 | 
487 | **Returns:**
488 | 
489 | the internal value representation, which can be a string, a number, a boolean or a date.
490 | 
491 | ---
492 | 
493 | ### isChecked
494 | 
495 | **Signature:** `isChecked() : boolean`
496 | 
497 | **Description:** Identifies if the current selected state of this field is checked. In case of a boolean field the method directly represent the boolean value. In case of a string or int field, the method returns true if the current value matched with the value specified as "selected-value". In this way a selected status can be as determined for non-boolean fields.
498 | 
499 | **Returns:**
500 | 
501 | true if current selected state of this field is checked.
502 | 
503 | ---
504 | 
505 | ### isMandatory
506 | 
507 | **Signature:** `isMandatory() : boolean`
508 | 
509 | **Description:** Indicates if the field is mandatory.
510 | 
511 | **Returns:**
512 | 
513 | true if the field is mandatory, false otherwise.
514 | 
515 | ---
516 | 
517 | ### isSelected
518 | 
519 | **Signature:** `isSelected() : boolean`
520 | 
521 | **Description:** Identifies if the current selected state of this field is selected. In case of a boolean field the method directly represent the boolean value. In case of a string or int field, the method returns true if the current value matched with the value specified as "selected-value". In this way a selected status can be as determined for non-boolean fields.
522 | 
523 | **Returns:**
524 | 
525 | true if current selected state of this field is checked.
526 | 
527 | ---
528 | 
529 | ### setHtmlValue
530 | 
531 | **Signature:** `setHtmlValue(htmlValue : String) : void`
532 | 
533 | **Description:** A form field has two value representations, the HTML value and the plain value. The HTML value is always a string representation of the field value. The plain value is the fully typed and validated field value. The sets the HTML value for a field. The method is typically called from the HTTP POST processing framework. The method than parses, validates and assigns the value to the typed field value (see getValue()). If the value is invalid the typed field value is set to null and the valid flag is set to false. The error property contains an error message for the form.
534 | 
535 | **Parameters:**
536 | 
537 | - `htmlValue`: the HTML value to use.
538 | 
539 | ---
540 | 
541 | ### setOptions
542 | 
543 | **Signature:** `setOptions(optionValues : Map) : void`
544 | 
545 | **Description:** The method can be called to update an option list based on the given key and values in the given map.
546 | 
547 | **Parameters:**
548 | 
549 | - `optionValues`: a Map with the values for the option list
550 | 
551 | ---
552 | 
553 | ### setOptions
554 | 
555 | **Signature:** `setOptions(optionValues : Map, begin : Number, end : Number) : void`
556 | 
557 | **Description:** The method can be called to update an option list based on the given key and values in the given map. The method also expects and index range. This index range only makes sense when the Map is a SortedMap.
558 | 
559 | **Parameters:**
560 | 
561 | - `optionValues`: a Map with the values for the option list.
562 | - `begin`: the index of the first element to use as option value.
563 | - `end`: the last of the last element to use as option value.
564 | 
565 | ---
566 | 
567 | ### setOptions
568 | 
569 | **Signature:** `setOptions(optionValues : Iterator, begin : Number, end : Number) : void`
570 | 
571 | **Description:** The method can be called to update an option list based on the given iterator with objects. The option list is updated using the bindings specified in the form definition. If no bindings are specified in the form definition the elements are interpreted as pure strings.
572 | 
573 | **Parameters:**
574 | 
575 | - `optionValues`: an iterator hows elements are used as option values
576 | - `begin`: the index of the first element to use as option value
577 | - `end`: the last of the last element to use as option value
578 | 
579 | ---
580 | 
581 | ### setOptions
582 | 
583 | **Signature:** `setOptions(optionValues : Iterator) : void`
584 | 
585 | **Description:** The method can be called to update an option list based on the given iterator with objects.
586 | 
587 | **Parameters:**
588 | 
589 | - `optionValues`: an iterator whose elements are used as option values
590 | 
591 | ---
592 | 
593 | ### setValue
594 | 
595 | **Signature:** `setValue(value : Object) : void`
596 | 
597 | **Description:** Sets the typed value of the field. The value is than immediately formatted into the external string representation, which is availble through the getHtmlValue() method. Also the valid flag is set to true. The actual value is not validated against the rules defined in the form definition. The method is typically used to directly set a typed value and to circumvent the validation rules. The type of the argument must match with the type of the field.
598 | 
599 | **Parameters:**
600 | 
601 | - `value`: the value to set.
602 | 
603 | ---
```

--------------------------------------------------------------------------------
/docs/dw_rpc/SOAPUtil.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.rpc
  2 | 
  3 | # Class SOAPUtil
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.rpc.SOAPUtil
  9 | 
 10 | ## Description
 11 | 
 12 | Utility class for working with SOAP web services. This class provides methods for setting SOAP headers and a set of constants representing the supported header names. If you want to use ws-security features, such as signing and encryption, with your RPC-style SOAP web service, use this class to construct a HashMap with security constants and values. Note: this method handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. The following example configures the ws-security actions taken for the request and response to a web service. importPackage( dw.system ); importPackage( dw.util ); importPackage( dw.rpc ); function execute( args : PipelineDictionary ) : Number { var WSU_NS : String = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; try { // define a map with all the secrets var secretsMap : Map = new HashMap(); secretsMap.put("myclientkey", "ckpass"); secretsMap.put("myservicekey", "ckpass"); secretsMap.put("username", "password"); var requestCfg : Map = new HashMap(); // define the ws actions to be performed requestCfg.put(SOAPUtil.WS_ACTION, SOAPUtil.WS_USERNAME_TOKEN + " " + SOAPUtil.WS_TIMESTAMP + " " + SOAPUtil.WS_SIGNATURE + " " + SOAPUtil.WS_ENCRYPT); requestCfg.put(SOAPUtil.WS_USER, "username"); requestCfg.put(SOAPUtil.WS_PASSWORD_TYPE, SOAPUtil.WS_PW_DIGEST ); requestCfg.put(SOAPUtil.WS_SIG_DIGEST_ALGO, "http://www.w3.org/2001/04/xmlenc#sha256" ); // define signature properties // the keystore file has the basename of the WSDL file and the // file extension based on the keystore type (e.g. HelloWorld.jks). // The keystore file has to be placed beside the WSDL file. requestCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks"); requestCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass"); requestCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myclientkey"); requestCfg.put(SOAPUtil.WS_SIGNATURE_USER, "myclientkey"); // define enrcryption properties requestCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks"); requestCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass"); requestCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myservicekey"); requestCfg.put(SOAPUtil.WS_ENCRYPTION_USER, "myservicekey"); requestCfg.put(SOAPUtil.WS_SIGNATURE_PARTS, "{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"); requestCfg.put(SOAPUtil.WS_ENCRYPTION_PARTS,"{Element}{" + WSU_NS + "} Timestamp;"+"{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"); // set the secrets for the callback requestCfg.put(SOAPUtil.WS_SECRETS_MAP, secretsMap); var responseCfg : Map = new HashMap(); // define the ws actions to be performed for the response responseCfg.put(SOAPUtil.WS_ACTION, SOAPUtil.WS_TIMESTAMP + " " + SOAPUtil.WS_SIGNATURE + " " + SOAPUtil.WS_ENCRYPT); // define signature properties responseCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks"); responseCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass"); responseCfg.put(SOAPUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myservicekey"); responseCfg.put(SOAPUtil.WS_SIGNATURE_USER, "myservicekey"); // define decryption properties responseCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks"); responseCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass"); responseCfg.put(SOAPUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myclientkey"); responseCfg.put(SOAPUtil.WS_ENCRYPTION_USER, "myclientkey"); // set the secrets for the callback responseCfg.put(SOAPUtil.WS_SECRETS_MAP, secretsMap); // get the service and stub var helloWorldService : WebReference = webreferences.HelloWorld; var stub : Stub = helloWorldService.defaultService; // set the security SOAPUtil.setWSSecurityConfig(stub, requestCfg, responseCfg); //var h : Hello = new helloWorldService.Hello(); var h = new helloWorldService.com.support.ws.security.test.Hello2(); h.setName('Send Text from client Axis ...'); // call the web service var response = stub.hello2(h); //var response = stub.hello(h); var result = response.getHello2Return(); args.OutStr = result; Logger.error("Hello World We Are SIGNED old version Send Text from client ...", result); return PIPELET_NEXT; } catch (e) { Logger.error("Error in helloWorldRpc.ds is: " + e); return PIPELET_ERROR; } }
 13 | 
 14 | ## Constants
 15 | 
 16 | ### WS_ACTION
 17 | 
 18 | **Type:** String = "action"
 19 | 
 20 | WS-Security action property name. Allowed property values are WS_NO_SECURITY, WS_TIMESTAMP, WS_ENCRYPT, WS_SIGNATURE, WS_USERNAME_TOKEN or a space separated String with multiple values.
 21 | 
 22 | ### WS_ENC_PROP_KEYSTORE_ALIAS
 23 | 
 24 | **Type:** String = "__EncryptionPropKeystoreAlias"
 25 | 
 26 | WS-Security encryption: the encryption/decryption keystore alias name
 27 | 
 28 | ### WS_ENC_PROP_KEYSTORE_PW
 29 | 
 30 | **Type:** String = "__EncryptionPropKeystorePassword"
 31 | 
 32 | WS-Security encryption: the encryption/decryption keystore password
 33 | 
 34 | ### WS_ENC_PROP_KEYSTORE_TYPE
 35 | 
 36 | **Type:** String = "__EncryptionPropKeystoreType"
 37 | 
 38 | WS-Security encryption: the encryption/decryption keystore type ( jks or pkcs12 ), default is jks. Note: the keystore file must have the basename of the WSDL file and the file extension based on the keystore type. For example: MyService.jks. The keystore file must be placed in the same cartridge directory as the WSDL file.
 39 | 
 40 | ### WS_ENCRYPT
 41 | 
 42 | **Type:** String = "Encrypt"
 43 | 
 44 | WS-Security action: encrypt the message. The encryption-specific parameters define how to encrypt, which keys to use, and other parameters.
 45 | 
 46 | ### WS_ENCRYPTION_PARTS
 47 | 
 48 | **Type:** String = "encryptionParts"
 49 | 
 50 | WS-Security encryption: defines which parts of the request are encrypted.
 51 | 
 52 | ### WS_ENCRYPTION_USER
 53 | 
 54 | **Type:** String = "encryptionUser"
 55 | 
 56 | WS-Security encryption: the user's name for encryption.
 57 | 
 58 | ### WS_NO_SECURITY
 59 | 
 60 | **Type:** String = "NoSecurity"
 61 | 
 62 | WS-Security action: no security
 63 | 
 64 | ### WS_PASSWORD_TYPE
 65 | 
 66 | **Type:** String = "passwordType"
 67 | 
 68 | WS-Security password type: parameter for UsernameToken action to define the encoding of the password. Allowed values are PW_DIGEST or PW_TEXT.
 69 | 
 70 | ### WS_PW_DIGEST
 71 | 
 72 | **Type:** String = "PasswordDigest"
 73 | 
 74 | WS-Security password of type digest: use a password digest to send the password information.
 75 | 
 76 | ### WS_PW_TEXT
 77 | 
 78 | **Type:** String = "PasswordText"
 79 | 
 80 | WS-Security password of type text: send the password information in clear text.
 81 | 
 82 | ### WS_SECRETS_MAP
 83 | 
 84 | **Type:** String = "__SecretsMap"
 85 | 
 86 | A secrets map with the username/password entries is needed to create the password callback object.
 87 | 
 88 | ### WS_SIG_DIGEST_ALGO
 89 | 
 90 | **Type:** String = "signatureDigestAlgorithm"
 91 | 
 92 | WS-Security signature: sets the signature digest algorithm to use.
 93 | 
 94 | ### WS_SIG_PROP_KEYSTORE_ALIAS
 95 | 
 96 | **Type:** String = "__SignaturePropKeystoreAlias"
 97 | 
 98 | WS-Security signature: the signature keystore alias name
 99 | 
100 | ### WS_SIG_PROP_KEYSTORE_PW
101 | 
102 | **Type:** String = "__SignaturePropKeystorePassword"
103 | 
104 | WS-Security signature: the signature keystore password.
105 | 
106 | ### WS_SIG_PROP_KEYSTORE_TYPE
107 | 
108 | **Type:** String = "__SignaturePropKeystoreType"
109 | 
110 | WS-Security: the signature keystore type ( jks or pkcs12 ). The default is jks. Note: The keystore file must have the basename of the WSDL file and the file extension of the keystore type. For example: MyService.jks. The keystore file must be placed in the same cartridge directory as the WSDL file.
111 | 
112 | ### WS_SIGNATURE
113 | 
114 | **Type:** String = "Signature"
115 | 
116 | WS-Security action: sign the message. The signature-specific parameters define how to sign, which keys to use, and other parameters.
117 | 
118 | ### WS_SIGNATURE_PARTS
119 | 
120 | **Type:** String = "signatureParts"
121 | 
122 | WS-Security signature: defines which parts of the request are signed.
123 | 
124 | ### WS_SIGNATURE_USER
125 | 
126 | **Type:** String = "signatureUser"
127 | 
128 | WS-Security signature: the user's name for signature.
129 | 
130 | ### WS_TIMESTAMP
131 | 
132 | **Type:** String = "Timestamp"
133 | 
134 | WS-Security action: add a timestamp to the security header.
135 | 
136 | ### WS_USER
137 | 
138 | **Type:** String = "user"
139 | 
140 | WS-Security user name.
141 | 
142 | ### WS_USERNAME_TOKEN
143 | 
144 | **Type:** String = "UsernameToken"
145 | 
146 | WS-Security action: add a UsernameToken identification.
147 | 
148 | ## Properties
149 | 
150 | ## Constructor Summary
151 | 
152 | SOAPUtil()
153 | 
154 | ## Method Summary
155 | 
156 | ### getHTTPRequestHeader
157 | 
158 | **Signature:** `static getHTTPRequestHeader(svc : Object, key : String) : String`
159 | 
160 | Returns an HTTP request header property value using the specified key.
161 | 
162 | ### getHTTPResponseHeader
163 | 
164 | **Signature:** `static getHTTPResponseHeader(svc : Object, key : String) : String`
165 | 
166 | Returns an HTTP response header property value using the specified key.
167 | 
168 | ### setHeader
169 | 
170 | **Signature:** `static setHeader(svc : Object, xml : String) : void`
171 | 
172 | Sets a new SOAPHeaderElement in the SOAP request with the namespace of the XML content.
173 | 
174 | ### setHeader
175 | 
176 | **Signature:** `static setHeader(svc : Object, xml : String, mustUnderstand : boolean) : void`
177 | 
178 | Sets a new SOAPHeaderElement in the SOAP request with the namespace of the XML content.
179 | 
180 | ### setHeader
181 | 
182 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String) : void`
183 | 
184 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
185 | 
186 | ### setHeader
187 | 
188 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String, mustUnderstand : boolean) : void`
189 | 
190 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
191 | 
192 | ### setHeader
193 | 
194 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String, mustUnderstand : boolean, actor : String) : void`
195 | 
196 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
197 | 
198 | ### setHeader
199 | 
200 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object) : void`
201 | 
202 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
203 | 
204 | ### setHeader
205 | 
206 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object, mustUnderstand : boolean) : void`
207 | 
208 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
209 | 
210 | ### setHeader
211 | 
212 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object, mustUnderstand : boolean, actor : String) : void`
213 | 
214 | Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
215 | 
216 | ### setHTTPRequestHeader
217 | 
218 | **Signature:** `static setHTTPRequestHeader(svc : Object, key : String, value : String) : void`
219 | 
220 | Sets an HTTP request header property using the specified key and value.
221 | 
222 | ### setWSSecurityConfig
223 | 
224 | **Signature:** `static setWSSecurityConfig(svc : Object, requestConfigMap : Object, responseConfigMap : Object) : void`
225 | 
226 | Sets the WS-Security configuration for the request and response based on the constants defined.
227 | 
228 | ## Constructor Detail
229 | 
230 | ## Method Detail
231 | 
232 | ## Method Details
233 | 
234 | ### getHTTPRequestHeader
235 | 
236 | **Signature:** `static getHTTPRequestHeader(svc : Object, key : String) : String`
237 | 
238 | **Description:** Returns an HTTP request header property value using the specified key. Null is returned if the key does not represent an HTTP header property.
239 | 
240 | **Deprecated:**
241 | 
242 | use webreferences2 instead
243 | 
244 | **Parameters:**
245 | 
246 | - `svc`: a service stub returned from getService().
247 | - `key`: the header property key.
248 | 
249 | **Returns:**
250 | 
251 | an HTTP request header property value using the specified key or null.
252 | 
253 | ---
254 | 
255 | ### getHTTPResponseHeader
256 | 
257 | **Signature:** `static getHTTPResponseHeader(svc : Object, key : String) : String`
258 | 
259 | **Description:** Returns an HTTP response header property value using the specified key. Null is returned if the key does not represent an HTTP response header property.
260 | 
261 | **Deprecated:**
262 | 
263 | use webreferences2 instead
264 | 
265 | **Parameters:**
266 | 
267 | - `svc`: a service stub returned from getService().
268 | - `key`: the header property key.
269 | 
270 | **Returns:**
271 | 
272 | an HTTP response header property value using the specified key or null.
273 | 
274 | ---
275 | 
276 | ### setHeader
277 | 
278 | **Signature:** `static setHeader(svc : Object, xml : String) : void`
279 | 
280 | **Description:** Sets a new SOAPHeaderElement in the SOAP request with the namespace of the XML content.
281 | 
282 | **Deprecated:**
283 | 
284 | use webreferences2 instead
285 | 
286 | **Parameters:**
287 | 
288 | - `svc`: a service stub returned from getService()
289 | - `xml`: a string with arbitrary XML content
290 | 
291 | ---
292 | 
293 | ### setHeader
294 | 
295 | **Signature:** `static setHeader(svc : Object, xml : String, mustUnderstand : boolean) : void`
296 | 
297 | **Description:** Sets a new SOAPHeaderElement in the SOAP request with the namespace of the XML content.
298 | 
299 | **Deprecated:**
300 | 
301 | use webreferences2 instead
302 | 
303 | **Parameters:**
304 | 
305 | - `svc`: a service stub returned from getService()
306 | - `xml`: a string with arbitrary XML content
307 | - `mustUnderstand`: sets the SOAP header attribute 'mustUnderstand'
308 | 
309 | ---
310 | 
311 | ### setHeader
312 | 
313 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String) : void`
314 | 
315 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
316 | 
317 | **Deprecated:**
318 | 
319 | use webreferences2 instead
320 | 
321 | **Parameters:**
322 | 
323 | - `svc`: a service stub returned from getService()
324 | - `namespace`: the namespace of the header element
325 | - `name`: the element name for the header element
326 | - `xml`: a string with arbitrary XML content
327 | 
328 | ---
329 | 
330 | ### setHeader
331 | 
332 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String, mustUnderstand : boolean) : void`
333 | 
334 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
335 | 
336 | **Deprecated:**
337 | 
338 | use webreferences2 instead
339 | 
340 | **Parameters:**
341 | 
342 | - `svc`: a service stub returned from getService()
343 | - `namespace`: the namespace of the header element
344 | - `name`: the element name for the header element
345 | - `xml`: a string with arbitrary XML content
346 | - `mustUnderstand`: sets the SOAP header attribute mustUnderstand
347 | 
348 | ---
349 | 
350 | ### setHeader
351 | 
352 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : String, mustUnderstand : boolean, actor : String) : void`
353 | 
354 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
355 | 
356 | **Deprecated:**
357 | 
358 | use webreferences2 instead
359 | 
360 | **Parameters:**
361 | 
362 | - `svc`: a service stub returned from getService()
363 | - `namespace`: the namespace of the header element
364 | - `name`: the element name for the header element
365 | - `xml`: a string with arbitrary XML content
366 | - `mustUnderstand`: sets the SOAP header attribute mustUnderstand
367 | - `actor`: the SOAP actor, which should be set for this header element. null removes any actor.
368 | 
369 | ---
370 | 
371 | ### setHeader
372 | 
373 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object) : void`
374 | 
375 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
376 | 
377 | **Deprecated:**
378 | 
379 | use webreferences2 instead
380 | 
381 | **Parameters:**
382 | 
383 | - `svc`: a service stub returned from getService()
384 | - `namespace`: the namespace of the header element
385 | - `name`: the element name for the header element
386 | - `xml`: a E4X XML object
387 | 
388 | ---
389 | 
390 | ### setHeader
391 | 
392 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object, mustUnderstand : boolean) : void`
393 | 
394 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it.
395 | 
396 | **Deprecated:**
397 | 
398 | use webreferences2 instead
399 | 
400 | **Parameters:**
401 | 
402 | - `svc`: a service stub returned from getService()
403 | - `namespace`: the namespace of the header element
404 | - `name`: the element name for the header element
405 | - `xml`: a E4X XML object
406 | - `mustUnderstand`: sets the SOAP header attribute mustUnderstand
407 | 
408 | ---
409 | 
410 | ### setHeader
411 | 
412 | **Signature:** `static setHeader(svc : Object, namespace : String, name : String, xml : Object, mustUnderstand : boolean, actor : String) : void`
413 | 
414 | **Description:** Creates a new SOAPHeaderElement with the name and namespace and places the given XML into it. var usernameToken : XML = <wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Username>{merchantID}</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"> {merchantPassword} </wsse:Password> </wsse:UsernameToken> SOAPUtil.setHeader( service, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", usernameToken, true, null
415 | 
416 | **Deprecated:**
417 | 
418 | use webreferences2 instead
419 | 
420 | **Parameters:**
421 | 
422 | - `svc`: a service stub returned from getService()
423 | - `namespace`: the namespace of the header element
424 | - `name`: the element name for the header element
425 | - `xml`: a E4X XML object
426 | - `mustUnderstand`: sets the SOAP header attribute 'mustUnderstand'
427 | - `actor`: the SOAP actor, which should be set for this header element. null removes any actor.
428 | 
429 | ---
430 | 
431 | ### setHTTPRequestHeader
432 | 
433 | **Signature:** `static setHTTPRequestHeader(svc : Object, key : String, value : String) : void`
434 | 
435 | **Description:** Sets an HTTP request header property using the specified key and value.
436 | 
437 | **Deprecated:**
438 | 
439 | use webreferences2 instead
440 | 
441 | **Parameters:**
442 | 
443 | - `svc`: a service stub returned from getService().
444 | - `key`: the header property key.
445 | - `value`: the header property value. If the value is null, the property identified by the key is removed from the HTTP request header.
446 | 
447 | ---
448 | 
449 | ### setWSSecurityConfig
450 | 
451 | **Signature:** `static setWSSecurityConfig(svc : Object, requestConfigMap : Object, responseConfigMap : Object) : void`
452 | 
453 | **Description:** Sets the WS-Security configuration for the request and response based on the constants defined.
454 | 
455 | **Deprecated:**
456 | 
457 | use webreferences2 instead
458 | 
459 | **Parameters:**
460 | 
461 | - `svc`: a service stub returned from getService()
462 | - `requestConfigMap`: the WS-Security request config
463 | - `responseConfigMap`: the WS-Security response config
464 | 
465 | ---
```

--------------------------------------------------------------------------------
/docs/dw_campaign/PromotionMgr.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.campaign
  2 | 
  3 | # Class PromotionMgr
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.campaign.PromotionMgr
  9 | 
 10 | ## Description
 11 | 
 12 | PromotionMgr is used to access campaigns and promotion definitions, display active or upcoming promotions in a storefront, and to calculate and apply promotional discounts to line item containers. To access campaign and promotion definitions, use methods getCampaigns(), getCampaign(String), getPromotions() and getPromotion(String). To display active or upcoming promotions in the storefront, e.g. on product pages, various methods are provided. getActivePromotions() returns a PromotionPlan with all enabled promotions scheduled for now. getActiveCustomerPromotions() returns a PromotionPlan with all enabled promotions scheduled for now and applicable for the session currency, current customer and active source code. getUpcomingPromotions(Number) returns a PromotionPlan with all promotions that are enabled, not scheduled for now, but scheduled for any time between now and now + previewTime(hours). Applying promotions to line item containers is a 3-step process, and PromotionMgr provides methods supporting each of these steps. Convenience methods can be used that execute all three steps at once, or the steps can be executed individually if you need to customize the output of the steps due to specific business rules. Step 1 - calculate active customer promotions: Determine the active promotions for the session currency, current customer, source code and redeemed coupons. Step 2 - calculate applicable discounts: Based on the active promotions determined in step 1, applicable discounts are calculated. Step 3 - apply discounts: applicable discounts are applied to a line item container by creating price adjustment line items. The simpliest way to execute steps 1-3 is to use method applyDiscounts(LineItemCtnr). The method identifies all active customer promotions, calculates and applies applicable discounts. Due to specific business rules it might be necessary to manipulate the set of applicable discounts calculated by the promotion engine before applying them to the line item container. To implement such a scenario, you want to use method getDiscounts(LineItemCtnr), which identifies all active customer promotions, and calculates and returns applicable discounts in an instance of DiscountPlan. The discount plan contains a description for all applicable discounts for the specified line item container, and you can remove discounts from it if necessary. The customized discount plan can then be passed on for application by using method applyDiscounts(DiscountPlan). Due to specific business rules it might be necessary to manipulate the set of active customer promotions before calculating applicable discounts from it. For example, you might want to add promotions to the plan or remove promotions from it. You want to use method getActiveCustomerPromotions() and getActiveCustomerPromotions(Boolean), which identifies all active customer promotions and returns an instance of PromotionPlan. You can add promotions to the promotion plan or remove promotions from the plan. The customized promotion plan can then be passed on to calculate applicable discounts by using method getDiscounts(LineItemCtnr, PromotionPlan).
 13 | 
 14 | ## Properties
 15 | 
 16 | ### activeCustomerPromotions
 17 | 
 18 | **Type:** PromotionPlan (Read Only)
 19 | 
 20 | All promotions scheduled for now and applicable for the
 21 |  session currency, current customer, source code, or presented coupons.
 22 | 
 23 |  The active promotions are returned in an instance of
 24 |  PromotionPlan. The promotion plan contains all
 25 |  promotions assigned to any customer group of the current customer, the
 26 |  current source code, or coupons in the current session basket.
 27 | 
 28 | ### activePromotions
 29 | 
 30 | **Type:** PromotionPlan (Read Only)
 31 | 
 32 | All promotions scheduled for now, and applicable for the
 33 |  session currency but regardless of current customer or source code.
 34 |  The active promotions are returned in an instance of PromotionPlan.
 35 | 
 36 | ### campaigns
 37 | 
 38 | **Type:** Collection (Read Only)
 39 | 
 40 | All campaigns of the current site in no specific order.
 41 | 
 42 | ### promotions
 43 | 
 44 | **Type:** Collection (Read Only)
 45 | 
 46 | All promotions of the current site in no specific order.
 47 | 
 48 | ## Constructor Summary
 49 | 
 50 | ## Method Summary
 51 | 
 52 | ### applyDiscounts
 53 | 
 54 | **Signature:** `static applyDiscounts(lineItemCtnr : LineItemCtnr) : void`
 55 | 
 56 | Identifies active promotions, calculates the applicable discounts from these promotions and applies these discounts to the specified line item container.
 57 | 
 58 | ### applyDiscounts
 59 | 
 60 | **Signature:** `static applyDiscounts(discountPlan : DiscountPlan) : void`
 61 | 
 62 | Applies the discounts contained in the specified discount plan to the line item container associated with the discount plan.
 63 | 
 64 | ### getActiveCustomerPromotions
 65 | 
 66 | **Signature:** `static getActiveCustomerPromotions() : PromotionPlan`
 67 | 
 68 | Returns all promotions scheduled for now and applicable for the session currency, current customer, source code, or presented coupons. The active promotions are returned in an instance of PromotionPlan.
 69 | 
 70 | ### getActiveCustomerPromotions
 71 | 
 72 | **Signature:** `static getActiveCustomerPromotions(ignoreCouponCondition : boolean) : PromotionPlan`
 73 | 
 74 | Returns all promotions scheduled for now and applicable for the session currency, current customer, source code, or presented coupons.
 75 | 
 76 | ### getActiveCustomerPromotionsForCampaign
 77 | 
 78 | **Signature:** `static getActiveCustomerPromotionsForCampaign(campaign : Campaign, from : Date, to : Date) : PromotionPlan`
 79 | 
 80 | Returns all promotions assigned to the passed campaign, which are active at some point within the specified date range, and are applicable for the current customer, source code, or presented coupons.
 81 | 
 82 | ### getActivePromotions
 83 | 
 84 | **Signature:** `static getActivePromotions() : PromotionPlan`
 85 | 
 86 | Returns all promotions scheduled for now, and applicable for the session currency but regardless of current customer or source code. The active promotions are returned in an instance of PromotionPlan.
 87 | 
 88 | ### getActivePromotionsForCampaign
 89 | 
 90 | **Signature:** `static getActivePromotionsForCampaign(campaign : Campaign, from : Date, to : Date) : PromotionPlan`
 91 | 
 92 | Returns all promotions assigned to the passed campaign which are active at some point within the specified date range.
 93 | 
 94 | ### getCampaign
 95 | 
 96 | **Signature:** `static getCampaign(id : String) : Campaign`
 97 | 
 98 | Returns the campaign identified by the specified ID.
 99 | 
100 | ### getCampaigns
101 | 
102 | **Signature:** `static getCampaigns() : Collection`
103 | 
104 | Returns all campaigns of the current site in no specific order.
105 | 
106 | ### getDiscounts
107 | 
108 | **Signature:** `static getDiscounts(lineItemCtnr : LineItemCtnr) : DiscountPlan`
109 | 
110 | Returns the discounts applicable for the current customer, active source code and specified line item container.
111 | 
112 | ### getDiscounts
113 | 
114 | **Signature:** `static getDiscounts(lineItemCtnr : LineItemCtnr, promotionPlan : PromotionPlan) : DiscountPlan`
115 | 
116 | Returns the discounts applicable for the current customer, active source code and specified line item container, based on the specified promotion plan.
117 | 
118 | ### getPromotion
119 | 
120 | **Signature:** `static getPromotion(id : String) : Promotion`
121 | 
122 | Returns the promotion identified by the specified ID.
123 | 
124 | ### getPromotions
125 | 
126 | **Signature:** `static getPromotions() : Collection`
127 | 
128 | Returns all promotions of the current site in no specific order.
129 | 
130 | ### getUpcomingCustomerPromotions
131 | 
132 | **Signature:** `static getUpcomingCustomerPromotions(previewTime : Number) : PromotionPlan`
133 | 
134 | Returns all promotions currently inactive, but scheduled for any time between now and now + previewTime(hours), and which are applicable based on the current customer, source code, and coupons in the current basket. The parameter previewTime specifies for how many hours promotions should be previewed.
135 | 
136 | ### getUpcomingPromotions
137 | 
138 | **Signature:** `static getUpcomingPromotions(previewTime : Number) : PromotionPlan`
139 | 
140 | Returns all promotions currently inactive, but scheduled for any time between now and now + previewTime(hours).
141 | 
142 | ## Method Detail
143 | 
144 | ## Method Details
145 | 
146 | ### applyDiscounts
147 | 
148 | **Signature:** `static applyDiscounts(lineItemCtnr : LineItemCtnr) : void`
149 | 
150 | **Description:** Identifies active promotions, calculates the applicable discounts from these promotions and applies these discounts to the specified line item container. As a result of this method, the specified line item container contains price adjustments and/or bonus product line items for all applicable discounts. The method also removes price adjustment and/or bonus product line items from the line item container if the related to promotions/discounts are no longer applicable.
151 | 
152 | **Parameters:**
153 | 
154 | - `lineItemCtnr`: Line item ctnr to apply promotions on
155 | 
156 | ---
157 | 
158 | ### applyDiscounts
159 | 
160 | **Signature:** `static applyDiscounts(discountPlan : DiscountPlan) : void`
161 | 
162 | **Description:** Applies the discounts contained in the specified discount plan to the line item container associated with the discount plan. As a result of this method, the specified line item container contains price adjustments and/or bonus product line items for all discounts contained in the specified discount plan. The method also removes price adjustment and/or bonus product line items from the line item container if the specified discount plan does not contain the related discount. Note that the method does not evaluate if the discounts in the specified discount plan are applicable nor that the promotions related to these discounts are active.
163 | 
164 | **Parameters:**
165 | 
166 | - `discountPlan`: Discount plan to apply to associated line item container
167 | 
168 | **See Also:**
169 | 
170 | getDiscounts(LineItemCtnr)
171 | applyDiscounts(LineItemCtnr)
172 | 
173 | ---
174 | 
175 | ### getActiveCustomerPromotions
176 | 
177 | **Signature:** `static getActiveCustomerPromotions() : PromotionPlan`
178 | 
179 | **Description:** Returns all promotions scheduled for now and applicable for the session currency, current customer, source code, or presented coupons. The active promotions are returned in an instance of PromotionPlan. The promotion plan contains all promotions assigned to any customer group of the current customer, the current source code, or coupons in the current session basket.
180 | 
181 | **Returns:**
182 | 
183 | PromotionPlan with active customer promotions
184 | 
185 | **See Also:**
186 | 
187 | getActivePromotions()
188 | 
189 | ---
190 | 
191 | ### getActiveCustomerPromotions
192 | 
193 | **Signature:** `static getActiveCustomerPromotions(ignoreCouponCondition : boolean) : PromotionPlan`
194 | 
195 | **Description:** Returns all promotions scheduled for now and applicable for the session currency, current customer, source code, or presented coupons. The active promotions are returned in an instance of PromotionPlan. The promotion plan contains all promotions assigned to any customer group of the current customer, the current source code, or coupons in the current session basket.
196 | 
197 | **Parameters:**
198 | 
199 | - `ignoreCouponCondition`: true if coupon condition will be ignored when get active promotions.
200 | 
201 | **Returns:**
202 | 
203 | PromotionPlan with active customer promotions
204 | 
205 | ---
206 | 
207 | ### getActiveCustomerPromotionsForCampaign
208 | 
209 | **Signature:** `static getActiveCustomerPromotionsForCampaign(campaign : Campaign, from : Date, to : Date) : PromotionPlan`
210 | 
211 | **Description:** Returns all promotions assigned to the passed campaign, which are active at some point within the specified date range, and are applicable for the current customer, source code, or presented coupons. A promotion must be active for an actual time period within the passed date range, and not just a single point. The passed campaign and dates must not be null or an exception is thrown. It is valid for the from and to dates to be in the past. If an invalid time range is specified (i.e. from is after to), the returned PromotionPlan will be empty.
212 | 
213 | **Parameters:**
214 | 
215 | - `campaign`: The campaign, must not be null or an exception is thrown.
216 | - `from`: The start of the date range to consider. If null, this signifies an open ended time period.
217 | - `to`: The end of the date range to consider. If null, this signifies an open ended time period.
218 | 
219 | **Returns:**
220 | 
221 | PromotionPlan with promotions matching all the criteria.
222 | 
223 | ---
224 | 
225 | ### getActivePromotions
226 | 
227 | **Signature:** `static getActivePromotions() : PromotionPlan`
228 | 
229 | **Description:** Returns all promotions scheduled for now, and applicable for the session currency but regardless of current customer or source code. The active promotions are returned in an instance of PromotionPlan.
230 | 
231 | **Returns:**
232 | 
233 | PromotionPlan with active promotions
234 | 
235 | **See Also:**
236 | 
237 | getActiveCustomerPromotions()
238 | 
239 | ---
240 | 
241 | ### getActivePromotionsForCampaign
242 | 
243 | **Signature:** `static getActivePromotionsForCampaign(campaign : Campaign, from : Date, to : Date) : PromotionPlan`
244 | 
245 | **Description:** Returns all promotions assigned to the passed campaign which are active at some point within the specified date range. A promotion must be active for an actual time period within the passed date range, and not just a single point. A promotion must be applicable for the session currency. This method considers only the enabled flags and time conditions of the promotion and campaign. It does not consider whether the current customer satisfies the qualifiers of the promotion (customer group, source code, or coupon). The passed campaign and dates must not be null or an exception is thrown. It is valid for the from and/or to dates to be in the past. If an invalid time range is specified (i.e. from is after to), the returned PromotionPlan will be empty.
246 | 
247 | **Parameters:**
248 | 
249 | - `campaign`: The campaign. Must not be null.
250 | - `from`: The start of the date range to consider. Must not be null
251 | - `to`: The end of the date range to consider. Must not be null.
252 | 
253 | **Returns:**
254 | 
255 | PromotionPlan with promotions matching all the criteria.
256 | 
257 | ---
258 | 
259 | ### getCampaign
260 | 
261 | **Signature:** `static getCampaign(id : String) : Campaign`
262 | 
263 | **Description:** Returns the campaign identified by the specified ID.
264 | 
265 | **Parameters:**
266 | 
267 | - `id`: Campaign ID
268 | 
269 | **Returns:**
270 | 
271 | Campaign or null if not found
272 | 
273 | ---
274 | 
275 | ### getCampaigns
276 | 
277 | **Signature:** `static getCampaigns() : Collection`
278 | 
279 | **Description:** Returns all campaigns of the current site in no specific order.
280 | 
281 | **Returns:**
282 | 
283 | All campaigns of current site
284 | 
285 | ---
286 | 
287 | ### getDiscounts
288 | 
289 | **Signature:** `static getDiscounts(lineItemCtnr : LineItemCtnr) : DiscountPlan`
290 | 
291 | **Description:** Returns the discounts applicable for the current customer, active source code and specified line item container. This method identifies all active promotions assigned to the customer groups of the current customer, the active source code and any coupon contained in the specified line item container, and calculates applicable discounts from these promotions. The applicable discounts are contained in the returned instance of DiscountPlan.
292 | 
293 | **Parameters:**
294 | 
295 | - `lineItemCtnr`: Line item container
296 | 
297 | **Returns:**
298 | 
299 | Discount plan with applicable discounts
300 | 
301 | ---
302 | 
303 | ### getDiscounts
304 | 
305 | **Signature:** `static getDiscounts(lineItemCtnr : LineItemCtnr, promotionPlan : PromotionPlan) : DiscountPlan`
306 | 
307 | **Description:** Returns the discounts applicable for the current customer, active source code and specified line item container, based on the specified promotion plan. This method calculates applicable discounts from the promotions in the specified promotion plan. Note that any promotion in the promotion plan that is inactive, or not applicable for the current customer or source code, is being ignored. The applicable discounts are contained in the returned instance of DiscountPlan.
308 | 
309 | **Parameters:**
310 | 
311 | - `lineItemCtnr`: Line item container
312 | - `promotionPlan`: Promotion plan with active promotions
313 | 
314 | **Returns:**
315 | 
316 | Discount plan with applicable discounts
317 | 
318 | ---
319 | 
320 | ### getPromotion
321 | 
322 | **Signature:** `static getPromotion(id : String) : Promotion`
323 | 
324 | **Description:** Returns the promotion identified by the specified ID. The same logical promotion may be assigned to multiple campaigns or AB-tests. In this case, the system returns the instance of highest rank. Some attributes of the returned Promotion may vary based on exactly which instance is returned. This method returns null if there is no promotion in the system with the given ID, or if a promotion with the given ID exists but it is not assigned to any campaign or AB-test.
325 | 
326 | **Parameters:**
327 | 
328 | - `id`: Promotion ID
329 | 
330 | **Returns:**
331 | 
332 | Promotion or null if not found
333 | 
334 | ---
335 | 
336 | ### getPromotions
337 | 
338 | **Signature:** `static getPromotions() : Collection`
339 | 
340 | **Description:** Returns all promotions of the current site in no specific order.
341 | 
342 | **Returns:**
343 | 
344 | All promotions of current site
345 | 
346 | ---
347 | 
348 | ### getUpcomingCustomerPromotions
349 | 
350 | **Signature:** `static getUpcomingCustomerPromotions(previewTime : Number) : PromotionPlan`
351 | 
352 | **Description:** Returns all promotions currently inactive, but scheduled for any time between now and now + previewTime(hours), and which are applicable based on the current customer, source code, and coupons in the current basket. The parameter previewTime specifies for how many hours promotions should be previewed.
353 | 
354 | **Parameters:**
355 | 
356 | - `previewTime`: Preview time in hours
357 | 
358 | **Returns:**
359 | 
360 | PromotionPlan with active promotions
361 | 
362 | **See Also:**
363 | 
364 | getActivePromotions()
365 | 
366 | ---
367 | 
368 | ### getUpcomingPromotions
369 | 
370 | **Signature:** `static getUpcomingPromotions(previewTime : Number) : PromotionPlan`
371 | 
372 | **Description:** Returns all promotions currently inactive, but scheduled for any time between now and now + previewTime(hours). The upcoming promotions are returned in an instance of PromotionPlan and might not necessarily be applicable for the current customer or source code. The parameter previewTime specifies for how many hours promotions should be previewed.
373 | 
374 | **Parameters:**
375 | 
376 | - `previewTime`: Preview time in hours
377 | 
378 | **Returns:**
379 | 
380 | PromotionPlan with active promotions
381 | 
382 | **See Also:**
383 | 
384 | getActivePromotions()
385 | 
386 | ---
```

--------------------------------------------------------------------------------
/docs/dw_catalog/ProductInventoryRecord.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.catalog
  2 | 
  3 | # Class ProductInventoryRecord
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.catalog.ProductInventoryRecord
 11 | 
 12 | ## Description
 13 | 
 14 | The ProductInventoryRecord holds information about a Product's inventory, and availability. When using Omnichannel Inventory (OCI): All ProductInventoryRecord properties are read-only. Calling any setter method throws an IllegalStateException. The ProductInventoryRecord class does not support custom properties. isPerpetual() and isPreorderable() always return false.
 15 | 
 16 | ## Properties
 17 | 
 18 | ### allocation
 19 | 
 20 | **Type:** Quantity
 21 | 
 22 | The allocation quantity that is currently set. The quantity unit is the same unit as the product itself.
 23 |  
 24 |  When using OCI, returns the physically available quantity. Corresponds to the On Hand quantity in OCI.
 25 | 
 26 | ### allocationResetDate
 27 | 
 28 | **Type:** Date (Read Only)
 29 | 
 30 | The date the allocation quantity was initialized or reset.
 31 |  
 32 |  When using OCI, corresponds to the Effective Date in OCI. The value can be null.
 33 | 
 34 | ### ATS
 35 | 
 36 | **Type:** Quantity (Read Only)
 37 | 
 38 | The quantity of items available to sell (ATS). This is calculated as the allocation
 39 |  (getAllocation()) plus the preorderBackorderAllocation (getPreorderBackorderAllocation()) minus
 40 |  the turnover (getTurnover()) minus the on order quantity (getOnOrder()).
 41 |  
 42 |  When using OCI, corresponds to the ATO (Available To Order) quantity in OCI.
 43 | 
 44 | ### backorderable
 45 | 
 46 | **Type:** boolean
 47 | 
 48 | Determines if the product is backorderable.
 49 |  
 50 |  When using OCI, returns true if the product has at least one Future quantity in OCI.
 51 | 
 52 | ### custom
 53 | 
 54 | **Type:** CustomAttributes (Read Only)
 55 | 
 56 | The custom attributes for this object. The returned object is used for retrieving and storing attribute
 57 |  values. See CustomAttributes for a detailed example of the syntax for working with custom
 58 |  attributes.
 59 |  
 60 |  When using Omnichannel Inventory (OCI), this class doesn't support custom attributes. If OCI is enabled, then
 61 |  attempting to set or modify a custom attribute value throws an UnsupportedOperationException.
 62 | 
 63 | ### inStockDate
 64 | 
 65 | **Type:** Date
 66 | 
 67 | The date that the item is expected to be in stock.
 68 |  
 69 |  When using OCI, returns the date of the earliest Future quantity. If the product has no Future quantities, it
 70 |  returns null.
 71 | 
 72 | ### onHand
 73 | 
 74 | **Type:** Quantity (Read Only)
 75 | 
 76 | The on-hand quantity, the actual quantity of available (on-hand) items.
 77 | 
 78 | ### onOrder
 79 | 
 80 | **Type:** Quantity (Read Only)
 81 | 
 82 | The quantity that is currently on order.
 83 |  
 84 |  This is only relevant when On Order Inventory is turned on for the related inventory list. On Order is a bucket
 85 |  of inventory used for the time between order creation and order export to external (warehouse) systems. On Order
 86 |  value is increased when an order is created. It will be decreased and with that turnover will be increased when
 87 |  the order is exported, see getTurnover(). Notice that Order.setExportStatus(Number) and
 88 |  OrderItem.allocateInventory(Boolean) will decrease the On Order value. On order will be included
 89 |  in the ATS calculation, see getATS().
 90 |  
 91 |  
 92 |  When using OCI, always returns zero. OCI doesn't support On Order inventory.
 93 | 
 94 | ### perpetual
 95 | 
 96 | **Type:** boolean
 97 | 
 98 | Determines if the product is perpetually in stock.
 99 |  
100 |  When using OCI, always returns false.
101 | 
102 | ### preorderable
103 | 
104 | **Type:** boolean
105 | 
106 | Determines if the product is preorderable.
107 |  
108 |  When using OCI, always returns false.
109 | 
110 | ### preorderBackorderAllocation
111 | 
112 | **Type:** Quantity
113 | 
114 | The quantity of items that are allocated for sale, beyond the initial stock allocation.
115 |  
116 |  When using OCI, returns the sum of all Future quantities. If the product has no Future quantities, it returns
117 |  zero.
118 | 
119 | ### reserved
120 | 
121 | **Type:** Quantity (Read Only)
122 | 
123 | The quantity of items that are reserved.
124 |  
125 |  Note that for products with high velocity and concurrency, the return value is extremely volatile and the
126 |  retrieval will be expensive.
127 |  
128 |  When using OCI, always returns zero.
129 | 
130 | ### stockLevel
131 | 
132 | **Type:** Quantity (Read Only)
133 | 
134 | The current stock level. This is calculated as the allocation minus the turnover.
135 |  
136 |  When using OCI, corresponds to the ATF (Available To Fulfill) quantity in OCI.
137 | 
138 | ### turnover
139 | 
140 | **Type:** Quantity (Read Only)
141 | 
142 | The sum of all inventory transactions (decrements and increments) recorded after the allocation reset
143 |  date. If the total decremented quantity is greater than the total incremented quantity, then this value is
144 |  negative.
145 |  
146 |  When using OCI, returns the total reserved quantity, including order, basket, and temporary reservations.
147 | 
148 | ## Constructor Summary
149 | 
150 | ## Method Summary
151 | 
152 | ### describe
153 | 
154 | **Signature:** `describe() : ObjectTypeDefinition`
155 | 
156 | Returns the meta data of this object.
157 | 
158 | ### getAllocation
159 | 
160 | **Signature:** `getAllocation() : Quantity`
161 | 
162 | Returns the allocation quantity that is currently set.
163 | 
164 | ### getAllocationResetDate
165 | 
166 | **Signature:** `getAllocationResetDate() : Date`
167 | 
168 | Returns the date the allocation quantity was initialized or reset.
169 | 
170 | ### getATS
171 | 
172 | **Signature:** `getATS() : Quantity`
173 | 
174 | Returns the quantity of items available to sell (ATS).
175 | 
176 | ### getCustom
177 | 
178 | **Signature:** `getCustom() : CustomAttributes`
179 | 
180 | Returns the custom attributes for this object.
181 | 
182 | ### getInStockDate
183 | 
184 | **Signature:** `getInStockDate() : Date`
185 | 
186 | Returns the date that the item is expected to be in stock.
187 | 
188 | ### getOnHand
189 | 
190 | **Signature:** `getOnHand() : Quantity`
191 | 
192 | Returns the on-hand quantity, the actual quantity of available (on-hand) items.
193 | 
194 | ### getOnOrder
195 | 
196 | **Signature:** `getOnOrder() : Quantity`
197 | 
198 | Returns the quantity that is currently on order.
199 | 
200 | ### getPreorderBackorderAllocation
201 | 
202 | **Signature:** `getPreorderBackorderAllocation() : Quantity`
203 | 
204 | Returns the quantity of items that are allocated for sale, beyond the initial stock allocation.
205 | 
206 | ### getReserved
207 | 
208 | **Signature:** `getReserved() : Quantity`
209 | 
210 | Returns the quantity of items that are reserved.
211 | 
212 | ### getStockLevel
213 | 
214 | **Signature:** `getStockLevel() : Quantity`
215 | 
216 | Returns the current stock level.
217 | 
218 | ### getTurnover
219 | 
220 | **Signature:** `getTurnover() : Quantity`
221 | 
222 | Returns the sum of all inventory transactions (decrements and increments) recorded after the allocation reset date.
223 | 
224 | ### isBackorderable
225 | 
226 | **Signature:** `isBackorderable() : boolean`
227 | 
228 | Determines if the product is backorderable.
229 | 
230 | ### isPerpetual
231 | 
232 | **Signature:** `isPerpetual() : boolean`
233 | 
234 | Determines if the product is perpetually in stock.
235 | 
236 | ### isPreorderable
237 | 
238 | **Signature:** `isPreorderable() : boolean`
239 | 
240 | Determines if the product is preorderable.
241 | 
242 | ### setAllocation
243 | 
244 | **Signature:** `setAllocation(quantity : Number) : void`
245 | 
246 | Sets the allocation quantity.
247 | 
248 | ### setAllocation
249 | 
250 | **Signature:** `setAllocation(quantity : Number, allocationResetDate : Date) : void`
251 | 
252 | Sets the allocation quantity.
253 | 
254 | ### setBackorderable
255 | 
256 | **Signature:** `setBackorderable(backorderableFlag : boolean) : void`
257 | 
258 | The method is used to set whether the product is backorderable.
259 | 
260 | ### setInStockDate
261 | 
262 | **Signature:** `setInStockDate(inStockDate : Date) : void`
263 | 
264 | Sets the date that the item is expected to be in stock.
265 | 
266 | ### setPerpetual
267 | 
268 | **Signature:** `setPerpetual(perpetualFlag : boolean) : void`
269 | 
270 | Sets if the product is perpetually in stock.
271 | 
272 | ### setPreorderable
273 | 
274 | **Signature:** `setPreorderable(preorderableFlag : boolean) : void`
275 | 
276 | The method is used to set whether the product is preorderable.
277 | 
278 | ### setPreorderBackorderAllocation
279 | 
280 | **Signature:** `setPreorderBackorderAllocation(quantity : Number) : void`
281 | 
282 | Sets the quantity of items that are allocated for sale, beyond the initial stock allocation.
283 | 
284 | ## Method Detail
285 | 
286 | ## Method Details
287 | 
288 | ### describe
289 | 
290 | **Signature:** `describe() : ObjectTypeDefinition`
291 | 
292 | **Description:** Returns the meta data of this object. If no meta data is available the method returns null. The returned ObjectTypeDefinition can be used to retrieve the metadata for any of the custom attributes. When using Omnichannel Inventory (OCI), this class doesn't support custom attributes. If OCI is enabled, then attempting to set or modify a custom attribute value throws an UnsupportedOperationException.
293 | 
294 | **Returns:**
295 | 
296 | the meta data of this object. If no meta data is available the method returns null.
297 | 
298 | ---
299 | 
300 | ### getAllocation
301 | 
302 | **Signature:** `getAllocation() : Quantity`
303 | 
304 | **Description:** Returns the allocation quantity that is currently set. The quantity unit is the same unit as the product itself. When using OCI, returns the physically available quantity. Corresponds to the On Hand quantity in OCI.
305 | 
306 | **Returns:**
307 | 
308 | the allocation quantity or quantity N/A if not available.
309 | 
310 | ---
311 | 
312 | ### getAllocationResetDate
313 | 
314 | **Signature:** `getAllocationResetDate() : Date`
315 | 
316 | **Description:** Returns the date the allocation quantity was initialized or reset. When using OCI, corresponds to the Effective Date in OCI. The value can be null.
317 | 
318 | **Returns:**
319 | 
320 | the allocation reset date.
321 | 
322 | ---
323 | 
324 | ### getATS
325 | 
326 | **Signature:** `getATS() : Quantity`
327 | 
328 | **Description:** Returns the quantity of items available to sell (ATS). This is calculated as the allocation (getAllocation()) plus the preorderBackorderAllocation (getPreorderBackorderAllocation()) minus the turnover (getTurnover()) minus the on order quantity (getOnOrder()). When using OCI, corresponds to the ATO (Available To Order) quantity in OCI.
329 | 
330 | **Returns:**
331 | 
332 | the quantity or quantity N/A if not available.
333 | 
334 | ---
335 | 
336 | ### getCustom
337 | 
338 | **Signature:** `getCustom() : CustomAttributes`
339 | 
340 | **Description:** Returns the custom attributes for this object. The returned object is used for retrieving and storing attribute values. See CustomAttributes for a detailed example of the syntax for working with custom attributes. When using Omnichannel Inventory (OCI), this class doesn't support custom attributes. If OCI is enabled, then attempting to set or modify a custom attribute value throws an UnsupportedOperationException.
341 | 
342 | **Returns:**
343 | 
344 | the custom attributes for this object.
345 | 
346 | ---
347 | 
348 | ### getInStockDate
349 | 
350 | **Signature:** `getInStockDate() : Date`
351 | 
352 | **Description:** Returns the date that the item is expected to be in stock. When using OCI, returns the date of the earliest Future quantity. If the product has no Future quantities, it returns null.
353 | 
354 | **Returns:**
355 | 
356 | the date that the item is expected to be in stock.
357 | 
358 | ---
359 | 
360 | ### getOnHand
361 | 
362 | **Signature:** `getOnHand() : Quantity`
363 | 
364 | **Description:** Returns the on-hand quantity, the actual quantity of available (on-hand) items.
365 | 
366 | **API Versioned:**
367 | 
368 | No longer available as of version 21.7.
369 | 
370 | **Deprecated:**
371 | 
372 | Use getStockLevel() instead.
373 | 
374 | **Returns:**
375 | 
376 | the on-hand quantity or quantity N/A if not available.
377 | 
378 | ---
379 | 
380 | ### getOnOrder
381 | 
382 | **Signature:** `getOnOrder() : Quantity`
383 | 
384 | **Description:** Returns the quantity that is currently on order. This is only relevant when On Order Inventory is turned on for the related inventory list. On Order is a bucket of inventory used for the time between order creation and order export to external (warehouse) systems. On Order value is increased when an order is created. It will be decreased and with that turnover will be increased when the order is exported, see getTurnover(). Notice that Order.setExportStatus(Number) and OrderItem.allocateInventory(Boolean) will decrease the On Order value. On order will be included in the ATS calculation, see getATS(). When using OCI, always returns zero. OCI doesn't support On Order inventory.
385 | 
386 | **Returns:**
387 | 
388 | the quantity or quantity N/A if not available.
389 | 
390 | ---
391 | 
392 | ### getPreorderBackorderAllocation
393 | 
394 | **Signature:** `getPreorderBackorderAllocation() : Quantity`
395 | 
396 | **Description:** Returns the quantity of items that are allocated for sale, beyond the initial stock allocation. When using OCI, returns the sum of all Future quantities. If the product has no Future quantities, it returns zero.
397 | 
398 | **Returns:**
399 | 
400 | the quantity or quantity N/A if not available.
401 | 
402 | ---
403 | 
404 | ### getReserved
405 | 
406 | **Signature:** `getReserved() : Quantity`
407 | 
408 | **Description:** Returns the quantity of items that are reserved. Note that for products with high velocity and concurrency, the return value is extremely volatile and the retrieval will be expensive. When using OCI, always returns zero.
409 | 
410 | **Returns:**
411 | 
412 | the quantity of items reserved for this product.
413 | 
414 | ---
415 | 
416 | ### getStockLevel
417 | 
418 | **Signature:** `getStockLevel() : Quantity`
419 | 
420 | **Description:** Returns the current stock level. This is calculated as the allocation minus the turnover. When using OCI, corresponds to the ATF (Available To Fulfill) quantity in OCI.
421 | 
422 | **Returns:**
423 | 
424 | the stock level or quantity N/A if not available.
425 | 
426 | ---
427 | 
428 | ### getTurnover
429 | 
430 | **Signature:** `getTurnover() : Quantity`
431 | 
432 | **Description:** Returns the sum of all inventory transactions (decrements and increments) recorded after the allocation reset date. If the total decremented quantity is greater than the total incremented quantity, then this value is negative. When using OCI, returns the total reserved quantity, including order, basket, and temporary reservations.
433 | 
434 | **Returns:**
435 | 
436 | the turnover or quantity N/A if not available.
437 | 
438 | ---
439 | 
440 | ### isBackorderable
441 | 
442 | **Signature:** `isBackorderable() : boolean`
443 | 
444 | **Description:** Determines if the product is backorderable. When using OCI, returns true if the product has at least one Future quantity in OCI.
445 | 
446 | **Returns:**
447 | 
448 | true if the product is backorderable.
449 | 
450 | ---
451 | 
452 | ### isPerpetual
453 | 
454 | **Signature:** `isPerpetual() : boolean`
455 | 
456 | **Description:** Determines if the product is perpetually in stock. When using OCI, always returns false.
457 | 
458 | **Returns:**
459 | 
460 | true if the product is perpetually in stock.
461 | 
462 | ---
463 | 
464 | ### isPreorderable
465 | 
466 | **Signature:** `isPreorderable() : boolean`
467 | 
468 | **Description:** Determines if the product is preorderable. When using OCI, always returns false.
469 | 
470 | **Returns:**
471 | 
472 | true if the product is preorderable.
473 | 
474 | ---
475 | 
476 | ### setAllocation
477 | 
478 | **Signature:** `setAllocation(quantity : Number) : void`
479 | 
480 | **Description:** Sets the allocation quantity. This also updates the allocation reset date. All final reservations will be considered as expired and will therefore no longer be considered for ATS. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request.
481 | 
482 | **API Versioned:**
483 | 
484 | No longer available as of version 21.7.
485 | 
486 | **Parameters:**
487 | 
488 | - `quantity`: the allocation quantity to set (must be greater than or equal to zero).
489 | 
490 | ---
491 | 
492 | ### setAllocation
493 | 
494 | **Signature:** `setAllocation(quantity : Number, allocationResetDate : Date) : void`
495 | 
496 | **Description:** Sets the allocation quantity. This also updates the allocation reset date. Any final reservations made prior to the allocation reset date will be considered as expired and will therefore no longer be considered for ATS. When using OCI, throws an IllegalStateException. This method must not be called within a storefront request.
497 | 
498 | **Parameters:**
499 | 
500 | - `quantity`: the allocation quantity to set (must be greater than or equal to zero).
501 | - `allocationResetDate`: the date allocation quantity was effectively calculated the reset date must not be older than 48 hours the reset date must not be older than the prior reset date. see getAllocationResetDate()
502 | 
503 | ---
504 | 
505 | ### setBackorderable
506 | 
507 | **Signature:** `setBackorderable(backorderableFlag : boolean) : void`
508 | 
509 | **Description:** The method is used to set whether the product is backorderable. Setting the backorderable flag to true will clear the preorderable flag. If the record's preorderable flag is set to true, calling this method with backorderableFlag==false will have no effect. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request. This method must not be called within a storefront request when the API version is 21.7 or later.
510 | 
511 | **Parameters:**
512 | 
513 | - `backorderableFlag`: the flag to set backorderable status.
514 | 
515 | ---
516 | 
517 | ### setInStockDate
518 | 
519 | **Signature:** `setInStockDate(inStockDate : Date) : void`
520 | 
521 | **Description:** Sets the date that the item is expected to be in stock. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request. This method must not be called within a storefront request when the API version is 21.7 or later.
522 | 
523 | **Parameters:**
524 | 
525 | - `inStockDate`: the date that the item is expected to be in stock.
526 | 
527 | ---
528 | 
529 | ### setPerpetual
530 | 
531 | **Signature:** `setPerpetual(perpetualFlag : boolean) : void`
532 | 
533 | **Description:** Sets if the product is perpetually in stock. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request. This method must not be called within a storefront request when the API version is 21.7 or later.
534 | 
535 | **Parameters:**
536 | 
537 | - `perpetualFlag`: true to set the product perpetually in stock.
538 | 
539 | ---
540 | 
541 | ### setPreorderable
542 | 
543 | **Signature:** `setPreorderable(preorderableFlag : boolean) : void`
544 | 
545 | **Description:** The method is used to set whether the product is preorderable. Setting the preorderable flag to true will clear the backorderable flag. If the record's backorderable flag is set to true, calling this method with preorderableFlag==false will have no effect. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request. This method must not be called within a storefront request when the API version is 21.7 or later.
546 | 
547 | **Parameters:**
548 | 
549 | - `preorderableFlag`: the flag to set preorderable status.
550 | 
551 | ---
552 | 
553 | ### setPreorderBackorderAllocation
554 | 
555 | **Signature:** `setPreorderBackorderAllocation(quantity : Number) : void`
556 | 
557 | **Description:** Sets the quantity of items that are allocated for sale, beyond the initial stock allocation. When using OCI, throws an IllegalStateException. This method should not be called within a storefront request. This method must not be called within a storefront request when the API version is 21.7 or later.
558 | 
559 | **Parameters:**
560 | 
561 | - `quantity`: the quantity to set.
562 | 
563 | ---
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-site-preferences.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # ==================================================================================
  2 | # SFCC MCP Server - search_site_preferences Tool YAML Tests (Full Mode)
  3 | # Streamlined smoke testing and declarative validation for core functionality
  4 | # Complex business logic, edge cases, and workflows are covered in programmatic tests
  5 | # 
  6 | # Available Preference Groups in Mock Server:
  7 | # - Storefront (shopping cart, max items, welcome message, etc.)
  8 | # - System (system-level preferences)
  9 | # - SFRA (storefront reference architecture preferences)
 10 | # - CCV (custom checkout validation preferences)
 11 | # - FastForward (fast forward specific preferences)
 12 | # - Integration (integration-specific preferences)
 13 | # 
 14 | # Quick Test Commands:
 15 | # aegis "tests/mcp/yaml/search-site-preferences.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose
 16 | # aegis query search_site_preferences '{"groupId": "Storefront", "instanceType": "sandbox", "searchRequest": {"query": {"match_all_query": {}}, "count": 3}}' --config "aegis.config.with-dw.json"
 17 | # ==================================================================================
 18 | 
 19 | description: "search_site_preferences tool smoke tests - Basic functionality validation"
 20 | 
 21 | tests:
 22 |   # ==================================================================================
 23 |   # TOOL AVAILABILITY VALIDATION
 24 |   # ==================================================================================
 25 |   - it: "should have search_site_preferences tool available with proper schema"
 26 |     request:
 27 |       jsonrpc: "2.0"
 28 |       id: "tool-available"
 29 |       method: "tools/list"
 30 |       params: {}
 31 |     expect:
 32 |       response:
 33 |         jsonrpc: "2.0"
 34 |         id: "tool-available"
 35 |         result:
 36 |           tools:
 37 |             match:arrayElements:
 38 |               match:partial:
 39 |                 name: "match:type:string"
 40 |                 description: "match:type:string"
 41 |           match:extractField: "tools.*.name"
 42 |           value: "match:arrayContains:search_site_preferences"
 43 |       stderr: "toBeEmpty"
 44 | 
 45 |   # ==================================================================================
 46 |   # CORE FUNCTIONALITY VALIDATION - match_all_query
 47 |   # ==================================================================================
 48 |   - it: "should successfully search Storefront preferences with match_all_query and return valid structure"
 49 |     request:
 50 |       jsonrpc: "2.0"
 51 |       id: "storefront-match-all-success"
 52 |       method: "tools/call"
 53 |       params:
 54 |         name: "search_site_preferences"
 55 |         arguments:
 56 |           groupId: "Storefront"
 57 |           instanceType: "sandbox"
 58 |           searchRequest:
 59 |             query:
 60 |               match_all_query: {}
 61 |             count: 3
 62 |     expect:
 63 |       response:
 64 |         jsonrpc: "2.0"
 65 |         id: "storefront-match-all-success"
 66 |         result:
 67 |           content:
 68 |             - type: "text"
 69 |               text: "match:contains:preference_value_search_result"
 70 |           isError: false
 71 |       performance:
 72 |         maxResponseTime: "2000ms"
 73 |       stderr: "toBeEmpty"
 74 | 
 75 |   - it: "should return valid JSON structure with preference data in match_all_query response"
 76 |     request:
 77 |       jsonrpc: "2.0"
 78 |       id: "storefront-structure"
 79 |       method: "tools/call"
 80 |       params:
 81 |         name: "search_site_preferences"
 82 |         arguments:
 83 |           groupId: "Storefront"
 84 |           instanceType: "sandbox"
 85 |           searchRequest:
 86 |             query:
 87 |               match_all_query: {}
 88 |             count: 2
 89 |     expect:
 90 |       response:
 91 |         jsonrpc: "2.0"
 92 |         id: "storefront-structure"
 93 |         result:
 94 |           content:
 95 |             - type: "text"
 96 |               text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"preference_value_search_result\"[\\s\\S]*"
 97 |           isError: false
 98 |       stderr: "toBeEmpty"
 99 | 
100 |   - it: "should include query echo and pagination info in match_all_query response"
101 |     request:
102 |       jsonrpc: "2.0"
103 |       id: "storefront-metadata"
104 |       method: "tools/call"
105 |       params:
106 |         name: "search_site_preferences"
107 |         arguments:
108 |           groupId: "Storefront"
109 |           instanceType: "sandbox"
110 |           searchRequest:
111 |             query:
112 |               match_all_query: {}
113 |             count: 2
114 |             start: 0
115 |     expect:
116 |       response:
117 |         jsonrpc: "2.0"
118 |         id: "storefront-metadata"
119 |         result:
120 |           content:
121 |             - type: "text"
122 |               text: "match:regex:[\\s\\S]*\"match_all_query\"[\\s\\S]*\"start\"[\\s\\S]*\"total\"[\\s\\S]*"
123 |           isError: false
124 |       stderr: "toBeEmpty"
125 | 
126 |   - it: "should return site preference data with attribute definitions and site values"
127 |     request:
128 |       jsonrpc: "2.0"
129 |       id: "storefront-content"
130 |       method: "tools/call"
131 |       params:
132 |         name: "search_site_preferences"
133 |         arguments:
134 |           groupId: "Storefront"
135 |           instanceType: "sandbox"
136 |           searchRequest:
137 |             query:
138 |               match_all_query: {}
139 |             count: 1
140 |     expect:
141 |       response:
142 |         jsonrpc: "2.0"
143 |         id: "storefront-content"
144 |         result:
145 |           content:
146 |             - type: "text"
147 |               text: "match:regex:[\\s\\S]*\"attribute_definition\"[\\s\\S]*\"site_values\"[\\s\\S]*\"value_type\"[\\s\\S]*"
148 |           isError: false
149 |       stderr: "toBeEmpty"
150 | 
151 |   # ==================================================================================
152 |   # TEXT QUERY FUNCTIONALITY VALIDATION
153 |   # ==================================================================================
154 |   - it: "should successfully filter preferences with text_query"
155 |     request:
156 |       jsonrpc: "2.0"
157 |       id: "text-query-success"
158 |       method: "tools/call"
159 |       params:
160 |         name: "search_site_preferences"
161 |         arguments:
162 |           groupId: "Storefront"
163 |           instanceType: "sandbox"
164 |           searchRequest:
165 |             query:
166 |               text_query:
167 |                 fields: ["id", "display_name"]
168 |                 search_phrase: "cart"
169 |             count: 5
170 |     expect:
171 |       response:
172 |         jsonrpc: "2.0"
173 |         id: "text-query-success"
174 |         result:
175 |           content:
176 |             - type: "text"
177 |               text: "match:regex:[\\s\\S]*\"text_query\"[\\s\\S]*\"cart\"[\\s\\S]*"
178 |           isError: false
179 |       performance:
180 |         maxResponseTime: "2000ms"
181 |       stderr: "toBeEmpty"
182 | 
183 |   - it: "should return filtered results when searching for cart preferences"
184 |     request:
185 |       jsonrpc: "2.0"
186 |       id: "text-query-filtered"
187 |       method: "tools/call"
188 |       params:
189 |         name: "search_site_preferences"
190 |         arguments:
191 |           groupId: "Storefront"
192 |           instanceType: "sandbox"
193 |           searchRequest:
194 |             query:
195 |               text_query:
196 |                 fields: ["id", "display_name"]
197 |                 search_phrase: "cart"
198 |     expect:
199 |       response:
200 |         jsonrpc: "2.0"
201 |         id: "text-query-filtered"
202 |         result:
203 |           content:
204 |             - type: "text"
205 |               text: "match:regex:[\\s\\S]*(?:shoppingCartEnabled|maxCartItems)[\\s\\S]*"
206 |           isError: false
207 |       stderr: "toBeEmpty"
208 | 
209 |   # ==================================================================================
210 |   # DIFFERENT PREFERENCE GROUPS VALIDATION
211 |   # ==================================================================================
212 |   - it: "should successfully search System preferences group"
213 |     request:
214 |       jsonrpc: "2.0"
215 |       id: "system-group-success"
216 |       method: "tools/call"
217 |       params:
218 |         name: "search_site_preferences"
219 |         arguments:
220 |           groupId: "System"
221 |           instanceType: "sandbox"
222 |           searchRequest:
223 |             query:
224 |               match_all_query: {}
225 |             count: 2
226 |     expect:
227 |       response:
228 |         jsonrpc: "2.0"
229 |         id: "system-group-success"
230 |         result:
231 |           content:
232 |             - type: "text"
233 |               text: "match:contains:preference_value_search_result"
234 |           isError: false
235 |       performance:
236 |         maxResponseTime: "2000ms"
237 |       stderr: "toBeEmpty"
238 | 
239 |   - it: "should successfully search SFRA preferences group"
240 |     request:
241 |       jsonrpc: "2.0"
242 |       id: "sfra-group-success"
243 |       method: "tools/call"
244 |       params:
245 |         name: "search_site_preferences"
246 |         arguments:
247 |           groupId: "SFRA"
248 |           instanceType: "sandbox"
249 |           searchRequest:
250 |             query:
251 |               match_all_query: {}
252 |             count: 1
253 |     expect:
254 |       response:
255 |         jsonrpc: "2.0"
256 |         id: "sfra-group-success"
257 |         result:
258 |           content:
259 |             - type: "text"
260 |               text: "match:contains:preference_value_search_result"
261 |           isError: false
262 |       stderr: "toBeEmpty"
263 | 
264 |   # ==================================================================================
265 |   # INSTANCE TYPE VALIDATION
266 |   # ==================================================================================
267 |   - it: "should accept different instance types (development)"
268 |     request:
269 |       jsonrpc: "2.0"
270 |       id: "instance-development"
271 |       method: "tools/call"
272 |       params:
273 |         name: "search_site_preferences"
274 |         arguments:
275 |           groupId: "Storefront"
276 |           instanceType: "development"
277 |           searchRequest:
278 |             query:
279 |               match_all_query: {}
280 |             count: 1
281 |     expect:
282 |       response:
283 |         jsonrpc: "2.0"
284 |         id: "instance-development"
285 |         result:
286 |           content:
287 |             - type: "text"
288 |               text: "match:contains:preference_value_search_result"
289 |           isError: false
290 |       stderr: "toBeEmpty"
291 | 
292 |   - it: "should accept staging instance type"
293 |     request:
294 |       jsonrpc: "2.0"
295 |       id: "instance-staging"
296 |       method: "tools/call"
297 |       params:
298 |         name: "search_site_preferences"
299 |         arguments:
300 |           groupId: "Storefront"
301 |           instanceType: "staging"
302 |           searchRequest:
303 |             query:
304 |               match_all_query: {}
305 |             count: 1
306 |     expect:
307 |       response:
308 |         jsonrpc: "2.0"
309 |         id: "instance-staging"
310 |         result:
311 |           content:
312 |             - type: "text"
313 |               text: "match:contains:preference_value_search_result"
314 |           isError: false
315 |       stderr: "toBeEmpty"
316 | 
317 |   # ==================================================================================
318 |   # PAGINATION VALIDATION
319 |   # ==================================================================================
320 |   - it: "should respect count parameter for result limiting"
321 |     request:
322 |       jsonrpc: "2.0"
323 |       id: "pagination-count"
324 |       method: "tools/call"
325 |       params:
326 |         name: "search_site_preferences"
327 |         arguments:
328 |           groupId: "Storefront"
329 |           instanceType: "sandbox"
330 |           searchRequest:
331 |             query:
332 |               match_all_query: {}
333 |             count: 1
334 |     expect:
335 |       response:
336 |         jsonrpc: "2.0"
337 |         id: "pagination-count"
338 |         result:
339 |           content:
340 |             - type: "text"
341 |               text: "match:regex:[\\s\\S]*\"count\"[\\s\\S]*:[\\s\\S]*1[\\s\\S]*"
342 |           isError: false
343 |       stderr: "toBeEmpty"
344 | 
345 |   - it: "should respect start parameter for pagination"
346 |     request:
347 |       jsonrpc: "2.0"
348 |       id: "pagination-start"
349 |       method: "tools/call"
350 |       params:
351 |         name: "search_site_preferences"
352 |         arguments:
353 |           groupId: "Storefront"
354 |           instanceType: "sandbox"
355 |           searchRequest:
356 |             query:
357 |               match_all_query: {}
358 |             start: 1
359 |             count: 2
360 |     expect:
361 |       response:
362 |         jsonrpc: "2.0"
363 |         id: "pagination-start"
364 |         result:
365 |           content:
366 |             - type: "text"
367 |               text: "match:regex:[\\s\\S]*\"start\"[\\s\\S]*:[\\s\\S]*1[\\s\\S]*"
368 |           isError: false
369 |       stderr: "toBeEmpty"
370 | 
371 |   # ==================================================================================
372 |   # ERROR HANDLING VALIDATION
373 |   # ==================================================================================
374 |   - it: "should return 404 error for non-existent preference group"
375 |     request:
376 |       jsonrpc: "2.0"
377 |       id: "error-nonexistent-group"
378 |       method: "tools/call"
379 |       params:
380 |         name: "search_site_preferences"
381 |         arguments:
382 |           groupId: "NonExistentGroup"
383 |           instanceType: "sandbox"
384 |           searchRequest:
385 |             query:
386 |               match_all_query: {}
387 |     expect:
388 |       response:
389 |         jsonrpc: "2.0"
390 |         id: "error-nonexistent-group"
391 |         result:
392 |           content:
393 |             - type: "text"
394 |               text: "match:contains:CustomPreferenceGroupNotFoundException"
395 |           isError: true
396 |       performance:
397 |         maxResponseTime: "1000ms"
398 |       stderr: "toBeEmpty"
399 | 
400 |   - it: "should handle invalid parameters gracefully"
401 |     request:
402 |       jsonrpc: "2.0"
403 |       id: "error-invalid-params"
404 |       method: "tools/call"
405 |       params:
406 |         name: "search_site_preferences"
407 |         arguments:
408 |           groupId: ""
409 |           instanceType: "sandbox"
410 |           searchRequest:
411 |             query:
412 |               match_all_query: {}
413 |     expect:
414 |       response:
415 |         jsonrpc: "2.0"
416 |         id: "error-invalid-params"
417 |         result:
418 |           content:
419 |             - type: "text"
420 |               text: "match:contains:groupId must be a non-empty string"
421 |           isError: true
422 |       performance:
423 |         maxResponseTime: "1000ms"
424 |       stderr: "toBeEmpty"
425 | 
426 |   # ==================================================================================
427 |   # ADVANCED QUERY VALIDATION
428 |   # ==================================================================================
429 |   - it: "should handle term_query with exact matches"
430 |     request:
431 |       jsonrpc: "2.0"
432 |       id: "term-query-success"
433 |       method: "tools/call"
434 |       params:
435 |         name: "search_site_preferences"
436 |         arguments:
437 |           groupId: "Storefront"
438 |           instanceType: "sandbox"
439 |           searchRequest:
440 |             query:
441 |               term_query:
442 |                 fields: ["value_type"]
443 |                 operator: "is"
444 |                 values: ["boolean"]
445 |     expect:
446 |       response:
447 |         jsonrpc: "2.0"
448 |         id: "term-query-success"
449 |         result:
450 |           content:
451 |             - type: "text"
452 |               text: "match:contains:preference_value_search_result"
453 |           isError: false
454 |       performance:
455 |         maxResponseTime: "2000ms"
456 |       stderr: "toBeEmpty"
457 | 
458 |   # ==================================================================================
459 |   # OPTIONS VALIDATION
460 |   # ==================================================================================
461 |   - it: "should accept options parameter with maskPasswords"
462 |     request:
463 |       jsonrpc: "2.0"
464 |       id: "options-mask-passwords"
465 |       method: "tools/call"
466 |       params:
467 |         name: "search_site_preferences"
468 |         arguments:
469 |           groupId: "Storefront"
470 |           instanceType: "sandbox"
471 |           searchRequest:
472 |             query:
473 |               match_all_query: {}
474 |             count: 1
475 |           options:
476 |             maskPasswords: true
477 |     expect:
478 |       response:
479 |         jsonrpc: "2.0"
480 |         id: "options-mask-passwords"
481 |         result:
482 |           content:
483 |             - type: "text"
484 |               text: "match:contains:preference_value_search_result"
485 |           isError: false
486 |       stderr: "toBeEmpty"
487 | 
488 |   - it: "should accept options parameter with expand value"
489 |     request:
490 |       jsonrpc: "2.0"
491 |       id: "options-expand-value"
492 |       method: "tools/call"
493 |       params:
494 |         name: "search_site_preferences"
495 |         arguments:
496 |           groupId: "Storefront"
497 |           instanceType: "sandbox"
498 |           searchRequest:
499 |             query:
500 |               match_all_query: {}
501 |             count: 1
502 |           options:
503 |             expand: "value"
504 |     expect:
505 |       response:
506 |         jsonrpc: "2.0"
507 |         id: "options-expand-value"
508 |         result:
509 |           content:
510 |             - type: "text"
511 |               text: "match:contains:preference_value_search_result"
512 |           isError: false
513 |       stderr: "toBeEmpty"
514 | 
515 |   # ==================================================================================
516 |   # SORTING VALIDATION
517 |   # ==================================================================================
518 |   - it: "should accept sorting parameters"
519 |     request:
520 |       jsonrpc: "2.0"
521 |       id: "sorting-test"
522 |       method: "tools/call"
523 |       params:
524 |         name: "search_site_preferences"
525 |         arguments:
526 |           groupId: "Storefront"
527 |           instanceType: "sandbox"
528 |           searchRequest:
529 |             query:
530 |               match_all_query: {}
531 |             sorts:
532 |               - field: "id"
533 |                 sort_order: "asc"
534 |             count: 2
535 |     expect:
536 |       response:
537 |         jsonrpc: "2.0"
538 |         id: "sorting-test"
539 |         result:
540 |           content:
541 |             - type: "text"
542 |               text: "match:contains:preference_value_search_result"
543 |           isError: false
544 |       stderr: "toBeEmpty"
545 | 
546 |   # ==================================================================================
547 |   # PERFORMANCE VALIDATION
548 |   # ==================================================================================
549 |   - it: "should complete large requests within reasonable time"
550 |     request:
551 |       jsonrpc: "2.0"
552 |       id: "performance-large-request"
553 |       method: "tools/call"
554 |       params:
555 |         name: "search_site_preferences"
556 |         arguments:
557 |           groupId: "Storefront"
558 |           instanceType: "sandbox"
559 |           searchRequest:
560 |             query:
561 |               match_all_query: {}
562 |             count: 50
563 |     expect:
564 |       response:
565 |         jsonrpc: "2.0"
566 |         id: "performance-large-request"
567 |         result:
568 |           content:
569 |             - type: "text"
570 |               text: "match:contains:preference_value_search_result"
571 |           isError: false
572 |       performance:
573 |         maxResponseTime: "3000ms"
574 |       stderr: "toBeEmpty"
```

--------------------------------------------------------------------------------
/tests/mcp/node/search-logs.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { test, describe, before, after, beforeEach } from 'node:test';
  2 | import { strict as assert } from 'node:assert';
  3 | import { connect } from 'mcp-aegis';
  4 | 
  5 | describe('search_logs - Full Mode Programmatic Tests (Complex Logic & Content Analysis)', () => {
  6 |   let client;
  7 | 
  8 |   before(async () => {
  9 |     client = await connect('./aegis.config.with-dw.json');
 10 |   });
 11 | 
 12 |   after(async () => {
 13 |     if (client?.connected) {
 14 |       await client.disconnect();
 15 |     }
 16 |   });
 17 | 
 18 |   beforeEach(() => {
 19 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 20 |     client.clearAllBuffers();
 21 |   });
 22 | 
 23 |   // Helper functions for complex content analysis
 24 |   function parseResponseText(text) {
 25 |     return text.startsWith('"') && text.endsWith('"') 
 26 |       ? JSON.parse(text) 
 27 |       : text;
 28 |   }
 29 | 
 30 |   function extractLogEntries(text) {
 31 |     // Extract individual log entries from the response - more flexible parsing
 32 |     const entries = [];
 33 |     const lines = text.split('\n');
 34 |     
 35 |     for (const line of lines) {
 36 |       const trimmed = line.trim();
 37 |       // Look for lines that contain log file references or timestamp patterns
 38 |       if (trimmed && (trimmed.includes('[') || /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(trimmed))) {
 39 |         // Skip header lines and summary lines
 40 |         if (!trimmed.startsWith('Found ') && !trimmed.startsWith('No matches') && trimmed.length > 10) {
 41 |           entries.push(trimmed);
 42 |         }
 43 |       }
 44 |     }
 45 |     return entries;
 46 |   }
 47 | 
 48 |   function parseLogEntry(entry) {
 49 |     // Parse structured information from a log entry - more robust parsing
 50 |     const logFileMatch = entry.match(/\[([^\]]+)\]/) || entry.match(/(\w+-blade-\d{8}-\d{6}\.log)/);
 51 |     const timestampMatch = entry.match(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{3})? GMT)/);
 52 |     const levelMatch = entry.match(/(ERROR|WARN|INFO|DEBUG)/i);
 53 |     const threadMatch = entry.match(/\|(\d+)\|/);
 54 |     const siteMatch = entry.match(/Sites-([^|\s]+)/);
 55 |     
 56 |     return {
 57 |       logFile: logFileMatch?.[1] || logFileMatch?.[0] || null,
 58 |       timestamp: timestampMatch?.[1] || null,
 59 |       level: levelMatch?.[1]?.toUpperCase() || null,
 60 |       threadId: threadMatch?.[1] || null,
 61 |       site: siteMatch?.[1] || null,
 62 |       fullEntry: entry,
 63 |       isValid: !!(logFileMatch || timestampMatch) // Entry is valid if it has either log file or timestamp
 64 |     };
 65 |   }
 66 | 
 67 |   function analyzeSearchResults(result, pattern) {
 68 |     assert.equal(result.isError, false, 'Should not be an error response');
 69 |     const text = parseResponseText(result.content[0].text);
 70 |     
 71 |     if (text.includes('No matches found')) {
 72 |       return { hasMatches: false, count: 0, pattern, entries: [] };
 73 |     }
 74 |     
 75 |     const foundMatch = text.match(/Found (\d+) matches for "([^"]+)"/);
 76 |     assert.ok(foundMatch, 'Should contain "Found X matches for" message');
 77 |     
 78 |     const count = parseInt(foundMatch[1]);
 79 |     const searchPattern = foundMatch[2];
 80 |     assert.equal(searchPattern, pattern, 'Pattern should match what was searched');
 81 |     
 82 |     const entries = extractLogEntries(text);
 83 |     return { hasMatches: true, count, pattern, entries, rawText: text };
 84 |   }
 85 | 
 86 |   function getCurrentDateString() {
 87 |     const now = new Date();
 88 |     const year = now.getFullYear();
 89 |     const month = String(now.getMonth() + 1).padStart(2, '0');
 90 |     const day = String(now.getDate()).padStart(2, '0');
 91 |     return `${year}${month}${day}`;
 92 |   }
 93 | 
 94 |   // Complex content analysis and business logic validation
 95 |   describe('Advanced Content Analysis', () => {
 96 |     test('should parse log entry structure and validate SFCC patterns', async () => {
 97 |       const result = await client.callTool('search_logs', {
 98 |         pattern: 'PipelineCallServlet',
 99 |         limit: 3
100 |       });
101 |       
102 |       const analysis = analyzeSearchResults(result, 'PipelineCallServlet');
103 |       
104 |       if (analysis.hasMatches && analysis.entries.length > 0) {
105 |         let validEntries = 0;
106 |         
107 |         // Parse each log entry for detailed validation
108 |         analysis.entries.forEach(entry => {
109 |           const parsed = parseLogEntry(entry);
110 |           
111 |           // Only validate entries that have recognizable structure
112 |           if (parsed.isValid) {
113 |             validEntries++;
114 |             
115 |             // Validate log file naming convention if present
116 |             if (parsed.logFile) {
117 |               assert.ok(/^(error|warn|info|debug)-blade-\d{8}-\d{6}\.log$/.test(parsed.logFile),
118 |                 `Log file "${parsed.logFile}" should follow SFCC naming convention`);
119 |             }
120 |             
121 |             // Validate timestamp format if present
122 |             if (parsed.timestamp) {
123 |               assert.ok(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{3})? GMT$/.test(parsed.timestamp),
124 |                 `Timestamp "${parsed.timestamp}" should be in GMT format`);
125 |             }
126 |             
127 |             // Validate thread ID structure if present
128 |             if (parsed.threadId) {
129 |               assert.ok(/^\d+$/.test(parsed.threadId),
130 |                 `Thread ID "${parsed.threadId}" should be numeric`);
131 |             }
132 |             
133 |             // Validate SFCC site context if present
134 |             if (parsed.site) {
135 |               assert.ok(parsed.site.length > 0,
136 |                 'Site name should not be empty');
137 |             }
138 |           }
139 |         });
140 |         
141 |         // At least some entries should be valid and parseable
142 |         assert.ok(validEntries > 0, 
143 |           `Should have at least some valid entries, found ${validEntries} out of ${analysis.entries.length}`);
144 |       }
145 |     });
146 | 
147 |     test('should validate log level filtering accuracy', async () => {
148 |       const logLevels = ['error', 'info'];
149 |       const results = {};
150 |       
151 |       // Test multiple log levels sequentially
152 |       for (const level of logLevels) {
153 |         const result = await client.callTool('search_logs', {
154 |           pattern: 'Sites',
155 |           logLevel: level,
156 |           limit: 5
157 |         });
158 |         
159 |         results[level] = analyzeSearchResults(result, 'Sites');
160 |         
161 |         if (results[level].hasMatches) {
162 |           // Validate that entries are related to the requested log level
163 |           results[level].entries.forEach(entry => {
164 |             const parsed = parseLogEntry(entry);
165 |             if (parsed.logFile) {
166 |               const levelPattern = new RegExp(`${level}-blade`, 'i');
167 |               assert.ok(levelPattern.test(parsed.logFile),
168 |                 `Log file should contain "${level}-blade" pattern: ${parsed.logFile}`);
169 |             }
170 |           });
171 |         }
172 |       }
173 |       
174 |       // Cross-validate that different log levels return different files (if both have results)
175 |       if (results.error.hasMatches && results.info.hasMatches) {
176 |         const errorLogFiles = results.error.entries
177 |           .map(e => parseLogEntry(e))
178 |           .filter(p => p.logFile)
179 |           .map(p => p.logFile);
180 |         
181 |         const infoLogFiles = results.info.entries
182 |           .map(e => parseLogEntry(e))
183 |           .filter(p => p.logFile)
184 |           .map(p => p.logFile);
185 |         
186 |         // Should have different log files for different levels (if we have sufficient data)
187 |         if (errorLogFiles.length > 0 && infoLogFiles.length > 0) {
188 |           const hasOverlap = errorLogFiles.some(file => infoLogFiles.includes(file));
189 |           // Note: This assertion might fail in some environments - that's useful information!
190 |           if (hasOverlap) {
191 |             // Warning: Error and info searches returned overlapping log files - this might indicate they search across all levels
192 |           } else {
193 |             // Good: Error and info searches returned different log files as expected
194 |           }
195 |         }
196 |       } else {
197 |         // Log level filtering test: insufficient data for cross-validation
198 |       }
199 |     });
200 | 
201 |     test('should validate chronological ordering of results', async () => {
202 |       const result = await client.callTool('search_logs', {
203 |         pattern: 'Job',
204 |         limit: 10
205 |       });
206 |       
207 |       const analysis = analyzeSearchResults(result, 'Job');
208 |       
209 |       if (analysis.hasMatches && analysis.entries.length > 1) {
210 |         const timestampEntries = analysis.entries
211 |           .map(entry => parseLogEntry(entry))
212 |           .filter(parsed => parsed.timestamp && parsed.isValid);
213 |         
214 |         if (timestampEntries.length > 1) {
215 |           const timestamps = timestampEntries.map(parsed => new Date(parsed.timestamp));
216 |           
217 |           // Validate that timestamps are in descending order (most recent first)
218 |           let orderingCorrect = true;
219 |           let orderingIssues = [];
220 |           
221 |           for (let i = 1; i < timestamps.length; i++) {
222 |             if (timestamps[i-1] < timestamps[i]) {
223 |               orderingCorrect = false;
224 |               orderingIssues.push({
225 |                 previous: timestamps[i-1].toISOString(),
226 |                 current: timestamps[i].toISOString(),
227 |                 index: i
228 |               });
229 |             }
230 |           }
231 |           
232 |           if (!orderingCorrect) {
233 |             // Log the ordering issues for debugging but don't fail the test
234 |           } else {
235 |             // Chronological ordering verified: timestamps are in descending order
236 |           }
237 |           
238 |           // Less strict assertion - log ordering may vary due to distributed logging or processing
239 |           const outOfOrderCount = orderingIssues.length;
240 |           const totalPairs = timestamps.length - 1;
241 |           const outOfOrderRatio = outOfOrderCount / totalPairs;
242 |           
243 |           // More realistic threshold - allow up to 70% out of order for distributed systems
244 |           assert.ok(outOfOrderRatio < 0.7, 
245 |             `More than 70% of timestamp pairs are out of order (${outOfOrderCount}/${totalPairs}). This suggests the system may have significant ordering challenges.`);
246 |           
247 |           if (outOfOrderRatio > 0.3) {
248 |             // Note: ${Math.round(outOfOrderRatio * 100)}% of entries are out of chronological order - this may be normal for distributed logging systems
249 |           }
250 |         } else {
251 |           // Insufficient timestamp data for chronological validation
252 |         }
253 |       } else {
254 |         // No Job entries found or insufficient data for chronological validation
255 |       }
256 |     });
257 |   });
258 | 
259 |   // Multi-step workflow testing
260 |   describe('Multi-Step Search Workflows', () => {
261 |     test('should support progressive search refinement', async () => {
262 |       // Step 1: Broad search
263 |       const broadResult = await client.callTool('search_logs', {
264 |         pattern: 'Order',
265 |         limit: 20
266 |       });
267 |       
268 |       const broadAnalysis = analyzeSearchResults(broadResult, 'Order');
269 |       
270 |       if (broadAnalysis.hasMatches) {
271 |         // Step 2: Refined search for specific context
272 |         const refinedResult = await client.callTool('search_logs', {
273 |           pattern: 'Order.*confirmation',
274 |           logLevel: 'info',
275 |           limit: 10
276 |         });
277 |         
278 |         const refinedAnalysis = analyzeSearchResults(refinedResult, 'Order.*confirmation');
279 |         
280 |         // Validate refinement effectiveness
281 |         if (refinedAnalysis.hasMatches) {
282 |           assert.ok(refinedAnalysis.count <= broadAnalysis.count,
283 |             'Refined search should return fewer or equal results');
284 |           
285 |           // All refined results should contain both Order and confirmation
286 |           refinedAnalysis.entries.forEach(entry => {
287 |             assert.ok(entry.toLowerCase().includes('order'),
288 |               'Refined entry should contain "order"');
289 |           });
290 |         }
291 |       }
292 |     });
293 | 
294 |     test('should maintain search consistency across multiple calls', async () => {
295 |       const searchParams = {
296 |         pattern: 'ERROR',
297 |         logLevel: 'error',
298 |         limit: 5
299 |       };
300 |       
301 |       // Execute same search multiple times
302 |       const results = [];
303 |       for (let i = 0; i < 3; i++) {
304 |         const result = await client.callTool('search_logs', searchParams);
305 |         results.push(analyzeSearchResults(result, searchParams.pattern));
306 |       }
307 |       
308 |       // Validate consistency
309 |       if (results[0].hasMatches) {
310 |         const baseCounts = results[0].count;
311 |         
312 |         results.slice(1).forEach((analysis, index) => {
313 |           assert.equal(analysis.count, baseCounts,
314 |             `Search ${index + 2} should return same count as first search`);
315 |           
316 |           if (analysis.hasMatches) {
317 |             assert.equal(analysis.entries.length, results[0].entries.length,
318 |               `Search ${index + 2} should return same number of entries`);
319 |           }
320 |         });
321 |       }
322 |     });
323 |   });
324 | 
325 |   // Dynamic parameter validation and boundary testing
326 |   describe('Dynamic Parameter Analysis', () => {
327 |     test('should validate limit parameter effectiveness across ranges', async () => {
328 |       const limits = [1, 3, 10];
329 |       const searchResults = [];
330 |       
331 |       // Test different limits sequentially
332 |       for (const limit of limits) {
333 |         const result = await client.callTool('search_logs', {
334 |           pattern: 'Sites',
335 |           limit
336 |         });
337 |         
338 |         const analysis = analyzeSearchResults(result, 'Sites');
339 |         searchResults.push({ limit, analysis });
340 |         
341 |         if (analysis.hasMatches) {
342 |           // Validate that returned count doesn't exceed requested limit
343 |           assert.ok(analysis.entries.length <= limit,
344 |             `Should return at most ${limit} entries, got ${analysis.entries.length}`);
345 |           
346 |           // Validate that limit is respected in the reported count
347 |           assert.ok(analysis.count <= limit,
348 |             `Reported count ${analysis.count} should not exceed limit ${limit}`);
349 |         }
350 |       }
351 |       
352 |       // Cross-validate limit behavior
353 |       const hasResults = searchResults.filter(r => r.analysis.hasMatches);
354 |       if (hasResults.length > 1) {
355 |         // Smaller limits should return subset of larger limits
356 |         hasResults.sort((a, b) => a.limit - b.limit);
357 |         
358 |         for (let i = 1; i < hasResults.length; i++) {
359 |           const smaller = hasResults[i-1];
360 |           const larger = hasResults[i];
361 |           
362 |           assert.ok(smaller.analysis.entries.length <= larger.analysis.entries.length,
363 |             `Limit ${smaller.limit} should return fewer or equal entries than limit ${larger.limit}`);
364 |         }
365 |       }
366 |     });
367 | 
368 |     test('should handle date-based search filtering accurately', async () => {
369 |       const currentDate = getCurrentDateString();
370 |       const pastDate = '20200101';
371 |       
372 |       // Current date search
373 |       const currentResult = await client.callTool('search_logs', {
374 |         pattern: 'INFO',
375 |         date: currentDate,
376 |         limit: 5
377 |       });
378 |       
379 |       const currentAnalysis = analyzeSearchResults(currentResult, 'INFO');
380 |       
381 |       // Past date search (should have no results)
382 |       const pastResult = await client.callTool('search_logs', {
383 |         pattern: 'INFO',
384 |         date: pastDate,
385 |         limit: 5
386 |       });
387 |       
388 |       const pastAnalysis = analyzeSearchResults(pastResult, 'INFO');
389 |       
390 |       // Validate date filtering logic
391 |       if (currentAnalysis.hasMatches) {
392 |         // Current date entries should contain current date in log file names
393 |         currentAnalysis.entries.forEach(entry => {
394 |           const parsed = parseLogEntry(entry);
395 |           if (parsed.logFile && parsed.isValid) {
396 |             assert.ok(parsed.logFile.includes(currentDate),
397 |               `Log file "${parsed.logFile}" should contain current date ${currentDate}`);
398 |           }
399 |         });
400 |       } else {
401 |         // No current date entries found - test still valid
402 |       }
403 |       
404 |       // Past date should return no matches
405 |       assert.ok(!pastAnalysis.hasMatches,
406 |         'Past date search should return no matches');
407 |     });
408 |   });
409 | 
410 |   // Business logic validation for SFCC-specific patterns
411 |   describe('SFCC Business Logic Validation', () => {
412 |     test('should identify and parse SFCC job execution patterns', async () => {
413 |       const result = await client.callTool('search_logs', {
414 |         pattern: 'SystemJobThread',
415 |         limit: 5
416 |       });
417 |       
418 |       const analysis = analyzeSearchResults(result, 'SystemJobThread');
419 |       
420 |       if (analysis.hasMatches) {
421 |         analysis.entries.forEach(entry => {
422 |           // Should contain job execution context
423 |           assert.ok(/SystemJobThread\|\d+\|/.test(entry),
424 |             'Should contain SystemJobThread with thread ID pattern');
425 |           
426 |           // Should contain job name or step information
427 |           const jobPatterns = [
428 |             /ProcessOrders/,
429 |             /ImportCustomers/,
430 |             /ExportCatalog/,
431 |             /SiteGenesis/
432 |           ];
433 |           
434 |           const hasJobPattern = jobPatterns.some(pattern => pattern.test(entry));
435 |           if (hasJobPattern) {
436 |             assert.ok(true, 'Entry contains recognizable SFCC job pattern');
437 |           }
438 |         });
439 |       }
440 |     });
441 | 
442 |     test('should validate customer activity tracking patterns', async () => {
443 |       const result = await client.callTool('search_logs', {
444 |         pattern: 'Customer',
445 |         logLevel: 'info',
446 |         limit: 10
447 |       });
448 |       
449 |       const analysis = analyzeSearchResults(result, 'Customer');
450 |       
451 |       if (analysis.hasMatches) {
452 |         
453 |         analysis.entries.forEach(entry => {
454 |           // Look for customer-related activities
455 |           const customerActivities = [
456 |             /customer\.id=/,
457 |             /Customer registration/,
458 |             /Customer login/,
459 |             /Customer profile/,
460 |             /Account-/
461 |           ];
462 |           
463 |           const hasActivity = customerActivities.some(pattern => pattern.test(entry));
464 |           if (hasActivity) {
465 |             
466 |             // Validate customer ID format if present
467 |             const customerIdMatch = entry.match(/customer\.id=([^\s]+)/);
468 |             if (customerIdMatch) {
469 |               const customerId = customerIdMatch[1];
470 |               assert.ok(customerId.length > 0,
471 |                 'Customer ID should not be empty');
472 |             }
473 |           }
474 |         });
475 |       }
476 |     });
477 |   });
478 | });
```

--------------------------------------------------------------------------------
/tests/mcp/node/get-latest-job-log-files.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { test, describe, before, after, beforeEach } from 'node:test';
  2 | import { strict as assert } from 'node:assert';
  3 | import { connect } from 'mcp-aegis';
  4 | 
  5 | describe('get_latest_job_log_files - Full Mode Programmatic Tests (Optimized)', () => {
  6 |   let client;
  7 |   let discoveredJobLogs = [];
  8 | 
  9 |   before(async () => {
 10 |     client = await connect('./aegis.config.with-dw.json');
 11 |     
 12 |     // Discover available job logs for advanced testing
 13 |     await discoverJobLogFiles();
 14 |   });
 15 | 
 16 |   after(async () => {
 17 |     if (client?.connected) {
 18 |       await client.disconnect();
 19 |     }
 20 |   });
 21 | 
 22 |   beforeEach(() => {
 23 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 24 |     client.clearAllBuffers(); // Recommended - comprehensive protection
 25 |   });
 26 | 
 27 |   // Helper functions for common validations
 28 |   function assertValidMCPResponse(result) {
 29 |     assert.ok(result.content, 'Should have content');
 30 |     assert.ok(Array.isArray(result.content), 'Content should be array');
 31 |     assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
 32 |   }
 33 | 
 34 |   function parseResponseText(text) {
 35 |     // The response may come wrapped in quotes, so parse if needed
 36 |     return text.startsWith('"') && text.endsWith('"') 
 37 |       ? JSON.parse(text) 
 38 |       : text;
 39 |   }
 40 | 
 41 |   function assertTextContent(result, expectedSubstring) {
 42 |     assertValidMCPResponse(result);
 43 |     assert.equal(result.content[0].type, 'text');
 44 |     const actualText = parseResponseText(result.content[0].text);
 45 |     assert.ok(actualText.includes(expectedSubstring),
 46 |       `Expected "${expectedSubstring}" in "${actualText}"`);
 47 |   }
 48 | 
 49 |   function assertSuccessResponse(result) {
 50 |     assertValidMCPResponse(result);
 51 |     assert.equal(result.isError, false, 'Should not be an error response');
 52 |     assert.equal(result.content[0].type, 'text');
 53 |   }
 54 | 
 55 |   function extractJobLogInfo(responseText) {
 56 |     const jobs = [];
 57 |     const text = parseResponseText(responseText);
 58 |     const sections = text.split('🔧 Job: ').slice(1); // Remove empty first element
 59 |     
 60 |     for (const section of sections) {
 61 |       const lines = section.split('\n');
 62 |       const jobName = lines[0].trim();
 63 |       
 64 |       let jobId = null;
 65 |       let fileName = null;
 66 |       let modified = null;
 67 |       let size = null;
 68 |       
 69 |       for (const line of lines) {
 70 |         const idMatch = line.match(/ID: (\d+)/);
 71 |         if (idMatch) jobId = idMatch[1];
 72 |         
 73 |         const fileMatch = line.match(/File: (.+\.log)/);
 74 |         if (fileMatch) fileName = fileMatch[1];
 75 |         
 76 |         const modifiedMatch = line.match(/Modified: (.+)/);
 77 |         if (modifiedMatch) modified = modifiedMatch[1];
 78 |         
 79 |         const sizeMatch = line.match(/Size: (.+)/);
 80 |         if (sizeMatch) size = sizeMatch[1];
 81 |       }
 82 |       
 83 |       if (jobName && jobId && fileName) {
 84 |         jobs.push({ jobName, jobId, fileName, modified, size });
 85 |       }
 86 |     }
 87 |     
 88 |     return jobs;
 89 |   }
 90 | 
 91 |   async function discoverJobLogFiles() {
 92 |     console.log('🔍 Discovering available job log files using MCP server...');
 93 |     
 94 |     try {
 95 |       const result = await client.callTool('get_latest_job_log_files', { limit: 10 });
 96 |       if (!result.isError) {
 97 |         discoveredJobLogs = extractJobLogInfo(result.content[0].text);
 98 |         console.log(`🔧 Found ${discoveredJobLogs.length} job logs:`, 
 99 |           discoveredJobLogs.map(j => `${j.jobName}(${j.jobId})`));
100 |       }
101 |     } catch (error) {
102 |       console.warn('⚠️ Could not discover job log files:', error.message);
103 |     }
104 |   }
105 | 
106 |   // Advanced functionality tests (basic functionality covered by YAML tests)
107 |   describe('Advanced Functionality', () => {
108 |     test('should return consistent job information across calls', async () => {
109 |       const result1 = await client.callTool('get_latest_job_log_files', { limit: 5 });
110 |       const result2 = await client.callTool('get_latest_job_log_files', { limit: 5 });
111 |       
112 |       assertSuccessResponse(result1);
113 |       assertSuccessResponse(result2);
114 |       
115 |       const jobs1 = extractJobLogInfo(result1.content[0].text);
116 |       const jobs2 = extractJobLogInfo(result2.content[0].text);
117 |       
118 |       // Job logs should be consistent (same files, same metadata)
119 |       assert.equal(jobs1.length, jobs2.length, 'Should return same number of jobs');
120 |       
121 |       for (let i = 0; i < jobs1.length; i++) {
122 |         assert.equal(jobs1[i].jobName, jobs2[i].jobName, 'Job names should be consistent');
123 |         assert.equal(jobs1[i].jobId, jobs2[i].jobId, 'Job IDs should be consistent');
124 |         assert.equal(jobs1[i].fileName, jobs2[i].fileName, 'File names should be consistent');
125 |         assert.equal(jobs1[i].size, jobs2[i].size, 'File sizes should be consistent');
126 |       }
127 |     });
128 |   });
129 | 
130 |   // Advanced parameter validation (basic validation covered by YAML tests)
131 |   describe('Advanced Parameter Validation', () => {
132 |     test('should handle large limit values correctly', async () => {
133 |       const result = await client.callTool('get_latest_job_log_files', { limit: 100 });
134 |       
135 |       assertSuccessResponse(result);
136 |       
137 |       const jobs = extractJobLogInfo(result.content[0].text);
138 |       assert.ok(jobs.length <= 100, 'Should handle large limits');
139 |       
140 |       // Should return all available jobs if limit exceeds available count
141 |       assert.equal(jobs.length, discoveredJobLogs.length, 
142 |         'Should return all available jobs when limit exceeds count');
143 |     });
144 | 
145 |     test('should handle missing arguments object gracefully', async () => {
146 |       const result = await client.callTool('get_latest_job_log_files');
147 |       
148 |       assertSuccessResponse(result);
149 |       
150 |       const jobs = extractJobLogInfo(result.content[0].text);
151 |       assert.ok(jobs.length <= 10, 'Should use default limit when no arguments provided');
152 |     });
153 | 
154 |     test('should ignore extraneous parameters', async () => {
155 |       const result = await client.callTool('get_latest_job_log_files', {
156 |         limit: 2,
157 |         invalidParam: 'should be ignored',
158 |         anotherParam: 123
159 |       });
160 |       
161 |       assertSuccessResponse(result);
162 |       
163 |       const jobs = extractJobLogInfo(result.content[0].text);
164 |       assert.ok(jobs.length <= 2, 'Should respect limit and ignore extraneous parameters');
165 |     });
166 |   });
167 | 
168 |   // Content validation tests (require complex programmatic logic)
169 |   describe('Content Validation', () => {
170 |     test('should include realistic SFCC job names', async () => {
171 |       const result = await client.callTool('get_latest_job_log_files', { limit: 5 });
172 |       
173 |       assertSuccessResponse(result);
174 |       const jobs = extractJobLogInfo(result.content[0].text);
175 |       
176 |       // Should contain common SFCC job patterns
177 |       const commonJobPatterns = [
178 |         'ImportCatalog',
179 |         'ProcessOrders', 
180 |         'ExportCustomers',
181 |         'UpdateInventory',
182 |         'GenerateReports'
183 |       ];
184 |       
185 |       const foundPatterns = jobs.some(job => 
186 |         commonJobPatterns.some(pattern => job.jobName.includes(pattern))
187 |       );
188 |       
189 |       assert.ok(foundPatterns, 'Should contain common SFCC job name patterns');
190 |     });
191 | 
192 |     test('should validate job ID format consistency', async () => {
193 |       const result = await client.callTool('get_latest_job_log_files', { limit: 5 });
194 |       
195 |       assertSuccessResponse(result);
196 |       const jobs = extractJobLogInfo(result.content[0].text);
197 |       
198 |       for (const job of jobs) {
199 |         // Job ID should be exactly 10 digits (timestamp-based)
200 |         assert.ok(/^\d{10}$/.test(job.jobId), 
201 |           `Job ID "${job.jobId}" should be 10 digits`);
202 |         
203 |         // File name should include job name and ID
204 |         assert.ok(job.fileName.includes(job.jobName), 
205 |           'File name should include job name');
206 |         assert.ok(job.fileName.includes(job.jobId), 
207 |           'File name should include job ID');
208 |       }
209 |     });
210 | 
211 |     test('should validate timestamp format consistency', async () => {
212 |       const result = await client.callTool('get_latest_job_log_files', { limit: 3 });
213 |       
214 |       assertSuccessResponse(result);
215 |       const jobs = extractJobLogInfo(result.content[0].text);
216 |       
217 |       for (const job of jobs) {
218 |         // Should be valid HTTP date format
219 |         assert.ok(/^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(job.modified),
220 |           `Modified time "${job.modified}" should match HTTP date format`);
221 |         
222 |         // Should parse as valid date
223 |         const date = new Date(job.modified);
224 |         assert.ok(!isNaN(date.getTime()), 'Modified time should be valid date');
225 |         
226 |         // Should be reasonable (not too far in past/future)
227 |         const now = new Date();
228 |         const daysDiff = Math.abs(now - date) / (1000 * 60 * 60 * 24);
229 |         assert.ok(daysDiff < 365, 'Modified time should be within last year');
230 |       }
231 |     });
232 | 
233 |     test('should validate file size format and reasonableness', async () => {
234 |       const result = await client.callTool('get_latest_job_log_files', { limit: 3 });
235 |       
236 |       assertSuccessResponse(result);
237 |       const jobs = extractJobLogInfo(result.content[0].text);
238 |       
239 |       for (const job of jobs) {
240 |         // Should match format: "X.XX KB" or "X.XX MB"
241 |         assert.ok(/^\d+\.\d{2} [KM]B$/.test(job.size),
242 |           `Size "${job.size}" should match expected format`);
243 |         
244 |         // Parse size and validate reasonableness
245 |         const [value, unit] = job.size.split(' ');
246 |         const sizeValue = parseFloat(value);
247 |         
248 |         assert.ok(sizeValue > 0, 'Size value should be positive');
249 |         assert.ok(sizeValue < 1000, 'Size value should be reasonable');
250 |         assert.ok(['KB', 'MB'].includes(unit), 'Size unit should be KB or MB');
251 |         
252 |         // Job logs should typically be small (under 10MB)
253 |         if (unit === 'MB') {
254 |           assert.ok(sizeValue < 10, 'Job logs should typically be under 10MB');
255 |         }
256 |       }
257 |     });
258 | 
259 |     test('should maintain chronological ordering', async () => {
260 |       const result = await client.callTool('get_latest_job_log_files', { limit: 5 });
261 |       
262 |       assertSuccessResponse(result);
263 |       const jobs = extractJobLogInfo(result.content[0].text);
264 |       
265 |       if (jobs.length > 1) {
266 |         // Jobs should be ordered by modification time (newest first)
267 |         for (let i = 0; i < jobs.length - 1; i++) {
268 |           const current = new Date(jobs[i].modified);
269 |           const next = new Date(jobs[i + 1].modified);
270 |           
271 |           assert.ok(current >= next, 
272 |             'Jobs should be ordered by modification time (newest first)');
273 |         }
274 |       }
275 |     });
276 |   });
277 | 
278 |   // Integration tests with other tools (require multi-tool workflows)
279 |   describe('Integration Tests', () => {
280 |     test('should integrate with get_log_file_contents for job logs', async () => {
281 |       const jobFilesResult = await client.callTool('get_latest_job_log_files', { limit: 1 });
282 |       
283 |       assertSuccessResponse(jobFilesResult);
284 |       const jobs = extractJobLogInfo(jobFilesResult.content[0].text);
285 |       
286 |       if (jobs.length > 0) {
287 |         const job = jobs[0];
288 |         const jobLogPath = `jobs/${job.jobName}/${job.fileName}`;
289 |         
290 |         // Try to read the job log file
291 |         const contentsResult = await client.callTool('get_log_file_contents', {
292 |           filename: jobLogPath
293 |         });
294 |         
295 |         assertSuccessResponse(contentsResult);
296 |         assertTextContent(contentsResult, 'Log File Contents:');
297 |         
298 |         console.log(`✅ Successfully integrated job file discovery with content reading: ${jobLogPath}`);
299 |       }
300 |     });
301 | 
302 |     test('should validate job log file paths for get_log_file_contents compatibility', async () => {
303 |       const result = await client.callTool('get_latest_job_log_files', { limit: 3 });
304 |       
305 |       assertSuccessResponse(result);
306 |       const jobs = extractJobLogInfo(result.content[0].text);
307 |       
308 |       for (const job of jobs) {
309 |         // Construct expected path format for get_log_file_contents
310 |         const expectedPath = `jobs/${job.jobName}/${job.fileName}`;
311 |         
312 |         // Validate path components
313 |         assert.ok(!expectedPath.includes('//'), 'Path should not have double slashes');
314 |         assert.ok(!expectedPath.includes(' '), 'Path should not contain spaces');
315 |         assert.ok(expectedPath.startsWith('jobs/'), 'Path should start with jobs/');
316 |         assert.ok(expectedPath.endsWith('.log'), 'Path should end with .log');
317 |         
318 |         console.log(`📝 Validated job log path format: ${expectedPath}`);
319 |       }
320 |     });
321 | 
322 |     test('should discover unique job types and patterns', async () => {
323 |       const result = await client.callTool('get_latest_job_log_files', { limit: 10 });
324 |       
325 |       assertSuccessResponse(result);
326 |       const jobs = extractJobLogInfo(result.content[0].text);
327 |       
328 |       // Extract unique job name patterns
329 |       const jobPatterns = new Set();
330 |       const jobTypes = new Set();
331 |       
332 |       for (const job of jobs) {
333 |         // Extract job type pattern (e.g., "ImportCatalog" from "ImportCatalogProducts")
334 |         const basePattern = job.jobName.replace(/[0-9]+$/, ''); // Remove trailing numbers
335 |         jobPatterns.add(basePattern);
336 |         
337 |         // Categorize job types
338 |         if (basePattern.includes('Import')) jobTypes.add('Import');
339 |         if (basePattern.includes('Export')) jobTypes.add('Export');
340 |         if (basePattern.includes('Process')) jobTypes.add('Process');
341 |         if (basePattern.includes('Generate')) jobTypes.add('Generate');
342 |         if (basePattern.includes('Update')) jobTypes.add('Update');
343 |       }
344 |       
345 |       console.log(`🔍 Found ${jobPatterns.size} unique job patterns:`, Array.from(jobPatterns));
346 |       console.log(`📊 Found ${jobTypes.size} job categories:`, Array.from(jobTypes));
347 |       
348 |       // Should have some variety in job types for a real SFCC environment
349 |       assert.ok(jobPatterns.size > 0, 'Should have discovered job patterns');
350 |     });
351 |   });
352 | 
353 |   // Edge cases and resilience testing (require complex validation logic)
354 |   describe('Edge Cases and Resilience', () => {
355 |     test('should handle boundary limit values', async () => {
356 |       const testLimits = [1, 50, 99];
357 |       
358 |       for (const limit of testLimits) {
359 |         const result = await client.callTool('get_latest_job_log_files', { limit });
360 |         
361 |         assertSuccessResponse(result);
362 |         const jobs = extractJobLogInfo(result.content[0].text);
363 |         
364 |         assert.ok(jobs.length <= limit, `Should not exceed limit ${limit}`);
365 |         assert.ok(jobs.length <= discoveredJobLogs.length, 
366 |           'Should not exceed available job count');
367 |         
368 |         console.log(`✅ Boundary test: limit=${limit}, returned=${jobs.length}`);
369 |       }
370 |     });
371 | 
372 |     test('should maintain response format consistency across different scenarios', async () => {
373 |       const scenarios = [
374 |         { limit: 1, description: 'single job' },
375 |         { limit: 5, description: 'multiple jobs' },
376 |         { limit: 100, description: 'exceed available' }
377 |       ];
378 |       
379 |       for (const scenario of scenarios) {
380 |         const result = await client.callTool('get_latest_job_log_files', { limit: scenario.limit });
381 |         
382 |         assertSuccessResponse(result);
383 |         const jobs = extractJobLogInfo(result.content[0].text);
384 |         
385 |         // Consistent response structure regardless of scenario
386 |         assert.equal(result.content.length, 1, 'Should have single content item');
387 |         assert.equal(result.content[0].type, 'text', 'Content should be text type');
388 |         
389 |         const text = parseResponseText(result.content[0].text);
390 |         assert.ok(text.includes('Found'), 'Should include count message');
391 |         
392 |         if (jobs.length > 0) {
393 |           assert.ok(text.includes('🔧 Job:'), 'Should include job emoji for non-empty results');
394 |         }
395 |         
396 |         console.log(`✅ Format consistency verified for ${scenario.description}: ${jobs.length} jobs`);
397 |       }
398 |     });
399 | 
400 |     test('should handle rapid sequential requests without interference', async () => {
401 |       const promises = [];
402 |       const requestCount = 5;
403 |       
404 |       // Fire multiple requests simultaneously
405 |       for (let i = 0; i < requestCount; i++) {
406 |         promises.push(client.callTool('get_latest_job_log_files', { limit: 2 }));
407 |       }
408 |       
409 |       const results = await Promise.all(promises);
410 |       
411 |       // All requests should succeed
412 |       for (let i = 0; i < requestCount; i++) {
413 |         assertSuccessResponse(results[i]);
414 |         
415 |         const jobs = extractJobLogInfo(results[i].content[0].text);
416 |         assert.ok(jobs.length <= 2, `Request ${i} should respect limit`);
417 |       }
418 |       
419 |       // Results should be consistent across requests
420 |       const firstJobsCount = extractJobLogInfo(results[0].content[0].text).length;
421 |       for (let i = 1; i < requestCount; i++) {
422 |         const jobsCount = extractJobLogInfo(results[i].content[0].text).length;
423 |         assert.equal(jobsCount, firstJobsCount, 
424 |           'Concurrent requests should return consistent results');
425 |       }
426 |       
427 |       console.log(`✅ Handled ${requestCount} concurrent requests successfully`);
428 |     });
429 | 
430 |     test('should have consistent content format across different parameters', async () => {
431 |       const testParams = [
432 |         {},
433 |         { limit: 1 },
434 |         { limit: 5 },
435 |         { limit: 10 }
436 |       ];
437 |       
438 |       for (const params of testParams) {
439 |         const result = await client.callTool('get_latest_job_log_files', params);
440 |         
441 |         assertValidMCPResponse(result);
442 |         assert.equal(result.isError, false, 'Should be successful response');
443 |         
444 |         const text = parseResponseText(result.content[0].text);
445 |         
446 |         // Consistent format elements
447 |         assert.ok(/Found \d+ job logs:/.test(text), 'Should have count pattern');
448 |         
449 |         const jobs = extractJobLogInfo(text);
450 |         if (jobs.length > 0) {
451 |           // Each job should have consistent format
452 |           for (const job of jobs) {
453 |             assert.ok(job.jobName && job.jobId && job.fileName, 
454 |               'Job should have required fields');
455 |           }
456 |         }
457 |       }
458 |     });
459 |   });
460 | });
```
Page 31/61FirstPrevNextLast