This is page 18 of 43. Use http://codebase.md/taurgis/sfcc-dev-mcp?page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/node/get-latest-debug.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript import { test, describe, before, after, beforeEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { connect } from 'mcp-aegis'; // Optimized programmatic tests focusing on complex business logic, dynamic validation, and integration scenarios // Basic functional testing is handled by the YAML tests for better CI reliability describe('get_latest_debug - Full Mode Programmatic Tests (Optimized)', () => { let client; before(async () => { client = await connect('./aegis.config.with-dw.json'); }); after(async () => { if (client?.connected) { await client.disconnect(); } }); beforeEach(() => { // CRITICAL: Clear all buffers to prevent leaking into next tests client.clearAllBuffers(); // Recommended - comprehensive protection }); // Enhanced helper functions for complex validations function assertValidMCPResponse(result) { assert.ok(result.content, 'Should have content'); assert.ok(Array.isArray(result.content), 'Content should be array'); assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); } function assertSuccessResponse(result) { assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error response'); assert.equal(result.content[0].type, 'text'); } function validateDebugLogStructure(result, expectedLimit) { assertSuccessResponse(result); const text = result.content[0].text; // Validate essential debug log components assert.ok(text.includes(`Latest ${expectedLimit} debug messages`), `Should mention "${expectedLimit}" debug messages`); assert.ok(/debug-blade-\d{8}-\d{6}\.log/.test(text), 'Should contain debug log file name pattern'); assert.ok(text.includes('DEBUG'), 'Should contain DEBUG level entries'); assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} GMT/.test(text), 'Should contain GMT timestamp pattern'); } function getCurrentDateString() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); return `${year}${month}${day}`; } // ======================================== // COMPLEX BUSINESS LOGIC VALIDATION // ======================================== describe('Business Logic and Content Analysis', () => { test('should parse and validate SFCC debug log structure comprehensively', async () => { const result = await client.callTool('get_latest_debug', { limit: 5 }); validateDebugLogStructure(result, 5); const text = result.content[0].text; // Validate SFCC-specific patterns that require complex logic const sfccPatterns = [ /PipelineCallServlet|SystemJobThread/, /Sites-RefArchGlobal-Site/, /PipelineCall|custom \[\]/, /---/ // Entry separators ]; sfccPatterns.forEach((pattern, index) => { assert.ok(pattern.test(text), `Should contain SFCC pattern ${index}: ${pattern.toString()}`); }); // Validate chronological ordering (newest first) - complex logic const entries = text.split('---').filter(entry => entry.trim()); if (entries.length > 1) { const timestamps = entries.map(entry => { const match = entry.match(/\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} GMT)\]/); return match ? new Date(match[1]) : null; }).filter(Boolean); for (let i = 1; i < timestamps.length; i++) { assert.ok(timestamps[i-1] >= timestamps[i], 'Debug entries should be in chronological order (newest first)'); } } }); test('should validate complex parameter combinations with business rules', async () => { const testScenarios = [ { params: { limit: 1 }, validator: (result) => { assert.ok(result.content[0].text.includes('Latest 1 debug messages')); assert.ok(!result.content[0].text.includes('---'), 'Single entry should not have separators'); } }, { params: { limit: 3, date: getCurrentDateString() }, validator: (result) => { assert.ok(result.content[0].text.includes('Latest 3 debug messages')); const separatorCount = (result.content[0].text.match(/---/g) || []).length; assert.ok(separatorCount >= 1, 'Multiple entries should have separators'); } }, { params: { limit: 20 }, validator: (result) => { assert.ok(result.content[0].text.includes('Latest 20 debug messages')); // Should handle larger limits gracefully assert.ok(result.content[0].text.length > 1000, 'Large limit should return substantial content'); } } ]; for (const scenario of testScenarios) { const result = await client.callTool('get_latest_debug', scenario.params); assertSuccessResponse(result); scenario.validator(result); } }); }); // ======================================== // DYNAMIC PARAMETER VALIDATION // ======================================== describe('Dynamic Parameter Validation', () => { test('should validate parameter types with complex edge case matrix', async () => { const testMatrix = [ // Valid scenarios { params: { limit: 5 }, expectSuccess: true, description: 'valid number limit' }, { params: { date: '20240101' }, expectSuccess: true, description: 'valid date format' }, { params: { limit: 1, date: getCurrentDateString() }, expectSuccess: true, description: 'valid combination' }, // Invalid scenarios requiring dynamic validation { params: { limit: '5' }, expectSuccess: false, expectedError: 'Invalid limit \'5\'', description: 'string limit' }, { params: { limit: 0 }, expectSuccess: false, expectedError: 'Invalid limit \'0\'', description: 'zero limit' }, { params: { limit: -1 }, expectSuccess: false, expectedError: 'Invalid limit', description: 'negative limit' }, { params: { limit: 9999 }, expectSuccess: false, expectedError: 'Invalid limit', description: 'excessive limit' }, // Complex type scenarios { params: { limit: null }, expectSuccess: true, description: 'null limit (uses default)' }, { params: { limit: [] }, expectSuccess: false, description: 'array limit' }, { params: { limit: {} }, expectSuccess: false, description: 'object limit' }, ]; for (const testCase of testMatrix) { const result = await client.callTool('get_latest_debug', testCase.params); assertValidMCPResponse(result); if (testCase.expectSuccess) { assert.equal(result.isError, false, `Should succeed for ${testCase.description}: ${JSON.stringify(testCase.params)}`); } else { assert.equal(result.isError, true, `Should fail for ${testCase.description}: ${JSON.stringify(testCase.params)}`); if (testCase.expectedError) { assert.ok(result.content[0].text.includes(testCase.expectedError), `Should contain expected error for ${testCase.description}`); } } } }); test('should handle date parameter edge cases with business logic', async () => { const dateScenarios = [ { date: 'invalid-date', expectMessage: 'No debug log files found' }, { date: '20261231', expectSuccess: true }, // Future date { date: '20240101', expectSuccess: true }, // Past date { date: 123, expectSuccess: true }, // Number converted to string ]; for (const scenario of dateScenarios) { const result = await client.callTool('get_latest_debug', { date: scenario.date, limit: 1 }); assertValidMCPResponse(result); if (scenario.expectSuccess) { assert.equal(result.isError, false, `Date ${scenario.date} should be handled gracefully`); } if (scenario.expectMessage) { assert.ok(result.content[0].text.includes(scenario.expectMessage), `Should contain expected message for date ${scenario.date}`); } } }); }); // ======================================== // INTEGRATION AND WORKFLOW TESTING // ======================================== describe('Integration and Multi-Step Workflows', () => { test('should integrate with MCP protocol and tool ecosystem', async () => { // Step 1: Verify tool discovery const tools = await client.listTools(); const debugTool = tools.find(tool => tool.name === 'get_latest_debug'); assert.ok(debugTool, 'get_latest_debug tool should be available'); assert.ok(debugTool.description, 'Tool should have description'); assert.ok(debugTool.inputSchema, 'Tool should have input schema'); assert.equal(debugTool.inputSchema.type, 'object'); // Step 2: Validate schema properties const properties = debugTool.inputSchema.properties; assert.ok(properties.limit, 'Should have limit parameter in schema'); assert.ok(properties.date, 'Should have date parameter in schema'); // Step 3: Verify execution matches schema const result = await client.callTool('get_latest_debug', {}); assertValidMCPResponse(result); }); test('should maintain consistency with related log tools', async () => { // Get results from multiple log tools for comparison const debugResult = await client.callTool('get_latest_debug', { limit: 3 }); const infoResult = await client.callTool('get_latest_info', { limit: 3 }); assertValidMCPResponse(debugResult); assertValidMCPResponse(infoResult); // Validate structural consistency assert.equal(debugResult.content.length, infoResult.content.length, 'Debug and info tools should have similar response structure'); assert.equal(typeof debugResult.isError, typeof infoResult.isError, 'Both tools should handle errors consistently'); assert.equal(debugResult.content[0].type, infoResult.content[0].type, 'Content types should be consistent across log tools'); }); test('should support progressive parameter refinement workflow', async () => { // Workflow: Start broad, then narrow down // Step 1: Get initial debug overview const broadResult = await client.callTool('get_latest_debug', { limit: 10 }); assertSuccessResponse(broadResult); // Step 2: Narrow down to specific recent entries const recentResult = await client.callTool('get_latest_debug', { limit: 3, date: getCurrentDateString() }); assertSuccessResponse(recentResult); // Step 3: Focus on single most recent entry const focusedResult = await client.callTool('get_latest_debug', { limit: 1 }); assertSuccessResponse(focusedResult); // Validate progressive refinement logic assert.ok(broadResult.content[0].text.length >= recentResult.content[0].text.length, 'Broader search should return more content'); assert.ok(recentResult.content[0].text.length >= focusedResult.content[0].text.length, 'More focused search should return less content'); }); }); // ======================================== // ERROR RECOVERY AND RESILIENCE // ======================================== describe('Error Recovery and Resilience', () => { test('should recover gracefully from various error conditions', async () => { // Test sequence of various error conditions followed by recovery const errorSequence = [ { limit: 'invalid-string' }, { limit: -50 }, { limit: null }, { date: null, limit: 'bad' }, { unknownParam: 'test', limit: [] } ]; // Execute error sequence - should not break the server for (const errorParams of errorSequence) { const result = await client.callTool('get_latest_debug', errorParams); assertValidMCPResponse(result); // Don't assert success/failure as some may be handled gracefully } // Verify server recovers and works normally const recoveryResult = await client.callTool('get_latest_debug', { limit: 2 }); assertSuccessResponse(recoveryResult); validateDebugLogStructure(recoveryResult, 2); }); test('should maintain state consistency across sequential operations', async () => { // Test that multiple operations don't interfere with each other const operations = [ { limit: 1 }, { limit: 5 }, { date: getCurrentDateString(), limit: 2 }, { limit: 10 }, { date: '20240101', limit: 1 } ]; const results = []; // Execute operations sequentially (no concurrent requests per AGENTS.md) for (const params of operations) { const result = await client.callTool('get_latest_debug', params); results.push({ params, result }); } // Validate each operation succeeded independently results.forEach(({ params, result }, index) => { assertValidMCPResponse(result); assert.equal(result.isError, false, `Operation ${index + 1} should succeed: ${JSON.stringify(params)}`); // Validate response format consistency assert.equal(result.content.length, 1, 'Should have exactly one content item'); assert.equal(result.content[0].type, 'text', 'Content type should be text'); assert.ok(result.content[0].text.length > 0, 'Content should not be empty'); }); }); }); }); ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/list-sfcc-classes.full-mode.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml # ================================================================================== # SFCC MCP Server - list_sfcc_classes Tool YAML Tests # Comprehensive testing for SFCC class listing functionality # Tests both successful responses and performance validation # # Quick Test Commands: # aegis "tests/mcp/yaml/list-sfcc-classes.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose # aegis "tests/mcp/yaml/list-sfcc-classes.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --debug --timing # aegis query list_sfcc_classes '{}' --config "aegis.config.with-dw.json" # ================================================================================== description: "SFCC MCP Server list_sfcc_classes tool - comprehensive validation" # ================================================================================== # BASIC TOOL STRUCTURE VALIDATION # ================================================================================== tests: - it: "should list list_sfcc_classes tool in available tools" request: jsonrpc: "2.0" id: "tool-available" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-available" result: match:extractField: "tools.*.name" value: "match:arrayContains:list_sfcc_classes" stderr: "toBeEmpty" - it: "should have list_sfcc_classes in tools list with proper structure" request: jsonrpc: "2.0" id: "tool-metadata" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-metadata" result: tools: "match:arrayContains:name:list_sfcc_classes" stderr: "toBeEmpty" # ================================================================================== # SUCCESSFUL EXECUTION TESTS - NO PARAMETERS NEEDED # ================================================================================== - it: "should execute with no parameters (empty object)" request: jsonrpc: "2.0" id: "exec-no-params" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-no-params" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" - it: "should return comprehensive class list structure" request: jsonrpc: "2.0" id: "exec-structure-validation" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-structure-validation" result: content: - type: "text" text: "match:contains:TopLevel" isError: false stderr: "toBeEmpty" - it: "should include core SFCC classes in response" request: jsonrpc: "2.0" id: "exec-core-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-core-classes" result: content: - type: "text" text: "match:contains:dw.catalog" isError: false stderr: "toBeEmpty" - it: "should include system classes in response" request: jsonrpc: "2.0" id: "exec-system-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-system-classes" result: content: - type: "text" text: "match:contains:dw.system" isError: false stderr: "toBeEmpty" - it: "should include order classes in response" request: jsonrpc: "2.0" id: "exec-order-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-order-classes" result: content: - type: "text" text: "match:contains:dw.order" isError: false stderr: "toBeEmpty" - it: "should include customer classes in response" request: jsonrpc: "2.0" id: "exec-customer-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-customer-classes" result: content: - type: "text" text: "match:contains:dw.customer" isError: false stderr: "toBeEmpty" # ================================================================================== # RESPONSE CONTENT VALIDATION # ================================================================================== - it: "should return substantial class list (not empty)" request: jsonrpc: "2.0" id: "content-substantial" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-substantial" result: content: - type: "text" text: "match:contains:dw.catalog.Product" # Should contain specific class isError: false stderr: "toBeEmpty" - it: "should contain structured class information" request: jsonrpc: "2.0" id: "content-structured" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-structured" result: content: - type: "text" text: "match:regex:dw\\.[a-zA-Z]+" # Should contain dw.* patterns isError: false stderr: "toBeEmpty" - it: "should include SFCC namespace classes" request: jsonrpc: "2.0" id: "content-hierarchy" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-hierarchy" result: content: - type: "text" text: "match:contains:dw.catalog" isError: false stderr: "toBeEmpty" # ================================================================================== # PERFORMANCE VALIDATION # ================================================================================== - it: "should complete class listing within reasonable time (metadata operation)" request: jsonrpc: "2.0" id: "perf-metadata-fast" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "perf-metadata-fast" result: content: - type: "text" text: "match:type:string" isError: false performance: maxResponseTime: "1000ms" # Should be fast for metadata stderr: "toBeEmpty" - it: "should handle class listing efficiently" request: jsonrpc: "2.0" id: "perf-efficient" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "perf-efficient" result: content: "match:type:array" isError: false performance: maxResponseTime: "2000ms" # Generous for comprehensive listing stderr: "toBeEmpty" # ================================================================================== # CONTENT QUALITY VALIDATION # ================================================================================== - it: "should include comprehensive namespace coverage" request: jsonrpc: "2.0" id: "namespace-coverage" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "namespace-coverage" result: content: - type: "text" text: "match:contains:dw.web" isError: false stderr: "toBeEmpty" - it: "should include utility classes" request: jsonrpc: "2.0" id: "utility-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "utility-classes" result: content: - type: "text" text: "match:contains:dw.util" isError: false stderr: "toBeEmpty" - it: "should include IO classes" request: jsonrpc: "2.0" id: "io-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "io-classes" result: content: - type: "text" text: "match:contains:dw.io" isError: false stderr: "toBeEmpty" - it: "should include service classes" request: jsonrpc: "2.0" id: "service-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "service-classes" result: content: - type: "text" text: "match:contains:dw.svc" isError: false stderr: "toBeEmpty" # ================================================================================== # ERROR HANDLING & EDGE CASES # ================================================================================== - it: "should handle call with empty arguments gracefully" request: jsonrpc: "2.0" id: "empty-args" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "empty-args" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" - it: "should handle empty arguments object consistently" request: jsonrpc: "2.0" id: "empty-args-consistent" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "empty-args-consistent" result: content: - type: "text" text: "match:not:contains:error" isError: false stderr: "toBeEmpty" - it: "should ignore additional parameters gracefully" request: jsonrpc: "2.0" id: "extra-params" method: "tools/call" params: name: "list_sfcc_classes" arguments: unexpectedParam: "should be ignored" anotherParam: 123 expect: response: jsonrpc: "2.0" id: "extra-params" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" # ================================================================================== # COMPREHENSIVE CONTENT VALIDATION # ================================================================================== - it: "should provide educational content for new developers" request: jsonrpc: "2.0" id: "educational-content" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "educational-content" result: content: - type: "text" text: "match:contains:dw." isError: false stderr: "toBeEmpty" - it: "should include discovery-friendly information" request: jsonrpc: "2.0" id: "discovery-friendly" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "discovery-friendly" result: content: - type: "text" text: "match:not:contains:undefined" isError: false stderr: "toBeEmpty" - it: "should be useful for SFCC API exploration" request: jsonrpc: "2.0" id: "api-exploration" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "api-exploration" result: content: - type: "text" text: "match:contains:dw.system.Site" # Should contain specific class isError: false stderr: "toBeEmpty" # ================================================================================== # FINAL INTEGRATION VALIDATION # ================================================================================== - it: "should provide consistent response structure across calls" request: jsonrpc: "2.0" id: "consistency-check" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "consistency-check" result: content: "match:type:array" isError: false stderr: "toBeEmpty" - it: "should serve as good starting point for SFCC development" request: jsonrpc: "2.0" id: "development-starting-point" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "development-starting-point" result: content: - type: "text" text: "match:contains:TopLevel" isError: false stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/list-sfcc-classes.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml # ================================================================================== # SFCC MCP Server - list_sfcc_classes Tool YAML Tests # Comprehensive testing for SFCC class listing functionality # Tests both successful responses and performance validation # # Quick Test Commands: # aegis "tests/mcp/yaml/list-sfcc-classes.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose # aegis "tests/mcp/yaml/list-sfcc-classes.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --debug --timing # aegis query list_sfcc_classes '{}' --config "aegis.config.docs-only.json" # ================================================================================== description: "SFCC MCP Server list_sfcc_classes tool - comprehensive validation" # ================================================================================== # BASIC TOOL STRUCTURE VALIDATION # ================================================================================== tests: - it: "should list list_sfcc_classes tool in available tools" request: jsonrpc: "2.0" id: "tool-available" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-available" result: match:extractField: "tools.*.name" value: "match:arrayContains:list_sfcc_classes" stderr: "toBeEmpty" - it: "should have list_sfcc_classes in tools list with proper structure" request: jsonrpc: "2.0" id: "tool-metadata" method: "tools/list" params: {} expect: response: jsonrpc: "2.0" id: "tool-metadata" result: tools: "match:arrayContains:name:list_sfcc_classes" stderr: "toBeEmpty" # ================================================================================== # SUCCESSFUL EXECUTION TESTS - NO PARAMETERS NEEDED # ================================================================================== - it: "should execute with no parameters (empty object)" request: jsonrpc: "2.0" id: "exec-no-params" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-no-params" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" - it: "should return comprehensive class list structure" request: jsonrpc: "2.0" id: "exec-structure-validation" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-structure-validation" result: content: - type: "text" text: "match:contains:TopLevel" isError: false stderr: "toBeEmpty" - it: "should include core SFCC classes in response" request: jsonrpc: "2.0" id: "exec-core-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-core-classes" result: content: - type: "text" text: "match:contains:dw.catalog" isError: false stderr: "toBeEmpty" - it: "should include system classes in response" request: jsonrpc: "2.0" id: "exec-system-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-system-classes" result: content: - type: "text" text: "match:contains:dw.system" isError: false stderr: "toBeEmpty" - it: "should include order classes in response" request: jsonrpc: "2.0" id: "exec-order-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-order-classes" result: content: - type: "text" text: "match:contains:dw.order" isError: false stderr: "toBeEmpty" - it: "should include customer classes in response" request: jsonrpc: "2.0" id: "exec-customer-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "exec-customer-classes" result: content: - type: "text" text: "match:contains:dw.customer" isError: false stderr: "toBeEmpty" # ================================================================================== # RESPONSE CONTENT VALIDATION # ================================================================================== - it: "should return substantial class list (not empty)" request: jsonrpc: "2.0" id: "content-substantial" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-substantial" result: content: - type: "text" text: "match:contains:dw.catalog.Product" # Should contain specific class isError: false stderr: "toBeEmpty" - it: "should contain structured class information" request: jsonrpc: "2.0" id: "content-structured" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-structured" result: content: - type: "text" text: "match:regex:dw\\.[a-zA-Z]+" # Should contain dw.* patterns isError: false stderr: "toBeEmpty" - it: "should include SFCC namespace classes" request: jsonrpc: "2.0" id: "content-hierarchy" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "content-hierarchy" result: content: - type: "text" text: "match:contains:dw.catalog" isError: false stderr: "toBeEmpty" # ================================================================================== # PERFORMANCE VALIDATION # ================================================================================== - it: "should complete class listing within reasonable time (metadata operation)" request: jsonrpc: "2.0" id: "perf-metadata-fast" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "perf-metadata-fast" result: content: - type: "text" text: "match:type:string" isError: false performance: maxResponseTime: "1000ms" # Should be fast for metadata stderr: "toBeEmpty" - it: "should handle class listing efficiently" request: jsonrpc: "2.0" id: "perf-efficient" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "perf-efficient" result: content: "match:type:array" isError: false performance: maxResponseTime: "2000ms" # Generous for comprehensive listing stderr: "toBeEmpty" # ================================================================================== # CONTENT QUALITY VALIDATION # ================================================================================== - it: "should include comprehensive namespace coverage" request: jsonrpc: "2.0" id: "namespace-coverage" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "namespace-coverage" result: content: - type: "text" text: "match:contains:dw.web" isError: false stderr: "toBeEmpty" - it: "should include utility classes" request: jsonrpc: "2.0" id: "utility-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "utility-classes" result: content: - type: "text" text: "match:contains:dw.util" isError: false stderr: "toBeEmpty" - it: "should include IO classes" request: jsonrpc: "2.0" id: "io-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "io-classes" result: content: - type: "text" text: "match:contains:dw.io" isError: false stderr: "toBeEmpty" - it: "should include service classes" request: jsonrpc: "2.0" id: "service-classes" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "service-classes" result: content: - type: "text" text: "match:contains:dw.svc" isError: false stderr: "toBeEmpty" # ================================================================================== # ERROR HANDLING & EDGE CASES # ================================================================================== - it: "should handle call with empty arguments gracefully" request: jsonrpc: "2.0" id: "empty-args" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "empty-args" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" - it: "should handle empty arguments object consistently" request: jsonrpc: "2.0" id: "empty-args-consistent" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "empty-args-consistent" result: content: - type: "text" text: "match:not:contains:error" isError: false stderr: "toBeEmpty" - it: "should ignore additional parameters gracefully" request: jsonrpc: "2.0" id: "extra-params" method: "tools/call" params: name: "list_sfcc_classes" arguments: unexpectedParam: "should be ignored" anotherParam: 123 expect: response: jsonrpc: "2.0" id: "extra-params" result: content: - type: "text" text: "match:type:string" isError: false stderr: "toBeEmpty" # ================================================================================== # COMPREHENSIVE CONTENT VALIDATION # ================================================================================== - it: "should provide educational content for new developers" request: jsonrpc: "2.0" id: "educational-content" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "educational-content" result: content: - type: "text" text: "match:contains:dw." isError: false stderr: "toBeEmpty" - it: "should include discovery-friendly information" request: jsonrpc: "2.0" id: "discovery-friendly" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "discovery-friendly" result: content: - type: "text" text: "match:not:contains:undefined" isError: false stderr: "toBeEmpty" - it: "should be useful for SFCC API exploration" request: jsonrpc: "2.0" id: "api-exploration" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "api-exploration" result: content: - type: "text" text: "match:contains:dw.system.Site" # Should contain specific class isError: false stderr: "toBeEmpty" # ================================================================================== # FINAL INTEGRATION VALIDATION # ================================================================================== - it: "should provide consistent response structure across calls" request: jsonrpc: "2.0" id: "consistency-check" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "consistency-check" result: content: "match:type:array" isError: false stderr: "toBeEmpty" - it: "should serve as good starting point for SFCC development" request: jsonrpc: "2.0" id: "development-starting-point" method: "tools/call" params: name: "list_sfcc_classes" arguments: {} expect: response: jsonrpc: "2.0" id: "development-starting-point" result: content: - type: "text" text: "match:contains:TopLevel" isError: false stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /docs/dw_io/XMLStreamWriter.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.io # Class XMLStreamWriter ## Inheritance Hierarchy - Object - dw.io.XMLStreamWriter ## Description The XMLStreamWriter can be used to write small and large XML feeds. Note: when this class is used with sensitive data, be careful in persisting sensitive information to disk. The XMLStreamWriter does not perform well-formedness checking on its input. However the writeCharacters method escapes '&' , '<' and '>'. For attribute values the writeAttribute method escapes the above characters plus '"' to ensure that all character content and attribute values are well formed. The following example illustrates how to use this class: var fileWriter : FileWriter = new FileWriter(file, "UTF-8"); var xsw : XMLStreamWriter = new XMLStreamWriter(fileWriter); xsw.writeStartDocument(); xsw.writeStartElement("products"); xsw.writeStartElement("product"); xsw.writeAttribute("id", "p42"); xsw.writeStartElement("name"); xsw.writeCharacters("blue t-shirt"); xsw.writeEndElement(); xsw.writeStartElement("rating"); xsw.writeCharacters("2.0"); xsw.writeEndElement(); xsw.writeEndElement(); xsw.writeEndElement(); xsw.writeEndDocument(); xsw.close(); fileWriter.close(); The code above will write the following to file: <?xml version="1.0" ?> <products> <product id="p42"> <name>a blue t-shirt</name> <rating>2.0</rating> </product> </products> Note: This output has been formatted for readability. See XMLIndentingStreamWriter. ## Properties ### defaultNamespace **Type:** String The current default name space. ## Constructor Summary XMLStreamWriter(writer : Writer) Constructs the XMLStreamWriter for a writer. ## Method Summary ### close **Signature:** `close() : void` Close this writer and free any resources associated with the writer. ### flush **Signature:** `flush() : void` Write any cached data to the underlying output mechanism. ### getDefaultNamespace **Signature:** `getDefaultNamespace() : String` Returns the current default name space. ### getPrefix **Signature:** `getPrefix(uri : String) : String` Gets the prefix the URI is bound to. ### setDefaultNamespace **Signature:** `setDefaultNamespace(uri : String) : void` Binds a URI to the default namespace. ### setPrefix **Signature:** `setPrefix(prefix : String, uri : String) : void` Sets the prefix the uri is bound to. ### writeAttribute **Signature:** `writeAttribute(localName : String, value : String) : void` Writes an attribute to the output stream without a prefix. ### writeAttribute **Signature:** `writeAttribute(prefix : String, namespaceURI : String, localName : String, value : String) : void` Writes an attribute to the output stream. ### writeAttribute **Signature:** `writeAttribute(namespaceURI : String, localName : String, value : String) : void` Writes an attribute to the output stream. ### writeCData **Signature:** `writeCData(data : String) : void` Writes a CData section. ### writeCharacters **Signature:** `writeCharacters(text : String) : void` Write text to the output. ### writeComment **Signature:** `writeComment(data : String) : void` Writes an XML comment with the data enclosed. ### writeDefaultNamespace **Signature:** `writeDefaultNamespace(namespaceURI : String) : void` Writes the default namespace to the stream. ### writeDTD **Signature:** `writeDTD(dtd : String) : void` Write a DTD section. ### writeEmptyElement **Signature:** `writeEmptyElement(namespaceURI : String, localName : String) : void` Writes an empty element tag to the output. ### writeEmptyElement **Signature:** `writeEmptyElement(prefix : String, localName : String, namespaceURI : String) : void` Writes an empty element tag to the output. ### writeEmptyElement **Signature:** `writeEmptyElement(localName : String) : void` Writes an empty element tag to the output. ### writeEndDocument **Signature:** `writeEndDocument() : void` Closes any start tags and writes corresponding end tags. ### writeEndElement **Signature:** `writeEndElement() : void` Writes an end tag to the output relying on the internal state of the writer to determine the prefix and local name of the event. ### writeEntityRef **Signature:** `writeEntityRef(name : String) : void` Writes an entity reference. ### writeNamespace **Signature:** `writeNamespace(prefix : String, namespaceURI : String) : void` Writes a namespace to the output stream. ### writeProcessingInstruction **Signature:** `writeProcessingInstruction(target : String) : void` Writes a processing instruction. ### writeProcessingInstruction **Signature:** `writeProcessingInstruction(target : String, data : String) : void` Writes a processing instruction. ### writeRaw **Signature:** `writeRaw(raw : String) : void` Writes the given string directly into the output stream. ### writeStartDocument **Signature:** `writeStartDocument() : void` Write the XML Declaration. ### writeStartDocument **Signature:** `writeStartDocument(version : String) : void` Write the XML Declaration. ### writeStartDocument **Signature:** `writeStartDocument(encoding : String, version : String) : void` Write the XML Declaration. ### writeStartElement **Signature:** `writeStartElement(localName : String) : void` Writes a start tag to the output. ### writeStartElement **Signature:** `writeStartElement(namespaceURI : String, localName : String) : void` Writes a start tag to the output. ### writeStartElement **Signature:** `writeStartElement(prefix : String, localName : String, namespaceURI : String) : void` Writes a start tag to the output. ## Constructor Detail ## Method Detail ## Method Details ### close **Signature:** `close() : void` **Description:** Close this writer and free any resources associated with the writer. This method does not close the underlying writer. --- ### flush **Signature:** `flush() : void` **Description:** Write any cached data to the underlying output mechanism. --- ### getDefaultNamespace **Signature:** `getDefaultNamespace() : String` **Description:** Returns the current default name space. **Returns:** the current default name space. --- ### getPrefix **Signature:** `getPrefix(uri : String) : String` **Description:** Gets the prefix the URI is bound to. **Parameters:** - `uri`: the URI to use. **Returns:** the prefix or null. --- ### setDefaultNamespace **Signature:** `setDefaultNamespace(uri : String) : void` **Description:** Binds a URI to the default namespace. This URI is bound in the scope of the current START_ELEMENT / END_ELEMENT pair. If this method is called before a START_ELEMENT has been written the uri is bound in the root scope. **Parameters:** - `uri`: the uri to bind to the default namespace, may be null. --- ### setPrefix **Signature:** `setPrefix(prefix : String, uri : String) : void` **Description:** Sets the prefix the uri is bound to. This prefix is bound in the scope of the current START_ELEMENT / END_ELEMENT pair. If this method is called before a START_ELEMENT has been written the prefix is bound in the root scope. **Parameters:** - `prefix`: the prefix to bind to the uri, may not be null. - `uri`: the uri to bind to the prefix, may be null. --- ### writeAttribute **Signature:** `writeAttribute(localName : String, value : String) : void` **Description:** Writes an attribute to the output stream without a prefix. **Parameters:** - `localName`: the local name of the attribute. - `value`: the value of the attribute. --- ### writeAttribute **Signature:** `writeAttribute(prefix : String, namespaceURI : String, localName : String, value : String) : void` **Description:** Writes an attribute to the output stream. **Parameters:** - `prefix`: the prefix for this attribute. - `namespaceURI`: the uri of the prefix for this attribute. - `localName`: the local name of the attribute. - `value`: the value of the attribute. --- ### writeAttribute **Signature:** `writeAttribute(namespaceURI : String, localName : String, value : String) : void` **Description:** Writes an attribute to the output stream. **Parameters:** - `namespaceURI`: the uri of the prefix for this attribute. - `localName`: the local name of the attribute. - `value`: the value of the attribute. --- ### writeCData **Signature:** `writeCData(data : String) : void` **Description:** Writes a CData section. **Parameters:** - `data`: the data contained in the CData Section, may not be null. --- ### writeCharacters **Signature:** `writeCharacters(text : String) : void` **Description:** Write text to the output. **Parameters:** - `text`: the value to write. --- ### writeComment **Signature:** `writeComment(data : String) : void` **Description:** Writes an XML comment with the data enclosed. **Parameters:** - `data`: the data contained in the comment, may be null. --- ### writeDefaultNamespace **Signature:** `writeDefaultNamespace(namespaceURI : String) : void` **Description:** Writes the default namespace to the stream. **Parameters:** - `namespaceURI`: the uri to bind the default namespace to. --- ### writeDTD **Signature:** `writeDTD(dtd : String) : void` **Description:** Write a DTD section. This string represents the entire doctypedecl production from the XML 1.0 specification. **Parameters:** - `dtd`: the DTD to be written. --- ### writeEmptyElement **Signature:** `writeEmptyElement(namespaceURI : String, localName : String) : void` **Description:** Writes an empty element tag to the output. **Parameters:** - `namespaceURI`: the uri to bind the tag to, may not be null. - `localName`: local name of the tag, may not be null. --- ### writeEmptyElement **Signature:** `writeEmptyElement(prefix : String, localName : String, namespaceURI : String) : void` **Description:** Writes an empty element tag to the output. **Parameters:** - `prefix`: the prefix of the tag, may not be null. - `localName`: local name of the tag, may not be null. - `namespaceURI`: the uri to bind the tag to, may not be null. --- ### writeEmptyElement **Signature:** `writeEmptyElement(localName : String) : void` **Description:** Writes an empty element tag to the output. **Parameters:** - `localName`: local name of the tag, may not be null. --- ### writeEndDocument **Signature:** `writeEndDocument() : void` **Description:** Closes any start tags and writes corresponding end tags. --- ### writeEndElement **Signature:** `writeEndElement() : void` **Description:** Writes an end tag to the output relying on the internal state of the writer to determine the prefix and local name of the event. --- ### writeEntityRef **Signature:** `writeEntityRef(name : String) : void` **Description:** Writes an entity reference. **Parameters:** - `name`: the name of the entity. --- ### writeNamespace **Signature:** `writeNamespace(prefix : String, namespaceURI : String) : void` **Description:** Writes a namespace to the output stream. If the prefix argument to this method is the empty string, "xmlns", or null this method will delegate to writeDefaultNamespace. **Parameters:** - `prefix`: the prefix to bind this namespace to. - `namespaceURI`: the uri to bind the prefix to. --- ### writeProcessingInstruction **Signature:** `writeProcessingInstruction(target : String) : void` **Description:** Writes a processing instruction. **Parameters:** - `target`: the target of the processing instruction, may not be null. --- ### writeProcessingInstruction **Signature:** `writeProcessingInstruction(target : String, data : String) : void` **Description:** Writes a processing instruction. **Parameters:** - `target`: the target of the processing instruction, may not be null. - `data`: the data contained in the processing instruction, may not be null. --- ### writeRaw **Signature:** `writeRaw(raw : String) : void` **Description:** Writes the given string directly into the output stream. No checks regarding the correctness of the XML are done. The caller must ensure that the final result is a correct XML. **Parameters:** - `raw`: the string to write to the output stream. --- ### writeStartDocument **Signature:** `writeStartDocument() : void` **Description:** Write the XML Declaration. Defaults the XML version to 1.0, and the encoding to utf-8 --- ### writeStartDocument **Signature:** `writeStartDocument(version : String) : void` **Description:** Write the XML Declaration. Defaults the XML version to 1.0 **Parameters:** - `version`: version of the xml document. --- ### writeStartDocument **Signature:** `writeStartDocument(encoding : String, version : String) : void` **Description:** Write the XML Declaration. Note that the encoding parameter does not set the actual encoding of the underlying output. That must be set when the instance of the XMLStreamWriter is created using the XMLOutputFactory. **Parameters:** - `encoding`: encoding of the xml declaration. - `version`: version of the xml document. --- ### writeStartElement **Signature:** `writeStartElement(localName : String) : void` **Description:** Writes a start tag to the output. All writeStartElement methods open a new scope in the internal namespace context. Writing the corresponding EndElement causes the scope to be closed. **Parameters:** - `localName`: local name of the tag, may not be null. --- ### writeStartElement **Signature:** `writeStartElement(namespaceURI : String, localName : String) : void` **Description:** Writes a start tag to the output. **Parameters:** - `namespaceURI`: the namespaceURI of the prefix to use, may not be null. - `localName`: local name of the tag, may not be null. --- ### writeStartElement **Signature:** `writeStartElement(prefix : String, localName : String, namespaceURI : String) : void` **Description:** Writes a start tag to the output. **Parameters:** - `prefix`: the prefix of the tag, may not be null. - `localName`: local name of the tag, may not be null. - `namespaceURI`: the uri to bind the prefix to, may not be null. --- ``` -------------------------------------------------------------------------------- /tests/mcp/node/summarize-logs.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript import { test, describe, before, after, beforeEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { connect } from 'mcp-aegis'; describe('summarize_logs - Full Mode Programmatic Tests', () => { let client; before(async () => { client = await connect('./aegis.config.with-dw.json'); }); after(async () => { if (client?.connected) { await client.disconnect(); } }); beforeEach(() => { // CRITICAL: Clear all buffers to prevent leaking into next tests client.clearAllBuffers(); // Recommended - comprehensive protection }); // Helper function to get current date in YYYYMMDD format function getCurrentDateString() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); return `${year}${month}${day}`; } // Comprehensive helper functions for complex validation function assertValidMCPResponse(result) { assert.ok(result.content, 'Should have content'); assert.ok(Array.isArray(result.content), 'Content should be array'); assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); } function assertSuccessResponse(result) { assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error response'); assert.equal(result.content[0].type, 'text'); } function validateCompleteLogSummary(result, expectedDate = null) { assertSuccessResponse(result); const text = result.content[0].text; // Validate header format if (expectedDate) { assert.ok(text.includes(`Log Summary for ${expectedDate}`), `Should contain "Log Summary for ${expectedDate}"`); } else { assert.ok(/Log Summary for \d{8}/.test(text), 'Should contain "Log Summary for YYYYMMDD" pattern'); } // Validate all required sections with complex logic const requiredSections = ['📊 Counts:', '📁 Log Files', '🔥 Key Issues:']; const missingSections = requiredSections.filter(section => !text.includes(section)); assert.equal(missingSections.length, 0, `Missing required sections: ${missingSections.join(', ')}`); // Validate numeric count patterns with regex precision const countPatterns = [ { pattern: /- Errors: \d+/, name: 'Errors' }, { pattern: /- Warnings: \d+/, name: 'Warnings' }, { pattern: /- Info: \d+/, name: 'Info' }, { pattern: /- Debug: \d+/, name: 'Debug' } ]; const failedCounts = countPatterns.filter(({ pattern, name }) => { const matches = pattern.test(text); if (!matches) { console.log(`Failed count validation for ${name}: pattern ${pattern} not found in text`); } return !matches; }); assert.equal(failedCounts.length, 0, `Failed count validations: ${failedCounts.map(f => f.name).join(', ')}`); return { text, sections: requiredSections }; } function validateLogFileSection(text) { // Complex validation for log file section format const logFileMatch = text.match(/📁 Log Files \((\d+)\):/); if (logFileMatch) { const fileCount = parseInt(logFileMatch[1], 10); // Count actual log file entries const logFilePatterns = [ /error-blade-\d{8}-\d{6}\.log/g, /warn-blade-\d{8}-\d{6}\.log/g, /info-blade-\d{8}-\d{6}\.log/g, /debug-blade-\d{8}-\d{6}\.log/g ]; let actualFileCount = 0; logFilePatterns.forEach(pattern => { const matches = text.match(pattern) || []; actualFileCount += matches.length; }); return { declaredCount: fileCount, actualCount: actualFileCount }; } return null; } function validateKeyIssuesSection(text) { // Extract and validate key issues content const keyIssuesSplit = text.split('🔥 Key Issues:'); if (keyIssuesSplit.length > 1) { const keyIssuesContent = keyIssuesSplit[1].trim(); // Should not be empty assert.ok(keyIssuesContent.length > 0, 'Key issues section should not be empty'); // Should contain meaningful assessment patterns const assessmentPatterns = [ /No major issues detected/i, /Key issues identified/i, /Critical errors found/i, /\d+ error/i, /\d+ warning/i, /detected/i, /found/i ]; const hasValidAssessment = assessmentPatterns.some(pattern => pattern.test(keyIssuesContent)); assert.ok(hasValidAssessment, `Key issues should contain meaningful assessment. Content: "${keyIssuesContent}"`); return keyIssuesContent; } return null; } // Core functionality - comprehensive validation describe('Comprehensive Functionality Validation', () => { test('should provide complete log summary with all sections and cross-validation', async () => { const result = await client.callTool('summarize_logs', {}); const validation = validateCompleteLogSummary(result); // Advanced cross-section validation const fileValidation = validateLogFileSection(validation.text); if (fileValidation) { // Log file count should be reasonable (not negative, not excessively high) assert.ok(fileValidation.declaredCount >= 0, 'File count should not be negative'); assert.ok(fileValidation.declaredCount <= 100, 'File count should be reasonable (<=100)'); // If we have specific file entries, count should match or be reasonable if (fileValidation.actualCount > 0) { assert.ok(fileValidation.declaredCount >= fileValidation.actualCount, `Declared count (${fileValidation.declaredCount}) should be >= actual entries (${fileValidation.actualCount})`); } } // Validate key issues assessment quality const keyIssuesContent = validateKeyIssuesSection(validation.text); assert.ok(keyIssuesContent, 'Should have key issues section'); }); test('should handle both log scenarios with appropriate responses', async () => { const result = await client.callTool('summarize_logs', {}); assertValidMCPResponse(result); const text = result.content[0].text; const hasLogSummary = text.includes('📊 Counts:'); const noLogsFound = text.includes('No log files found'); assert.ok(hasLogSummary || noLogsFound, 'Should either show log summary or no logs message'); if (hasLogSummary) { // Comprehensive validation for log summary scenario validateCompleteLogSummary(result); } else { // Validation for no logs scenario assert.ok(/No log files found for date \d{8}/.test(text), 'No logs message should include specific date in YYYYMMDD format'); } }); }); // Dynamic date validation with cross-scenario consistency describe('Dynamic Date Validation and Consistency', () => { test('should maintain consistent response format across multiple date scenarios', async () => { const testScenarios = [ { date: getCurrentDateString(), description: 'current date' }, { date: '20220101', description: 'old date' }, { date: '20301231', description: 'future date' }, { date: 'invalid-format', description: 'invalid format' }, { date: '2024-01-01', description: 'wrong format' }, { date: '', description: 'empty string' } ]; const results = []; // Sequential execution to avoid buffer conflicts for (const scenario of testScenarios) { const result = await client.callTool('summarize_logs', { date: scenario.date }); assertValidMCPResponse(result); results.push({ ...scenario, result, text: result.content[0].text, isError: result.isError, hasLogSummary: result.content[0].text.includes('📊 Counts:'), isNoLogsMessage: result.content[0].text.includes('No log files found') }); } // Cross-scenario validation results.forEach(({ date, description, result, text, isError, hasLogSummary, isNoLogsMessage }) => { // All should follow same response structure assert.equal(result.content.length, 1, `${description} should have exactly one content element`); assert.equal(result.content[0].type, 'text', `${description} content type should be text`); assert.equal(isError, false, `${description} should not be marked as error`); // Should either have log summary or no logs message assert.ok(hasLogSummary || isNoLogsMessage, `${description} should either show summary or no logs message`); // Date format validation in response if (hasLogSummary) { if (date === '') { // Empty string date results in "Log Summary for :" format assert.ok(text.includes('Log Summary for :'), `${description} with empty date should include "Log Summary for :"`); } else { assert.ok(/Log Summary for \d{8}/.test(text), `${description} summary should include YYYYMMDD date`); } } else if (isNoLogsMessage) { assert.ok(text.includes(`No log files found for date ${date}`), `${description} should include the exact date parameter in no-logs message`); } }); // Validate consistent behavior for invalid dates const invalidDateResults = results.filter(r => ['invalid-format', 'wrong format'].includes(r.description)); // Removed 'empty string' since it acts like default invalidDateResults.forEach(({ description, isNoLogsMessage }) => { assert.ok(isNoLogsMessage, `${description} should result in no logs message`); }); }); test('should handle parameter variations and edge cases dynamically', async () => { const parameterVariations = [ { args: {}, description: 'empty object' }, { args: undefined, description: 'undefined args' }, { args: { date: null }, description: 'null date' }, { args: { date: getCurrentDateString(), extra: 'ignored' }, description: 'extra parameters' } ]; for (const { args, description } of parameterVariations) { const result = await client.callTool('summarize_logs', args); assertValidMCPResponse(result); // Should handle gracefully without errors assert.equal(result.isError, false, `${description} should not cause error response`); const text = result.content[0].text; const hasValidResponse = text.includes('📊 Counts:') || text.includes('No log files found'); assert.ok(hasValidResponse, `${description} should produce valid response format`); } }); }); // Advanced content analysis and business logic validation describe('Advanced Content Analysis', () => { test('should validate log count arithmetic and consistency', async () => { const result = await client.callTool('summarize_logs', {}); assertSuccessResponse(result); const text = result.content[0].text; if (text.includes('📊 Counts:')) { // Extract all numeric counts const countMatches = { errors: text.match(/- Errors: (\d+)/), warnings: text.match(/- Warnings: (\d+)/), info: text.match(/- Info: (\d+)/), debug: text.match(/- Debug: (\d+)/) }; // Validate all counts were found Object.entries(countMatches).forEach(([level, match]) => { assert.ok(match, `Should find ${level} count in response`); const count = parseInt(match[1], 10); assert.ok(count >= 0, `${level} count should not be negative`); assert.ok(count < 1000000, `${level} count should be reasonable (<1M)`); }); // Advanced business logic: total log entries should be reasonable const totalEntries = Object.values(countMatches) .map(match => parseInt(match[1], 10)) .reduce((sum, count) => sum + count, 0); assert.ok(totalEntries >= 0, 'Total log entries should not be negative'); // If we have log entries, we should have log files if (totalEntries > 0) { assert.ok(text.includes('📁 Log Files'), 'Should have log files section when log entries exist'); } } }); test('should validate emoji consistency and section ordering', async () => { const result = await client.callTool('summarize_logs', {}); assertSuccessResponse(result); const text = result.content[0].text; if (text.includes('📊 Counts:')) { // Validate section ordering by finding their positions const sectionPositions = { counts: text.indexOf('📊 Counts:'), files: text.indexOf('📁 Log Files'), issues: text.indexOf('🔥 Key Issues:') }; // All sections should be found Object.entries(sectionPositions).forEach(([section, position]) => { assert.ok(position >= 0, `Should find ${section} section`); }); // Validate logical ordering: counts -> files -> issues assert.ok(sectionPositions.counts < sectionPositions.files, 'Counts section should come before files section'); assert.ok(sectionPositions.files < sectionPositions.issues, 'Files section should come before issues section'); // Validate consistent emoji usage const expectedEmojis = ['📊', '📁', '🔥']; expectedEmojis.forEach(emoji => { assert.ok(text.includes(emoji), `Should use ${emoji} emoji consistently`); }); } }); }); }); ``` -------------------------------------------------------------------------------- /docs/dw_suggest/SuggestModel.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.suggest # Class SuggestModel ## Inheritance Hierarchy - Object - dw.suggest.SuggestModel ## Description The Suggest model provides methods and functions to access search suggestions. The search suggestion feature basically covers two functional areas. First is just to suggest words, based on the users input, utilizing spell correction or prediction (also known as auto completion). The second functional area is also often referred to as search-as-you-type, where, based on the users input, specific items are already looked up, before the user actually has completed typing a word or even fired up the search. This model combines both functional areas and provides access to both - the suggested words and the items found while using the predicted words. This model supports various types of items that are being suggested, like products, categories, brands, content pages as well merchant provided search phrases. For each type, there is a Suggestions implementation available and accessible through this model: ProductSuggestions, CategorySuggestions, BrandSuggestions, ContentSuggestions, and CustomSuggestions. For each type of suggestions, the actual suggested items (like products) can by obtained, and, on the other hand, a list of terms is provided which were used to lookup the found items. The terms can be used to present a advanced user experience in the storefront, e.g. show auto completed words, spell corrections and so on. The SuggestModel script API will always create suggestions with Autocorrections regardless of the value of "Search Autocorrections" search preference. ## Constants ### MAX_SUGGESTIONS **Type:** Number = 10 The maximum number of suggestions that can be obtain from this model: 10 ## Properties ### brandSuggestions **Type:** BrandSuggestions (Read Only) A BrandSuggestions container for the current search phrase. The BrandSuggestions container provides access to the found brands (if any) and the terms suggested by the system with respect to the known product brands in the catalog. ### categorySuggestions **Type:** CategorySuggestions (Read Only) A CategorySuggestions container for the current search phrase. The CategorySuggestions container provides access to the found categories (if any) and the terms suggested by the system with respect to the known categories in the catalog. ### contentSuggestions **Type:** ContentSuggestions (Read Only) A ContentSuggestions container for the current search phrase. The ContentSuggestions container provides access to the found content pages (if any) and the terms suggested by the system with respect to the known content in the library. ### customSuggestions **Type:** CustomSuggestions (Read Only) A CustomSuggestions container for the current search phrase. The CustomSuggestions container provides access to matching custom phrases (if any) and the terms suggested by the system with respect to the merchant provided custom phrases. ### filteredByFolder **Type:** boolean The method returns true, if the search suggestions are filtered by the folder. If this returns true it is not possible for search suggestions to contain Page Designer content as it belongs to no folder. ### popularSearchPhrases **Type:** Iterator (Read Only) Use this method to obtain a list of search phrases that currently are very popular among all users across the Site. The search phrases are specific to the region (based on user's IP address), language (locale) and the user's browser type (agent). ### productSuggestions **Type:** ProductSuggestions (Read Only) A ProductSuggestions container for the current search phrase. The ProductSuggestions container provides access to the found products (if any) and the terms suggested by the system with respect to the known products in the catalog. ### recentSearchPhrases **Type:** Iterator (Read Only) Use this method to obtain a list of personalized search phrases that the current user entered recently. The user is being identified by the CQuotient tracking cookie. ## Constructor Summary SuggestModel() Constructs a new SuggestModel. ## Method Summary ### addRefinementValues **Signature:** `addRefinementValues(attributeID : String, values : String) : void` Adds a refinement for product suggestions. ### getBrandSuggestions **Signature:** `getBrandSuggestions() : BrandSuggestions` Returns a BrandSuggestions container for the current search phrase. ### getCategorySuggestions **Signature:** `getCategorySuggestions() : CategorySuggestions` Returns a CategorySuggestions container for the current search phrase. ### getContentSuggestions **Signature:** `getContentSuggestions() : ContentSuggestions` Returns a ContentSuggestions container for the current search phrase. ### getCustomSuggestions **Signature:** `getCustomSuggestions() : CustomSuggestions` Returns a CustomSuggestions container for the current search phrase. ### getPopularSearchPhrases **Signature:** `getPopularSearchPhrases() : Iterator` Use this method to obtain a list of search phrases that currently are very popular among all users across the Site. ### getProductSuggestions **Signature:** `getProductSuggestions() : ProductSuggestions` Returns a ProductSuggestions container for the current search phrase. ### getRecentSearchPhrases **Signature:** `getRecentSearchPhrases() : Iterator` Use this method to obtain a list of personalized search phrases that the current user entered recently. ### isFilteredByFolder **Signature:** `isFilteredByFolder() : boolean` The method returns true, if the search suggestions are filtered by the folder. ### removeRefinementValues **Signature:** `removeRefinementValues(attributeID : String, values : String) : void` Removes a refinement. ### setCategoryID **Signature:** `setCategoryID(categoryID : String) : void` Apply a category ID to filter product, brand and category suggestions. ### setFilteredByFolder **Signature:** `setFilteredByFolder(filteredByFolder : boolean) : void` Set a flag to indicate if the search suggestions filter for elements that do not belong to a folder. ### setMaxSuggestions **Signature:** `setMaxSuggestions(maxSuggestions : Number) : void` Use this method to setup the maximum number of returned suggested items. ### setRefinementValues **Signature:** `setRefinementValues(attributeID : String, values : String) : void` Sets product suggestion refinement values for an attribute. ### setSearchPhrase **Signature:** `setSearchPhrase(searchPhrase : String) : void` Sets the user input search phrase. ## Constructor Detail ## Method Detail ## Method Details ### addRefinementValues **Signature:** `addRefinementValues(attributeID : String, values : String) : void` **Description:** Adds a refinement for product suggestions. The method can be called to add an additional query parameter specified as name-value pair. The values string may encode multiple values delimited by the pipe symbol ('|'). **Parameters:** - `attributeID`: The ID of the refinement attribute. - `values`: the refinement value to set --- ### getBrandSuggestions **Signature:** `getBrandSuggestions() : BrandSuggestions` **Description:** Returns a BrandSuggestions container for the current search phrase. The BrandSuggestions container provides access to the found brands (if any) and the terms suggested by the system with respect to the known product brands in the catalog. **Returns:** a brand suggestions container for the current search phrase, returns null for insufficient search input **See Also:** setMaxSuggestions(Number) setSearchPhrase(String) --- ### getCategorySuggestions **Signature:** `getCategorySuggestions() : CategorySuggestions` **Description:** Returns a CategorySuggestions container for the current search phrase. The CategorySuggestions container provides access to the found categories (if any) and the terms suggested by the system with respect to the known categories in the catalog. **Returns:** a category suggestions container for the current search phrase, returns null for insufficient search input **See Also:** setMaxSuggestions(Number) setSearchPhrase(String) --- ### getContentSuggestions **Signature:** `getContentSuggestions() : ContentSuggestions` **Description:** Returns a ContentSuggestions container for the current search phrase. The ContentSuggestions container provides access to the found content pages (if any) and the terms suggested by the system with respect to the known content in the library. **Returns:** a content suggestions container for the current search phrase, returns null for insufficient search input **See Also:** setMaxSuggestions(Number) setSearchPhrase(String) --- ### getCustomSuggestions **Signature:** `getCustomSuggestions() : CustomSuggestions` **Description:** Returns a CustomSuggestions container for the current search phrase. The CustomSuggestions container provides access to matching custom phrases (if any) and the terms suggested by the system with respect to the merchant provided custom phrases. **Returns:** a custom suggestions container for the current search phrase, returns null for insufficient search input **See Also:** setMaxSuggestions(Number) setSearchPhrase(String) --- ### getPopularSearchPhrases **Signature:** `getPopularSearchPhrases() : Iterator` **Description:** Use this method to obtain a list of search phrases that currently are very popular among all users across the Site. The search phrases are specific to the region (based on user's IP address), language (locale) and the user's browser type (agent). **Returns:** a list of personalized popular search phrases --- ### getProductSuggestions **Signature:** `getProductSuggestions() : ProductSuggestions` **Description:** Returns a ProductSuggestions container for the current search phrase. The ProductSuggestions container provides access to the found products (if any) and the terms suggested by the system with respect to the known products in the catalog. **Returns:** a product suggestions container for the current search phrase, returns null for insufficient search input **See Also:** setMaxSuggestions(Number) setSearchPhrase(String) --- ### getRecentSearchPhrases **Signature:** `getRecentSearchPhrases() : Iterator` **Description:** Use this method to obtain a list of personalized search phrases that the current user entered recently. The user is being identified by the CQuotient tracking cookie. **Returns:** a list of recent search phrases of the current user --- ### isFilteredByFolder **Signature:** `isFilteredByFolder() : boolean` **Description:** The method returns true, if the search suggestions are filtered by the folder. If this returns true it is not possible for search suggestions to contain Page Designer content as it belongs to no folder. **Returns:** True if search suggestions are filtered by the folder of the content asset. --- ### removeRefinementValues **Signature:** `removeRefinementValues(attributeID : String, values : String) : void` **Description:** Removes a refinement. The method can be called to remove previously added refinement values. The values string may encode multiple values delimited by the pipe symbol ('|'). **Parameters:** - `attributeID`: The ID of the refinement attribute. - `values`: the refinement value to remove or null to remove all values --- ### setCategoryID **Signature:** `setCategoryID(categoryID : String) : void` **Description:** Apply a category ID to filter product, brand and category suggestions. Suggested products, brands and categories, as well as corrected and completed terms are specific to the given category or one of it's sub categories. For example, in the specified category "television", the search term "pla" will be auto completed to "plasma" (instead of e.g. "player") and only televisions will be included in the list of suggested products. **Parameters:** - `categoryID`: the category to filter suggestions for --- ### setFilteredByFolder **Signature:** `setFilteredByFolder(filteredByFolder : boolean) : void` **Description:** Set a flag to indicate if the search suggestions filter for elements that do not belong to a folder. Must be set to false to return content assets that do not belong to any folder. **Parameters:** - `filteredByFolder`: filter the search suggestions by folder --- ### setMaxSuggestions **Signature:** `setMaxSuggestions(maxSuggestions : Number) : void` **Description:** Use this method to setup the maximum number of returned suggested items. For example, set this to 3 in order to only retrieve the 3 most relevant suggested products. The maximum number of suggestions that can be queried are defined as MAX_SUGGESTIONS. **Parameters:** - `maxSuggestions`: the number of suggested items to be returned by this model instance --- ### setRefinementValues **Signature:** `setRefinementValues(attributeID : String, values : String) : void` **Description:** Sets product suggestion refinement values for an attribute. The method can be called to set an additional query parameter specified as name-value pair. The value string may encode multiple values delimited by the pipe symbol ('|'). Existing refinement values for the attribute will be removed. **Parameters:** - `attributeID`: The ID of the refinement attribute. - `values`: the refinement values to set (delimited by '|') or null to remove all values --- ### setSearchPhrase **Signature:** `setSearchPhrase(searchPhrase : String) : void` **Description:** Sets the user input search phrase. This search phrase is being processed by applying auto completion, spell correction and enhancement with alternative similar search terms. The resulting search phrase is used to lookup the actual items, like products or categories (search-as-you-type). In order to access the processed terms, one can use the SearchPhraseSuggestions.getSuggestedTerms() method of each of the respective results returned by the methods in this model. **Parameters:** - `searchPhrase`: the user input search phrase **See Also:** SearchPhraseSuggestions.getSuggestedTerms() --- ``` -------------------------------------------------------------------------------- /tests/log-handler.test.ts: -------------------------------------------------------------------------------- ```typescript import { LogToolHandler } from '../src/core/handlers/log-handler.js'; import { HandlerContext } from '../src/core/handlers/base-handler.js'; import { SFCCLogClient } from '../src/clients/log-client.js'; import { Logger } from '../src/utils/logger.js'; // Mock the SFCCLogClient jest.mock('../src/clients/log-client.js'); describe('LogToolHandler', () => { let mockLogger: jest.Mocked<Logger>; let mockLogClient: jest.Mocked<SFCCLogClient>; let context: HandlerContext; let handler: LogToolHandler; beforeEach(() => { mockLogger = { debug: jest.fn(), log: jest.fn(), error: jest.fn(), timing: jest.fn(), methodEntry: jest.fn(), methodExit: jest.fn(), } as any; mockLogClient = { getLatestLogs: jest.fn(), summarizeLogs: jest.fn(), searchLogs: jest.fn(), listLogFiles: jest.fn(), getLogFileContents: jest.fn(), } as any; (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockImplementation(() => mockLogClient); jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger); context = { logger: mockLogger, config: { hostname: 'test.demandware.net', username: 'test', password: 'test', clientId: 'test', clientSecret: 'test', }, capabilities: { canAccessLogs: true, canAccessOCAPI: true }, }; handler = new LogToolHandler(context, 'Log'); }); afterEach(() => { jest.restoreAllMocks(); }); describe('canHandle', () => { it('should handle log-related tools', () => { expect(handler.canHandle('get_latest_error')).toBe(true); expect(handler.canHandle('get_latest_warn')).toBe(true); expect(handler.canHandle('get_latest_info')).toBe(true); expect(handler.canHandle('get_latest_debug')).toBe(true); expect(handler.canHandle('summarize_logs')).toBe(true); expect(handler.canHandle('search_logs')).toBe(true); expect(handler.canHandle('list_log_files')).toBe(true); expect(handler.canHandle('get_log_file_contents')).toBe(true); }); it('should not handle non-log tools', () => { expect(handler.canHandle('get_sfcc_class_info')).toBe(false); expect(handler.canHandle('unknown_tool')).toBe(false); }); }); // Helper function to initialize handler for tests that need it const initializeHandler = async () => { await (handler as any).initialize(); }; describe('initialization', () => { it('should initialize log client when capabilities allow', async () => { // Manually trigger initialization await (handler as any).initialize(); expect(SFCCLogClient).toHaveBeenCalledWith(context.config); expect(mockLogger.debug).toHaveBeenCalledWith('Log client initialized'); }); it('should not initialize log client when capabilities do not allow', async () => { const contextWithoutLogs = { ...context, capabilities: { canAccessLogs: false, canAccessOCAPI: false }, }; const handlerWithoutLogs = new LogToolHandler(contextWithoutLogs, 'Log'); // Manually trigger initialization to test the capabilities check await (handlerWithoutLogs as any).initialize(); // Reset the mock counter for this test (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockClear(); const result = await handlerWithoutLogs.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.'); expect(SFCCLogClient).not.toHaveBeenCalled(); }); it('should not initialize without config', async () => { const contextWithoutConfig = { ...context, config: null as any, }; const handlerWithoutConfig = new LogToolHandler(contextWithoutConfig, 'Log'); const result = await handlerWithoutConfig.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Log client not configured - ensure log access is enabled.'); }); }); describe('disposal', () => { it('should dispose log client properly', async () => { // Initialize first await initializeHandler(); // Dispose await (handler as any).dispose(); expect(mockLogger.debug).toHaveBeenCalledWith('Log client disposed'); }); }); describe('get_latest_* tools', () => { beforeEach(async () => { await initializeHandler(); mockLogClient.getLatestLogs.mockResolvedValue('Test log entry\n2023-01-01T00:00:00Z'); }); it('should handle get_latest_error', async () => { const result = await handler.handle('get_latest_error', { limit: 5, date: '20230101' }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('error', 5, '20230101'); expect(result.content[0].text).toContain('Test log entry'); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=20230101'); }); it('should handle get_latest_warn with default parameters', async () => { await handler.handle('get_latest_warn', {}, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('warn', 10, undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest warn logs limit=10 date=today'); }); it('should handle get_latest_info', async () => { await handler.handle('get_latest_info', { limit: 15 }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('info', 15, undefined); }); it('should handle get_latest_debug', async () => { await handler.handle('get_latest_debug', { date: '20230101' }, Date.now()); expect(mockLogClient.getLatestLogs).toHaveBeenCalledWith('debug', 10, '20230101'); }); }); describe('summarize_logs tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle summarize_logs', async () => { const mockSummary = JSON.stringify({ date: '20230101', totalLogs: 100, errorCount: 5, warnCount: 10, infoCount: 85, }); mockLogClient.summarizeLogs.mockResolvedValue(mockSummary); await handler.handle('summarize_logs', { date: '20230101' }, Date.now()); expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith('20230101'); expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date 20230101'); }); it('should handle summarize_logs with default date', async () => { const mockSummary = JSON.stringify({ date: 'today', totalLogs: 50 }); mockLogClient.summarizeLogs.mockResolvedValue(mockSummary); await handler.handle('summarize_logs', {}, Date.now()); expect(mockLogClient.summarizeLogs).toHaveBeenCalledWith(undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Summarizing logs for date today'); }); }); describe('search_logs tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle search_logs with required pattern', async () => { const mockResults = JSON.stringify({ results: [{ message: 'Error occurred', timestamp: '2023-01-01T00:00:00Z' }], total: 1, }); mockLogClient.searchLogs.mockResolvedValue(mockResults); const args = { pattern: 'error', logLevel: 'error', limit: 25, date: '20230101' }; const result = await handler.handle('search_logs', args, Date.now()); expect(mockLogClient.searchLogs).toHaveBeenCalledWith('error', 'error', 25, '20230101'); expect(result.content[0].text).toContain('Error occurred'); expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="error" level=error limit=25'); }); it('should handle search_logs with default parameters', async () => { const mockResults = JSON.stringify({ results: [], total: 0 }); mockLogClient.searchLogs.mockResolvedValue(mockResults); await handler.handle('search_logs', { pattern: 'test' }, Date.now()); expect(mockLogClient.searchLogs).toHaveBeenCalledWith('test', undefined, 20, undefined); expect(mockLogger.debug).toHaveBeenCalledWith('Searching logs pattern="test" level=all limit=20'); }); it('should throw error when pattern is missing', async () => { const result = await handler.handle('search_logs', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('pattern must be a non-empty string'); }); it('should throw error when pattern is empty', async () => { const result = await handler.handle('search_logs', { pattern: '' }, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('pattern must be a non-empty string'); }); }); describe('get_log_file_contents tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle get_log_file_contents with filename', async () => { const mockFileContents = 'Log file contents with some test data\nMore log entries...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'error-2023-01-01.log' }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('error-2023-01-01.log', undefined, undefined); expect(result.content[0].text).toContain('Log file contents with some test data'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: error-2023-01-01.log (maxBytes=default, tailOnly=false)'); }); it('should handle get_log_file_contents with maxBytes and tailOnly options', async () => { const mockFileContents = 'Tail content of log file...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'large-log.log', maxBytes: 1024, tailOnly: true, }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 1024, true); expect(result.content[0].text).toContain('Tail content of log file'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=1024, tailOnly=true)'); }); it('should handle get_log_file_contents with maxBytes and tailOnly=false (full file with size limit)', async () => { const mockFileContents = 'Full file content with size limit applied...'; mockLogClient.getLogFileContents.mockResolvedValue(mockFileContents); const result = await handler.handle('get_log_file_contents', { filename: 'large-log.log', maxBytes: 512, tailOnly: false, }, Date.now()); expect(mockLogClient.getLogFileContents).toHaveBeenCalledWith('large-log.log', 512, false); expect(result.content[0].text).toContain('Full file content with size limit'); expect(mockLogger.debug).toHaveBeenCalledWith('Reading log file contents: large-log.log (maxBytes=512, tailOnly=false)'); }); it('should require filename parameter', async () => { const result = await handler.handle('get_log_file_contents', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('filename must be a non-empty string'); }); it('should handle empty filename', async () => { const result = await handler.handle('get_log_file_contents', { filename: '' }, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('filename must be a non-empty string'); }); }); describe('list_log_files tool', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle list_log_files', async () => { const mockFiles = JSON.stringify([ { name: 'error-2023-01-01.log', size: 1024, modified: '2023-01-01T00:00:00Z' }, { name: 'info-2023-01-01.log', size: 2048, modified: '2023-01-01T00:00:00Z' }, ]); mockLogClient.listLogFiles.mockResolvedValue(mockFiles); const result = await handler.handle('list_log_files', {}, Date.now()); expect(mockLogClient.listLogFiles).toHaveBeenCalled(); expect(result.content[0].text).toContain('error-2023-01-01.log'); expect(result.content[0].text).toContain('info-2023-01-01.log'); expect(mockLogger.debug).toHaveBeenCalledWith('Listing log files'); }); }); describe('error handling', () => { beforeEach(async () => { await initializeHandler(); }); it('should handle client errors gracefully', async () => { mockLogClient.getLatestLogs.mockRejectedValue(new Error('Client connection failed')); const result = await handler.handle('get_latest_error', {}, Date.now()); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Client connection failed'); }); it('should throw error for unsupported tools', async () => { await expect(handler.handle('unsupported_tool', {}, Date.now())) .rejects.toThrow('Unsupported tool'); }); }); describe('timing and logging', () => { beforeEach(async () => { await initializeHandler(); }); it('should log timing information', async () => { mockLogClient.getLatestLogs.mockResolvedValue('empty logs'); const startTime = Date.now(); await handler.handle('get_latest_error', {}, startTime); expect(mockLogger.timing).toHaveBeenCalledWith('get_latest_error', startTime); }); it('should log execution details', async () => { mockLogClient.getLatestLogs.mockResolvedValue('test logs'); await handler.handle('get_latest_error', { limit: 5 }, Date.now()); expect(mockLogger.debug).toHaveBeenCalledWith('Fetching latest error logs limit=5 date=today'); expect(mockLogger.debug).toHaveBeenCalledWith( 'get_latest_error completed successfully', expect.any(Object), ); }); }); }); ``` -------------------------------------------------------------------------------- /docs/TopLevel/Object.md: -------------------------------------------------------------------------------- ```markdown ## Package: TopLevel # Class Object ## Inheritance Hierarchy - Object ## Description The Object object is the foundation of all native JavaScript objects. Also, the Object object can be used to generate items in your scripts with behaviors that are defined by custom properties and/or methods. You generally start by creating a blank object with the constructor function and then assign values to new properties of that object. ## Constructor Summary Object() Object constructor. ## Method Summary ### assign **Signature:** `static assign(target : Object, sources : Object...) : Object` Copies the values of all of the enumerable own properties from one or more source objects to a target object. ### create **Signature:** `static create(prototype : Object) : Object` Creates a new object based on a prototype object. ### create **Signature:** `static create(prototype : Object, properties : Object) : Object` Creates a new object based on a prototype object and additional property definitions. ### defineProperties **Signature:** `static defineProperties(object : Object, properties : Object) : Object` Defines or modifies properties of the passed object. ### defineProperty **Signature:** `static defineProperty(object : Object, propertyKey : Object, descriptor : Object) : Object` Defines or modifies a single property of the passed object. ### entries **Signature:** `static entries(object : Object) : Array` Returns the enumerable property names and their values of the passed object. ### freeze **Signature:** `static freeze(object : Object) : Object` Freezes the passed object. ### fromEntries **Signature:** `static fromEntries(properties : Iterable) : Object` Creates a new object with defined properties. ### getOwnPropertyDescriptor **Signature:** `static getOwnPropertyDescriptor(object : Object, propertyKey : Object) : Object` Returns the descriptor for a single property of the passed object. ### getOwnPropertyNames **Signature:** `static getOwnPropertyNames(object : Object) : Array` Returns an arrays containing the names of all enumerable and non-enumerable properties owned by the passed object. ### getOwnPropertySymbols **Signature:** `static getOwnPropertySymbols(object : Object) : Array` Returns an array containing the symbol of all symbol properties owned by the passed object. ### getPrototypeOf **Signature:** `static getPrototypeOf(object : Object) : Object` Returns the prototype of the passed object. ### hasOwnProperty **Signature:** `hasOwnProperty(propName : String) : boolean` Returns Boolean true if at the time the current object's instance was created its constructor (or literal assignment) contained a property with a name that matches the parameter value. ### is **Signature:** `static is(value1 : Object, value2 : Object) : boolean` Checks if the two values are equal in terms of being the same value. ### isExtensible **Signature:** `static isExtensible(object : Object) : boolean` Returns if new properties can be added to an object. ### isFrozen **Signature:** `static isFrozen(object : Object) : boolean` Returns if the object is frozen. ### isPrototypeOf **Signature:** `isPrototypeOf(prototype : Object) : boolean` Returns true if the current object and the object passed as a prameter conincide at some point along each object's prototype inheritance chain. ### isSealed **Signature:** `static isSealed(object : Object) : boolean` Returns if the object is sealed. ### keys **Signature:** `static keys(object : Object) : Array` Returns the enumerable property names of the passed object. ### preventExtensions **Signature:** `static preventExtensions(object : Object) : Object` Makes the passed object non-extensible. ### propertyIsEnumerable **Signature:** `propertyIsEnumerable(propName : String) : boolean` Return true if the specified property exposes itself to for/in property inspection through the object. ### seal **Signature:** `static seal(object : Object) : Object` Seals the passed object. ### setPrototypeOf **Signature:** `static setPrototypeOf(object : Object, prototype : Object) : Object` Changes the prototype of the passed object. ### toLocaleString **Signature:** `toLocaleString() : String` Converts the object to a localized String. ### toString **Signature:** `toString() : String` Converts the object to a String. ### valueOf **Signature:** `valueOf() : Object` Returns the object's value. ### values **Signature:** `static values(object : Object) : Array` Returns the enumerable property values of the passed object. ## Constructor Detail ## Method Detail ## Method Details ### assign **Signature:** `static assign(target : Object, sources : Object...) : Object` **Description:** Copies the values of all of the enumerable own properties from one or more source objects to a target object. **API Versioned:** From version 21.2. **Parameters:** - `target`: The target object. - `sources`: The source objects. **Returns:** The target object. --- ### create **Signature:** `static create(prototype : Object) : Object` **Description:** Creates a new object based on a prototype object. **Parameters:** - `prototype`: The prototype for the new object. **Returns:** The newly created object. --- ### create **Signature:** `static create(prototype : Object, properties : Object) : Object` **Description:** Creates a new object based on a prototype object and additional property definitions. The properties are given in the same format as described for defineProperties(Object, Object). **Parameters:** - `prototype`: The prototype for the new object. - `properties`: The property definitions. **Returns:** The newly created object. --- ### defineProperties **Signature:** `static defineProperties(object : Object, properties : Object) : Object` **Description:** Defines or modifies properties of the passed object. A descriptor for a property supports these properties: configurable, enumerable, value, writable, set and get. **Parameters:** - `object`: The object to change. - `properties`: The new property definitions. **Returns:** The modified object. --- ### defineProperty **Signature:** `static defineProperty(object : Object, propertyKey : Object, descriptor : Object) : Object` **Description:** Defines or modifies a single property of the passed object. A descriptor for a property supports these properties: configurable, enumerable, value, writable, set and get. **Parameters:** - `object`: The object to change. - `propertyKey`: The property name. - `descriptor`: The property descriptor object. **Returns:** The modified object. --- ### entries **Signature:** `static entries(object : Object) : Array` **Description:** Returns the enumerable property names and their values of the passed object. **API Versioned:** From version 22.7. **Parameters:** - `object`: The object to get the enumerable property names from. **Returns:** An array of key/value pairs ( as two element arrays ) that holds all the enumerable properties of the given object. --- ### freeze **Signature:** `static freeze(object : Object) : Object` **Description:** Freezes the passed object. Properties can't be added or removed from the frozen object. Also, definitions of existing object properties can't be changed. Although property values are immutable, setters and getters can be called. **Parameters:** - `object`: The object to be frozen. **Returns:** The frozen object. --- ### fromEntries **Signature:** `static fromEntries(properties : Iterable) : Object` **Description:** Creates a new object with defined properties. The properties are defined by an iterable that produces two element array like objects, which are the key-value pairs. Iterables are e.g. Array, Map or any other Iterable. **API Versioned:** From version 22.7. **Parameters:** - `properties`: The properties. **Returns:** The newly created object. --- ### getOwnPropertyDescriptor **Signature:** `static getOwnPropertyDescriptor(object : Object, propertyKey : Object) : Object` **Description:** Returns the descriptor for a single property of the passed object. **Parameters:** - `object`: The property owning object. - `propertyKey`: The property to look for. **Returns:** The descriptor object for the property or undefined if the property does not exist. --- ### getOwnPropertyNames **Signature:** `static getOwnPropertyNames(object : Object) : Array` **Description:** Returns an arrays containing the names of all enumerable and non-enumerable properties owned by the passed object. **Parameters:** - `object`: The object owning properties. **Returns:** An array of strings that are the properties found directly in the passed object. --- ### getOwnPropertySymbols **Signature:** `static getOwnPropertySymbols(object : Object) : Array` **Description:** Returns an array containing the symbol of all symbol properties owned by the passed object. **API Versioned:** From version 21.2. **Parameters:** - `object`: The object owning properties. **Returns:** An array of symbol properties found directly in the passed object. --- ### getPrototypeOf **Signature:** `static getPrototypeOf(object : Object) : Object` **Description:** Returns the prototype of the passed object. **Parameters:** - `object`: The object to get the prototype from. **Returns:** The prototype object or null if there is none. --- ### hasOwnProperty **Signature:** `hasOwnProperty(propName : String) : boolean` **Description:** Returns Boolean true if at the time the current object's instance was created its constructor (or literal assignment) contained a property with a name that matches the parameter value. **Parameters:** - `propName`: the property name of the object's property. **Returns:** true if at the object contains a property that matches the parameter, false otherwise. --- ### is **Signature:** `static is(value1 : Object, value2 : Object) : boolean` **Description:** Checks if the two values are equal in terms of being the same value. No coercion is performed, thus -0 and +0 is not equal and NaN is equal to NaN. **API Versioned:** From version 21.2. **Parameters:** - `value1`: The first value. - `value2`: The second value. **Returns:** true if both values are the same value else false. --- ### isExtensible **Signature:** `static isExtensible(object : Object) : boolean` **Description:** Returns if new properties can be added to an object. By default new objects are extensible. The methods freeze(Object), seal(Object) and preventExtensions(Object) make objects non-extensible. **Parameters:** - `object`: The object to check. **Returns:** true if new properties can be added else false. --- ### isFrozen **Signature:** `static isFrozen(object : Object) : boolean` **Description:** Returns if the object is frozen. **Parameters:** - `object`: The object to check. **Returns:** true if the object is frozen else false. --- ### isPrototypeOf **Signature:** `isPrototypeOf(prototype : Object) : boolean` **Description:** Returns true if the current object and the object passed as a prameter conincide at some point along each object's prototype inheritance chain. **Parameters:** - `prototype`: the object to test. **Returns:** true if the current object and the object passed as a prameter conincide at some point, false otherwise. --- ### isSealed **Signature:** `static isSealed(object : Object) : boolean` **Description:** Returns if the object is sealed. **Parameters:** - `object`: The object to check. **Returns:** true if the object is sealed else false. --- ### keys **Signature:** `static keys(object : Object) : Array` **Description:** Returns the enumerable property names of the passed object. **Parameters:** - `object`: The object to get the enumerable property names from. **Returns:** An array of strings that holds all the enumerable properties of the given object. --- ### preventExtensions **Signature:** `static preventExtensions(object : Object) : Object` **Description:** Makes the passed object non-extensible. This means that no new properties can be added to this object. **Parameters:** - `object`: The object to make non-extensible. **Returns:** The passed object. --- ### propertyIsEnumerable **Signature:** `propertyIsEnumerable(propName : String) : boolean` **Description:** Return true if the specified property exposes itself to for/in property inspection through the object. **Parameters:** - `propName`: the property to test. **Returns:** true if the specified property exposes itself to for/in property inspection through the object, false otherwise. --- ### seal **Signature:** `static seal(object : Object) : Object` **Description:** Seals the passed object. This means properties can't be added or removed. Also, property definitions of existing properties can't be changed. **Parameters:** - `object`: The object to be frozen. **Returns:** The sealed object. --- ### setPrototypeOf **Signature:** `static setPrototypeOf(object : Object, prototype : Object) : Object` **Description:** Changes the prototype of the passed object. **API Versioned:** From version 21.2. **Parameters:** - `object`: The object whose prototype should change. - `prototype`: The object to set as the new prototype. **Returns:** The object with the changed prototype. --- ### toLocaleString **Signature:** `toLocaleString() : String` **Description:** Converts the object to a localized String. **Returns:** a localized version of the object. --- ### toString **Signature:** `toString() : String` **Description:** Converts the object to a String. **Returns:** the String representation of the object. --- ### valueOf **Signature:** `valueOf() : Object` **Description:** Returns the object's value. **Returns:** the object's value. --- ### values **Signature:** `static values(object : Object) : Array` **Description:** Returns the enumerable property values of the passed object. **API Versioned:** From version 22.7. **Parameters:** - `object`: The object to get the enumerable property values from. **Returns:** An array of values that holds all the enumerable properties of the given object. --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/mock-data/ocapi/site-preferences-storefront.json: -------------------------------------------------------------------------------- ```json { "_v": "23.2", "_type": "preference_value_search_result", "count": 8, "hits": [ { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront1-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Enable or disable the shopping cart functionality" }, "display_name": { "default": "Shopping Cart Enabled" }, "effective_id": "c_shoppingCartEnabled", "externally_defined": false, "externally_managed": false, "id": "shoppingCartEnabled", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/shoppingCartEnabled", "localizable": false, "mandatory": false, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": false, "site_specific": true, "system": false, "value_type": "boolean", "visible": true }, "description": { "default": "Enable or disable the shopping cart functionality" }, "display_name": { "default": "Shopping Cart Enabled" }, "id": "shoppingCartEnabled", "site_values": { "RefArch": true, "RefArchGlobal": true, "pxl_1": false, "pxl_2": true, "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "boolean" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront2-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Maximum number of items allowed in shopping cart" }, "display_name": { "default": "Max Cart Items" }, "effective_id": "c_maxCartItems", "externally_defined": false, "externally_managed": false, "id": "maxCartItems", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/maxCartItems", "localizable": false, "mandatory": true, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": false, "site_specific": true, "system": false, "value_type": "int", "visible": true }, "description": { "default": "Maximum number of items allowed in shopping cart" }, "display_name": { "default": "Max Cart Items" }, "id": "maxCartItems", "site_values": { "RefArch": 50, "RefArchGlobal": 100, "pxl_1": 25, "pxl_2": 75, "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "int" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront3-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Welcome message displayed on homepage" }, "display_name": { "default": "Homepage Welcome Message" }, "effective_id": "c_homepageWelcomeMessage", "externally_defined": false, "externally_managed": false, "field_height": 5, "id": "homepageWelcomeMessage", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/homepageWelcomeMessage", "localizable": true, "mandatory": false, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": true, "searchable": true, "set_value_type": false, "site_specific": true, "system": false, "value_type": "text", "visible": true }, "description": { "default": "Welcome message displayed on homepage" }, "display_name": { "default": "Homepage Welcome Message" }, "id": "homepageWelcomeMessage", "site_values": { "RefArch": "Welcome to our amazing store!", "RefArchGlobal": "Discover the best products worldwide!", "pxl_1": "Pixels Store - Your tech destination", "pxl_2": "Premium electronics await you", "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "text" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront4-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Discount percentage for new customers" }, "display_name": { "default": "New Customer Discount" }, "effective_id": "c_newCustomerDiscount", "externally_defined": false, "externally_managed": false, "id": "newCustomerDiscount", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/newCustomerDiscount", "localizable": false, "mandatory": false, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": false, "site_specific": true, "system": false, "value_type": "double", "visible": true }, "description": { "default": "Discount percentage for new customers" }, "display_name": { "default": "New Customer Discount" }, "id": "newCustomerDiscount", "site_values": { "RefArch": 10.5, "RefArchGlobal": 15.0, "pxl_1": 5.0, "pxl_2": 12.5, "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "double" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront5-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Available payment methods for checkout" }, "display_name": { "default": "Payment Methods" }, "effective_id": "c_paymentMethods", "externally_defined": false, "externally_managed": false, "id": "paymentMethods", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/paymentMethods", "localizable": false, "mandatory": false, "multi_value_type": true, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": true, "site_specific": true, "system": false, "value_type": "set_of_string", "visible": true }, "description": { "default": "Available payment methods for checkout" }, "display_name": { "default": "Payment Methods" }, "id": "paymentMethods", "site_values": { "RefArch": "[\"CREDIT_CARD\", \"PAYPAL\", \"APPLE_PAY\"]", "RefArchGlobal": "[\"CREDIT_CARD\", \"PAYPAL\", \"GOOGLE_PAY\", \"BANK_TRANSFER\"]", "pxl_1": "[\"CREDIT_CARD\", \"PAYPAL\"]", "pxl_2": "[\"CREDIT_CARD\", \"APPLE_PAY\", \"GOOGLE_PAY\"]", "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "set_of_string" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront6-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Date when the current promotion ends" }, "display_name": { "default": "Promotion End Date" }, "effective_id": "c_promotionEndDate", "externally_defined": false, "externally_managed": false, "id": "promotionEndDate", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/promotionEndDate", "localizable": false, "mandatory": false, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": false, "site_specific": true, "system": false, "value_type": "date", "visible": true }, "description": { "default": "Date when the current promotion ends" }, "display_name": { "default": "Promotion End Date" }, "id": "promotionEndDate", "site_values": { "RefArch": "2025-12-31", "RefArchGlobal": "2025-11-30", "pxl_1": "2025-10-31", "pxl_2": "2025-12-25", "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "date" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront7-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Custom CSS for site styling" }, "display_name": { "default": "Custom Site CSS" }, "effective_id": "c_customSiteCSS", "externally_defined": false, "externally_managed": false, "field_height": 15, "id": "customSiteCSS", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/customSiteCSS", "localizable": false, "mandatory": false, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": true, "searchable": false, "set_value_type": false, "site_specific": true, "system": false, "value_type": "html", "visible": true }, "description": { "default": "Custom CSS for site styling" }, "display_name": { "default": "Custom Site CSS" }, "id": "customSiteCSS", "site_values": { "RefArch": ".header { background-color: #ff6b35; }", "RefArchGlobal": ".global-header { background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); }", "pxl_1": ".tech-theme { color: #00ff00; background: #000; }", "pxl_2": ".premium-theme { border: 2px solid gold; }", "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "html" }, { "_type": "preference_value", "attribute_definition": { "_type": "object_attribute_definition", "_resource_state": "storefront8-resource-state", "creation_date": "2024-01-01T00:00:00.000Z", "description": { "default": "Password strength requirement level" }, "display_name": { "default": "Password Strength Level" }, "effective_id": "c_passwordStrengthLevel", "externally_defined": false, "externally_managed": false, "id": "passwordStrengthLevel", "key": false, "last_modified": "2024-01-01T00:00:00.000Z", "link": "https://localhost:3000/s/-/dw/data/v23_2/system_object_definitions/SitePreferences/attribute_definitions/passwordStrengthLevel", "localizable": false, "mandatory": true, "multi_value_type": false, "order_required": false, "queryable": false, "read_only": false, "requires_encoding": false, "searchable": false, "set_value_type": false, "site_specific": false, "system": false, "value_type": "enum_of_string", "value_definitions": { "WEAK": { "display_value": {"default": "Weak"}, "value": "WEAK" }, "MEDIUM": { "display_value": {"default": "Medium"}, "value": "MEDIUM" }, "STRONG": { "display_value": {"default": "Strong"}, "value": "STRONG" } }, "visible": true }, "description": { "default": "Password strength requirement level" }, "display_name": { "default": "Password Strength Level" }, "id": "passwordStrengthLevel", "site_values": { "RefArch": "MEDIUM", "RefArchGlobal": "STRONG", "pxl_1": "WEAK", "pxl_2": "STRONG", "pxl_3": null, "pxl_4": null, "pxl_5": null, "pxl_6": null }, "value_type": "enum_of_string" } ], "query": { "match_all_query": { "_type": "match_all_query" } }, "select": "(**)", "start": 0, "total": 8 } ``` -------------------------------------------------------------------------------- /docs/dw_customer/Customer.md: -------------------------------------------------------------------------------- ```markdown ## Package: dw.customer # Class Customer ## Inheritance Hierarchy - Object - dw.customer.Customer ## Description Represents a customer. ## Properties ### activeData **Type:** CustomerActiveData (Read Only) The active data for this customer. ### addressBook **Type:** AddressBook (Read Only) The address book for the profile of this customer, or null if this customer has no profile, such as for an anonymous customer. ### anonymous **Type:** boolean (Read Only) Identifies if the customer is anonymous. An anonymous customer is the opposite of a registered customer. ### authenticated **Type:** boolean (Read Only) Identifies if the customer is authenticated. This method checks whether this customer is the customer associated with the session and than checks whether the session in an authenticated state. Note: The pipeline debugger will always show 'false' for this value regardless of whether the customer is authenticated or not. ### CDPData **Type:** CustomerCDPData (Read Only) The Salesforce CDP (Customer Data Platform) data for this customer. ### customerGroups **Type:** Collection (Static) (Read Only) The customer groups this customer is member of. Result contains static customer groups in storefront and job session Result contains dynamic customer groups in storefront and job session. Dynamic customer groups referring session or request data are not available when processing the customer in a job session, or when this customer is not the customer assigned to the current session. Result contains system groups 'Everyone', 'Unregistered', 'Registered' for all customers in storefront and job sessions ### externallyAuthenticated **Type:** boolean (Read Only) Identifies if the customer is externally authenticated. An externally authenticated customer does not have the password stored in our system but logs in through an external OAuth provider (Google, Facebook, LinkedIn, etc.) ### externalProfiles **Type:** Collection (Read Only) A collection of any external profiles the customer may have ### globalPartyID **Type:** String (Read Only) The Global Party ID for the customer, if there is one. Global Party ID is created by Customer 360 and identifies a person across multiple systems. ### ID **Type:** String (Read Only) The unique, system generated ID of the customer. ### note **Type:** String The note for this customer, or null if this customer has no note, such as for an anonymous customer or when note has 0 length. ### orderHistory **Type:** OrderHistory (Read Only) The customer order history. ### profile **Type:** Profile (Read Only) The customer profile. ### registered **Type:** boolean (Read Only) Identifies if the customer is registered. A registered customer may or may not be authenticated. This method checks whether the user has a profile. ## Constructor Summary ## Method Summary ### createExternalProfile **Signature:** `createExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile` Creates an externalProfile and attaches it to the list of external profiles for the customer ### getActiveData **Signature:** `getActiveData() : CustomerActiveData` Returns the active data for this customer. ### getAddressBook **Signature:** `getAddressBook() : AddressBook` Returns the address book for the profile of this customer, or null if this customer has no profile, such as for an anonymous customer. ### getCDPData **Signature:** `getCDPData() : CustomerCDPData` Returns the Salesforce CDP (Customer Data Platform) data for this customer. ### getCustomerGroups **Signature:** `getCustomerGroups() : Collection` Returns the customer groups this customer is member of. ### getExternalProfile **Signature:** `getExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile` A convenience method for finding an external profile among the customer's external profiles collection ### getExternalProfiles **Signature:** `getExternalProfiles() : Collection` Returns a collection of any external profiles the customer may have ### getGlobalPartyID **Signature:** `getGlobalPartyID() : String` Returns the Global Party ID for the customer, if there is one. ### getID **Signature:** `getID() : String` Returns the unique, system generated ID of the customer. ### getNote **Signature:** `getNote() : String` Returns the note for this customer, or null if this customer has no note, such as for an anonymous customer or when note has 0 length. ### getOrderHistory **Signature:** `getOrderHistory() : OrderHistory` Returns the customer order history. ### getProductLists **Signature:** `getProductLists(type : Number) : Collection` Returns the product lists of the specified type. ### getProfile **Signature:** `getProfile() : Profile` Returns the customer profile. ### isAnonymous **Signature:** `isAnonymous() : boolean` Identifies if the customer is anonymous. ### isAuthenticated **Signature:** `isAuthenticated() : boolean` Identifies if the customer is authenticated. ### isExternallyAuthenticated **Signature:** `isExternallyAuthenticated() : boolean` Identifies if the customer is externally authenticated. ### isMemberOfAnyCustomerGroup **Signature:** `isMemberOfAnyCustomerGroup(groupIDs : String...) : boolean` Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of at least one of that groups. ### isMemberOfCustomerGroup **Signature:** `isMemberOfCustomerGroup(group : CustomerGroup) : boolean` Returns true if the customer is member of the specified CustomerGroup. ### isMemberOfCustomerGroup **Signature:** `isMemberOfCustomerGroup(groupID : String) : boolean` Returns true if there is a CustomerGroup with such an ID and the customer is member of that group. ### isMemberOfCustomerGroups **Signature:** `isMemberOfCustomerGroups(groupIDs : String...) : boolean` Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of all that groups. ### isRegistered **Signature:** `isRegistered() : boolean` Identifies if the customer is registered. ### removeExternalProfile **Signature:** `removeExternalProfile(externalProfile : ExternalProfile) : void` Removes an external profile from the customer ### setNote **Signature:** `setNote(aValue : String) : void` Sets the note for this customer. ## Method Detail ## Method Details ### createExternalProfile **Signature:** `createExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile` **Description:** Creates an externalProfile and attaches it to the list of external profiles for the customer **Parameters:** - `authenticationProviderId`: the authenticationProviderId for the externalProfile - `externalId`: the externalId for the external Profile **Returns:** the new externalProfile --- ### getActiveData **Signature:** `getActiveData() : CustomerActiveData` **Description:** Returns the active data for this customer. **Returns:** the active data for this customer. --- ### getAddressBook **Signature:** `getAddressBook() : AddressBook` **Description:** Returns the address book for the profile of this customer, or null if this customer has no profile, such as for an anonymous customer. --- ### getCDPData **Signature:** `getCDPData() : CustomerCDPData` **Description:** Returns the Salesforce CDP (Customer Data Platform) data for this customer. **Returns:** the Salesforce CDP data for this customer. --- ### getCustomerGroups **Signature:** `getCustomerGroups() : Collection` **Description:** Returns the customer groups this customer is member of. Result contains static customer groups in storefront and job session Result contains dynamic customer groups in storefront and job session. Dynamic customer groups referring session or request data are not available when processing the customer in a job session, or when this customer is not the customer assigned to the current session. Result contains system groups 'Everyone', 'Unregistered', 'Registered' for all customers in storefront and job sessions **Returns:** Collection of customer groups of this customer --- ### getExternalProfile **Signature:** `getExternalProfile(authenticationProviderId : String, externalId : String) : ExternalProfile` **Description:** A convenience method for finding an external profile among the customer's external profiles collection **Parameters:** - `authenticationProviderId`: the authenticationProviderId to look for - `externalId`: the externalId to look for **Returns:** the externalProfile found among the customer's external profile or null if not found --- ### getExternalProfiles **Signature:** `getExternalProfiles() : Collection` **Description:** Returns a collection of any external profiles the customer may have **Returns:** a collection of any external profiles the customer may have --- ### getGlobalPartyID **Signature:** `getGlobalPartyID() : String` **Description:** Returns the Global Party ID for the customer, if there is one. Global Party ID is created by Customer 360 and identifies a person across multiple systems. **Returns:** The global party ID --- ### getID **Signature:** `getID() : String` **Description:** Returns the unique, system generated ID of the customer. **Returns:** the ID of the customer. --- ### getNote **Signature:** `getNote() : String` **Description:** Returns the note for this customer, or null if this customer has no note, such as for an anonymous customer or when note has 0 length. **Returns:** the note for this customer. --- ### getOrderHistory **Signature:** `getOrderHistory() : OrderHistory` **Description:** Returns the customer order history. **Returns:** the customer order history. --- ### getProductLists **Signature:** `getProductLists(type : Number) : Collection` **Description:** Returns the product lists of the specified type. **Parameters:** - `type`: the type of product lists to return. **Returns:** the product lists of the specified type. **See Also:** ProductList --- ### getProfile **Signature:** `getProfile() : Profile` **Description:** Returns the customer profile. **Returns:** the customer profile. --- ### isAnonymous **Signature:** `isAnonymous() : boolean` **Description:** Identifies if the customer is anonymous. An anonymous customer is the opposite of a registered customer. **Returns:** true if the customer is anonymous, false otherwise. Note: this method handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. --- ### isAuthenticated **Signature:** `isAuthenticated() : boolean` **Description:** Identifies if the customer is authenticated. This method checks whether this customer is the customer associated with the session and than checks whether the session in an authenticated state. Note: The pipeline debugger will always show 'false' for this value regardless of whether the customer is authenticated or not. **Returns:** true if the customer is authenticated, false otherwise. --- ### isExternallyAuthenticated **Signature:** `isExternallyAuthenticated() : boolean` **Description:** Identifies if the customer is externally authenticated. An externally authenticated customer does not have the password stored in our system but logs in through an external OAuth provider (Google, Facebook, LinkedIn, etc.) **Returns:** true if the customer is externally authenticated, false otherwise. Note: this method handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. --- ### isMemberOfAnyCustomerGroup **Signature:** `isMemberOfAnyCustomerGroup(groupIDs : String...) : boolean` **Description:** Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of at least one of that groups. **Parameters:** - `groupIDs`: A list of unique semantic customer group IDs. **Returns:** True if customer groups exist for the given IDs and the customer is member of at least one of that existing groups. False if none of customer groups exist or if the customer is not a member of any of that existing groups. --- ### isMemberOfCustomerGroup **Signature:** `isMemberOfCustomerGroup(group : CustomerGroup) : boolean` **Description:** Returns true if the customer is member of the specified CustomerGroup. **Parameters:** - `group`: Customer group **Returns:** True if customer is member of the group, otherwise false. --- ### isMemberOfCustomerGroup **Signature:** `isMemberOfCustomerGroup(groupID : String) : boolean` **Description:** Returns true if there is a CustomerGroup with such an ID and the customer is member of that group. **Parameters:** - `groupID`: The unique semantic customer group ID. **Returns:** True if a customer group with such an ID exist and the customer is member of that group. False if no such customer group exist or, if the group exist, the customer is not member of that group. --- ### isMemberOfCustomerGroups **Signature:** `isMemberOfCustomerGroups(groupIDs : String...) : boolean` **Description:** Returns true if there exist CustomerGroup for all of the given IDs and the customer is member of all that groups. **Parameters:** - `groupIDs`: A list of unique semantic customer group IDs. **Returns:** True if customer groups exist for all of the given IDs and the customer is member of all that groups. False if there is at least one ID for which no customer group exist or, if all groups exist, the customer is not member of all that groups. --- ### isRegistered **Signature:** `isRegistered() : boolean` **Description:** Identifies if the customer is registered. A registered customer may or may not be authenticated. This method checks whether the user has a profile. **Returns:** true if the customer is registered, false otherwise. --- ### removeExternalProfile **Signature:** `removeExternalProfile(externalProfile : ExternalProfile) : void` **Description:** Removes an external profile from the customer **Parameters:** - `externalProfile`: the externalProfile to be removed --- ### setNote **Signature:** `setNote(aValue : String) : void` **Description:** Sets the note for this customer. This is a no-op for an anonymous customer. **Parameters:** - `aValue`: the value of the note --- ``` -------------------------------------------------------------------------------- /tests/cache.test.ts: -------------------------------------------------------------------------------- ```typescript import { InMemoryCache, CacheManager } from '../src/utils/cache'; describe('InMemoryCache', () => { let cache: InMemoryCache<string>; beforeEach(() => { cache = new InMemoryCache<string>({ maxSize: 3, ttlMs: 1000, // 1 second for testing cleanupIntervalMs: 100, // 100ms for faster testing }); }); afterEach(() => { cache.destroy(); }); describe('constructor', () => { it('should use default options when none provided', () => { const defaultCache = new InMemoryCache(); const stats = defaultCache.getStats(); expect(stats.maxSize).toBe(1000); defaultCache.destroy(); }); it('should accept custom options', () => { const customCache = new InMemoryCache({ maxSize: 50, ttlMs: 5000, cleanupIntervalMs: 1000, }); const stats = customCache.getStats(); expect(stats.maxSize).toBe(50); customCache.destroy(); }); }); describe('set and get', () => { it('should store and retrieve values', () => { cache.set('key1', 'value1'); expect(cache.get('key1')).toBe('value1'); }); it('should return undefined for non-existent keys', () => { expect(cache.get('nonexistent')).toBeUndefined(); }); it('should update access statistics on get', () => { cache.set('key1', 'value1'); cache.get('key1'); // First access cache.get('key1'); // Second access const stats = cache.getStats(); const entry = stats.entries.find(e => e.key === 'key1'); expect(entry?.accessCount).toBe(2); }); it('should update lastAccessed timestamp on get', async () => { cache.set('key1', 'value1'); const initialGet = cache.get('key1'); // Wait a bit and access again await new Promise(resolve => setTimeout(resolve, 10)); const secondGet = cache.get('key1'); expect(initialGet).toBe('value1'); expect(secondGet).toBe('value1'); const stats = cache.getStats(); const entry = stats.entries.find(e => e.key === 'key1'); expect(entry?.accessCount).toBe(2); }); }); describe('has', () => { it('should return true for existing keys', () => { cache.set('key1', 'value1'); expect(cache.has('key1')).toBe(true); }); it('should return false for non-existent keys', () => { expect(cache.has('nonexistent')).toBe(false); }); it('should not update access statistics', () => { cache.set('key1', 'value1'); cache.has('key1'); const stats = cache.getStats(); const entry = stats.entries.find(e => e.key === 'key1'); expect(entry?.accessCount).toBe(0); }); }); describe('delete', () => { it('should remove existing keys', () => { cache.set('key1', 'value1'); expect(cache.has('key1')).toBe(true); const deleted = cache.delete('key1'); expect(deleted).toBe(true); expect(cache.has('key1')).toBe(false); }); it('should return false for non-existent keys', () => { const deleted = cache.delete('nonexistent'); expect(deleted).toBe(false); }); }); describe('clear', () => { it('should remove all entries', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.getStats().size).toBe(2); cache.clear(); expect(cache.getStats().size).toBe(0); }); }); describe('TTL (Time To Live)', () => { it('should expire entries after TTL', async () => { const shortTtlCache = new InMemoryCache<string>({ ttlMs: 50, // 50ms cleanupIntervalMs: 1000, // Don't auto-cleanup for this test }); shortTtlCache.set('key1', 'value1'); expect(shortTtlCache.get('key1')).toBe('value1'); // Wait for expiration await new Promise(resolve => setTimeout(resolve, 60)); expect(shortTtlCache.get('key1')).toBeUndefined(); shortTtlCache.destroy(); }); it('should remove expired entries when checking has()', async () => { const shortTtlCache = new InMemoryCache<string>({ ttlMs: 50, cleanupIntervalMs: 1000, }); shortTtlCache.set('key1', 'value1'); expect(shortTtlCache.has('key1')).toBe(true); await new Promise(resolve => setTimeout(resolve, 60)); expect(shortTtlCache.has('key1')).toBe(false); shortTtlCache.destroy(); }); }); describe('LRU (Least Recently Used) eviction', () => { it('should evict LRU item when max size is reached', async () => { // Fill cache to max capacity cache.set('key1', 'value1'); await new Promise(resolve => setTimeout(resolve, 1)); // Small delay cache.set('key2', 'value2'); await new Promise(resolve => setTimeout(resolve, 1)); // Small delay cache.set('key3', 'value3'); expect(cache.getStats().size).toBe(3); // Wait a bit then access key1 and key2 to make key3 the least recently used await new Promise(resolve => setTimeout(resolve, 5)); cache.get('key1'); cache.get('key2'); // Add new item, should evict key3 cache.set('key4', 'value4'); expect(cache.getStats().size).toBe(3); expect(cache.has('key3')).toBe(false); expect(cache.has('key1')).toBe(true); expect(cache.has('key2')).toBe(true); expect(cache.has('key4')).toBe(true); }); it('should not evict when updating existing key', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.set('key3', 'value3'); // Update existing key cache.set('key1', 'newvalue1'); expect(cache.getStats().size).toBe(3); expect(cache.get('key1')).toBe('newvalue1'); }); }); describe('getStats', () => { it('should return correct cache statistics', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.get('key1'); // Access key1 const stats = cache.getStats(); expect(stats.size).toBe(2); expect(stats.maxSize).toBe(3); expect(stats.entries).toHaveLength(2); const key1Entry = stats.entries.find(e => e.key === 'key1'); const key2Entry = stats.entries.find(e => e.key === 'key2'); expect(key1Entry?.accessCount).toBe(1); expect(key2Entry?.accessCount).toBe(0); }); it('should calculate hit rate correctly', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.get('key1'); // Hit cache.get('key1'); // Hit // key2 never accessed const stats = cache.getStats(); // Total accesses: 2, hits: 1 (key1 was accessed, key2 wasn't) expect(stats.hitRate).toBe(0.5); }); }); describe('cleanup', () => { it('should automatically cleanup expired entries', async () => { const autoCleanupCache = new InMemoryCache<string>({ ttlMs: 50, cleanupIntervalMs: 60, // Very frequent cleanup }); autoCleanupCache.set('key1', 'value1'); expect(autoCleanupCache.getStats().size).toBe(1); // Wait for TTL + cleanup interval await new Promise(resolve => setTimeout(resolve, 120)); const stats = autoCleanupCache.getStats(); expect(stats.size).toBe(0); autoCleanupCache.destroy(); }); }); describe('destroy', () => { it('should clear cache and stop cleanup timer', () => { cache.set('key1', 'value1'); expect(cache.getStats().size).toBe(1); cache.destroy(); expect(cache.getStats().size).toBe(0); // Cache should still be usable but without automatic cleanup cache.set('key2', 'value2'); expect(cache.get('key2')).toBe('value2'); }); }); }); describe('CacheManager', () => { let cacheManager: CacheManager; beforeEach(() => { cacheManager = new CacheManager(); }); afterEach(() => { cacheManager.destroy(); }); describe('file content cache', () => { it('should store and retrieve file content', () => { const content = 'file content'; cacheManager.setFileContent('file1.ts', content); expect(cacheManager.getFileContent('file1.ts')).toBe(content); expect(cacheManager.getFileContent('nonexistent.ts')).toBeUndefined(); }); }); describe('class details cache', () => { it('should store and retrieve class details', () => { const details = { name: 'TestClass', methods: ['method1', 'method2'] }; cacheManager.setClassDetails('TestClass', details); expect(cacheManager.getClassDetails('TestClass')).toEqual(details); expect(cacheManager.getClassDetails('NonExistentClass')).toBeUndefined(); }); }); describe('search results cache', () => { it('should store and retrieve search results', () => { const results = [{ name: 'result1' }, { name: 'result2' }]; cacheManager.setSearchResults('query1', results); expect(cacheManager.getSearchResults('query1')).toEqual(results); expect(cacheManager.getSearchResults('query2')).toBeUndefined(); }); }); describe('method search cache', () => { it('should store and retrieve method search results', () => { const results = [{ method: 'getValue', class: 'TestClass' }]; cacheManager.setMethodSearch('getValue', results); expect(cacheManager.getMethodSearch('getValue')).toEqual(results); expect(cacheManager.getMethodSearch('nonexistent')).toBeUndefined(); }); }); describe('getAllStats', () => { it('should return statistics for all caches', () => { cacheManager.setFileContent('file1.ts', 'content'); cacheManager.setClassDetails('Class1', { name: 'Class1' }); cacheManager.setSearchResults('query1', []); cacheManager.setMethodSearch('method1', []); const allStats = cacheManager.getAllStats(); expect(allStats.fileContent.size).toBe(1); expect(allStats.classDetails.size).toBe(1); expect(allStats.searchResults.size).toBe(1); expect(allStats.methodSearch.size).toBe(1); expect(allStats.fileContent.maxSize).toBe(500); expect(allStats.classDetails.maxSize).toBe(300); expect(allStats.searchResults.maxSize).toBe(200); expect(allStats.methodSearch.maxSize).toBe(100); }); }); describe('clearAll', () => { it('should clear all caches', () => { cacheManager.setFileContent('file1.ts', 'content'); cacheManager.setClassDetails('Class1', { name: 'Class1' }); cacheManager.setSearchResults('query1', []); cacheManager.setMethodSearch('method1', []); let allStats = cacheManager.getAllStats(); expect(allStats.fileContent.size).toBe(1); expect(allStats.classDetails.size).toBe(1); expect(allStats.searchResults.size).toBe(1); expect(allStats.methodSearch.size).toBe(1); cacheManager.clearAll(); allStats = cacheManager.getAllStats(); expect(allStats.fileContent.size).toBe(0); expect(allStats.classDetails.size).toBe(0); expect(allStats.searchResults.size).toBe(0); expect(allStats.methodSearch.size).toBe(0); }); }); describe('destroy', () => { it('should destroy all underlying caches', () => { cacheManager.setFileContent('file1.ts', 'content'); let allStats = cacheManager.getAllStats(); expect(allStats.fileContent.size).toBe(1); cacheManager.destroy(); // After destroy, the caches should be cleared allStats = cacheManager.getAllStats(); expect(allStats.fileContent.size).toBe(0); }); }); describe('different TTL configurations', () => { it('should have different TTL settings for different cache types', () => { // We can't directly test TTL values, but we can verify the caches work independently cacheManager.setFileContent('file1.ts', 'content'); cacheManager.setClassDetails('Class1', { name: 'Class1' }); cacheManager.setSearchResults('query1', ['result1']); cacheManager.setMethodSearch('method1', ['method1']); // All should be accessible expect(cacheManager.getFileContent('file1.ts')).toBe('content'); expect(cacheManager.getClassDetails('Class1')).toEqual({ name: 'Class1' }); expect(cacheManager.getSearchResults('query1')).toEqual(['result1']); expect(cacheManager.getMethodSearch('method1')).toEqual(['method1']); }); }); }); describe('Edge cases and error handling', () => { describe('InMemoryCache edge cases', () => { it('should handle zero max size gracefully', () => { const zeroSizeCache = new InMemoryCache<string>({ maxSize: 0, ttlMs: 1000, }); zeroSizeCache.set('key1', 'value1'); // With maxSize 0, nothing should be stored expect(zeroSizeCache.get('key1')).toBeUndefined(); expect(zeroSizeCache.getStats().size).toBe(0); zeroSizeCache.destroy(); }); it('should handle very short TTL', async () => { const shortTtlCache = new InMemoryCache<string>({ ttlMs: 1, // 1ms cleanupIntervalMs: 1000, }); shortTtlCache.set('key1', 'value1'); // Wait longer than TTL await new Promise(resolve => setTimeout(resolve, 5)); expect(shortTtlCache.get('key1')).toBeUndefined(); shortTtlCache.destroy(); }); it('should handle multiple destroy calls', () => { const cache = new InMemoryCache<string>(); cache.set('key1', 'value1'); cache.destroy(); cache.destroy(); // Second destroy should not throw expect(cache.getStats().size).toBe(0); }); }); describe('Type safety', () => { it('should maintain type safety for different value types', () => { const stringCache = new InMemoryCache<string>(); const numberCache = new InMemoryCache<number>(); const objectCache = new InMemoryCache<{ id: number; name: string }>(); stringCache.set('key1', 'string value'); numberCache.set('key1', 42); objectCache.set('key1', { id: 1, name: 'test' }); const stringValue: string | undefined = stringCache.get('key1'); const numberValue: number | undefined = numberCache.get('key1'); const objectValue: { id: number; name: string } | undefined = objectCache.get('key1'); expect(typeof stringValue).toBe('string'); expect(typeof numberValue).toBe('number'); expect(typeof objectValue).toBe('object'); stringCache.destroy(); numberCache.destroy(); objectCache.destroy(); }); }); }); ``` -------------------------------------------------------------------------------- /tests/mcp/node/search-sfcc-classes.docs-only.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript /** * Programmatic tests for search_sfcc_classes tool * * These tests provide advanced verification capabilities beyond YAML pattern matching, * including dynamic validation, error categorization and * comprehensive response structure analysis. * * Response format discovered via aegis query: * - Success: { content: [{ type: "text", text: "["class1", "class2", ...]" }] } * - Empty: { content: [{ type: "text", text: "[]" }] } * - Error: { content: [{ type: "text", text: "Error: ..." }], isError: true } */ import { test, describe, before, after, beforeEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { connect } from 'mcp-aegis'; describe('search_sfcc_classes Programmatic Tests', () => { let client; before(async () => { client = await connect('./aegis.config.docs-only.json'); }); after(async () => { if (client?.connected) { await client.disconnect(); } }); beforeEach(() => { // CRITICAL: Clear all buffers to prevent test interference client.clearAllBuffers(); // Recommended - comprehensive protection }); describe('Protocol Compliance', () => { test('should be properly connected to MCP server', async () => { assert.ok(client.connected, 'Client should be connected'); }); test('should have search_sfcc_classes tool available', async () => { const tools = await client.listTools(); const searchTool = tools.find(tool => tool.name === 'search_sfcc_classes'); assert.ok(searchTool, 'search_sfcc_classes tool should be available'); assert.equal(searchTool.name, 'search_sfcc_classes'); assert.ok(searchTool.description, 'Tool should have description'); assert.ok(searchTool.inputSchema, 'Tool should have input schema'); assert.equal(searchTool.inputSchema.type, 'object'); assert.ok(searchTool.inputSchema.properties.query, 'Tool should require query parameter'); }); }); describe('Response Structure Validation', () => { test('should return properly structured MCP response for valid query', async () => { const result = await client.callTool('search_sfcc_classes', { query: 'catalog' }); // Validate MCP response structure assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error response'); assert.equal(result.content.length, 1, 'Should have exactly one content item'); assert.equal(result.content[0].type, 'text', 'Content should be text type'); // Validate JSON array structure const classArray = parseClassArray(result.content[0].text); assert.ok(Array.isArray(classArray), 'Response should contain valid JSON array'); assert.ok(classArray.length > 0, 'Should return at least one class for catalog query'); // Validate class name format classArray.forEach(className => { assert.equal(typeof className, 'string', 'Each class name should be a string'); // SFCC includes multiple namespaces assert.ok( className.startsWith('dw.') || className.startsWith('TopLevel.') || className.startsWith('best-practices.') || className.startsWith('sfra.'), `Class name "${className}" should start with recognized namespace` ); assert.ok(className.includes('catalog'), 'Results should be relevant to query'); }); }); test('should return empty array for no matches', async () => { const result = await client.callTool('search_sfcc_classes', { query: 'zzznothingfound' }); assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error response'); const classArray = parseClassArray(result.content[0].text); assert.ok(Array.isArray(classArray), 'Response should be valid JSON array'); assert.equal(classArray.length, 0, 'Should return empty array for no matches'); }); test('should return error response for invalid parameters', async () => { const result = await client.callTool('search_sfcc_classes', { query: '' }); assertValidMCPResponse(result); assert.equal(result.isError, true, 'Should be an error response'); assert.ok(result.content[0].text.includes('Error:'), 'Should contain error message'); assert.ok(result.content[0].text.includes('non-empty string'), 'Should specify validation requirement'); }); }); describe('Dynamic Search Testing', () => { const testQueries = [ { query: 'catalog', expectedMin: 10, category: 'namespace' }, { query: 'product', expectedMin: 5, category: 'common' }, { query: 'customer', expectedMin: 3, category: 'namespace' }, { query: 'order', expectedMin: 5, category: 'namespace' }, { query: 'system', expectedMin: 3, category: 'namespace' }, { query: 'Campaign', expectedMin: 1, category: 'specific' }, { query: 'Manager', expectedMin: 0, category: 'pattern' } // "Manager" returns no results - SFCC uses "Mgr" ]; testQueries.forEach(({ query, expectedMin, category }) => { test(`should find relevant classes for ${category} query: "${query}"`, async () => { const result = await client.callTool('search_sfcc_classes', { query }); assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error'); const classArray = parseClassArray(result.content[0].text); assert.ok(classArray.length >= expectedMin, `Should find at least ${expectedMin} classes for "${query}", found ${classArray.length}`); // Validate relevance - all results should contain the query term (case insensitive) classArray.forEach(className => { const lowerClassName = className.toLowerCase(); const lowerQuery = query.toLowerCase(); assert.ok(lowerClassName.includes(lowerQuery), `Class "${className}" should contain query term "${query}"`); }); }); }); }); describe('Edge Case Validation', () => { const edgeCases = [ { query: 'A', description: 'single character' }, { query: 'dw', description: 'namespace prefix' }, { query: 'CATALOG', description: 'uppercase query' }, { query: 'catalog.Product', description: 'full class path segment' }, { query: '123', description: 'numeric query' }, { query: 'xyz_nonexistent_abc', description: 'clearly non-existent term' } ]; edgeCases.forEach(({ query, description }) => { test(`should handle ${description} query: "${query}"`, async () => { const result = await client.callTool('search_sfcc_classes', { query }); assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not be an error for valid string'); const classArray = parseClassArray(result.content[0].text); assert.ok(Array.isArray(classArray), 'Should return valid array'); // All results should be valid class names classArray.forEach(className => { assert.equal(typeof className, 'string', 'Should be string'); // SFCC includes dw.*, TopLevel.*, best-practices.*, and sfra.* classes assert.ok( className.startsWith('dw.') || className.startsWith('TopLevel.') || className.startsWith('best-practices.') || className.startsWith('sfra.'), `Class name "${className}" should start with dw., TopLevel., best-practices., or sfra.` ); }); }); }); }); describe('Error Handling and Validation', () => { const errorCases = [ { params: {}, expectedError: 'non-empty string', description: 'missing query parameter' }, { params: { query: '' }, expectedError: 'non-empty string', description: 'empty string query' }, { params: { query: null }, expectedError: 'string', description: 'null query' }, { params: { query: 123 }, expectedError: 'string', description: 'numeric query parameter' }, { params: { query: [] }, expectedError: 'string', description: 'array query parameter' }, { params: { query: {} }, expectedError: 'string', description: 'object query parameter' } ]; errorCases.forEach(({ params, expectedError, description }) => { test(`should validate ${description}`, async () => { const result = await client.callTool('search_sfcc_classes', params); assertValidMCPResponse(result); assert.equal(result.isError, true, 'Should be an error response'); const errorMessage = result.content[0].text.toLowerCase(); assert.ok(errorMessage.includes('error'), 'Should contain error indicator'); assert.ok(errorMessage.includes(expectedError.toLowerCase()), `Error message should mention "${expectedError}"`); // Error categorization const errorType = categorizeError(result.content[0].text); assert.equal(errorType, 'validation', 'Should be categorized as validation error'); }); }); }); describe('Cross-Validation with YAML Tests', () => { test('should match YAML test expectations for common queries', async () => { // Test cases that mirror the YAML test file const yamlTestCases = [ { query: 'catalog', shouldHave: ['dw.catalog.Catalog', 'dw.catalog.Product'] }, { query: 'customer', shouldHave: ['dw.customer.Customer'] }, { query: 'order', shouldHave: ['dw.order.Order'] }, { query: 'system', shouldHave: ['dw.system.System'] } ]; for (const testCase of yamlTestCases) { const result = await client.callTool('search_sfcc_classes', { query: testCase.query }); assertValidMCPResponse(result); assert.equal(result.isError, false); const classArray = parseClassArray(result.content[0].text); // Verify expected classes are present testCase.shouldHave.forEach(expectedClass => { assert.ok(classArray.includes(expectedClass), `Results for "${testCase.query}" should include "${expectedClass}"`); }); } }); test('should handle edge cases consistently with YAML tests', async () => { // Mirror YAML edge case tests const result = await client.callTool('search_sfcc_classes', { query: 'zzznothingfound' }); assertValidMCPResponse(result); assert.equal(result.isError, false, 'Should not error for non-matching query'); const classArray = parseClassArray(result.content[0].text); assert.equal(classArray.length, 0, 'Should return empty array for no matches'); }); }); describe('Response Data Quality', () => { test('should return unique class names without duplicates', async () => { const result = await client.callTool('search_sfcc_classes', { query: 'catalog' }); assertValidMCPResponse(result); const classArray = parseClassArray(result.content[0].text); const uniqueClasses = new Set(classArray); assert.equal(classArray.length, uniqueClasses.size, 'Should not contain duplicate class names'); }); test('should return results in consistent order', async () => { // Call the same query multiple times sequentially to avoid message interference const results = []; for (let i = 0; i < 3; i++) { const result = await client.callTool('search_sfcc_classes', { query: 'catalog' }); results.push(result); } const arrays = results.map(result => parseClassArray(result.content[0].text)); // All arrays should be identical assert.deepEqual(arrays[0], arrays[1], 'Results should be consistent across calls'); assert.deepEqual(arrays[1], arrays[2], 'Results should be consistent across calls'); }); test('should validate class name patterns and format', async () => { const result = await client.callTool('search_sfcc_classes', { query: 'catalog' }); assertValidMCPResponse(result); const classArray = parseClassArray(result.content[0].text); classArray.forEach(className => { // Validate class name format - updated for all SFCC namespaces assert.match(className, /^(dw\.|TopLevel\.|best-practices\.|sfra\.)[a-zA-Z0-9_./-]+$/, `Class name "${className}" should follow valid pattern`); // Should not contain spaces or invalid characters assert.ok(!className.includes(' '), `Class name "${className}" should not contain spaces`); // Should have reasonable length assert.ok(className.length > 3, `Class name "${className}" should be reasonable length`); assert.ok(className.length < 100, `Class name "${className}" should not be excessively long`); }); }); }); }); // Helper functions /** * Validates that a response follows proper MCP structure */ function assertValidMCPResponse(result) { assert.ok(result.content, 'Response should have content property'); assert.ok(Array.isArray(result.content), 'Content should be an array'); assert.ok(result.content.length > 0, 'Content array should not be empty'); assert.equal(result.content[0].type, 'text', 'First content item should be text type'); assert.equal(typeof result.content[0].text, 'string', 'Text content should be a string'); // isError property should always be present and boolean assert.ok(Object.prototype.hasOwnProperty.call(result, 'isError'), 'isError property should always be present'); assert.equal(typeof result.isError, 'boolean', 'isError should be a boolean'); } /** * Parses the class array from the response text */ function parseClassArray(text) { try { return JSON.parse(text); } catch { throw new Error(`Failed to parse class array from response: ${text}`); } } /** * Categorizes error messages by type */ function categorizeError(errorText) { const errorPatterns = [ { type: 'validation', keywords: ['required', 'invalid', 'missing', 'non-empty', 'string'] }, { type: 'not_found', keywords: ['not found', 'does not exist'] }, { type: 'permission', keywords: ['permission', 'unauthorized', 'forbidden'] }, { type: 'network', keywords: ['connection', 'timeout', 'unreachable'] } ]; const lowerText = errorText.toLowerCase(); for (const pattern of errorPatterns) { if (pattern.keywords.some(keyword => lowerText.includes(keyword))) { return pattern.type; } } return 'unknown'; } ```