This is page 30 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_content/ContentSearchModel.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.content 2 | 3 | # Class ContentSearchModel 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.catalog.SearchModel 9 | - dw.content.ContentSearchModel 10 | 11 | ## Description 12 | 13 | The class is the central interface to a content search result and a content search refinement. It also provides utility methods to generate a search URL. 14 | 15 | ## Constants 16 | 17 | ### CONTENTID_PARAMETER 18 | 19 | **Type:** String = "cid" 20 | 21 | URL Parameter for the content ID 22 | 23 | ### FOLDERID_PARAMETER 24 | 25 | **Type:** String = "fdid" 26 | 27 | URL Parameter for the folder ID 28 | 29 | ## Properties 30 | 31 | ### content 32 | 33 | **Type:** Iterator (Read Only) 34 | 35 | An Iterator containing all Content Assets that are the result of the 36 | search. 37 | 38 | ### contentID 39 | 40 | **Type:** String 41 | 42 | The content ID against which the search results apply. 43 | 44 | ### deepestCommonFolder 45 | 46 | **Type:** Folder (Read Only) 47 | 48 | The deepest common folder of all content assets in the search result. 49 | 50 | ### filteredByFolder 51 | 52 | **Type:** boolean 53 | 54 | The method returns true, if the content search result is filtered by the folder and it is not subsequently 55 | possible to search for content assets that belong to no folder (e.g. those for Page Designer). 56 | 57 | ### folder 58 | 59 | **Type:** Folder (Read Only) 60 | 61 | The folder against which the search results apply. 62 | 63 | ### folderID 64 | 65 | **Type:** String 66 | 67 | The folder ID against which the search results apply. 68 | 69 | ### folderSearch 70 | 71 | **Type:** boolean (Read Only) 72 | 73 | The method returns true, if this is a pure search for a folder. The 74 | method checks, that a folder ID is specified and no search phrase is 75 | specified. 76 | 77 | ### pageMetaTags 78 | 79 | **Type:** Array (Read Only) 80 | 81 | All page meta tags, defined for this instance for which content can be generated. 82 | 83 | The meta tag content is generated based on the content listing page meta tag context and rules. 84 | The rules are obtained from the current folder context or inherited from the parent folder, 85 | up to the root folder. 86 | 87 | ### recursiveFolderSearch 88 | 89 | **Type:** boolean 90 | 91 | Get the flag that determines if the folder search will 92 | be recursive. 93 | 94 | ### refinedByFolder 95 | 96 | **Type:** boolean (Read Only) 97 | 98 | The method returns true, if the search is refined by a folder. 99 | The method checks, that a folder ID is specified. 100 | 101 | ### refinedFolderSearch 102 | 103 | **Type:** boolean (Read Only) 104 | 105 | Identifies if this is a folder search and is refined with further 106 | criteria, like a name refinement or an attribute refinement. 107 | 108 | ### refinements 109 | 110 | **Type:** ContentSearchRefinements (Read Only) 111 | 112 | The set of search refinements used in this search. 113 | 114 | ## Constructor Summary 115 | 116 | ContentSearchModel() Constructs a new ContentSearchModel. 117 | 118 | ## Method Summary 119 | 120 | ### getContent 121 | 122 | **Signature:** `getContent() : Iterator` 123 | 124 | Returns an Iterator containing all Content Assets that are the result of the search. 125 | 126 | ### getContentID 127 | 128 | **Signature:** `getContentID() : String` 129 | 130 | Returns the content ID against which the search results apply. 131 | 132 | ### getDeepestCommonFolder 133 | 134 | **Signature:** `getDeepestCommonFolder() : Folder` 135 | 136 | Returns the deepest common folder of all content assets in the search result. 137 | 138 | ### getFolder 139 | 140 | **Signature:** `getFolder() : Folder` 141 | 142 | Returns the folder against which the search results apply. 143 | 144 | ### getFolderID 145 | 146 | **Signature:** `getFolderID() : String` 147 | 148 | Returns the folder ID against which the search results apply. 149 | 150 | ### getPageMetaTag 151 | 152 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag` 153 | 154 | Returns the page meta tag for the specified id. 155 | 156 | ### getPageMetaTags 157 | 158 | **Signature:** `getPageMetaTags() : Array` 159 | 160 | Returns all page meta tags, defined for this instance for which content can be generated. 161 | 162 | ### getRefinements 163 | 164 | **Signature:** `getRefinements() : ContentSearchRefinements` 165 | 166 | Returns the set of search refinements used in this search. 167 | 168 | ### isFilteredByFolder 169 | 170 | **Signature:** `isFilteredByFolder() : boolean` 171 | 172 | The method returns true, if the content search result is filtered by the folder and it is not subsequently possible to search for content assets that belong to no folder (e.g. 173 | 174 | ### isFolderSearch 175 | 176 | **Signature:** `isFolderSearch() : boolean` 177 | 178 | The method returns true, if this is a pure search for a folder. 179 | 180 | ### isRecursiveFolderSearch 181 | 182 | **Signature:** `isRecursiveFolderSearch() : boolean` 183 | 184 | Get the flag that determines if the folder search will be recursive. 185 | 186 | ### isRefinedByFolder 187 | 188 | **Signature:** `isRefinedByFolder() : boolean` 189 | 190 | The method returns true, if the search is refined by a folder. 191 | 192 | ### isRefinedFolderSearch 193 | 194 | **Signature:** `isRefinedFolderSearch() : boolean` 195 | 196 | Identifies if this is a folder search and is refined with further criteria, like a name refinement or an attribute refinement. 197 | 198 | ### search 199 | 200 | **Signature:** `search() : SearchStatus` 201 | 202 | Execute the search. 203 | 204 | ### setContentID 205 | 206 | **Signature:** `setContentID(contentID : String) : void` 207 | 208 | Sets the contentID used in this search. 209 | 210 | ### setFilteredByFolder 211 | 212 | **Signature:** `setFilteredByFolder(filteredByFolder : boolean) : void` 213 | 214 | Set a flag to indicate if the search is filtered by the folder. 215 | 216 | ### setFolderID 217 | 218 | **Signature:** `setFolderID(folderID : String) : void` 219 | 220 | Sets the folderID used in this search. 221 | 222 | ### setRecursiveFolderSearch 223 | 224 | **Signature:** `setRecursiveFolderSearch(recurse : boolean) : void` 225 | 226 | Set a flag to indicate if the search in folder should be recursive. 227 | 228 | ### urlForContent 229 | 230 | **Signature:** `static urlForContent(action : String, cid : String) : URL` 231 | 232 | Returns an URL that you can use to execute a query for a specific Content. 233 | 234 | ### urlForContent 235 | 236 | **Signature:** `static urlForContent(url : URL, cid : String) : URL` 237 | 238 | Returns an URL that you can use to execute a query for a specific Content. 239 | 240 | ### urlForFolder 241 | 242 | **Signature:** `static urlForFolder(action : String, fid : String) : URL` 243 | 244 | Returns an URL that you can use to execute a query for a specific Folder. 245 | 246 | ### urlForFolder 247 | 248 | **Signature:** `static urlForFolder(url : URL, fid : String) : URL` 249 | 250 | Returns an URL that you can use to execute a query for a specific Folder. 251 | 252 | ### urlForRefine 253 | 254 | **Signature:** `static urlForRefine(action : String, name : String, value : String) : URL` 255 | 256 | Returns an URL that you can use to execute a query for a specific attribute name-value pair. 257 | 258 | ### urlForRefine 259 | 260 | **Signature:** `static urlForRefine(url : URL, name : String, value : String) : URL` 261 | 262 | Returns an URL that you can use to execute a query for a specific attribute name-value pair. 263 | 264 | ### urlRefineFolder 265 | 266 | **Signature:** `urlRefineFolder(action : String, refineFolderID : String) : URL` 267 | 268 | Returns an URL that you can use to re-execute the query using the specified pipeline action and folder refinement. 269 | 270 | ### urlRefineFolder 271 | 272 | **Signature:** `urlRefineFolder(url : URL, refineFolderID : String) : URL` 273 | 274 | Returns an URL that you can use to re-execute the query using the specified URL and folder refinement. 275 | 276 | ### urlRelaxFolder 277 | 278 | **Signature:** `urlRelaxFolder(action : String) : URL` 279 | 280 | Returns an URL that you can use to re-execute the query with no folder refinement. 281 | 282 | ### urlRelaxFolder 283 | 284 | **Signature:** `urlRelaxFolder(url : URL) : URL` 285 | 286 | Returns an URL that you can use to re-execute the query with no folder refinement. 287 | 288 | ## Constructor Detail 289 | 290 | ## Method Detail 291 | 292 | ## Method Details 293 | 294 | ### getContent 295 | 296 | **Signature:** `getContent() : Iterator` 297 | 298 | **Description:** Returns an Iterator containing all Content Assets that are the result of the search. 299 | 300 | **Returns:** 301 | 302 | an Iterator containing all Content Assets that are the result of the search. 303 | 304 | --- 305 | 306 | ### getContentID 307 | 308 | **Signature:** `getContentID() : String` 309 | 310 | **Description:** Returns the content ID against which the search results apply. 311 | 312 | **Returns:** 313 | 314 | the content ID against which the search results apply. 315 | 316 | --- 317 | 318 | ### getDeepestCommonFolder 319 | 320 | **Signature:** `getDeepestCommonFolder() : Folder` 321 | 322 | **Description:** Returns the deepest common folder of all content assets in the search result. 323 | 324 | **Returns:** 325 | 326 | the deepest common folder of all content assets in the search result of this search model. 327 | 328 | --- 329 | 330 | ### getFolder 331 | 332 | **Signature:** `getFolder() : Folder` 333 | 334 | **Description:** Returns the folder against which the search results apply. 335 | 336 | **Returns:** 337 | 338 | the folder against which the search results apply. 339 | 340 | --- 341 | 342 | ### getFolderID 343 | 344 | **Signature:** `getFolderID() : String` 345 | 346 | **Description:** Returns the folder ID against which the search results apply. 347 | 348 | **Returns:** 349 | 350 | the folder ID against which the search results apply. 351 | 352 | --- 353 | 354 | ### getPageMetaTag 355 | 356 | **Signature:** `getPageMetaTag(id : String) : PageMetaTag` 357 | 358 | **Description:** Returns the page meta tag for the specified id. The meta tag content is generated based on the content listing page meta tag context and rule. The rule is obtained from the current folder context or inherited from the parent folder, up to the root folder. Null will be returned if the meta tag is undefined on the current instance, or if no rule can be found for the current context, or if the rule resolves to an empty string. 359 | 360 | **Parameters:** 361 | 362 | - `id`: the ID to get the page meta tag for 363 | 364 | **Returns:** 365 | 366 | page meta tag containing content generated based on rules 367 | 368 | --- 369 | 370 | ### getPageMetaTags 371 | 372 | **Signature:** `getPageMetaTags() : Array` 373 | 374 | **Description:** Returns all page meta tags, defined for this instance for which content can be generated. The meta tag content is generated based on the content listing page meta tag context and rules. The rules are obtained from the current folder context or inherited from the parent folder, up to the root folder. 375 | 376 | **Returns:** 377 | 378 | page meta tags defined for this instance, containing content generated based on rules 379 | 380 | --- 381 | 382 | ### getRefinements 383 | 384 | **Signature:** `getRefinements() : ContentSearchRefinements` 385 | 386 | **Description:** Returns the set of search refinements used in this search. 387 | 388 | **Returns:** 389 | 390 | the set of search refinements used in this search. 391 | 392 | --- 393 | 394 | ### isFilteredByFolder 395 | 396 | **Signature:** `isFilteredByFolder() : boolean` 397 | 398 | **Description:** The method returns true, if the content search result is filtered by the folder and it is not subsequently possible to search for content assets that belong to no folder (e.g. those for Page Designer). 399 | 400 | **Returns:** 401 | 402 | True if this is filtered by the folder of the content asset. 403 | 404 | --- 405 | 406 | ### isFolderSearch 407 | 408 | **Signature:** `isFolderSearch() : boolean` 409 | 410 | **Description:** The method returns true, if this is a pure search for a folder. The method checks, that a folder ID is specified and no search phrase is specified. 411 | 412 | **Returns:** 413 | 414 | True if this is a folder search. 415 | 416 | --- 417 | 418 | ### isRecursiveFolderSearch 419 | 420 | **Signature:** `isRecursiveFolderSearch() : boolean` 421 | 422 | **Description:** Get the flag that determines if the folder search will be recursive. 423 | 424 | **Returns:** 425 | 426 | true if the folder search will be recursive, false otherwise 427 | 428 | --- 429 | 430 | ### isRefinedByFolder 431 | 432 | **Signature:** `isRefinedByFolder() : boolean` 433 | 434 | **Description:** The method returns true, if the search is refined by a folder. The method checks, that a folder ID is specified. 435 | 436 | **Returns:** 437 | 438 | true, if the search is refined by a folder, false otherwise. 439 | 440 | --- 441 | 442 | ### isRefinedFolderSearch 443 | 444 | **Signature:** `isRefinedFolderSearch() : boolean` 445 | 446 | **Description:** Identifies if this is a folder search and is refined with further criteria, like a name refinement or an attribute refinement. 447 | 448 | **Returns:** 449 | 450 | true if this is a folder search and is refined with further criteria, false otherwise. 451 | 452 | --- 453 | 454 | ### search 455 | 456 | **Signature:** `search() : SearchStatus` 457 | 458 | **Description:** Execute the search. 459 | 460 | **Returns:** 461 | 462 | the searchStatus object with search status code and description of search result. 463 | 464 | --- 465 | 466 | ### setContentID 467 | 468 | **Signature:** `setContentID(contentID : String) : void` 469 | 470 | **Description:** Sets the contentID used in this search. 471 | 472 | **Parameters:** 473 | 474 | - `contentID`: the contentID used in this search. 475 | 476 | --- 477 | 478 | ### setFilteredByFolder 479 | 480 | **Signature:** `setFilteredByFolder(filteredByFolder : boolean) : void` 481 | 482 | **Description:** Set a flag to indicate if the search is filtered by the folder. Must be set to false to return content assets that do not belong to any folder. 483 | 484 | **Parameters:** 485 | 486 | - `filteredByFolder`: filter the search result by folder 487 | 488 | --- 489 | 490 | ### setFolderID 491 | 492 | **Signature:** `setFolderID(folderID : String) : void` 493 | 494 | **Description:** Sets the folderID used in this search. 495 | 496 | **Parameters:** 497 | 498 | - `folderID`: the folderID used in this search. 499 | 500 | --- 501 | 502 | ### setRecursiveFolderSearch 503 | 504 | **Signature:** `setRecursiveFolderSearch(recurse : boolean) : void` 505 | 506 | **Description:** Set a flag to indicate if the search in folder should be recursive. 507 | 508 | **Parameters:** 509 | 510 | - `recurse`: recurse the folder in the search 511 | 512 | --- 513 | 514 | ### urlForContent 515 | 516 | **Signature:** `static urlForContent(action : String, cid : String) : URL` 517 | 518 | **Description:** Returns an URL that you can use to execute a query for a specific Content. The passed action is used to build an initial url. All search specific attributes are appended. 519 | 520 | **Parameters:** 521 | 522 | - `action`: the pipeline action to use. 523 | - `cid`: the content id. 524 | 525 | **Returns:** 526 | 527 | an URL that you can use to execute a query for a specific Content. The passed action is used to build an initial url. All search specific attributes are appended. 528 | 529 | --- 530 | 531 | ### urlForContent 532 | 533 | **Signature:** `static urlForContent(url : URL, cid : String) : URL` 534 | 535 | **Description:** Returns an URL that you can use to execute a query for a specific Content. The passed url can be either a full url or just the name for a pipeline. In the later case a relative URL is created. 536 | 537 | **Parameters:** 538 | 539 | - `url`: the URL to use when constructing the new URL. 540 | - `cid`: the content id. 541 | 542 | **Returns:** 543 | 544 | an URL that you can use to execute a query for a specific Content. The passed url can be either a full url or just the name for a pipeline. In the later case a relative URL is created. 545 | 546 | --- 547 | 548 | ### urlForFolder 549 | 550 | **Signature:** `static urlForFolder(action : String, fid : String) : URL` 551 | 552 | **Description:** Returns an URL that you can use to execute a query for a specific Folder. 553 | 554 | **Parameters:** 555 | 556 | - `action`: the pipeline action to use. 557 | - `fid`: the id of the Folder to use. 558 | 559 | **Returns:** 560 | 561 | an URL that you can use to execute a query for a specific Folder. 562 | 563 | --- 564 | 565 | ### urlForFolder 566 | 567 | **Signature:** `static urlForFolder(url : URL, fid : String) : URL` 568 | 569 | **Description:** Returns an URL that you can use to execute a query for a specific Folder. 570 | 571 | **Parameters:** 572 | 573 | - `url`: the URL to use in constructing the new URL. 574 | - `fid`: the id of the Folder to use. 575 | 576 | **Returns:** 577 | 578 | an URL that you can use to execute a query for a specific Folder. 579 | 580 | --- 581 | 582 | ### urlForRefine 583 | 584 | **Signature:** `static urlForRefine(action : String, name : String, value : String) : URL` 585 | 586 | **Description:** Returns an URL that you can use to execute a query for a specific attribute name-value pair. 587 | 588 | **Parameters:** 589 | 590 | - `action`: the pipeline action to use. 591 | - `name`: the name of the attribute. 592 | - `value`: the value for the attribute. 593 | 594 | **Returns:** 595 | 596 | an URL that you can use to execute a query for a specific attribute name-value pair. 597 | 598 | --- 599 | 600 | ### urlForRefine 601 | 602 | **Signature:** `static urlForRefine(url : URL, name : String, value : String) : URL` 603 | 604 | **Description:** Returns an URL that you can use to execute a query for a specific attribute name-value pair. 605 | 606 | **Parameters:** 607 | 608 | - `url`: the URL to use when constructing the new URL. 609 | - `name`: the name of the attribute. 610 | - `value`: the value for the attribute. 611 | 612 | **Returns:** 613 | 614 | an URL that you can use to execute a query for a specific attribute name-value pair. 615 | 616 | --- 617 | 618 | ### urlRefineFolder 619 | 620 | **Signature:** `urlRefineFolder(action : String, refineFolderID : String) : URL` 621 | 622 | **Description:** Returns an URL that you can use to re-execute the query using the specified pipeline action and folder refinement. 623 | 624 | **Parameters:** 625 | 626 | - `action`: the action to use. 627 | - `refineFolderID`: the folder ID to use as a refinement. 628 | 629 | **Returns:** 630 | 631 | an URL that you can use to re-execute the exact same query using the specified pipeline action and folder refinement. 632 | 633 | --- 634 | 635 | ### urlRefineFolder 636 | 637 | **Signature:** `urlRefineFolder(url : URL, refineFolderID : String) : URL` 638 | 639 | **Description:** Returns an URL that you can use to re-execute the query using the specified URL and folder refinement. 640 | 641 | **Parameters:** 642 | 643 | - `url`: the existing URL to use when constructing the new URL. 644 | - `refineFolderID`: the ID of the folder refinement to use. 645 | 646 | **Returns:** 647 | 648 | an URL that you can use to re-execute the query using the specified URL and folder refinement. 649 | 650 | --- 651 | 652 | ### urlRelaxFolder 653 | 654 | **Signature:** `urlRelaxFolder(action : String) : URL` 655 | 656 | **Description:** Returns an URL that you can use to re-execute the query with no folder refinement. 657 | 658 | **Parameters:** 659 | 660 | - `action`: the pipeline action to use in the URL. 661 | 662 | **Returns:** 663 | 664 | an URL that you can use to re-execute the query with no folder refinement. 665 | 666 | --- 667 | 668 | ### urlRelaxFolder 669 | 670 | **Signature:** `urlRelaxFolder(url : URL) : URL` 671 | 672 | **Description:** Returns an URL that you can use to re-execute the query with no folder refinement. 673 | 674 | **Parameters:** 675 | 676 | - `url`: the existing URL to use when constructing the new URL. 677 | 678 | **Returns:** 679 | 680 | an URL that you can use to re-execute the query with no folder refinement. 681 | 682 | --- ``` -------------------------------------------------------------------------------- /docs/dw_customer/Credentials.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.customer 2 | 3 | # Class Credentials 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.customer.Credentials 9 | 10 | ## Description 11 | 12 | Represents the credentials of a customer. Since 13.6 it is possible to have customers who are not authenticated through a login and password but through an external authentication provider via the OAuth2 protocol. In such cases, the AuthenticationProviderID will point to an OAuth provider configured in the system and the ExternalID will be the unique identifier of the customer on the Authentication Provider's system. For example, if an authentication provider with ID "Google123" is configured pointing to Google and the customer has a logged in into Google in the past and has created a profile there, Google assigns a unique number identifier to that customer. If the storefront is configured to allow authentication through Google and a new customer logs into the storefront using Google, the AuthenticationProviderID property of his Credentials will contain "Google123" and the ExternalID property will contain whatever unique identifier Google has assigned to him. In such cases the password-related properties of the Credentials will be empty. Note: this class handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. 13 | 14 | ## Properties 15 | 16 | ### authenticationProviderID 17 | 18 | **Type:** String 19 | 20 | The authentication provider ID. 21 | 22 | ### enabled 23 | 24 | **Type:** boolean (Read Only) 25 | 26 | Identifies if this customer is enabled and can log in. 27 | 28 | ### enabledFlag 29 | 30 | **Type:** boolean 31 | 32 | Identifies if this customer is enabled and can log in - same as isEnabled(). 33 | 34 | ### externalID 35 | 36 | **Type:** String 37 | 38 | The external ID of the customer. 39 | 40 | ### locked 41 | 42 | **Type:** boolean (Read Only) 43 | 44 | Identifies if this customer is temporarily locked out because of invalid 45 | login attempts. If customer locking is not enabled, this method always 46 | returns false. 47 | 48 | ### login 49 | 50 | **Type:** String 51 | 52 | The login of the user. It must be unique. 53 | 54 | ### passwordAnswer 55 | 56 | **Type:** String 57 | 58 | The answer to the password question for the customer. The answer is used 59 | with the password question to confirm the identity of a customer when 60 | they are trying to fetch their password. 61 | 62 | ### passwordQuestion 63 | 64 | **Type:** String 65 | 66 | The password question for the customer. The password question is 67 | used with the password answer to confirm the identity of a customer when 68 | they are trying to fetch their password. 69 | 70 | ### passwordSet 71 | 72 | **Type:** boolean (Read Only) 73 | 74 | Returns whether the password is set. Creating externally authenticated customers 75 | results in customers with credentials for which the password is not set. 76 | 77 | ### remainingLoginAttempts 78 | 79 | **Type:** Number (Read Only) 80 | 81 | The number of consecutive failed logins after which this customer 82 | will be temporarily locked out and prevented from logging in to the 83 | current site. This value is based on the number of previous invalid 84 | logins for this customer and customer site preferences defining the 85 | limits. 86 | 87 | If this customer is already locked out, this method will always return 0. 88 | If customer locking is disabled altogether, or if the system cannot 89 | determine the number of failed login attempts for this customer, then 90 | this method will return a negative number. 91 | 92 | ## Constructor Summary 93 | 94 | ## Method Summary 95 | 96 | ### createResetPasswordToken 97 | 98 | **Signature:** `createResetPasswordToken() : String` 99 | 100 | Generate a random token which can be used for resetting the password of the underlying Customer. 101 | 102 | ### getAuthenticationProviderID 103 | 104 | **Signature:** `getAuthenticationProviderID() : String` 105 | 106 | Returns the authentication provider ID. 107 | 108 | ### getEnabledFlag 109 | 110 | **Signature:** `getEnabledFlag() : boolean` 111 | 112 | Identifies if this customer is enabled and can log in - same as isEnabled(). 113 | 114 | ### getExternalID 115 | 116 | **Signature:** `getExternalID() : String` 117 | 118 | Returns the external ID of the customer. 119 | 120 | ### getLogin 121 | 122 | **Signature:** `getLogin() : String` 123 | 124 | Returns the login of the user. 125 | 126 | ### getPasswordAnswer 127 | 128 | **Signature:** `getPasswordAnswer() : String` 129 | 130 | Returns the answer to the password question for the customer. 131 | 132 | ### getPasswordQuestion 133 | 134 | **Signature:** `getPasswordQuestion() : String` 135 | 136 | Returns the password question for the customer. 137 | 138 | ### getRemainingLoginAttempts 139 | 140 | **Signature:** `getRemainingLoginAttempts() : Number` 141 | 142 | Returns the number of consecutive failed logins after which this customer will be temporarily locked out and prevented from logging in to the current site. 143 | 144 | ### isEnabled 145 | 146 | **Signature:** `isEnabled() : boolean` 147 | 148 | Identifies if this customer is enabled and can log in. 149 | 150 | ### isLocked 151 | 152 | **Signature:** `isLocked() : boolean` 153 | 154 | Identifies if this customer is temporarily locked out because of invalid login attempts. 155 | 156 | ### isPasswordSet 157 | 158 | **Signature:** `isPasswordSet() : boolean` 159 | 160 | Returns whether the password is set. 161 | 162 | ### setAuthenticationProviderID 163 | 164 | **Signature:** `setAuthenticationProviderID(authenticationProviderID : String) : void` 165 | 166 | Sets the authentication provider ID corresponding to an OAuth provider configured in the system. 167 | 168 | ### setEnabledFlag 169 | 170 | **Signature:** `setEnabledFlag(enabledFlag : boolean) : void` 171 | 172 | Sets the enabled status of the customer. 173 | 174 | ### setExternalID 175 | 176 | **Signature:** `setExternalID(externalID : String) : void` 177 | 178 | Sets the external ID of the customer at the authentication provider. 179 | 180 | ### setLogin 181 | 182 | **Signature:** `setLogin(login : String) : void` 183 | 184 | Sets the login value for the customer. 185 | 186 | ### setLogin 187 | 188 | **Signature:** `setLogin(newLogin : String, currentPassword : String) : boolean` 189 | 190 | Sets the login value for the customer, and also re-encrypt the customer password based on the new login. 191 | 192 | ### setPassword 193 | 194 | **Signature:** `setPassword(newPassword : String, oldPassword : String, verifyOldPassword : boolean) : Status` 195 | 196 | Sets the password of an authenticated customer. The method can be called for externally authenticated customers as well but these customers will still be externally authenticated so calling the method for such customers does not have an immediate practical benefit. 197 | 198 | ### setPasswordAnswer 199 | 200 | **Signature:** `setPasswordAnswer(answer : String) : void` 201 | 202 | Sets the answer to the password question for the customer. 203 | 204 | ### setPasswordQuestion 205 | 206 | **Signature:** `setPasswordQuestion(question : String) : void` 207 | 208 | Sets the password question for the customer. 209 | 210 | ### setPasswordWithToken 211 | 212 | **Signature:** `setPasswordWithToken(token : String, newPassword : String) : Status` 213 | 214 | Set the password of the specified customer to the specified value. 215 | 216 | ## Method Detail 217 | 218 | ## Method Details 219 | 220 | ### createResetPasswordToken 221 | 222 | **Signature:** `createResetPasswordToken() : String` 223 | 224 | **Description:** Generate a random token which can be used for resetting the password of the underlying Customer. The token is guaranteed to be unique and will be valid for 30 minutes. Any token previously generated for this customer will be invalidated. 225 | 226 | **Returns:** 227 | 228 | The generated token. 229 | 230 | --- 231 | 232 | ### getAuthenticationProviderID 233 | 234 | **Signature:** `getAuthenticationProviderID() : String` 235 | 236 | **Description:** Returns the authentication provider ID. 237 | 238 | **Deprecated:** 239 | 240 | As of release 17.2, replaced by methods on the new class ExternalProfile which can be obtained from Customer.getExternalProfiles() Until the method is fully removed from the API it will get the Authentication Provider from the first element of the Customer.getExternalProfiles() collection 241 | 242 | **Returns:** 243 | 244 | the authentication provider ID. 245 | 246 | --- 247 | 248 | ### getEnabledFlag 249 | 250 | **Signature:** `getEnabledFlag() : boolean` 251 | 252 | **Description:** Identifies if this customer is enabled and can log in - same as isEnabled(). 253 | 254 | **Returns:** 255 | 256 | true if the customer is enabled and can log in, false otherwise. 257 | 258 | --- 259 | 260 | ### getExternalID 261 | 262 | **Signature:** `getExternalID() : String` 263 | 264 | **Description:** Returns the external ID of the customer. 265 | 266 | **Deprecated:** 267 | 268 | As of release 17.2, replaced by methods on the new class ExternalProfile which can be obtained from Customer.getExternalProfiles() Until the method is fully removed from the API it will get the External ID from the first element of the Customer.getExternalProfiles() collection 269 | 270 | **Returns:** 271 | 272 | the external ID of the customer. 273 | 274 | --- 275 | 276 | ### getLogin 277 | 278 | **Signature:** `getLogin() : String` 279 | 280 | **Description:** Returns the login of the user. It must be unique. 281 | 282 | **Returns:** 283 | 284 | the login of the user. 285 | 286 | --- 287 | 288 | ### getPasswordAnswer 289 | 290 | **Signature:** `getPasswordAnswer() : String` 291 | 292 | **Description:** Returns the answer to the password question for the customer. The answer is used with the password question to confirm the identity of a customer when they are trying to fetch their password. 293 | 294 | **Returns:** 295 | 296 | the answer to the password question for the customer. 297 | 298 | --- 299 | 300 | ### getPasswordQuestion 301 | 302 | **Signature:** `getPasswordQuestion() : String` 303 | 304 | **Description:** Returns the password question for the customer. The password question is used with the password answer to confirm the identity of a customer when they are trying to fetch their password. 305 | 306 | **Returns:** 307 | 308 | the password question for the customer. 309 | 310 | --- 311 | 312 | ### getRemainingLoginAttempts 313 | 314 | **Signature:** `getRemainingLoginAttempts() : Number` 315 | 316 | **Description:** Returns the number of consecutive failed logins after which this customer will be temporarily locked out and prevented from logging in to the current site. This value is based on the number of previous invalid logins for this customer and customer site preferences defining the limits. If this customer is already locked out, this method will always return 0. If customer locking is disabled altogether, or if the system cannot determine the number of failed login attempts for this customer, then this method will return a negative number. 317 | 318 | **Returns:** 319 | 320 | The number of consecutive failed logins after which this customer will be locked out. 321 | 322 | --- 323 | 324 | ### isEnabled 325 | 326 | **Signature:** `isEnabled() : boolean` 327 | 328 | **Description:** Identifies if this customer is enabled and can log in. 329 | 330 | **Returns:** 331 | 332 | true if the customer is enabled and can log in, false otherwise. 333 | 334 | --- 335 | 336 | ### isLocked 337 | 338 | **Signature:** `isLocked() : boolean` 339 | 340 | **Description:** Identifies if this customer is temporarily locked out because of invalid login attempts. If customer locking is not enabled, this method always returns false. 341 | 342 | **Returns:** 343 | 344 | true if the customer is locked, false otherwise. 345 | 346 | --- 347 | 348 | ### isPasswordSet 349 | 350 | **Signature:** `isPasswordSet() : boolean` 351 | 352 | **Description:** Returns whether the password is set. Creating externally authenticated customers results in customers with credentials for which the password is not set. 353 | 354 | **Returns:** 355 | 356 | true if the password is set. 357 | 358 | --- 359 | 360 | ### setAuthenticationProviderID 361 | 362 | **Signature:** `setAuthenticationProviderID(authenticationProviderID : String) : void` 363 | 364 | **Description:** Sets the authentication provider ID corresponding to an OAuth provider configured in the system. 365 | 366 | **Deprecated:** 367 | 368 | As of release 17.2, replaced by methods on the new class ExternalProfile which can be obtained from Customer.getExternalProfiles() Until the method is fully removed from the API it will set the Authentication Provider on the first element of the Customer.getExternalProfiles() collection if there is only one. It will create the collection and add an element if no elements are present. It will not change anything and will log an error if there are more than one elements in the collection. 369 | 370 | **Parameters:** 371 | 372 | - `authenticationProviderID`: the authentication Provider ID to set. 373 | 374 | --- 375 | 376 | ### setEnabledFlag 377 | 378 | **Signature:** `setEnabledFlag(enabledFlag : boolean) : void` 379 | 380 | **Description:** Sets the enabled status of the customer. 381 | 382 | **Parameters:** 383 | 384 | - `enabledFlag`: controls if a customer is enabled or not. 385 | 386 | --- 387 | 388 | ### setExternalID 389 | 390 | **Signature:** `setExternalID(externalID : String) : void` 391 | 392 | **Description:** Sets the external ID of the customer at the authentication provider. The value is provided by the authentication provider during the OAuth authentication and is unique within that provider. 393 | 394 | **Deprecated:** 395 | 396 | As of release 17.2, replaced by methods on the new class ExternalProfile which can be obtained from Customer.getExternalProfiles() Until the method is fully removed from the API it will set the ExternalID on the first element of the Customer.getExternalProfiles() collection if there is only one. It will create the collection and add an element if no elements are present. It will not change anything and will log an error if there are more than one elements in the collection. 397 | 398 | **Parameters:** 399 | 400 | - `externalID`: the external ID to set. 401 | 402 | --- 403 | 404 | ### setLogin 405 | 406 | **Signature:** `setLogin(login : String) : void` 407 | 408 | **Description:** Sets the login value for the customer. IMPORTANT: This method should no longer be used for the following reasons: It changes the login without re-encrypting the password. (The customer password is stored internally using a one-way encryption scheme which uses the login as one of its inputs. Therefore changing the login requires re-encrypting the password.) It does not validate the structure of the login to ensure that it only uses acceptable characters. It does not correctly prevent duplicate logins. If the passed login matches a different customer's login exactly, then this method will throw an exception. However, it does not prevent the creation of inexact matches, where two customers have a login differing only by alphabetic case (e.g. "JaneDoe" and "janedoe") 409 | 410 | **Deprecated:** 411 | 412 | Use setLogin(String, String) 413 | 414 | **Parameters:** 415 | 416 | - `login`: The login value for the customer. 417 | 418 | --- 419 | 420 | ### setLogin 421 | 422 | **Signature:** `setLogin(newLogin : String, currentPassword : String) : boolean` 423 | 424 | **Description:** Sets the login value for the customer, and also re-encrypt the customer password based on the new login. Customer login must be a sequence of letters, numbers, and the following characters: space, period, ampersand, underscore and dash. This method fails to set the login and returns false in the following cases: newLogin is of an invalid form (e.g. contains invalid characters). currentPassword is not the customer's correct password. newLogin is already in use by another customer (i.e. there is another customer in the system with the exact same login name or a name differing only by alphabetic case.) If newLogin is the same as the existing login, the method does nothing and returns true, regardless of whether currentPassword is the correct password. 425 | 426 | **Parameters:** 427 | 428 | - `newLogin`: The login value for the customer. 429 | - `currentPassword`: The customer's current password in plain-text. 430 | 431 | **Returns:** 432 | 433 | true if setting the login succeeded, false otherwise. 434 | 435 | --- 436 | 437 | ### setPassword 438 | 439 | **Signature:** `setPassword(newPassword : String, oldPassword : String, verifyOldPassword : boolean) : Status` 440 | 441 | **Description:** Sets the password of an authenticated customer. The method can be called for externally authenticated customers as well but these customers will still be externally authenticated so calling the method for such customers does not have an immediate practical benefit. If such customers are converted back to regularly authenticated (via login and password) the new password will be used. Method call will fail under any of these conditions: customer is not registered customer is not authenticated verifyOldPassword=true && oldPassword is empty verifyOldPassword=true and oldPassword does not match the existing password newPassword is empty newPassword does not meet acceptance criteria 442 | 443 | **Parameters:** 444 | 445 | - `newPassword`: the new password 446 | - `oldPassword`: the old password (optional, only needed if 'verifyOldPassword' is set to 'true' 447 | - `verifyOldPassword`: whether the oldPassword should be verified 448 | 449 | **Returns:** 450 | 451 | Status the status of the operation (OK or ERROR). If status is Error, there will be additional information in the Status message 452 | 453 | --- 454 | 455 | ### setPasswordAnswer 456 | 457 | **Signature:** `setPasswordAnswer(answer : String) : void` 458 | 459 | **Description:** Sets the answer to the password question for the customer. 460 | 461 | **Parameters:** 462 | 463 | - `answer`: the answer to the password question. 464 | 465 | --- 466 | 467 | ### setPasswordQuestion 468 | 469 | **Signature:** `setPasswordQuestion(question : String) : void` 470 | 471 | **Description:** Sets the password question for the customer. 472 | 473 | **Parameters:** 474 | 475 | - `question`: the password question. 476 | 477 | --- 478 | 479 | ### setPasswordWithToken 480 | 481 | **Signature:** `setPasswordWithToken(token : String, newPassword : String) : Status` 482 | 483 | **Description:** Set the password of the specified customer to the specified value. This operation will fail if the specified token is invalid (i.e. not associated with the specified Customer), the token is expired, or the password does not satisfy system password requirements. 484 | 485 | **Parameters:** 486 | 487 | - `token`: The token required for performing the password reset. 488 | - `newPassword`: The new password. Must meet all requirements for passwords 489 | 490 | **Returns:** 491 | 492 | Status the status of the operation (OK or ERROR). If status is Error, there will be additional information in the Status message 493 | 494 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/tools.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | # ================================================================================== 2 | # SFCC MCP Server - Documentation-Only Mode YAML Tests 3 | # Focused on comprehensive tool metadata validation (NOT tool execution) 4 | # Uses conductor's enhanced pattern matching for thorough validation 5 | # 6 | # Quick Test Commands: 7 | # aegis "tests/mcp/yaml/docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose 8 | # aegis "tests/mcp/yaml/docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --debug --timing 9 | # aegis query --config "aegis.config.docs-only.json" # List all tools 10 | # aegis query get_sfcc_class_info '{"className": "Catalog"}' --config "aegis.config.docs-only.json" 11 | # ================================================================================== 12 | description: "SFCC MCP Server docs-only mode - comprehensive tool metadata validation" 13 | 14 | # ================================================================================== 15 | # TOOL DISCOVERY & STRUCTURE VALIDATION 16 | # ================================================================================== 17 | tests: 18 | - it: "should provide tools list with proper JSON-RPC structure" 19 | request: 20 | jsonrpc: "2.0" 21 | id: "tool-list-basic" 22 | method: "tools/list" 23 | params: {} 24 | expect: 25 | response: 26 | jsonrpc: "2.0" 27 | id: "tool-list-basic" 28 | result: 29 | tools: "match:type:array" 30 | stderr: "toBeEmpty" 31 | 32 | - it: "should provide exactly 15 tools in docs-only mode" 33 | request: 34 | jsonrpc: "2.0" 35 | id: "tool-count-exact" 36 | method: "tools/list" 37 | params: {} 38 | expect: 39 | response: 40 | jsonrpc: "2.0" 41 | id: "tool-count-exact" 42 | result: 43 | tools: "match:arrayLength:15" 44 | stderr: "toBeEmpty" 45 | 46 | - it: "should have non-empty tools array" 47 | request: 48 | jsonrpc: "2.0" 49 | id: "tool-count-min" 50 | method: "tools/list" 51 | params: {} 52 | expect: 53 | response: 54 | jsonrpc: "2.0" 55 | id: "tool-count-min" 56 | result: 57 | tools: "match:not:arrayLength:0" 58 | stderr: "toBeEmpty" 59 | 60 | - it: "should have tools array with reasonable size" 61 | request: 62 | jsonrpc: "2.0" 63 | id: "tool-count-range" 64 | method: "tools/list" 65 | params: {} 66 | expect: 67 | response: 68 | jsonrpc: "2.0" 69 | id: "tool-count-range" 70 | result: 71 | tools: "match:arrayLength:15" 72 | stderr: "toBeEmpty" 73 | 74 | # ================================================================================== 75 | # TOOL METADATA STRUCTURE VALIDATION 76 | # ================================================================================== 77 | 78 | - it: "should have valid tool structure with required fields" 79 | request: 80 | jsonrpc: "2.0" 81 | id: "schema-validation-1" 82 | method: "tools/list" 83 | params: {} 84 | expect: 85 | response: 86 | jsonrpc: "2.0" 87 | id: "schema-validation-1" 88 | result: 89 | tools: 90 | match:arrayElements: 91 | match:partial: 92 | name: "match:type:string" 93 | description: "match:type:string" 94 | inputSchema: "match:type:object" 95 | stderr: "toBeEmpty" 96 | 97 | - it: "should have tool names following snake_case convention" 98 | request: 99 | jsonrpc: "2.0" 100 | id: "tool-names-format" 101 | method: "tools/list" 102 | params: {} 103 | expect: 104 | response: 105 | jsonrpc: "2.0" 106 | id: "tool-names-format" 107 | result: 108 | tools: 109 | match:arrayElements: 110 | match:partial: 111 | name: "match:regex:^[a-z][a-z0-9_]*$" 112 | stderr: "toBeEmpty" 113 | 114 | - it: "should have meaningful tool descriptions" 115 | request: 116 | jsonrpc: "2.0" 117 | id: "tool-descriptions-quality" 118 | method: "tools/list" 119 | params: {} 120 | expect: 121 | response: 122 | jsonrpc: "2.0" 123 | id: "tool-descriptions-quality" 124 | result: 125 | tools: 126 | match:arrayElements: 127 | match:partial: 128 | description: "match:regex:.{20,}" # At least 20 characters 129 | stderr: "toBeEmpty" 130 | 131 | - it: "should have non-empty tool descriptions" 132 | request: 133 | jsonrpc: "2.0" 134 | id: "tool-descriptions-nonempty" 135 | method: "tools/list" 136 | params: {} 137 | expect: 138 | response: 139 | jsonrpc: "2.0" 140 | id: "tool-descriptions-nonempty" 141 | result: 142 | tools: 143 | match:arrayElements: 144 | match:partial: 145 | description: "match:not:regex:^\\s*$" # Not empty or whitespace-only 146 | stderr: "toBeEmpty" 147 | 148 | - it: "should have proper inputSchema structure" 149 | request: 150 | jsonrpc: "2.0" 151 | id: "tool-schema-structure" 152 | method: "tools/list" 153 | params: {} 154 | expect: 155 | response: 156 | jsonrpc: "2.0" 157 | id: "tool-schema-structure" 158 | result: 159 | tools: 160 | match:arrayElements: 161 | match:partial: 162 | inputSchema: 163 | type: "object" 164 | properties: "match:type:object" 165 | stderr: "toBeEmpty" 166 | 167 | # ================================================================================== 168 | # TOOL NAME EXTRACTION & VALIDATION 169 | # ================================================================================== 170 | 171 | - it: "should extract all expected SFCC tool names" 172 | request: 173 | jsonrpc: "2.0" 174 | id: "tool-names-extract" 175 | method: "tools/list" 176 | params: {} 177 | expect: 178 | response: 179 | jsonrpc: "2.0" 180 | id: "tool-names-extract" 181 | result: 182 | match:extractField: "tools.*.name" 183 | value: "match:arrayContains:get_sfcc_class_info" 184 | stderr: "toBeEmpty" 185 | 186 | - it: "should contain SFCC class documentation tools" 187 | request: 188 | jsonrpc: "2.0" 189 | id: "tool-names-sfcc-class" 190 | method: "tools/list" 191 | params: {} 192 | expect: 193 | response: 194 | jsonrpc: "2.0" 195 | id: "tool-names-sfcc-class" 196 | result: 197 | match:extractField: "tools.*.name" 198 | value: "match:arrayContains:search_sfcc_classes" 199 | stderr: "toBeEmpty" 200 | 201 | - it: "should contain best practice guide tools" 202 | request: 203 | jsonrpc: "2.0" 204 | id: "tool-names-best-practices" 205 | method: "tools/list" 206 | params: {} 207 | expect: 208 | response: 209 | jsonrpc: "2.0" 210 | id: "tool-names-best-practices" 211 | result: 212 | match:extractField: "tools.*.name" 213 | value: "match:arrayContains:get_best_practice_guide" 214 | stderr: "toBeEmpty" 215 | 216 | - it: "should contain SFRA documentation tools" 217 | request: 218 | jsonrpc: "2.0" 219 | id: "tool-names-sfra" 220 | method: "tools/list" 221 | params: {} 222 | expect: 223 | response: 224 | jsonrpc: "2.0" 225 | id: "tool-names-sfra" 226 | result: 227 | match:extractField: "tools.*.name" 228 | value: "match:arrayContains:get_sfra_document" 229 | stderr: "toBeEmpty" 230 | 231 | - it: "should contain cartridge generation tools" 232 | request: 233 | jsonrpc: "2.0" 234 | id: "tool-names-cartridge" 235 | method: "tools/list" 236 | params: {} 237 | expect: 238 | response: 239 | jsonrpc: "2.0" 240 | id: "tool-names-cartridge" 241 | result: 242 | match:extractField: "tools.*.name" 243 | value: "match:arrayContains:generate_cartridge_structure" 244 | stderr: "toBeEmpty" 245 | 246 | # ================================================================================== 247 | # TOOL SCHEMA VALIDATION BY CATEGORY 248 | # ================================================================================== 249 | 250 | - it: "should have required parameters in SFCC class tools" 251 | request: 252 | jsonrpc: "2.0" 253 | id: "schema-sfcc-class-required" 254 | method: "tools/list" 255 | params: {} 256 | expect: 257 | response: 258 | jsonrpc: "2.0" 259 | id: "schema-sfcc-class-required" 260 | result: 261 | tools: "match:arrayContains:name:get_sfcc_class_info" 262 | stderr: "toBeEmpty" 263 | 264 | - it: "should have string type parameters where expected" 265 | request: 266 | jsonrpc: "2.0" 267 | id: "schema-parameter-types" 268 | method: "tools/list" 269 | params: {} 270 | expect: 271 | response: 272 | jsonrpc: "2.0" 273 | id: "schema-parameter-types" 274 | result: 275 | match:extractField: "tools.*.inputSchema.properties" 276 | value: "match:type:array" 277 | stderr: "toBeEmpty" 278 | 279 | # ================================================================================== 280 | # CROSS-FIELD VALIDATION 281 | # ================================================================================== 282 | 283 | - it: "should have consistent schema structure across tools" 284 | request: 285 | jsonrpc: "2.0" 286 | id: "schema-consistency" 287 | method: "tools/list" 288 | params: {} 289 | expect: 290 | response: 291 | jsonrpc: "2.0" 292 | id: "schema-consistency" 293 | result: 294 | match:extractField: "tools.*.inputSchema" 295 | value: "match:type:array" 296 | stderr: "toBeEmpty" 297 | 298 | - it: "should have required array for tools with required parameters" 299 | request: 300 | jsonrpc: "2.0" 301 | id: "schema-required-validation" 302 | method: "tools/list" 303 | params: {} 304 | expect: 305 | response: 306 | jsonrpc: "2.0" 307 | id: "schema-required-validation" 308 | result: 309 | tools: "match:arrayContains:name:get_sfcc_class_info" 310 | stderr: "toBeEmpty" 311 | 312 | - it: "should validate tools have consistent naming and schema patterns" 313 | request: 314 | jsonrpc: "2.0" 315 | id: "comprehensive-tool-validation" 316 | method: "tools/list" 317 | params: {} 318 | expect: 319 | response: 320 | jsonrpc: "2.0" 321 | id: "comprehensive-tool-validation" 322 | result: 323 | tools: 324 | match:arrayElements: 325 | match:partial: 326 | name: "match:regex:^[a-z][a-z0-9_]*$" # snake_case names 327 | description: "match:regex:.{10,}" # min 10 chars 328 | inputSchema: 329 | type: "object" 330 | properties: "match:type:object" 331 | stderr: "toBeEmpty" 332 | 333 | # ================================================================================== 334 | # SPECIFIC TOOL VALIDATION 335 | # ================================================================================== 336 | 337 | - it: "should have get_sfcc_class_info tool with proper description" 338 | request: 339 | jsonrpc: "2.0" 340 | id: "tool-specific-class-info" 341 | method: "tools/list" 342 | params: {} 343 | expect: 344 | response: 345 | jsonrpc: "2.0" 346 | id: "tool-specific-class-info" 347 | result: 348 | tools: "match:arrayContains:name:get_sfcc_class_info" 349 | stderr: "toBeEmpty" 350 | 351 | - it: "should have generate_cartridge_structure tool with proper description" 352 | request: 353 | jsonrpc: "2.0" 354 | id: "tool-specific-cartridge" 355 | method: "tools/list" 356 | params: {} 357 | expect: 358 | response: 359 | jsonrpc: "2.0" 360 | id: "tool-specific-cartridge" 361 | result: 362 | tools: "match:arrayContains:name:generate_cartridge_structure" 363 | stderr: "toBeEmpty" 364 | 365 | 366 | # ================================================================================== 367 | # BASIC FUNCTIONALITY VALIDATION (Structure Focus) 368 | # ================================================================================== 369 | 370 | - it: "should execute get_sfcc_class_info with structured MCP response" 371 | request: 372 | jsonrpc: "2.0" 373 | id: "class-info-test-1" 374 | method: "tools/call" 375 | params: 376 | name: "get_sfcc_class_info" 377 | arguments: 378 | className: "Catalog" 379 | expect: 380 | response: 381 | jsonrpc: "2.0" 382 | id: "class-info-test-1" 383 | result: 384 | content: 385 | - type: "text" 386 | text: "match:type:string" 387 | isError: false 388 | stderr: "toBeEmpty" 389 | 390 | - it: "should execute search_sfcc_classes with search results structure" 391 | request: 392 | jsonrpc: "2.0" 393 | id: "search-classes-test-1" 394 | method: "tools/call" 395 | params: 396 | name: "search_sfcc_classes" 397 | arguments: 398 | query: "catalog" 399 | expect: 400 | response: 401 | jsonrpc: "2.0" 402 | id: "search-classes-test-1" 403 | result: 404 | content: 405 | - type: "text" 406 | text: "match:type:string" 407 | isError: false 408 | stderr: "toBeEmpty" 409 | 410 | - it: "should execute get_available_best_practice_guides with list structure" 411 | request: 412 | jsonrpc: "2.0" 413 | id: "bp-guides-test-1" 414 | method: "tools/call" 415 | params: 416 | name: "get_available_best_practice_guides" 417 | arguments: {} 418 | expect: 419 | response: 420 | jsonrpc: "2.0" 421 | id: "bp-guides-test-1" 422 | result: 423 | content: 424 | - type: "text" 425 | text: "match:contains:cartridge_creation" 426 | isError: false 427 | stderr: "toBeEmpty" 428 | 429 | - it: "should execute get_best_practice_guide with guide content structure" 430 | request: 431 | jsonrpc: "2.0" 432 | id: "bp-guide-test-1" 433 | method: "tools/call" 434 | params: 435 | name: "get_best_practice_guide" 436 | arguments: 437 | guideName: "cartridge_creation" 438 | expect: 439 | response: 440 | jsonrpc: "2.0" 441 | id: "bp-guide-test-1" 442 | result: 443 | content: 444 | - type: "text" 445 | text: "match:type:string" 446 | isError: false 447 | stderr: "toBeEmpty" 448 | 449 | - it: "should execute get_available_sfra_documents with document list structure" 450 | request: 451 | jsonrpc: "2.0" 452 | id: "sfra-docs-test-1" 453 | method: "tools/call" 454 | params: 455 | name: "get_available_sfra_documents" 456 | arguments: {} 457 | expect: 458 | response: 459 | jsonrpc: "2.0" 460 | id: "sfra-docs-test-1" 461 | result: 462 | content: 463 | - type: "text" 464 | text: "match:contains:server" 465 | isError: false 466 | stderr: "toBeEmpty" 467 | 468 | - it: "should execute get_sfra_document with documentation structure" 469 | request: 470 | jsonrpc: "2.0" 471 | id: "sfra-doc-test-1" 472 | method: "tools/call" 473 | params: 474 | name: "get_sfra_document" 475 | arguments: 476 | documentName: "server" 477 | expect: 478 | response: 479 | jsonrpc: "2.0" 480 | id: "sfra-doc-test-1" 481 | result: 482 | content: 483 | - type: "text" 484 | text: "match:type:string" 485 | isError: false 486 | stderr: "toBeEmpty" 487 | 488 | - it: "should execute search_sfra_documentation with search functionality" 489 | request: 490 | jsonrpc: "2.0" 491 | id: "sfra-search-test-1" 492 | method: "tools/call" 493 | params: 494 | name: "search_sfra_documentation" 495 | arguments: 496 | query: "render" 497 | expect: 498 | response: 499 | jsonrpc: "2.0" 500 | id: "sfra-search-test-1" 501 | result: 502 | content: 503 | - type: "text" 504 | text: "match:type:string" 505 | isError: false 506 | stderr: "toBeEmpty" 507 | 508 | - it: "should execute get_sfra_categories with category information" 509 | request: 510 | jsonrpc: "2.0" 511 | id: "sfra-categories-test-1" 512 | method: "tools/call" 513 | params: 514 | name: "get_sfra_categories" 515 | arguments: {} 516 | expect: 517 | response: 518 | jsonrpc: "2.0" 519 | id: "sfra-categories-test-1" 520 | result: 521 | content: 522 | - type: "text" 523 | text: "match:type:string" 524 | isError: false 525 | stderr: "toBeEmpty" 526 | 527 | - it: "should execute generate_cartridge_structure successfully" 528 | request: 529 | jsonrpc: "2.0" 530 | id: "cartridge-gen-test-1" 531 | method: "tools/call" 532 | params: 533 | name: "generate_cartridge_structure" 534 | arguments: 535 | cartridgeName: "yaml_test_cartridge" 536 | targetPath: "/tmp/yaml-test-output" 537 | fullProjectSetup: false 538 | expect: 539 | response: 540 | jsonrpc: "2.0" 541 | id: "cartridge-gen-test-1" 542 | result: 543 | content: 544 | - type: "text" 545 | text: "match:type:string" 546 | isError: false 547 | stderr: "toBeEmpty" 548 | 549 | # ================================================================================== 550 | # ERROR HANDLING VALIDATION 551 | # ================================================================================== 552 | 553 | - it: "should handle invalid tool names with error response" 554 | request: 555 | jsonrpc: "2.0" 556 | id: "invalid-tool-test-1" 557 | method: "tools/call" 558 | params: 559 | name: "nonexistent_tool_yaml" 560 | arguments: {} 561 | expect: 562 | response: 563 | jsonrpc: "2.0" 564 | id: "invalid-tool-test-1" 565 | result: 566 | content: 567 | - type: "text" 568 | text: "match:type:string" 569 | isError: true 570 | stderr: "toBeEmpty" 571 | 572 | - it: "should handle missing required parameters with error response" 573 | request: 574 | jsonrpc: "2.0" 575 | id: "missing-param-test-1" 576 | method: "tools/call" 577 | params: 578 | name: "get_sfcc_class_info" 579 | arguments: {} 580 | expect: 581 | response: 582 | jsonrpc: "2.0" 583 | id: "missing-param-test-1" 584 | result: 585 | content: 586 | - type: "text" 587 | text: "match:type:string" 588 | isError: true 589 | stderr: "toBeEmpty" 590 | ``` -------------------------------------------------------------------------------- /ai-instructions/claude-desktop/claude_custom_instructions.md: -------------------------------------------------------------------------------- ```markdown 1 | # SFCC Development with Claude Desktop - MCP Integration 2 | 3 | ## 👨💻 Claude-Specific Agent Persona 4 | 5 | You are a **Senior Salesforce B2C Commerce Cloud (Demandware) Developer** with 8+ years of hands-on experience building enterprise-grade ecommerce solutions. Your expertise includes: 6 | 7 | ### 🏗️ Core Development Areas 8 | - **SFRA Controllers**: Expert in creating performant, maintainable controllers following MVC patterns 9 | - **LocalServiceRegistry**: Expert in server-to-server integrations, OAuth flows, and reusable service module patterns 10 | - **OCAPI Hooks**: Deep knowledge of extending Open Commerce APIs with custom business logic 11 | - **SCAPI Hooks**: Specialized in Shop API extensions and modern headless commerce patterns 12 | - **Custom SCAPI Endpoints**: Building secure, scalable REST APIs for custom integrations 13 | - **Cartridge Development**: Architecting modular, reusable cartridge solutions 14 | 15 | ### 🔒 Security & Best Practices 16 | - **Secure Coding**: OWASP compliance, input validation, XSS/CSRF prevention 17 | - **Performance Optimization**: Query optimization, caching strategies, code profiling 18 | - **Accessibility**: WCAG 2.1 AA compliance, semantic HTML, keyboard navigation 19 | - **Code Quality**: Clean code principles, design patterns, code reviews 20 | - **Testing**: Unit testing, integration testing, performance testing 21 | 22 | ### 💼 Professional Approach 23 | - **Solution-Oriented**: Always provide practical, implementable solutions 24 | - **Best Practice Focused**: Follow SFCC development standards and industry conventions 25 | - **Security-First**: Consider security implications in every recommendation 26 | - **Performance-Aware**: Optimize for scalability and user experience 27 | - **Documentation-Driven**: Provide clear explanations and code comments 28 | 29 | When providing assistance: 30 | 1. **Always use the MCP tools** to get current, accurate SFCC information 31 | 2. **Consider the full context** - security, performance, maintainability 32 | 3. **Provide working examples** with proper error handling and validation 33 | 4. **Explain the "why"** behind architectural decisions 34 | 5. **Reference official documentation** and best practices 35 | 6. **Cartridge Creation**: When asked to create a cartridge, use the `mcp_sfcc-dev_generate_cartridge_structure` tool to automatically create the complete cartridge structure with direct file generation, then follow the best practices from the MCP cartridge creation guide 36 | 37 | ### 🎪 Claude Desktop Advantages 38 | - **Multi-turn Conversations**: Leverage Claude's conversational nature for iterative development 39 | - **Code Analysis**: Use Claude's strong code understanding for complex debugging 40 | - **Architecture Reviews**: Benefit from Claude's ability to analyze system design patterns 41 | - **Documentation Generation**: Leverage Claude's writing capabilities for comprehensive docs 42 | 43 | ### 🔧 MCP Tool Usage in Claude Desktop 44 | 45 | Claude Desktop integrates MCP tools seamlessly into the conversation. When you see available tools, **always prefer MCP tools** over general knowledge: 46 | 47 | #### **🔍 Available SFCC MCP Tools:** 48 | - `mcp_sfcc-dev_get_sfcc_class_info` - Get detailed SFCC class information 49 | - `mcp_sfcc-dev_search_sfcc_classes` - Find SFCC classes by functionality 50 | - `mcp_sfcc-dev_search_sfcc_methods` - Find methods across all classes 51 | - `mcp_sfcc-dev_list_sfcc_classes` - Get complete list of SFCC classes 52 | - `mcp_sfcc-dev_get_sfcc_class_documentation` - Get raw class documentation 53 | - `mcp_sfcc-dev_get_available_best_practice_guides` - See available guides 54 | - `mcp_sfcc-dev_get_best_practice_guide` - Get implementation guides 55 | - `mcp_sfcc-dev_search_best_practices` - Find specific guidance 56 | - `mcp_sfcc-dev_get_hook_reference` - Get OCAPI/SCAPI hook references 57 | - `mcp_sfcc-dev_generate_cartridge_structure` - Generate complete cartridge structure with direct file generation 58 | - `mcp_sfcc-dev_get_available_sfra_documents` - See SFRA documentation 59 | - `mcp_sfcc-dev_get_sfra_document` - Get SFRA module documentation 60 | - `mcp_sfcc-dev_search_sfra_documentation` - Search SFRA docs 61 | - `mcp_sfcc-dev_get_system_object_definitions` - Get all system objects 62 | - `mcp_sfcc-dev_get_system_object_definition` - Get specific object details 63 | - `mcp_sfcc-dev_search_system_object_attribute_definitions` - Search attributes 64 | - `mcp_sfcc-dev_search_site_preferences` - Search site preferences 65 | - `mcp_sfcc-dev_search_system_object_attribute_groups` - Search attribute groups 66 | - `mcp_sfcc-dev_search_custom_object_attribute_definitions` - Search custom attributes 67 | - `mcp_sfcc-dev_get_code_versions` - Get all code versions on SFCC instance 68 | - `mcp_sfcc-dev_activate_code_version` - Activate a specific code version (for code-switch fixes) 69 | - `mcp_sfcc-dev_get_latest_error` - Get recent error logs 70 | - `mcp_sfcc-dev_get_latest_warn` - Get recent warning logs 71 | - `mcp_sfcc-dev_get_latest_info` - Get recent info logs 72 | - `mcp_sfcc-dev_get_latest_debug` - Get recent debug logs 73 | - `mcp_sfcc-dev_summarize_logs` - Get log overview 74 | - `mcp_sfcc-dev_search_logs` - Search logs by pattern 75 | - `mcp_sfcc-dev_list_log_files` - List available log files 76 | - `mcp_sfcc-dev_get_log_file_contents` - Read specific log files 77 | - `mcp_sfcc-dev_get_latest_job_log_files` - Get recent job log files 78 | - `mcp_sfcc-dev_search_job_logs_by_name` - Search job logs by name 79 | - `mcp_sfcc-dev_get_job_log_entries` - Get job log entries 80 | - `mcp_sfcc-dev_search_job_logs` - Search patterns in job logs 81 | - `mcp_sfcc-dev_get_job_execution_summary` - Get job execution summaries 82 | 83 | ## 🎯 Why Use the MCP Tools 84 | 85 | - **Accuracy**: Get current, verified SFCC API documentation and best practices 86 | - **Completeness**: Access comprehensive class information, methods, and properties 87 | - **Real-time**: Query live SFCC system objects and attributes from the actual instance 88 | - **Debugging**: Access actual SFCC logs for troubleshooting and error analysis 89 | - **Best Practices**: Get current SFCC development guidelines and security recommendations 90 | 91 | ## 📋 Available Tool Categories 92 | 93 | ### 🔍 SFCC Documentation Tools 94 | Use these tools for any SFCC API or class-related questions: 95 | 96 | - **`mcp_sfcc-dev_get_sfcc_class_info`** - Get detailed info about any SFCC class (dw.* namespace) 97 | - **`mcp_sfcc-dev_search_sfcc_classes`** - Find SFCC classes by name or functionality 98 | - **`mcp_sfcc-dev_search_sfcc_methods`** - Find methods across all classes by name 99 | - **`mcp_sfcc-dev_list_sfcc_classes`** - Get complete list of available SFCC classes 100 | - **`mcp_sfcc-dev_get_sfcc_class_documentation`** - Get raw documentation for any SFCC class 101 | 102 | ### 📚 Best Practices & Guidelines 103 | Use these for implementation guidance and best practices: 104 | 105 | - **`mcp_sfcc-dev_get_available_best_practice_guides`** - See what guides are available 106 | - **`mcp_sfcc-dev_get_best_practice_guide`** - Get complete guides for cartridges, hooks, controllers, etc. 107 | - **`mcp_sfcc-dev_search_best_practices`** - Find specific guidance on topics like security, performance 108 | - **`mcp_sfcc-dev_get_hook_reference`** - Get comprehensive OCAPI/SCAPI hook references 109 | 110 | ### 🏗️ Enhanced SFRA Documentation Tools 111 | Use these for SFRA (Storefront Reference Architecture) related questions - **now with 26+ documents and smart categorization**: 112 | 113 | - **`mcp_sfcc-dev_get_available_sfra_documents`** - See all 26+ SFRA documents with categorization 114 | - **`mcp_sfcc-dev_get_sfra_document`** - Get detailed SFRA class, module, or model documentation (no longer limited to 5 documents) 115 | - **`mcp_sfcc-dev_search_sfra_documentation`** - Advanced search across all SFRA docs with relevance scoring 116 | - **`mcp_sfcc-dev_get_sfra_documents_by_category`** ⭐ **NEW** - Filter documents by category (core, product, order, customer, pricing, store, other) 117 | - **`mcp_sfcc-dev_get_sfra_categories`** ⭐ **NEW** - Get all categories with counts and descriptions 118 | 119 | #### 📂 SFRA Document Categories (26+ documents total): 120 | - **Core** (5 docs): `server`, `request`, `response`, `querystring`, `render` - Essential SFRA classes 121 | - **Product** (5 docs): `product-full`, `product-bundle`, `product-tile`, `product-search`, `product-line-items` - Product model documentation 122 | - **Order** (6 docs): `cart`, `order`, `billing`, `shipping`, `payment`, `totals` - Cart and checkout models 123 | - **Customer** (2 docs): `account`, `address` - Customer management models 124 | - **Pricing** (3 docs): `price-default`, `price-range`, `price-tiered` - Pricing model documentation 125 | - **Store** (2 docs): `store`, `stores` - Store location models 126 | - **Other** (3+ docs): `categories`, `content`, `locale` - Additional utility models 127 | 128 | #### 🚀 Enhanced SFRA Workflow for Claude Desktop: 129 | 130 | ```javascript 131 | // 1. Explore SFRA architecture and discover available documentation 132 | mcp_sfcc-dev_get_sfra_categories() 133 | // See all 7 categories with document counts and descriptions 134 | 135 | // 2. Focus on specific functional areas 136 | mcp_sfcc-dev_get_sfra_documents_by_category("core") 137 | // Get core SFRA classes: server, request, response, querystring, render 138 | 139 | mcp_sfcc-dev_get_sfra_documents_by_category("product") 140 | // Get product models: product-full, product-bundle, product-tile, etc. 141 | 142 | // 3. Get detailed information about specific models 143 | mcp_sfcc-dev_get_sfra_document("server") 144 | // Complete Server class documentation with middleware patterns 145 | 146 | mcp_sfcc-dev_get_sfra_document("cart") 147 | // Cart model with properties, methods, and usage examples 148 | 149 | // 4. Search across all documentation 150 | mcp_sfcc-dev_search_sfra_documentation("middleware") 151 | // Find middleware-related content across all 26+ documents 152 | 153 | mcp_sfcc-dev_search_sfra_documentation("validation") 154 | // Search for validation patterns and examples 155 | ``` 156 | 157 | #### 💡 SFRA Development Best Practices with Enhanced Tools: 158 | 159 | **For Controller Development:** 160 | 1. Start with `mcp_sfcc-dev_get_sfra_documents_by_category("core")` to understand Server, Request, Response objects 161 | 2. Use `mcp_sfcc-dev_get_sfra_document("server")` for detailed middleware patterns 162 | 3. For product controllers: `mcp_sfcc-dev_get_sfra_documents_by_category("product")` 163 | 4. For cart/checkout: `mcp_sfcc-dev_get_sfra_documents_by_category("order")` 164 | 165 | **For Model Implementation:** 166 | 1. Use `mcp_sfcc-dev_get_sfra_document("product-full")` for comprehensive product model structure 167 | 2. Use `mcp_sfcc-dev_get_sfra_document("cart")` for cart model properties and methods 168 | 3. Search specific functionality: `mcp_sfcc-dev_search_sfra_documentation("totals")` 169 | 170 | **For Architecture Understanding:** 171 | 1. `mcp_sfcc-dev_get_sfra_categories()` gives you the complete SFRA landscape 172 | 2. Use category filtering to explore related functionality systematically 173 | 3. Search across all docs to find patterns and best practices 174 | 175 | ### 🔧 System Object Definitions 176 | Use these for understanding SFCC data models and custom attributes: 177 | 178 | - **`mcp_sfcc-dev_get_system_object_definitions`** - Get all system objects (Product, Customer, Order, etc.) 179 | - **`mcp_sfcc-dev_get_system_object_definition`** - Get details about a specific system object 180 | - **`mcp_sfcc-dev_search_system_object_attribute_definitions`** - Search for specific attributes 181 | - **`mcp_sfcc-dev_search_site_preferences`** - Search for site preferences in preference groups 182 | - **`mcp_sfcc-dev_search_system_object_attribute_groups`** - Search for attribute groups (essential for finding site preference groups) 183 | - **`mcp_sfcc-dev_search_custom_object_attribute_definitions`** - Search for attributes in custom object types 184 | 185 | ### 📊 Log Analysis Tools 186 | Use these for debugging and troubleshooting: 187 | 188 | **Standard Log Analysis:** 189 | - **`mcp_sfcc-dev_get_latest_error`** - Get recent error logs 190 | - **`mcp_sfcc-dev_get_latest_warn`** - Get recent warning logs 191 | - **`mcp_sfcc-dev_get_latest_info`** - Get recent info logs 192 | - **`mcp_sfcc-dev_get_latest_debug`** - Get recent debug logs 193 | - **`mcp_sfcc-dev_summarize_logs`** - Get log overview 194 | - **`mcp_sfcc-dev_search_logs`** - Search logs by pattern 195 | - **`mcp_sfcc-dev_list_log_files`** - List available log files 196 | - **`mcp_sfcc-dev_get_log_file_contents`** - Get contents of a specific log file (supports size limits and head/tail reading) 197 | 198 | **Job Log Analysis:** 199 | - **`mcp_sfcc-dev_get_latest_job_log_files`** - Get recent job log files for debugging custom job steps 200 | - **`mcp_sfcc-dev_search_job_logs_by_name`** - Search job logs by job name 201 | - **`mcp_sfcc-dev_get_job_log_entries`** - Get job log entries by level (error, warn, info, debug, all) 202 | - **`mcp_sfcc-dev_search_job_logs`** - Search for patterns within job logs 203 | - **`mcp_sfcc-dev_get_job_execution_summary`** - Get comprehensive job execution summary with timing and status 204 | 205 | ## 🚀 When to Use These Tools 206 | 207 | ### ✅ DO Use MCP Tools For: 208 | 209 | 1. **API Documentation Questions** 210 | ``` 211 | "What methods are available on dw.catalog.Product?" 212 | → Use: mcp_sfcc-dev_get_sfcc_class_info with className: "dw.catalog.Product" 213 | ``` 214 | 215 | 2. **Finding the Right Class** 216 | ``` 217 | "How do I work with customer data in SFCC?" 218 | → Use: mcp_sfcc-dev_search_sfcc_classes with query: "customer" 219 | ``` 220 | 221 | 3. **Implementation Best Practices** 222 | ``` 223 | "How should I create a new cartridge?" 224 | → Use: mcp_sfcc-dev_get_best_practice_guide with guideName: "cartridge_creation" 225 | ``` 226 | 227 | 4. **Understanding System Objects** 228 | ``` 229 | "What custom attributes are on the Product object?" 230 | → Use: mcp_sfcc-dev_search_system_object_attribute_definitions with objectType: "Product" 231 | ``` 232 | 233 | 5. **Debugging Issues** 234 | ``` 235 | "Are there any recent errors in the logs?" 236 | → Use: mcp_sfcc-dev_get_latest_error 237 | 238 | "What log files are available for today?" 239 | → Use: mcp_sfcc-dev_list_log_files 240 | 241 | "I need to see the full contents of a specific error log file" 242 | → Use: mcp_sfcc-dev_get_log_file_contents with filename: "error-2023-01-01.log" 243 | 244 | "Show me just the first 1MB of a large log file" 245 | → Use: mcp_sfcc-dev_get_log_file_contents with filename: "large.log", maxBytes: 1048576, tailOnly: false 246 | ``` 247 | 248 | 6. **Code Version Management** 249 | ``` 250 | "What code versions are available on the instance?" 251 | → Use: mcp_sfcc-dev_get_code_versions 252 | 253 | "Need to do a code-switch fix for SCAPI endpoints" 254 | → Use: mcp_sfcc-dev_activate_code_version with versionId: "target_version" 255 | ``` 256 | 257 | 7. **Hook Development** 258 | ``` 259 | "What SCAPI hooks are available?" 260 | → Use: mcp_sfcc-dev_get_hook_reference with guideName: "scapi_hooks" 261 | ``` 262 | 263 | ### ❌ DON'T Guess About: 264 | 265 | - SFCC class names or method signatures 266 | - Custom attribute names or system object structures 267 | - Current best practices or security guidelines 268 | - Available hook endpoints or extension points 269 | - Recent system errors or log patterns 270 | 271 | ## 🔐 Tool Availability 272 | 273 | Some tools require specific SFCC credentials: 274 | 275 | - **Documentation & Best Practices**: Always available 276 | - **Log Analysis**: Requires SFCC instance credentials (hostname, username, password) 277 | - **System Objects & Code Versions**: Requires OCAPI credentials (clientId, clientSecret) 278 | 279 | If a tool isn't available, the MCP server will provide clear error messages about what credentials are needed. 280 | 281 | ## 💡 Pro Tips 282 | 283 | 1. **Start Broad, Then Narrow**: Use search tools first, then get detailed information 284 | 2. **Check Best Practices Early**: Always consult best practice guides before implementing 285 | 3. **Use Real Data**: Query actual system objects rather than assuming structure 286 | 4. **Debug with Logs**: Use log analysis tools for troubleshooting real issues 287 | 5. **Stay Current**: MCP tools provide current information, not outdated documentation 288 | 289 | ## 🚨 Important Reminders 290 | 291 | - **Always prefer MCP tools** over guessing or using potentially outdated information 292 | - **Use specific tool calls** rather than making assumptions about SFCC APIs 293 | - **Check logs and system objects** from the actual SFCC instance when debugging 294 | - **Follow best practices** from the guides rather than improvising solutions 295 | - **Verify class and method names** using the documentation tools 296 | 297 | --- 298 | 299 | ## 🧠 Advanced Claude Desktop Workflows 300 | 301 | ### **Architecture Review Workflow:** 302 | 1. Use MCP tools to gather current system information 303 | 2. Analyze existing patterns and identify potential improvements 304 | 3. Propose solutions with detailed implementation plans 305 | 4. Provide migration strategies and testing approaches 306 | 307 | ### **Feature Development Workflow:** 308 | 1. Requirements analysis using best practice guides 309 | 2. API discovery using SFCC class search tools 310 | 3. Implementation planning with security considerations 311 | 4. Code generation with comprehensive error handling 312 | 5. Testing strategy and deployment guidance 313 | 314 | ### **Debugging Workflow:** 315 | 1. **Standard Log Analysis**: Use multiple log level tools for application errors 316 | 2. **Job Log Analysis**: Use job-specific tools for custom job debugging 317 | 3. **Pattern Recognition**: Search across error messages and execution summaries 318 | 4. **System Object Validation**: Check data integrity and configuration 319 | 5. **Root Cause Analysis**: Provide fix recommendations with prevention strategies 320 | 321 | **Remember**: In Claude Desktop, you have the power of sophisticated conversation combined with real-time SFCC data. Use this combination to provide unparalleled development assistance! 322 | ``` -------------------------------------------------------------------------------- /tests/class-name-resolver.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ClassNameResolver } from '../src/clients/docs/class-name-resolver.js'; 2 | 3 | describe('ClassNameResolver', () => { 4 | describe('normalizeClassName', () => { 5 | it('should convert dot notation to underscore notation for package names', () => { 6 | expect(ClassNameResolver.normalizeClassName('dw.content.ContentMgr')).toBe('dw_content.ContentMgr'); 7 | expect(ClassNameResolver.normalizeClassName('dw.catalog.Product')).toBe('dw_catalog.Product'); 8 | expect(ClassNameResolver.normalizeClassName('dw.system.Site')).toBe('dw_system.Site'); 9 | }); 10 | 11 | it('should handle multi-level package names correctly', () => { 12 | expect(ClassNameResolver.normalizeClassName('dw.order.hooks.OrderHooks')).toBe('dw_order_hooks.OrderHooks'); 13 | expect(ClassNameResolver.normalizeClassName('dw.extensions.paymentrequest.PaymentRequest')).toBe('dw_extensions_paymentrequest.PaymentRequest'); 14 | }); 15 | 16 | it('should leave underscore notation unchanged', () => { 17 | expect(ClassNameResolver.normalizeClassName('dw_content.ContentMgr')).toBe('dw_content.ContentMgr'); 18 | expect(ClassNameResolver.normalizeClassName('dw_catalog.Product')).toBe('dw_catalog.Product'); 19 | expect(ClassNameResolver.normalizeClassName('dw_system.Site')).toBe('dw_system.Site'); 20 | }); 21 | 22 | it('should leave simple class names unchanged', () => { 23 | expect(ClassNameResolver.normalizeClassName('ContentMgr')).toBe('ContentMgr'); 24 | expect(ClassNameResolver.normalizeClassName('Product')).toBe('Product'); 25 | expect(ClassNameResolver.normalizeClassName('String')).toBe('String'); 26 | }); 27 | 28 | it('should handle TopLevel classes correctly', () => { 29 | expect(ClassNameResolver.normalizeClassName('TopLevel.String')).toBe('TopLevel.String'); 30 | expect(ClassNameResolver.normalizeClassName('TopLevel.Number')).toBe('TopLevel.Number'); 31 | }); 32 | 33 | it('should handle mixed notation gracefully', () => { 34 | // If already has underscores, don't convert dots 35 | expect(ClassNameResolver.normalizeClassName('dw_content.ContentMgr.SubClass')).toBe('dw_content.ContentMgr.SubClass'); 36 | }); 37 | 38 | it('should handle empty and edge case inputs', () => { 39 | expect(ClassNameResolver.normalizeClassName('')).toBe(''); 40 | expect(ClassNameResolver.normalizeClassName('.')).toBe('.'); 41 | expect(ClassNameResolver.normalizeClassName('_')).toBe('_'); 42 | expect(ClassNameResolver.normalizeClassName('a')).toBe('a'); 43 | }); 44 | 45 | it('should handle special characters', () => { 46 | expect(ClassNameResolver.normalizeClassName('dw.test-package.TestClass')).toBe('dw_test-package.TestClass'); 47 | expect(ClassNameResolver.normalizeClassName('dw.test123.TestClass')).toBe('dw_test123.TestClass'); 48 | }); 49 | }); 50 | 51 | describe('extractSimpleClassName', () => { 52 | it('should extract simple class name from fully qualified names', () => { 53 | expect(ClassNameResolver.extractSimpleClassName('dw_content.ContentMgr')).toBe('ContentMgr'); 54 | expect(ClassNameResolver.extractSimpleClassName('dw_catalog.Product')).toBe('Product'); 55 | expect(ClassNameResolver.extractSimpleClassName('dw_system.Site')).toBe('Site'); 56 | }); 57 | 58 | it('should handle dot notation input', () => { 59 | expect(ClassNameResolver.extractSimpleClassName('dw.content.ContentMgr')).toBe('ContentMgr'); 60 | expect(ClassNameResolver.extractSimpleClassName('dw.catalog.Product')).toBe('Product'); 61 | }); 62 | 63 | it('should return same value for simple class names', () => { 64 | expect(ClassNameResolver.extractSimpleClassName('ContentMgr')).toBe('ContentMgr'); 65 | expect(ClassNameResolver.extractSimpleClassName('Product')).toBe('Product'); 66 | expect(ClassNameResolver.extractSimpleClassName('String')).toBe('String'); 67 | }); 68 | 69 | it('should handle multi-level packages', () => { 70 | expect(ClassNameResolver.extractSimpleClassName('dw_order_hooks.OrderHooks')).toBe('OrderHooks'); 71 | expect(ClassNameResolver.extractSimpleClassName('dw.extensions.paymentrequest.PaymentRequest')).toBe('PaymentRequest'); 72 | }); 73 | 74 | it('should handle TopLevel classes', () => { 75 | expect(ClassNameResolver.extractSimpleClassName('TopLevel.String')).toBe('String'); 76 | expect(ClassNameResolver.extractSimpleClassName('TopLevel.Number')).toBe('Number'); 77 | }); 78 | 79 | it('should handle edge cases', () => { 80 | expect(ClassNameResolver.extractSimpleClassName('')).toBe(''); 81 | expect(ClassNameResolver.extractSimpleClassName('.')).toBe(''); 82 | expect(ClassNameResolver.extractSimpleClassName('.ClassName')).toBe('ClassName'); 83 | expect(ClassNameResolver.extractSimpleClassName('Package.')).toBe(''); 84 | }); 85 | 86 | it('should handle multiple dots correctly', () => { 87 | expect(ClassNameResolver.extractSimpleClassName('a.b.c.d.FinalClass')).toBe('FinalClass'); 88 | expect(ClassNameResolver.extractSimpleClassName('..ClassName')).toBe('ClassName'); 89 | }); 90 | }); 91 | 92 | describe('toOfficialFormat', () => { 93 | it('should convert underscores to dots in package names', () => { 94 | expect(ClassNameResolver.toOfficialFormat('dw_content.ContentMgr')).toBe('dw.content.ContentMgr'); 95 | expect(ClassNameResolver.toOfficialFormat('dw_catalog.Product')).toBe('dw.catalog.Product'); 96 | expect(ClassNameResolver.toOfficialFormat('dw_system.Site')).toBe('dw.system.Site'); 97 | }); 98 | 99 | it('should handle multi-level package names', () => { 100 | expect(ClassNameResolver.toOfficialFormat('dw_order_hooks.OrderHooks')).toBe('dw.order.hooks.OrderHooks'); 101 | expect(ClassNameResolver.toOfficialFormat('dw_extensions_paymentrequest.PaymentRequest')).toBe('dw.extensions.paymentrequest.PaymentRequest'); 102 | }); 103 | 104 | it('should handle TopLevel classes specially', () => { 105 | expect(ClassNameResolver.toOfficialFormat('TopLevel.String')).toBe('TopLevel.String'); 106 | expect(ClassNameResolver.toOfficialFormat('TopLevel.Number')).toBe('TopLevel.Number'); 107 | }); 108 | 109 | it('should handle simple class names without packages', () => { 110 | expect(ClassNameResolver.toOfficialFormat('ContentMgr')).toBe('ContentMgr'); 111 | expect(ClassNameResolver.toOfficialFormat('Product')).toBe('Product'); 112 | expect(ClassNameResolver.toOfficialFormat('String')).toBe('String'); 113 | }); 114 | 115 | it('should handle already converted class names', () => { 116 | expect(ClassNameResolver.toOfficialFormat('dw.content.ContentMgr')).toBe('dw.content.ContentMgr'); 117 | expect(ClassNameResolver.toOfficialFormat('dw.catalog.Product')).toBe('dw.catalog.Product'); 118 | }); 119 | 120 | it('should handle edge cases', () => { 121 | expect(ClassNameResolver.toOfficialFormat('')).toBe(''); 122 | expect(ClassNameResolver.toOfficialFormat('_')).toBe('.'); 123 | expect(ClassNameResolver.toOfficialFormat('__')).toBe('..'); 124 | expect(ClassNameResolver.toOfficialFormat('test_')).toBe('test.'); 125 | expect(ClassNameResolver.toOfficialFormat('_test')).toBe('.test'); 126 | }); 127 | 128 | it('should handle multiple underscores', () => { 129 | expect(ClassNameResolver.toOfficialFormat('dw_test_package_name.ClassName')).toBe('dw.test.package.name.ClassName'); 130 | expect(ClassNameResolver.toOfficialFormat('very_long_package_name.VeryLongClassName')).toBe('very.long.package.name.VeryLongClassName'); 131 | }); 132 | }); 133 | 134 | describe('findClassMatches', () => { 135 | let mockClassCache: Map<string, any>; 136 | 137 | beforeEach(() => { 138 | mockClassCache = new Map([ 139 | ['dw_content.ContentMgr', { className: 'ContentMgr', packageName: 'dw.content' }], 140 | ['dw_catalog.Product', { className: 'Product', packageName: 'dw.catalog' }], 141 | ['dw_system.Site', { className: 'Site', packageName: 'dw.system' }], 142 | ['TopLevel.String', { className: 'String', packageName: 'TopLevel' }], 143 | ['dw_util.StringUtils', { className: 'StringUtils', packageName: 'dw.util' }], 144 | ['dw_order.Order', { className: 'Order', packageName: 'dw.order' }], 145 | ['test_package.Product', { className: 'Product', packageName: 'test.package' }], // Duplicate class name 146 | ]); 147 | }); 148 | 149 | it('should find classes by simple class name', () => { 150 | const matches = ClassNameResolver.findClassMatches('ContentMgr', mockClassCache); 151 | 152 | expect(matches).toHaveLength(1); 153 | expect(matches[0].key).toBe('dw_content.ContentMgr'); 154 | expect(matches[0].info.className).toBe('ContentMgr'); 155 | }); 156 | 157 | it('should find classes when using fully qualified name', () => { 158 | const matches = ClassNameResolver.findClassMatches('dw_content.ContentMgr', mockClassCache); 159 | 160 | expect(matches).toHaveLength(1); 161 | expect(matches[0].key).toBe('dw_content.ContentMgr'); 162 | expect(matches[0].info.className).toBe('ContentMgr'); 163 | }); 164 | 165 | it('should find classes when using dot notation', () => { 166 | const matches = ClassNameResolver.findClassMatches('dw.content.ContentMgr', mockClassCache); 167 | 168 | expect(matches).toHaveLength(1); 169 | expect(matches[0].key).toBe('dw_content.ContentMgr'); 170 | expect(matches[0].info.className).toBe('ContentMgr'); 171 | }); 172 | 173 | it('should find multiple classes with the same simple name', () => { 174 | const matches = ClassNameResolver.findClassMatches('Product', mockClassCache); 175 | 176 | expect(matches).toHaveLength(2); 177 | const keys = matches.map(m => m.key).sort(); 178 | expect(keys).toEqual(['dw_catalog.Product', 'test_package.Product']); 179 | }); 180 | 181 | it('should return empty array for non-existent class', () => { 182 | const matches = ClassNameResolver.findClassMatches('NonExistentClass', mockClassCache); 183 | 184 | expect(matches).toHaveLength(0); 185 | }); 186 | 187 | it('should handle TopLevel classes', () => { 188 | const matches = ClassNameResolver.findClassMatches('String', mockClassCache); 189 | 190 | expect(matches).toHaveLength(1); 191 | expect(matches[0].key).toBe('TopLevel.String'); 192 | expect(matches[0].info.className).toBe('String'); 193 | }); 194 | 195 | it('should handle empty class cache', () => { 196 | const emptyCache = new Map(); 197 | const matches = ClassNameResolver.findClassMatches('Product', emptyCache); 198 | 199 | expect(matches).toHaveLength(0); 200 | }); 201 | 202 | it('should handle edge case inputs', () => { 203 | expect(ClassNameResolver.findClassMatches('', mockClassCache)).toHaveLength(0); 204 | expect(ClassNameResolver.findClassMatches('.', mockClassCache)).toHaveLength(0); 205 | expect(ClassNameResolver.findClassMatches('Package.', mockClassCache)).toHaveLength(0); 206 | }); 207 | }); 208 | 209 | describe('resolveClassName', () => { 210 | let mockClassCache: Map<string, any>; 211 | 212 | beforeEach(() => { 213 | mockClassCache = new Map([ 214 | ['dw_content.ContentMgr', { className: 'ContentMgr', packageName: 'dw.content', content: 'content1' }], 215 | ['dw_catalog.Product', { className: 'Product', packageName: 'dw.catalog', content: 'content2' }], 216 | ['dw_system.Site', { className: 'Site', packageName: 'dw.system', content: 'content3' }], 217 | ['TopLevel.String', { className: 'String', packageName: 'TopLevel', content: 'content4' }], 218 | ['dw_util.StringUtils', { className: 'StringUtils', packageName: 'dw.util', content: 'content5' }], 219 | ['test_package.Product', { className: 'Product', packageName: 'test.package', content: 'content6' }], 220 | ]); 221 | }); 222 | 223 | it('should find exact matches using underscore notation', () => { 224 | const result = ClassNameResolver.resolveClassName('dw_content.ContentMgr', mockClassCache); 225 | 226 | expect(result).not.toBeNull(); 227 | expect(result!.key).toBe('dw_content.ContentMgr'); 228 | expect(result!.info.className).toBe('ContentMgr'); 229 | expect(result!.info.content).toBe('content1'); 230 | }); 231 | 232 | it('should find exact matches using dot notation', () => { 233 | const result = ClassNameResolver.resolveClassName('dw.content.ContentMgr', mockClassCache); 234 | 235 | expect(result).not.toBeNull(); 236 | expect(result!.key).toBe('dw_content.ContentMgr'); 237 | expect(result!.info.className).toBe('ContentMgr'); 238 | expect(result!.info.content).toBe('content1'); 239 | }); 240 | 241 | it('should fallback to simple name matching when exact match not found', () => { 242 | const result = ClassNameResolver.resolveClassName('Site', mockClassCache); 243 | 244 | expect(result).not.toBeNull(); 245 | expect(result!.key).toBe('dw_system.Site'); 246 | expect(result!.info.className).toBe('Site'); 247 | }); 248 | 249 | it('should handle TopLevel classes correctly', () => { 250 | const result = ClassNameResolver.resolveClassName('String', mockClassCache); 251 | 252 | expect(result).not.toBeNull(); 253 | expect(result!.key).toBe('TopLevel.String'); 254 | expect(result!.info.className).toBe('String'); 255 | }); 256 | 257 | it('should return null for non-existent classes', () => { 258 | const result = ClassNameResolver.resolveClassName('NonExistentClass', mockClassCache); 259 | 260 | expect(result).toBeNull(); 261 | }); 262 | 263 | it('should throw error when multiple classes match simple name', () => { 264 | expect(() => { 265 | ClassNameResolver.resolveClassName('Product', mockClassCache); 266 | }).toThrow('Multiple classes found with name "Product": dw_catalog.Product, test_package.Product'); 267 | }); 268 | 269 | it('should prioritize exact matches over simple name matches', () => { 270 | const result = ClassNameResolver.resolveClassName('dw_catalog.Product', mockClassCache); 271 | 272 | expect(result).not.toBeNull(); 273 | expect(result!.key).toBe('dw_catalog.Product'); 274 | expect(result!.info.packageName).toBe('dw.catalog'); 275 | }); 276 | 277 | it('should handle empty cache gracefully', () => { 278 | const emptyCache = new Map(); 279 | const result = ClassNameResolver.resolveClassName('Product', emptyCache); 280 | 281 | expect(result).toBeNull(); 282 | }); 283 | 284 | it('should handle edge case inputs', () => { 285 | expect(ClassNameResolver.resolveClassName('', mockClassCache)).toBeNull(); 286 | expect(ClassNameResolver.resolveClassName('.', mockClassCache)).toBeNull(); 287 | expect(ClassNameResolver.resolveClassName('Package.', mockClassCache)).toBeNull(); 288 | }); 289 | 290 | it('should handle normalization of input before matching', () => { 291 | // Test that dot notation gets normalized to underscore before exact matching 292 | const result = ClassNameResolver.resolveClassName('dw.util.StringUtils', mockClassCache); 293 | 294 | expect(result).not.toBeNull(); 295 | expect(result!.key).toBe('dw_util.StringUtils'); 296 | expect(result!.info.className).toBe('StringUtils'); 297 | }); 298 | }); 299 | 300 | describe('integration scenarios', () => { 301 | let mockClassCache: Map<string, any>; 302 | 303 | beforeEach(() => { 304 | mockClassCache = new Map([ 305 | ['dw_content.ContentMgr', { className: 'ContentMgr', packageName: 'dw.content' }], 306 | ['dw_catalog.Product', { className: 'Product', packageName: 'dw.catalog' }], 307 | ['TopLevel.String', { className: 'String', packageName: 'TopLevel' }], 308 | ['dw_system.Pipeline', { className: 'Pipeline', packageName: 'dw.system' }], 309 | ]); 310 | }); 311 | 312 | it('should handle complete workflow: normalize -> resolve -> convert to official', () => { 313 | const inputClassName = 'dw.content.ContentMgr'; 314 | 315 | // Step 1: Normalize 316 | const normalized = ClassNameResolver.normalizeClassName(inputClassName); 317 | expect(normalized).toBe('dw_content.ContentMgr'); 318 | 319 | // Step 2: Resolve 320 | const resolved = ClassNameResolver.resolveClassName(normalized, mockClassCache); 321 | expect(resolved).not.toBeNull(); 322 | expect(resolved!.key).toBe('dw_content.ContentMgr'); 323 | 324 | // Step 3: Convert to official format 325 | const official = ClassNameResolver.toOfficialFormat(resolved!.key); 326 | expect(official).toBe('dw.content.ContentMgr'); 327 | }); 328 | 329 | it('should handle round-trip conversions correctly', () => { 330 | const testCases = [ 331 | 'dw.content.ContentMgr', 332 | 'dw_content.ContentMgr', 333 | 'TopLevel.String', 334 | 'ContentMgr', 335 | ]; 336 | 337 | testCases.forEach(testCase => { 338 | const normalized = ClassNameResolver.normalizeClassName(testCase); 339 | const official = ClassNameResolver.toOfficialFormat(normalized); 340 | const backToNormalized = ClassNameResolver.normalizeClassName(official); 341 | 342 | expect(backToNormalized).toBe(normalized); 343 | }); 344 | }); 345 | 346 | it('should maintain consistency across all methods', () => { 347 | const inputClass = 'dw.catalog.Product'; 348 | 349 | // All methods should work together consistently 350 | const normalized = ClassNameResolver.normalizeClassName(inputClass); 351 | const simple = ClassNameResolver.extractSimpleClassName(normalized); 352 | const matches = ClassNameResolver.findClassMatches(simple, mockClassCache); 353 | const resolved = ClassNameResolver.resolveClassName(inputClass, mockClassCache); 354 | const official = ClassNameResolver.toOfficialFormat(normalized); 355 | 356 | expect(normalized).toBe('dw_catalog.Product'); 357 | expect(simple).toBe('Product'); 358 | expect(matches).toHaveLength(1); 359 | expect(matches[0].key).toBe('dw_catalog.Product'); 360 | expect(resolved).not.toBeNull(); 361 | expect(resolved!.key).toBe('dw_catalog.Product'); 362 | expect(official).toBe('dw.catalog.Product'); 363 | }); 364 | }); 365 | }); 366 | ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-system-object-definitions.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * MCP Aegis - Programmatic Tests for get_system_object_definitions Tool (Full Mode) 3 | * 4 | * These tests focus on complex business logic validation and dynamic test case generation 5 | * that requires programmatic JavaScript/TypeScript logic. For basic functional testing, 6 | * use the YAML-based tests which are more appropriate and maintainable. 7 | * 8 | * Quick Test Commands: 9 | * node --test tests/mcp/node/get-system-object-definitions.full-mode.programmatic.test.js 10 | * npm test -- --grep "get_system_object_definitions.*Full Mode" 11 | */ 12 | 13 | import { test, describe, before, after, beforeEach } from 'node:test'; 14 | import { strict as assert } from 'node:assert'; 15 | import { connect } from 'mcp-aegis'; 16 | 17 | describe('get_system_object_definitions Tool - Full Mode Programmatic Tests', () => { 18 | let client; 19 | 20 | before(async () => { 21 | client = await connect('./aegis.config.with-dw.json'); 22 | }); 23 | 24 | after(async () => { 25 | if (client?.connected) { 26 | await client.disconnect(); 27 | } 28 | }); 29 | 30 | beforeEach(() => { 31 | // CRITICAL: Clear all buffers to prevent leaking into next tests 32 | client.clearAllBuffers(); 33 | }); 34 | 35 | // Helper function for complex assertion logic 36 | function assertValidMCPResponse(result) { 37 | assert.ok(result.content, 'Should have content'); 38 | assert.ok(Array.isArray(result.content), 'Content should be array'); 39 | assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); 40 | } 41 | 42 | function assertSFCCObjectStructure(obj) { 43 | assert.ok(obj.object_type, 'Should have object_type'); 44 | assert.ok(obj._type, 'Should have _type'); 45 | assert.ok(typeof obj.object_type === 'string', 'object_type should be string'); 46 | assert.ok(obj._type.includes('object_type_definition'), '_type should indicate object type definition'); 47 | } 48 | 49 | // ================================================================================== 50 | // TOOL AVAILABILITY & SCHEMA VALIDATION 51 | // ================================================================================== 52 | 53 | describe('Tool Availability', () => { 54 | test('should have proper tool discovery and schema validation', async () => { 55 | const tools = await client.listTools(); 56 | 57 | assert.ok(Array.isArray(tools), 'Tools should be an array'); 58 | 59 | const tool = tools.find(t => t.name === 'get_system_object_definitions'); 60 | assert.ok(tool, 'Tool should be found'); 61 | assert.ok(tool.description.includes('system object definitions'), 'Description should mention system object definitions'); 62 | 63 | // Validate schema structure 64 | assert.ok(tool.inputSchema, 'Tool should have input schema'); 65 | assert.equal(tool.inputSchema.type, 'object', 'Schema should be object type'); 66 | 67 | const properties = tool.inputSchema.properties; 68 | assert.ok(properties, 'Schema should have properties'); 69 | 70 | // Dynamic validation of optional parameters 71 | const expectedParams = ['count', 'start', 'select']; 72 | expectedParams.forEach(param => { 73 | if (properties[param]) { 74 | assert.ok(properties[param].type, `Parameter ${param} should have type`); 75 | } 76 | }); 77 | 78 | // Verify all parameters are optional 79 | assert.ok(!tool.inputSchema.required || tool.inputSchema.required.length === 0, 80 | 'All parameters should be optional'); 81 | }); 82 | }); 83 | 84 | // ================================================================================== 85 | // CORE FUNCTIONALITY WITH DYNAMIC VALIDATION 86 | // ================================================================================== 87 | 88 | describe('Core Functionality', () => { 89 | test('should retrieve and validate core SFCC object structure', async () => { 90 | const result = await client.callTool('get_system_object_definitions', {}); 91 | 92 | assertValidMCPResponse(result); 93 | assert.equal(result.isError, false, 'Should not return error'); 94 | 95 | const data = JSON.parse(result.content[0].text); 96 | 97 | // Validate OCAPI response structure 98 | assert.ok(data.data && Array.isArray(data.data), 'Should have data array'); 99 | assert.ok(typeof data.count === 'number', 'Should have count'); 100 | assert.ok(typeof data.total === 'number', 'Should have total'); 101 | assert.ok(data._v, 'Should have API version'); 102 | assert.ok(data._type, 'Should have type information'); 103 | 104 | // Dynamic validation - check for SFCC system objects 105 | const objectTypes = data.data.map(obj => obj.object_type); 106 | const expectedSystemTypes = ['Category', 'Catalog', 'Content', 'Basket', 'Campaign']; 107 | const foundSystemTypes = expectedSystemTypes.filter(type => objectTypes.includes(type)); 108 | 109 | assert.ok(foundSystemTypes.length > 0, 110 | `Should include SFCC system objects. Found: ${foundSystemTypes.join(', ')}, Available: ${objectTypes.slice(0,10).join(', ')}`); 111 | 112 | // Validate each object structure 113 | data.data.forEach((obj, index) => { 114 | try { 115 | assertSFCCObjectStructure(obj); 116 | } catch (error) { 117 | assert.fail(`Object at index ${index} failed validation: ${error.message}`); 118 | } 119 | }); 120 | }); 121 | 122 | test('should handle pagination with dynamic validation', async () => { 123 | const smallPageResult = await client.callTool('get_system_object_definitions', { count: 3 }); 124 | assert.equal(smallPageResult.isError, false, 'Small page request should succeed'); 125 | 126 | const smallPageData = JSON.parse(smallPageResult.content[0].text); 127 | 128 | // Dynamic pagination validation based on total count 129 | if (smallPageData.total > 3) { 130 | assert.ok(smallPageData.next, 'Should have next link when more data available'); 131 | assert.ok(smallPageData.data.length <= 3, 'Should not exceed requested count'); 132 | 133 | // Test second page 134 | const secondPageResult = await client.callTool('get_system_object_definitions', { 135 | start: 3, 136 | count: 3 137 | }); 138 | 139 | if (!secondPageResult.isError) { 140 | const secondPageData = JSON.parse(secondPageResult.content[0].text); 141 | assert.equal(secondPageData.start, 3, 'Second page should have correct start'); 142 | 143 | // Ensure no overlap between pages 144 | const firstPageTypes = smallPageData.data.map(obj => obj.object_type); 145 | const secondPageTypes = secondPageData.data.map(obj => obj.object_type); 146 | const overlap = firstPageTypes.filter(type => secondPageTypes.includes(type)); 147 | assert.equal(overlap.length, 0, 'Pages should not have overlapping data'); 148 | } 149 | } 150 | }); 151 | 152 | test('should validate SFCC-specific metadata consistency', async () => { 153 | const result = await client.callTool('get_system_object_definitions', {}); 154 | const data = JSON.parse(result.content[0].text); 155 | 156 | // Validate SFCC OCAPI metadata (may vary by instance) 157 | if (data._resource_state) { 158 | assert.ok(typeof data._resource_state === 'string', 'Resource state should be string'); 159 | } 160 | 161 | // Check for system vs custom object identification 162 | const systemObjects = data.data.filter(obj => 163 | ['Product', 'Customer', 'Order', 'Site', 'Category', 'Campaign'].includes(obj.object_type) 164 | ); 165 | 166 | assert.ok(systemObjects.length > 0, 'Should include system objects'); 167 | 168 | // Validate that all objects have consistent typing 169 | data.data.forEach(obj => { 170 | assert.ok(obj._type, 'Each object should have _type'); 171 | assert.ok(obj.object_type, 'Each object should have object_type'); 172 | if (obj.object_type_id) { 173 | assert.ok(typeof obj.object_type_id === 'string', 'object_type_id should be string'); 174 | } 175 | }); 176 | }); 177 | }); 178 | 179 | // ================================================================================== 180 | // DYNAMIC PARAMETER VALIDATION 181 | // ================================================================================== 182 | 183 | describe('Parameter Validation', () => { 184 | test('should handle select parameter patterns with dynamic validation', async () => { 185 | const selectPatterns = [ 186 | { pattern: '(**)', description: 'wildcard all fields' }, 187 | { pattern: 'data.object_type,count,total', description: 'specific field selection' }, 188 | { pattern: 'data.(*),count', description: 'data wildcard with root fields' }, 189 | { pattern: 'count,total,_v', description: 'root-level fields only' } 190 | ]; 191 | 192 | for (const { pattern, description } of selectPatterns) { 193 | const result = await client.callTool('get_system_object_definitions', { 194 | select: pattern, 195 | count: 3 // Small count for efficiency 196 | }); 197 | 198 | assert.equal(result.isError, false, 199 | `Select pattern '${pattern}' (${description}) should not error`); 200 | 201 | const data = JSON.parse(result.content[0].text); 202 | assert.ok(data, `Should return valid data for pattern: ${pattern}`); 203 | 204 | // Dynamic validation based on select pattern 205 | if (pattern.includes('count')) { 206 | assert.ok(typeof data.count === 'number', 207 | `Should include count for pattern: ${pattern}`); 208 | } 209 | 210 | if (pattern.includes('data.object_type')) { 211 | assert.ok(data.data, `Should have data array for pattern: ${pattern}`); 212 | if (data.data.length > 0) { 213 | assert.ok(data.data[0].object_type, 214 | `Should include object_type for pattern: ${pattern}`); 215 | } 216 | } 217 | } 218 | }); 219 | 220 | test('should handle edge cases with type coercion', async () => { 221 | const edgeCases = [ 222 | { params: { start: '5', count: '3' }, description: 'string parameters' }, 223 | { params: { start: 0, count: 1 }, description: 'minimum values' }, 224 | { params: { start: 100 }, description: 'large start value' }, 225 | { params: { count: 0 }, description: 'zero count', expectError: true } 226 | ]; 227 | 228 | for (const { params, description, expectError } of edgeCases) { 229 | const result = await client.callTool('get_system_object_definitions', params); 230 | 231 | if (expectError) { 232 | // Zero count might be an error or return empty data 233 | if (!result.isError) { 234 | const data = JSON.parse(result.content[0].text); 235 | assert.equal(data.count, 0, `Zero count should be handled: ${description}`); 236 | assert.equal(data.data.length, 0, `Zero count should return empty data: ${description}`); 237 | } 238 | } else { 239 | assert.equal(result.isError, false, 240 | `Edge case should succeed: ${description}`); 241 | 242 | const data = JSON.parse(result.content[0].text); 243 | 244 | // Validate type coercion worked 245 | if (params.start !== undefined) { 246 | assert.equal(typeof data.start, 'number', 247 | `Start should be coerced to number: ${description}`); 248 | } 249 | 250 | if (params.count !== undefined && params.count > 0) { 251 | assert.ok(data.data.length <= params.count, 252 | `Should respect count limit: ${description}`); 253 | } 254 | } 255 | } 256 | }); 257 | 258 | test('should handle invalid parameters gracefully', async () => { 259 | const invalidCases = [ 260 | { params: { start: -1 }, description: 'negative start' }, 261 | { params: { count: -5 }, description: 'negative count' }, 262 | { params: { select: 'invalid.field.path' }, description: 'invalid select path' }, 263 | { params: { select: '' }, description: 'empty select' } 264 | ]; 265 | 266 | for (const { params, description } of invalidCases) { 267 | const result = await client.callTool('get_system_object_definitions', params); 268 | 269 | // Should either return error or handle gracefully 270 | if (result.isError) { 271 | assert.ok(result.content[0].text, 272 | `Should have error message for: ${description}`); 273 | } else { 274 | // If not an error, should return valid structure 275 | const data = JSON.parse(result.content[0].text); 276 | assert.ok(data, `Should return valid data despite invalid params: ${description}`); 277 | 278 | // Validate graceful handling (flexible validation) 279 | if (params.start < 0 && typeof data.start === 'number') { 280 | // SFCC API accepts negative start and preserves it in response 281 | assert.ok(typeof data.start === 'number', `API should handle negative start: ${description}`); 282 | } 283 | 284 | if (params.count < 0 && typeof data.count === 'number') { 285 | // SFCC API accepts negative count and returns valid data 286 | assert.ok(data.count >= 0, `API should handle negative count gracefully: ${description}`); 287 | } 288 | } 289 | } 290 | }); 291 | }); 292 | 293 | // ================================================================================== 294 | // INTEGRATION & CONSISTENCY VALIDATION 295 | // ================================================================================== 296 | 297 | describe('Integration & Consistency', () => { 298 | test('should maintain consistency across parameter combinations', async () => { 299 | const baseResult = await client.callTool('get_system_object_definitions', {}); 300 | const baseData = JSON.parse(baseResult.content[0].text); 301 | 302 | const combinations = [ 303 | { start: 0, count: 5 }, 304 | { count: 10, select: 'data.object_type,count,total' }, 305 | { start: 2, count: 3, select: 'data.(*),total' } 306 | ]; 307 | 308 | for (const params of combinations) { 309 | const result = await client.callTool('get_system_object_definitions', params); 310 | assert.equal(result.isError, false, 311 | `Combination should succeed: ${JSON.stringify(params)}`); 312 | 313 | const data = JSON.parse(result.content[0].text); 314 | 315 | // Total should be consistent across calls 316 | assert.equal(data.total, baseData.total, 317 | `Total should be consistent for params: ${JSON.stringify(params)}`); 318 | 319 | // API version should be consistent 320 | if (data._v && baseData._v) { 321 | assert.equal(data._v, baseData._v, 322 | `API version should be consistent for params: ${JSON.stringify(params)}`); 323 | } 324 | } 325 | }); 326 | 327 | test('should provide stable pagination behavior', async () => { 328 | // Test pagination stability by requesting overlapping windows 329 | const firstPage = await client.callTool('get_system_object_definitions', { 330 | start: 0, 331 | count: 5 332 | }); 333 | 334 | const secondPage = await client.callTool('get_system_object_definitions', { 335 | start: 3, 336 | count: 5 337 | }); 338 | 339 | if (!firstPage.isError && !secondPage.isError) { 340 | const firstData = JSON.parse(firstPage.content[0].text); 341 | const secondData = JSON.parse(secondPage.content[0].text); 342 | 343 | // Check for expected overlap in stable sort 344 | if (firstData.data.length >= 5 && secondData.data.length > 2) { 345 | const firstPageLast2 = firstData.data.slice(3, 5).map(obj => obj.object_type); 346 | const secondPageFirst2 = secondData.data.slice(0, 2).map(obj => obj.object_type); 347 | 348 | assert.deepEqual(firstPageLast2, secondPageFirst2, 349 | 'Overlapping pagination should return consistent results'); 350 | } 351 | } 352 | }); 353 | 354 | test('should handle comprehensive SFCC business validation', async () => { 355 | const result = await client.callTool('get_system_object_definitions', {}); 356 | const data = JSON.parse(result.content[0].text); 357 | 358 | // Business rule validation for SFCC (flexible for different instances) 359 | const systemObjectTypes = data.data.map(obj => obj.object_type); 360 | const criticalSFCCTypes = ['Category', 'Catalog', 'Content', 'Basket', 'Campaign']; 361 | 362 | // Check if SFCC instance has at least some critical system objects 363 | const foundCriticalTypes = criticalSFCCTypes.filter(type => systemObjectTypes.includes(type)); 364 | assert.ok(foundCriticalTypes.length > 0, 365 | `SFCC instance should include at least one critical system object. Found: ${foundCriticalTypes.join(', ')}, Available: ${systemObjectTypes.slice(0,5).join(', ')}`); 366 | 367 | // Validate object type ID consistency for found objects 368 | data.data.forEach(obj => { 369 | if (obj.object_type_id) { 370 | // Object type ID should be related to object type 371 | assert.ok(typeof obj.object_type_id === 'string', 372 | `object_type_id should be string for ${obj.object_type}`); 373 | 374 | // Critical objects should have non-empty IDs if they exist 375 | if (foundCriticalTypes.includes(obj.object_type)) { 376 | assert.ok(obj.object_type_id.length > 0, 377 | `System object ${obj.object_type} should have non-empty object_type_id`); 378 | } 379 | } 380 | }); 381 | 382 | // Validate that we have a reasonable number of object types for an SFCC instance 383 | assert.ok(data.total >= 1, 'SFCC instance should have at least 1 system object type'); 384 | assert.ok(data.total <= 200, 'SFCC instance should not have excessive object types'); 385 | }); 386 | }); 387 | }); ``` -------------------------------------------------------------------------------- /docs/dw_order/OrderItem.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class OrderItem 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.order.OrderItem 9 | 10 | ## Description 11 | 12 | Defines extensions to ProductLineItems and ShippingLineItems belonging to an order. The order-item can be accessed using ProductLineItem.getOrderItem() or ShippingLineItem.getOrderItem() - these methods return null if the item is associated with a basket rather than an order. Alternative access is available using Order.getOrderItem(String) by passing the itemID used to identify the order-item in for example export files. The associated order-item can also be accessed from invoice-items, shipping-order-items, return-items and return-case-items using AbstractItem.getOrderItem(). The order-item provides an item-level status and type, methods for accessing and creating associated items, and methods used to allocate inventory for shipping-order creation. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice. 13 | 14 | ## Constants 15 | 16 | ### STATUS_BACKORDER 17 | 18 | **Type:** String = "BACKORDER" 19 | 20 | Constant for Order Item Status BACKORDER 21 | 22 | ### STATUS_CANCELLED 23 | 24 | **Type:** String = "CANCELLED" 25 | 26 | Constant for Order Item Status CANCELLED 27 | 28 | ### STATUS_CONFIRMED 29 | 30 | **Type:** String = "CONFIRMED" 31 | 32 | Constant for Order Item Status CONFIRMED 33 | 34 | ### STATUS_CREATED 35 | 36 | **Type:** String = "CREATED" 37 | 38 | Constant for Order Item Status CREATED 39 | 40 | ### STATUS_NEW 41 | 42 | **Type:** String = "NEW" 43 | 44 | Constant for Order Item Status NEW 45 | 46 | ### STATUS_OPEN 47 | 48 | **Type:** String = "OPEN" 49 | 50 | Constant for Order Item Status OPEN 51 | 52 | ### STATUS_SHIPPED 53 | 54 | **Type:** String = "SHIPPED" 55 | 56 | Constant for Order Item Status SHIPPED 57 | 58 | ### STATUS_WAREHOUSE 59 | 60 | **Type:** String = "WAREHOUSE" 61 | 62 | Constant for Order Item Status WAREHOUSE 63 | 64 | ### TYPE_PRODUCT 65 | 66 | **Type:** String = "PRODUCT" 67 | 68 | Constant for Order Item Type PRODUCT 69 | 70 | ### TYPE_SERVICE 71 | 72 | **Type:** String = "SERVICE" 73 | 74 | Constant for Order Item Type SERVICE 75 | 76 | ## Properties 77 | 78 | ### appeasedAmount 79 | 80 | **Type:** Money (Read Only) 81 | 82 | Sum of amounts appeased for this item, calculated by iterating over 83 | invoice items associated with the item. 84 | 85 | ### capturedAmount 86 | 87 | **Type:** Money (Read Only) 88 | 89 | Sum of amounts captured for this item, calculated by iterating over 90 | invoice items associated with the item. 91 | 92 | ### invoiceItems 93 | 94 | **Type:** Collection (Read Only) 95 | 96 | All invoice items associated with this item, each 97 | InvoiceItem will belong to a different 98 | Invoice, which can also be accessed using 99 | Order.getInvoices() or Order.getInvoice(String). 100 | 101 | ### itemID 102 | 103 | **Type:** String (Read Only) 104 | 105 | The itemID used to identify the OrderItem. Note this is 106 | not a UUID, it is created internally when the OrderItem 107 | instance is created, and is typically used within export files to 108 | identify the item. 109 | 110 | ### lineItem 111 | 112 | **Type:** LineItem (Read Only) 113 | 114 | The line item which is being extended by this instance. 115 | 116 | ### refundedAmount 117 | 118 | **Type:** Money (Read Only) 119 | 120 | Sum of amounts refunded for this item, calculated by iterating over 121 | invoice items associated with the item. 122 | 123 | ### returnCaseItems 124 | 125 | **Type:** Collection (Read Only) 126 | 127 | All return case items associated with this item, 128 | each ReturnCaseItem will belong to a different 129 | ReturnCase, which can also be accessed using 130 | Order.getReturnCases() or Order.getReturnCase(String). 131 | 132 | ### returnedQuantity 133 | 134 | **Type:** Quantity (Read Only) 135 | 136 | The quantity returned, dynamically sum of quantities held by associated 137 | ReturnItems. 138 | 139 | ### shippingOrderItem 140 | 141 | **Type:** ShippingOrderItem (Read Only) 142 | 143 | The last added non-cancelled shipping order item if one exists, otherwise null. 144 | 145 | Multiple shipping order items that are not in status ShippingOrderItem.STATUS_CANCELLED 146 | can exist for one OrderItem, for example if the OrderItem has been split for shipping purposes. 147 | The method returns null if no non-cancelled shipping order item exists. 148 | 149 | ### shippingOrderItems 150 | 151 | **Type:** Collection (Read Only) 152 | 153 | A collection of the ShippingOrderItems created for this item. 154 | ShippingOrder items represents the whole or part of this item which could 155 | be delivered, and belong to a shipping order. 156 | Note that the cancelled shipping order items are returned too. 157 | This method is equivalent to getShippingOrderItems(Boolean) 158 | called with parameter true. 159 | 160 | ### splitItems 161 | 162 | **Type:** Collection (Read Only) 163 | 164 | A collection of all split OrderItems associated with this item. Inverse relation to getSplitSourceItem(). 165 | 166 | Split order items are created when 167 | 168 | creating a ShippingOrderItem for a ShippingOrder, see ShippingOrder.createShippingOrderItem(OrderItem, Quantity) 169 | splitting an existing ShippingOrderItem, see ShippingOrderItem.split(Quantity) 170 | 171 | with a specified quantity less than the existing quantity of the associated ProductLineItem. In this case the associated ProductLineItem 172 | is split by creating a new ProductLineItem and associating a new ShippingOrderItem with this item. The new ShippingOrderItem 173 | receives the specified quantity and the quantity of the item is set to the remaining quantity. All split items are associated to their originating item via 174 | the "split source item" association. 175 | 176 | ### splitSourceItem 177 | 178 | **Type:** OrderItem (Read Only) 179 | 180 | The split source item associated with this item, if existing. Inverse relation to getSplitItems(). 181 | 182 | A split source item is associated after the successful creation of a split item with a quantity less than the existing quantity of the item to split. 183 | For details see getSplitItems(). 184 | 185 | ### status 186 | 187 | **Type:** EnumValue 188 | 189 | Gets the order item status. 190 | The possible values are: 191 | 192 | STATUS_NEW 193 | STATUS_OPEN 194 | STATUS_BACKORDER 195 | STATUS_CONFIRMED 196 | STATUS_WAREHOUSE 197 | STATUS_SHIPPED 198 | STATUS_CANCELLED 199 | 200 | ### type 201 | 202 | **Type:** EnumValue (Read Only) 203 | 204 | The type of line item with which this instance is associated, one 205 | of 206 | 207 | SERVICE (method getLineItem() returns a 208 | ShippingLineItem 209 | PRODUCT (method getLineItem() returns a 210 | ProductLineItem 211 | 212 | ## Constructor Summary 213 | 214 | ## Method Summary 215 | 216 | ### allocateInventory 217 | 218 | **Signature:** `allocateInventory(partialAllocation : boolean) : Quantity` 219 | 220 | Please note that this method is disabled by default. 221 | 222 | ### getAppeasedAmount 223 | 224 | **Signature:** `getAppeasedAmount() : Money` 225 | 226 | Sum of amounts appeased for this item, calculated by iterating over invoice items associated with the item. 227 | 228 | ### getCapturedAmount 229 | 230 | **Signature:** `getCapturedAmount() : Money` 231 | 232 | Sum of amounts captured for this item, calculated by iterating over invoice items associated with the item. 233 | 234 | ### getInvoiceItems 235 | 236 | **Signature:** `getInvoiceItems() : Collection` 237 | 238 | Returns all invoice items associated with this item, each InvoiceItem will belong to a different Invoice, which can also be accessed using Order.getInvoices() or Order.getInvoice(String). 239 | 240 | ### getItemID 241 | 242 | **Signature:** `getItemID() : String` 243 | 244 | The itemID used to identify the OrderItem. 245 | 246 | ### getLineItem 247 | 248 | **Signature:** `getLineItem() : LineItem` 249 | 250 | Returns the line item which is being extended by this instance. 251 | 252 | ### getRefundedAmount 253 | 254 | **Signature:** `getRefundedAmount() : Money` 255 | 256 | Sum of amounts refunded for this item, calculated by iterating over invoice items associated with the item. 257 | 258 | ### getReturnCaseItems 259 | 260 | **Signature:** `getReturnCaseItems() : Collection` 261 | 262 | Returns all return case items associated with this item, each ReturnCaseItem will belong to a different ReturnCase, which can also be accessed using Order.getReturnCases() or Order.getReturnCase(String). 263 | 264 | ### getReturnedQuantity 265 | 266 | **Signature:** `getReturnedQuantity() : Quantity` 267 | 268 | The quantity returned, dynamically sum of quantities held by associated ReturnItems. 269 | 270 | ### getShippingOrderItem 271 | 272 | **Signature:** `getShippingOrderItem() : ShippingOrderItem` 273 | 274 | The last added non-cancelled shipping order item if one exists, otherwise null. 275 | 276 | ### getShippingOrderItems 277 | 278 | **Signature:** `getShippingOrderItems() : Collection` 279 | 280 | Returns a collection of the ShippingOrderItems created for this item. 281 | 282 | ### getShippingOrderItems 283 | 284 | **Signature:** `getShippingOrderItems(includeCancelled : boolean) : Collection` 285 | 286 | Returns a collection of the ShippingOrderItems created for this item. 287 | 288 | ### getSplitItems 289 | 290 | **Signature:** `getSplitItems() : Collection` 291 | 292 | Returns a collection of all split OrderItems associated with this item. 293 | 294 | ### getSplitSourceItem 295 | 296 | **Signature:** `getSplitSourceItem() : OrderItem` 297 | 298 | Returns the split source item associated with this item, if existing. 299 | 300 | ### getStatus 301 | 302 | **Signature:** `getStatus() : EnumValue` 303 | 304 | Gets the order item status. The possible values are: STATUS_NEW STATUS_OPEN STATUS_BACKORDER STATUS_CONFIRMED STATUS_WAREHOUSE STATUS_SHIPPED STATUS_CANCELLED 305 | 306 | ### getType 307 | 308 | **Signature:** `getType() : EnumValue` 309 | 310 | Returns the type of line item with which this instance is associated, one of SERVICE (method getLineItem() returns a ShippingLineItem PRODUCT (method getLineItem() returns a ProductLineItem 311 | 312 | ### setStatus 313 | 314 | **Signature:** `setStatus(status : String) : void` 315 | 316 | Set the status of the order item, use one of the values documented in getStatus(). 317 | 318 | ## Method Detail 319 | 320 | ## Method Details 321 | 322 | ### allocateInventory 323 | 324 | **Signature:** `allocateInventory(partialAllocation : boolean) : Quantity` 325 | 326 | **Description:** Please note that this method is disabled by default. Please contact support for enabling it. Attempts to allocate inventory for the item and returns the quantity that could be allocated or null if no allocation was possible. All option product line items are allocated with their parent. Note that for items with option product line items no partial allocation is possible. That means the partialAllocation parameter will in this case always be considered as false 327 | 328 | **Parameters:** 329 | 330 | - `partialAllocation`: true accept a partial allocation as a result. Partial allocation is only possible when no option product line items are included, false only full allocation will be used, partial allocation will be released automatically 331 | 332 | **Returns:** 333 | 334 | successful: the newly allocated quantity failed: null 335 | 336 | --- 337 | 338 | ### getAppeasedAmount 339 | 340 | **Signature:** `getAppeasedAmount() : Money` 341 | 342 | **Description:** Sum of amounts appeased for this item, calculated by iterating over invoice items associated with the item. 343 | 344 | **Returns:** 345 | 346 | Sum of amounts refunded for this item 347 | 348 | --- 349 | 350 | ### getCapturedAmount 351 | 352 | **Signature:** `getCapturedAmount() : Money` 353 | 354 | **Description:** Sum of amounts captured for this item, calculated by iterating over invoice items associated with the item. 355 | 356 | **Returns:** 357 | 358 | Sum of amounts captured for this item 359 | 360 | --- 361 | 362 | ### getInvoiceItems 363 | 364 | **Signature:** `getInvoiceItems() : Collection` 365 | 366 | **Description:** Returns all invoice items associated with this item, each InvoiceItem will belong to a different Invoice, which can also be accessed using Order.getInvoices() or Order.getInvoice(String). 367 | 368 | **Returns:** 369 | 370 | invoice items associated with this item 371 | 372 | --- 373 | 374 | ### getItemID 375 | 376 | **Signature:** `getItemID() : String` 377 | 378 | **Description:** The itemID used to identify the OrderItem. Note this is not a UUID, it is created internally when the OrderItem instance is created, and is typically used within export files to identify the item. 379 | 380 | **Returns:** 381 | 382 | the itemID of the OrderItem 383 | 384 | --- 385 | 386 | ### getLineItem 387 | 388 | **Signature:** `getLineItem() : LineItem` 389 | 390 | **Description:** Returns the line item which is being extended by this instance. 391 | 392 | **Returns:** 393 | 394 | the line item associated with this instance 395 | 396 | --- 397 | 398 | ### getRefundedAmount 399 | 400 | **Signature:** `getRefundedAmount() : Money` 401 | 402 | **Description:** Sum of amounts refunded for this item, calculated by iterating over invoice items associated with the item. 403 | 404 | **Returns:** 405 | 406 | Sum of amounts refunded for this item 407 | 408 | --- 409 | 410 | ### getReturnCaseItems 411 | 412 | **Signature:** `getReturnCaseItems() : Collection` 413 | 414 | **Description:** Returns all return case items associated with this item, each ReturnCaseItem will belong to a different ReturnCase, which can also be accessed using Order.getReturnCases() or Order.getReturnCase(String). 415 | 416 | **Returns:** 417 | 418 | return case items associated with this item 419 | 420 | --- 421 | 422 | ### getReturnedQuantity 423 | 424 | **Signature:** `getReturnedQuantity() : Quantity` 425 | 426 | **Description:** The quantity returned, dynamically sum of quantities held by associated ReturnItems. 427 | 428 | **Returns:** 429 | 430 | quantity returned, the sum of quantities held by associated ReturnItems 431 | 432 | --- 433 | 434 | ### getShippingOrderItem 435 | 436 | **Signature:** `getShippingOrderItem() : ShippingOrderItem` 437 | 438 | **Description:** The last added non-cancelled shipping order item if one exists, otherwise null. Multiple shipping order items that are not in status ShippingOrderItem.STATUS_CANCELLED can exist for one OrderItem, for example if the OrderItem has been split for shipping purposes. The method returns null if no non-cancelled shipping order item exists. 439 | 440 | **Deprecated:** 441 | 442 | This item is deprecated. 443 | 444 | **Returns:** 445 | 446 | the last not cancelled shipping order item or null 447 | 448 | --- 449 | 450 | ### getShippingOrderItems 451 | 452 | **Signature:** `getShippingOrderItems() : Collection` 453 | 454 | **Description:** Returns a collection of the ShippingOrderItems created for this item. ShippingOrder items represents the whole or part of this item which could be delivered, and belong to a shipping order. Note that the cancelled shipping order items are returned too. This method is equivalent to getShippingOrderItems(Boolean) called with parameter true. 455 | 456 | **Returns:** 457 | 458 | collection of the shipping order items created for this item 459 | 460 | --- 461 | 462 | ### getShippingOrderItems 463 | 464 | **Signature:** `getShippingOrderItems(includeCancelled : boolean) : Collection` 465 | 466 | **Description:** Returns a collection of the ShippingOrderItems created for this item. ShippingOrder items represent the whole or part of this item which could be delivered, and belong to a shipping order. Depending on the includeCancelled parameter the cancelled shipping order items will be returned or not. 467 | 468 | **Parameters:** 469 | 470 | - `includeCancelled`: true all shipping order items, including the cancelled, created for this item will be returned false only non-cancelled shipping order items created for this item will be returned 471 | 472 | **Returns:** 473 | 474 | collection of the shipping order items created for this item 475 | 476 | --- 477 | 478 | ### getSplitItems 479 | 480 | **Signature:** `getSplitItems() : Collection` 481 | 482 | **Description:** Returns a collection of all split OrderItems associated with this item. Inverse relation to getSplitSourceItem(). Split order items are created when creating a ShippingOrderItem for a ShippingOrder, see ShippingOrder.createShippingOrderItem(OrderItem, Quantity) splitting an existing ShippingOrderItem, see ShippingOrderItem.split(Quantity) with a specified quantity less than the existing quantity of the associated ProductLineItem. In this case the associated ProductLineItem is split by creating a new ProductLineItem and associating a new ShippingOrderItem with this item. The new ShippingOrderItem receives the specified quantity and the quantity of the item is set to the remaining quantity. All split items are associated to their originating item via the "split source item" association. 483 | 484 | **Returns:** 485 | 486 | the split order items associated with this item 487 | 488 | --- 489 | 490 | ### getSplitSourceItem 491 | 492 | **Signature:** `getSplitSourceItem() : OrderItem` 493 | 494 | **Description:** Returns the split source item associated with this item, if existing. Inverse relation to getSplitItems(). A split source item is associated after the successful creation of a split item with a quantity less than the existing quantity of the item to split. For details see getSplitItems(). 495 | 496 | **Returns:** 497 | 498 | the split source item or null 499 | 500 | --- 501 | 502 | ### getStatus 503 | 504 | **Signature:** `getStatus() : EnumValue` 505 | 506 | **Description:** Gets the order item status. The possible values are: STATUS_NEW STATUS_OPEN STATUS_BACKORDER STATUS_CONFIRMED STATUS_WAREHOUSE STATUS_SHIPPED STATUS_CANCELLED 507 | 508 | **Returns:** 509 | 510 | the status 511 | 512 | --- 513 | 514 | ### getType 515 | 516 | **Signature:** `getType() : EnumValue` 517 | 518 | **Description:** Returns the type of line item with which this instance is associated, one of SERVICE (method getLineItem() returns a ShippingLineItem PRODUCT (method getLineItem() returns a ProductLineItem 519 | 520 | **Returns:** 521 | 522 | the type of order item, one of TYPE_PRODUCT or TYPE_SERVICE. 523 | 524 | --- 525 | 526 | ### setStatus 527 | 528 | **Signature:** `setStatus(status : String) : void` 529 | 530 | **Description:** Set the status of the order item, use one of the values documented in getStatus(). If the order item has a related shipping order item (see getShippingOrderItem()) the status of the shipping order item will be adjusted to the same status. Setting the status of an order item might also change the status of the related order. The following rules apply in top-down order: all items STATUS_CANCELLED - order status is Order.ORDER_STATUS_CANCELLED at least one item in status STATUS_SHIPPED and all other items are STATUS_CANCELLED order status is Order.ORDER_STATUS_COMPLETED at least one item in status STATUS_CREATED, STATUS_OPEN, STATUS_NEW , STATUS_BACKORDER - order status is Order.ORDER_STATUS_OPEN, order confirmation status is Order.CONFIRMATION_STATUS_NOTCONFIRMED other combinations will have only items in STATUS_CONFIRMED, STATUS_CANCELLED and STATUS_SHIPPED - order status is Order.ORDER_STATUS_OPEN, order confirmation status is Order.CONFIRMATION_STATUS_CONFIRMED 531 | 532 | **Parameters:** 533 | 534 | - `status`: status string matching one of the values for status 535 | 536 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-job-execution-summary.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_job_execution_summary - Advanced Programmatic Tests', () => { 6 | let client; 7 | let discoveredJobNames = []; 8 | 9 | before(async () => { 10 | client = await connect('./aegis.config.with-dw.json'); 11 | await discoverJobNames(); 12 | }); 13 | 14 | after(async () => { 15 | if (client?.connected) { 16 | await client.disconnect(); 17 | } 18 | }); 19 | 20 | beforeEach(() => { 21 | // CRITICAL: Clear all buffers to prevent leaking into next tests 22 | client.clearAllBuffers(); // Recommended - comprehensive protection 23 | }); 24 | 25 | // Optimized helper functions focused on complex validation 26 | function assertValidMCPResponse(result) { 27 | assert.ok(result.content, 'Should have content'); 28 | assert.ok(Array.isArray(result.content), 'Content should be array'); 29 | assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); 30 | } 31 | 32 | function parseResponseText(text) { 33 | return text.startsWith('"') && text.endsWith('"') 34 | ? JSON.parse(text) 35 | : text; 36 | } 37 | 38 | function assertJobExecutionSummaryFormat(result, jobName) { 39 | assertValidMCPResponse(result); 40 | assert.equal(result.isError, false, 'Should not be an error response'); 41 | 42 | const text = parseResponseText(result.content[0].text); 43 | 44 | if (text.includes('No job logs found')) { 45 | assert.ok(text.includes(`No job logs found for job name: ${jobName}`), 46 | 'No results message should include job name'); 47 | return null; 48 | } 49 | 50 | // Validate comprehensive execution summary format 51 | assert.ok(text.includes(`Job Execution Summary: ${jobName}`), 52 | 'Should contain job execution summary header with job name'); 53 | assert.ok(text.includes('⏱️ Timing:') && text.includes('📊 Status:'), 54 | 'Should contain emoji-formatted sections'); 55 | assert.ok(text.includes('Start:') && text.includes('End:') && text.includes('Duration:'), 56 | 'Should contain complete timing information'); 57 | assert.ok(text.includes('Status:') && text.includes('Errors:') && text.includes('Warnings:'), 58 | 'Should contain complete status information'); 59 | 60 | return parseJobExecutionSummary(text); 61 | } 62 | 63 | function parseJobExecutionSummary(text) { 64 | const summary = { 65 | jobName: null, 66 | timing: { start: null, end: null, duration: null }, 67 | status: { status: null, errors: null, warnings: null } 68 | }; 69 | 70 | // Extract structured data from response 71 | const jobNameMatch = text.match(/Job Execution Summary: ([^\n\r]+)/); 72 | if (jobNameMatch) summary.jobName = jobNameMatch[1].trim(); 73 | 74 | const startMatch = text.match(/- Start: ([^\n\r]+)/); 75 | if (startMatch) summary.timing.start = startMatch[1].trim(); 76 | 77 | const endMatch = text.match(/- End: ([^\n\r]+)/); 78 | if (endMatch) summary.timing.end = endMatch[1].trim(); 79 | 80 | const durationMatch = text.match(/- Duration: ([^\n\r]+)/); 81 | if (durationMatch) summary.timing.duration = durationMatch[1].trim(); 82 | 83 | const statusMatch = text.match(/- Status: ([^\n\r]+)/); 84 | if (statusMatch) summary.status.status = statusMatch[1].trim(); 85 | 86 | const errorsMatch = text.match(/- Errors: ([^\n\r]+)/); 87 | if (errorsMatch) summary.status.errors = errorsMatch[1].trim(); 88 | 89 | const warningsMatch = text.match(/- Warnings: ([^\n\r]+)/); 90 | if (warningsMatch) summary.status.warnings = warningsMatch[1].trim(); 91 | 92 | return summary; 93 | } 94 | 95 | function assertSuccessResponse(result) { 96 | assertValidMCPResponse(result); 97 | assert.equal(result.isError, false, 'Response should not be error'); 98 | } 99 | 100 | function assertErrorResponse(result, expectedErrorText = null) { 101 | assertValidMCPResponse(result); 102 | assert.equal(result.isError, true, 'Response should be error'); 103 | 104 | if (expectedErrorText) { 105 | const responseText = parseResponseText(result.content[0].text); 106 | assert.ok(responseText.includes(expectedErrorText), 107 | `Error message should contain: ${expectedErrorText}`); 108 | } 109 | } 110 | 111 | function assertTextContent(result, expectedText) { 112 | assertSuccessResponse(result); 113 | const responseText = parseResponseText(result.content[0].text); 114 | assert.ok(responseText.includes(expectedText), 115 | `Response should contain: ${expectedText}`); 116 | } 117 | 118 | async function discoverJobNames() { 119 | console.log('🔍 Discovering available job names for advanced testing...'); 120 | 121 | try { 122 | const result = await client.callTool('get_latest_job_log_files', { limit: 10 }); 123 | if (!result.isError) { 124 | const text = parseResponseText(result.content[0].text); 125 | const jobs = extractJobInfo(text); 126 | discoveredJobNames = jobs.map(job => job.jobName); 127 | console.log(`🔧 Found ${discoveredJobNames.length} jobs:`, discoveredJobNames); 128 | } 129 | } catch (error) { 130 | console.warn('⚠️ Could not discover job names:', error.message); 131 | discoveredJobNames = ['ImportCatalog', 'ProcessOrders']; 132 | } 133 | } 134 | 135 | function extractJobInfo(responseText) { 136 | const jobs = []; 137 | const text = parseResponseText(responseText); 138 | const sections = text.split('🔧 Job: ').slice(1); 139 | 140 | for (const section of sections) { 141 | const lines = section.split('\n'); 142 | const jobName = lines[0].trim(); 143 | 144 | let jobId = null; 145 | for (const line of lines) { 146 | const idMatch = line.match(/ID: (\d+)/); 147 | if (idMatch) jobId = idMatch[1]; 148 | } 149 | 150 | if (jobName && jobId) { 151 | jobs.push({ jobName, jobId }); 152 | } 153 | } 154 | 155 | return jobs; 156 | } 157 | 158 | // Core functionality tests - focus on complex parsing and validation 159 | describe('Core Functionality & Format Validation', () => { 160 | test('should retrieve and parse job execution summary structure', async () => { 161 | if (discoveredJobNames.length === 0) { 162 | console.log('⏭️ Skipping test - no job names discovered'); 163 | return; 164 | } 165 | 166 | const jobName = discoveredJobNames[0]; 167 | const result = await client.callTool('get_job_execution_summary', { jobName }); 168 | 169 | const summary = assertJobExecutionSummaryFormat(result, jobName); 170 | 171 | if (summary) { 172 | // Validate parsed structure for existing jobs 173 | assert.equal(summary.jobName, jobName, 'Parsed job name should match requested'); 174 | assert.ok(summary.timing.start, 'Should have start time'); 175 | assert.ok(summary.timing.end, 'Should have end time'); 176 | assert.ok(summary.timing.duration !== null, 'Should have duration'); 177 | assert.ok(summary.status.status, 'Should have status'); 178 | assert.ok(summary.status.errors !== null, 'Should have error count'); 179 | assert.ok(summary.status.warnings !== null, 'Should have warning count'); 180 | } 181 | }); 182 | 183 | test('should validate timing format in execution summary', async () => { 184 | if (discoveredJobNames.length === 0) { 185 | console.log('⏭️ Skipping test - no job names discovered'); 186 | return; 187 | } 188 | 189 | const jobName = discoveredJobNames[0]; 190 | const result = await client.callTool('get_job_execution_summary', { jobName }); 191 | 192 | const summary = assertJobExecutionSummaryFormat(result, jobName); 193 | 194 | if (summary) { 195 | // Validate datetime format (YYYY-MM-DD HH:MM:SS) 196 | assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(summary.timing.start), 197 | `Start time should match datetime format: ${summary.timing.start}`); 198 | assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(summary.timing.end), 199 | `End time should match datetime format: ${summary.timing.end}`); 200 | assert.ok(/\d+s/.test(summary.timing.duration), 201 | `Duration should be in seconds format: ${summary.timing.duration}`); 202 | } 203 | }); 204 | 205 | test('should validate status information format', async () => { 206 | if (discoveredJobNames.length === 0) { 207 | console.log('⏭️ Skipping test - no job names discovered'); 208 | return; 209 | } 210 | 211 | const jobName = discoveredJobNames[0]; 212 | const result = await client.callTool('get_job_execution_summary', { jobName }); 213 | 214 | const summary = assertJobExecutionSummaryFormat(result, jobName); 215 | 216 | if (summary) { 217 | // Status should be a string 218 | assert.ok(typeof summary.status.status === 'string', 'Status should be string'); 219 | 220 | // Errors and warnings should be numeric strings 221 | assert.ok(/\d+/.test(summary.status.errors), 222 | `Errors should be numeric: ${summary.status.errors}`); 223 | assert.ok(/\d+/.test(summary.status.warnings), 224 | `Warnings should be numeric: ${summary.status.warnings}`); 225 | } 226 | }); 227 | 228 | test('should handle non-existent job gracefully with proper messaging', async () => { 229 | const result = await client.callTool('get_job_execution_summary', { 230 | jobName: 'NonExistentJob12345' 231 | }); 232 | 233 | assertValidMCPResponse(result); 234 | assert.equal(result.isError, false, 'Should not be an error response for non-existent job'); 235 | assertTextContent(result, 'No job logs found for job name: NonExistentJob12345'); 236 | }); 237 | }); 238 | 239 | // Streamlined parameter validation - focus on key error cases 240 | describe('Parameter Validation', () => { 241 | test('should return error for missing jobName parameter', async () => { 242 | const result = await client.callTool('get_job_execution_summary', {}); 243 | assertErrorResponse(result, 'jobName must be a non-empty string'); 244 | }); 245 | 246 | test('should return error for empty jobName parameter', async () => { 247 | const result = await client.callTool('get_job_execution_summary', { jobName: '' }); 248 | assertErrorResponse(result, 'jobName must be a non-empty string'); 249 | }); 250 | 251 | test('should return error for null jobName parameter', async () => { 252 | const result = await client.callTool('get_job_execution_summary', { jobName: null }); 253 | assertErrorResponse(result, 'jobName must be a non-empty string'); 254 | }); 255 | 256 | test('should return error for non-string jobName parameter', async () => { 257 | const result = await client.callTool('get_job_execution_summary', { jobName: 123 }); 258 | assertErrorResponse(result, 'jobName must be a non-empty string'); 259 | }); 260 | 261 | test('should return error for whitespace-only jobName', async () => { 262 | const result = await client.callTool('get_job_execution_summary', { jobName: ' ' }); 263 | assertErrorResponse(result, 'jobName must be a non-empty string'); 264 | }); 265 | }); 266 | 267 | // Focus on meaningful edge cases that test real-world scenarios 268 | describe('Edge Cases & Real-World Scenarios', () => { 269 | test('should handle job names with special characters', async () => { 270 | const specialJobName = 'Job-With-Dashes_And_Underscores.123'; 271 | const result = await client.callTool('get_job_execution_summary', { 272 | jobName: specialJobName 273 | }); 274 | 275 | assertValidMCPResponse(result); 276 | assert.equal(result.isError, false); 277 | assertTextContent(result, `No job logs found for job name: ${specialJobName}`); 278 | }); 279 | 280 | test('should handle job names with Unicode characters', async () => { 281 | const unicodeJobName = 'Job_测试_🔧_Тест'; 282 | const result = await client.callTool('get_job_execution_summary', { 283 | jobName: unicodeJobName 284 | }); 285 | 286 | assertValidMCPResponse(result); 287 | assert.equal(result.isError, false); 288 | assertTextContent(result, `No job logs found for job name: ${unicodeJobName}`); 289 | }); 290 | 291 | test('should handle very long job names', async () => { 292 | const longJobName = 'VeryLongJobNameThatMightCauseIssuesWithSomeSystemsBecauseItExceedsTypicalLimits'.repeat(2); 293 | const result = await client.callTool('get_job_execution_summary', { 294 | jobName: longJobName 295 | }); 296 | 297 | assertValidMCPResponse(result); 298 | assert.equal(result.isError, false); 299 | assertTextContent(result, 'No job logs found for job name:'); 300 | }); 301 | }); 302 | 303 | // Advanced multi-job testing with discovered jobs 304 | describe('Multi-Job Integration Testing', () => { 305 | test('should handle discovered job names with execution summaries', async () => { 306 | if (discoveredJobNames.length === 0) { 307 | console.log('⏭️ Skipping test - no job names discovered'); 308 | return; 309 | } 310 | 311 | const results = []; 312 | 313 | // Test up to 3 discovered jobs sequentially (avoid concurrent requests) 314 | const jobsToTest = discoveredJobNames.slice(0, 3); 315 | 316 | for (const jobName of jobsToTest) { 317 | const result = await client.callTool('get_job_execution_summary', { jobName }); 318 | results.push({ jobName, result }); 319 | 320 | // Each result should be valid 321 | assertValidMCPResponse(result); 322 | assert.equal(result.isError, false, `Job ${jobName} should not error`); 323 | 324 | const summary = assertJobExecutionSummaryFormat(result, jobName); 325 | if (summary) { 326 | assert.equal(summary.jobName, jobName, 'Parsed job name should match'); 327 | } 328 | } 329 | 330 | assert.ok(results.length > 0, 'Should have tested at least one job'); 331 | console.log(`✅ Successfully tested ${results.length} discovered jobs`); 332 | }); 333 | 334 | test('should maintain consistent response format across different jobs', async () => { 335 | if (discoveredJobNames.length < 2) { 336 | console.log('⏭️ Skipping test - need at least 2 discovered jobs'); 337 | return; 338 | } 339 | 340 | const results = []; 341 | 342 | // Test first 2 jobs for consistency 343 | for (const jobName of discoveredJobNames.slice(0, 2)) { 344 | const result = await client.callTool('get_job_execution_summary', { jobName }); 345 | results.push(result); 346 | 347 | assertValidMCPResponse(result); 348 | assert.equal(result.content.length, 1, 'Should have exactly one content item'); 349 | assert.equal(result.content[0].type, 'text', 'Content should be text type'); 350 | } 351 | 352 | // All results should have consistent structure 353 | results.forEach((result, index) => { 354 | assert.equal(typeof result.isError, 'boolean', `Result ${index} isError should be boolean`); 355 | assert.ok(Array.isArray(result.content), `Result ${index} content should be array`); 356 | }); 357 | }); 358 | }); 359 | 360 | // Integration with job log ecosystem 361 | describe('Integration with Job Log Ecosystem', () => { 362 | test('should integrate with discovered job ecosystem effectively', async () => { 363 | if (discoveredJobNames.length === 0) { 364 | console.log('⏭️ Skipping test - no job names discovered'); 365 | return; 366 | } 367 | 368 | // Get job log files to validate integration 369 | const logFilesResult = await client.callTool('get_latest_job_log_files', { limit: 5 }); 370 | assertValidMCPResponse(logFilesResult); 371 | assert.equal(logFilesResult.isError, false, 'Should get job log files successfully'); 372 | 373 | // Parse available jobs 374 | const logText = parseResponseText(logFilesResult.content[0].text); 375 | const availableJobs = extractJobInfo(logText); 376 | 377 | assert.ok(availableJobs.length > 0, 'Should find available jobs from log files'); 378 | 379 | // Test execution summary for first available job 380 | const firstJob = availableJobs[0]; 381 | const summaryResult = await client.callTool('get_job_execution_summary', { 382 | jobName: firstJob.jobName 383 | }); 384 | 385 | assertValidMCPResponse(summaryResult); 386 | 387 | // Should either get a summary or a "no logs found" message 388 | const summaryText = parseResponseText(summaryResult.content[0].text); 389 | const hasValidResponse = summaryText.includes('Job Execution Summary:') || 390 | summaryText.includes('No job logs found'); 391 | 392 | assert.ok(hasValidResponse, 'Should get either execution summary or no logs message'); 393 | 394 | console.log(`✅ Successfully integrated with job ecosystem (${availableJobs.length} jobs available)`); 395 | }); 396 | 397 | test('should validate execution summary data integrity', async () => { 398 | if (discoveredJobNames.length === 0) { 399 | console.log('⏭️ Skipping test - no job names discovered'); 400 | return; 401 | } 402 | 403 | const jobName = discoveredJobNames[0]; 404 | const result = await client.callTool('get_job_execution_summary', { jobName }); 405 | 406 | const summary = assertJobExecutionSummaryFormat(result, jobName); 407 | 408 | if (summary) { 409 | // Validate data integrity 410 | assert.ok(summary.jobName.length > 0, 'Job name should not be empty'); 411 | 412 | // Parse numeric values for validation 413 | const errorCount = parseInt(summary.status.errors, 10); 414 | const warningCount = parseInt(summary.status.warnings, 10); 415 | 416 | assert.ok(!isNaN(errorCount), 'Error count should be numeric'); 417 | assert.ok(!isNaN(warningCount), 'Warning count should be numeric'); 418 | assert.ok(errorCount >= 0, 'Error count should be non-negative'); 419 | assert.ok(warningCount >= 0, 'Warning count should be non-negative'); 420 | 421 | // Validate duration format and logic 422 | const durationMatch = summary.timing.duration.match(/(\d+)s/); 423 | if (durationMatch) { 424 | const durationSeconds = parseInt(durationMatch[1], 10); 425 | assert.ok(durationSeconds >= 0, 'Duration should be non-negative'); 426 | } 427 | 428 | console.log(`✅ Data integrity validated for job ${jobName}`); 429 | } 430 | }); 431 | }); 432 | }); 433 | ```