This is page 34 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /tests/mcp/node/get-system-object-definition.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * ================================================================================== 3 | * SFCC MCP Server - get_system_object_definition Tool Programmatic Tests (Full Mode) 4 | * Streamlined programmatic testing focused on tool functionality and critical workflows 5 | * 6 | * These tests require SFCC credentials and use the mock server for realistic testing 7 | * Focus on complex business logic, multi-step workflows, and advanced error handling 8 | * that cannot be effectively tested in YAML format 9 | * 10 | * Quick Test Commands: 11 | * node --test tests/mcp/node/get-system-object-definition.full-mode.programmatic.test.js 12 | * npm test -- --grep "get_system_object_definition" 13 | * ================================================================================== 14 | */ 15 | 16 | import { test, describe, before, after, beforeEach } from 'node:test'; 17 | import { strict as assert } from 'node:assert'; 18 | import { connect } from 'mcp-aegis'; 19 | 20 | describe('get_system_object_definition Tool - Full Mode Programmatic Tests', () => { 21 | let client; 22 | 23 | // Streamlined test data - focus on representative objects 24 | const coreObjectTypes = ['Product', 'Customer', 'Order']; 25 | const expectedSystemObjectFlags = { 26 | 'Product': { content_object: true, queryable: false, read_only: false }, 27 | 'Customer': { content_object: false, queryable: true, read_only: false }, 28 | 'Order': { content_object: false, queryable: true, read_only: true } 29 | }; 30 | 31 | before(async () => { 32 | client = await connect('./aegis.config.with-dw.json'); 33 | }); 34 | 35 | after(async () => { 36 | if (client?.connected) { 37 | await client.disconnect(); 38 | } 39 | }); 40 | 41 | beforeEach(() => { 42 | // CRITICAL: Clear all buffers to prevent test interference 43 | client.clearAllBuffers(); 44 | }); 45 | 46 | // ================================================================================== 47 | // CONSOLIDATED TOOL DISCOVERY AND FUNCTIONALITY 48 | // ================================================================================== 49 | 50 | describe('Tool Discovery and Core Functionality', () => { 51 | test('should discover tool with comprehensive schema and execute successfully', async () => { 52 | // Tool discovery with schema validation 53 | const tools = await client.listTools(); 54 | const systemObjectTool = tools.find(tool => tool.name === 'get_system_object_definition'); 55 | 56 | assert.ok(systemObjectTool, 'get_system_object_definition tool should be available in full mode'); 57 | assert.ok(systemObjectTool.description.includes('system object'), 'Description should mention system objects'); 58 | 59 | const schema = systemObjectTool.inputSchema; 60 | assert.equal(schema.type, 'object', 'Schema should be object type'); 61 | assert.ok(schema.properties?.objectType, 'Schema should have objectType property'); 62 | assert.equal(schema.properties.objectType.type, 'string', 'objectType should be string type'); 63 | assert.ok(schema.required.includes('objectType'), 'objectType should be required'); 64 | 65 | // Functional validation - execute tool successfully 66 | const result = await client.callTool('get_system_object_definition', { objectType: 'Product' }); 67 | assert.equal(result.isError, false, 'Should execute successfully with valid parameters'); 68 | assert.ok(result.content?.[0]?.text, 'Should return content'); 69 | 70 | // Validate response structure 71 | const objectData = JSON.parse(result.content[0].text); 72 | assertValidObjectDefinition(objectData, 'Product'); 73 | }); 74 | 75 | test('should validate tool context and availability with other system tools', async () => { 76 | const tools = await client.listTools(); 77 | const toolNames = tools.map(t => t.name); 78 | 79 | // Should have related system object tools 80 | assert.ok(toolNames.includes('get_system_object_definition'), 'Should have single object tool'); 81 | assert.ok(toolNames.includes('get_system_object_definitions'), 'Should have multi-object tool'); 82 | assert.ok(toolNames.includes('search_system_object_attribute_definitions'), 'Should have attribute search tool'); 83 | 84 | // Should have substantial tool count in full mode 85 | assert.ok(tools.length >= 30, `Should have many tools in full mode, got ${tools.length}`); 86 | }); 87 | }); 88 | 89 | // ================================================================================== 90 | // STREAMLINED BUSINESS LOGIC VALIDATION 91 | // ================================================================================== 92 | 93 | describe('Business Logic and System Object Validation', () => { 94 | test('should validate core SFCC object characteristics and flag logic', async () => { 95 | const results = new Map(); 96 | 97 | // Test core object types sequentially (streamlined from 5 to 3 objects) 98 | for (const objectType of coreObjectTypes) { 99 | const result = await client.callTool('get_system_object_definition', { objectType }); 100 | 101 | assert.equal(result.isError, false, `${objectType} should be retrieved successfully`); 102 | const objectData = JSON.parse(result.content[0].text); 103 | results.set(objectType, objectData); 104 | 105 | // Use helper function for comprehensive validation 106 | assertValidObjectDefinition(objectData, objectType); 107 | 108 | // Validate expected flags (resilient to mock data changes) 109 | const expectedFlags = expectedSystemObjectFlags[objectType]; 110 | if (expectedFlags) { 111 | assert.equal(objectData.content_object, expectedFlags.content_object, 112 | `${objectType} content_object should match expected value`); 113 | assert.equal(objectData.queryable, expectedFlags.queryable, 114 | `${objectType} queryable should match expected value`); 115 | assert.equal(objectData.read_only, expectedFlags.read_only, 116 | `${objectType} read_only should match expected value`); 117 | } 118 | } 119 | 120 | // Cross-object business rule validation 121 | const productData = results.get('Product'); 122 | const customerData = results.get('Customer'); 123 | const orderData = results.get('Order'); 124 | 125 | // Validate business rules (resilient assertions) 126 | assert.ok(productData.attribute_definition_count > 0, 'Product should have attributes'); 127 | assert.ok(customerData.attribute_definition_count > 0, 'Customer should have attributes'); 128 | assert.equal(orderData.read_only, true, 'Order should be read-only'); 129 | assert.equal(customerData.queryable, true, 'Customer should be queryable'); 130 | }); 131 | 132 | test('should validate localization structure and metadata consistency', async () => { 133 | const result = await client.callTool('get_system_object_definition', { objectType: 'Product' }); 134 | const objectData = JSON.parse(result.content[0].text); 135 | 136 | // Validate localization structure (flexible assertions) 137 | validateLocalizationStructure(objectData.display_name, 'display_name'); 138 | assert.ok(objectData.description?.default, 'Should have default description'); 139 | 140 | // Validate timestamps and metadata 141 | assert.ok(objectData.creation_date, 'Should have creation date'); 142 | assert.ok(objectData.last_modified, 'Should have last modified date'); 143 | assert.ok(!isNaN(Date.parse(objectData.creation_date)), 'Creation date should be valid'); 144 | assert.ok(!isNaN(Date.parse(objectData.last_modified)), 'Last modified should be valid'); 145 | }); 146 | }); 147 | 148 | // ================================================================================== 149 | // ESSENTIAL WORKFLOW VALIDATION 150 | // ================================================================================== 151 | 152 | describe('Multi-Tool Integration Workflows', () => { 153 | test('should support object discovery and analysis workflow', async () => { 154 | // Step 1: Get all system object definitions 155 | const allObjectsResult = await client.callTool('get_system_object_definitions', {}); 156 | assert.equal(allObjectsResult.isError, false, 'Should retrieve all object definitions'); 157 | 158 | const allObjects = JSON.parse(allObjectsResult.content[0].text); 159 | assert.ok(Array.isArray(allObjects.data), 'Should return array of objects'); 160 | assert.ok(allObjects.data.length > 0, 'Should have multiple object types'); 161 | 162 | // Step 2: Analyze representative objects in detail 163 | const analysisResults = []; 164 | const testObjects = ['Product', 'Customer']; // Streamlined from 3 to 2 objects 165 | 166 | for (const objectType of testObjects) { 167 | const detailResult = await client.callTool('get_system_object_definition', { objectType }); 168 | assert.equal(detailResult.isError, false, `Should get details for ${objectType}`); 169 | 170 | const detailData = JSON.parse(detailResult.content[0].text); 171 | analysisResults.push({ 172 | objectType, 173 | attributes: detailData.attribute_definition_count, 174 | groups: detailData.attribute_group_count, 175 | flags: { 176 | content_object: detailData.content_object, 177 | queryable: detailData.queryable, 178 | read_only: detailData.read_only 179 | } 180 | }); 181 | } 182 | 183 | // Step 3: Validate workflow results 184 | assert.equal(analysisResults.length, 2, 'Should analyze expected objects'); 185 | 186 | // At least one should be queryable 187 | const queryableCount = analysisResults.filter(r => r.flags.queryable).length; 188 | assert.ok(queryableCount > 0, 'Should have at least one queryable object (Customer)'); 189 | }); 190 | 191 | test('should support attribute discovery and cross-tool integration', async () => { 192 | // Step 1: Get object definition for context 193 | const productResult = await client.callTool('get_system_object_definition', { objectType: 'Product' }); 194 | assert.equal(productResult.isError, false, 'Should get Product definition successfully'); 195 | 196 | // Step 2: Search for Product attributes using integration tool 197 | const attributeSearchResult = await client.callTool('search_system_object_attribute_definitions', { 198 | objectType: 'Product', 199 | searchRequest: { 200 | query: { match_all_query: {} }, 201 | count: 10 // Reduced from 20 to 10 for efficiency 202 | } 203 | }); 204 | 205 | assert.equal(attributeSearchResult.isError, false, 'Should search attributes successfully'); 206 | const attributeData = JSON.parse(attributeSearchResult.content[0].text); 207 | 208 | // Step 3: Validate cross-tool consistency 209 | assert.ok(attributeData.hits !== undefined, 'Should have search results structure'); 210 | assert.ok(attributeData.total >= 0, 'Should have reasonable total count'); 211 | 212 | // Validate attribute structure if results exist 213 | if (attributeData.hits.length > 0) { 214 | const sampleAttribute = attributeData.hits[0]; 215 | assert.ok(sampleAttribute.id, 'Attribute should have ID'); 216 | assert.ok(sampleAttribute._type === 'object_attribute_definition', 'Should have correct type'); 217 | } 218 | }); 219 | }); 220 | 221 | // ================================================================================== 222 | // COMPREHENSIVE ERROR HANDLING 223 | // ================================================================================== 224 | 225 | describe('Advanced Error Handling and Edge Cases', () => { 226 | test('should handle comprehensive parameter validation scenarios', async () => { 227 | const invalidCases = [ 228 | { args: {}, description: 'missing objectType', shouldError: true }, 229 | { args: { objectType: null }, description: 'null objectType', shouldError: true }, 230 | { args: { objectType: 123 }, description: 'numeric objectType', shouldError: true }, 231 | { args: { objectType: '' }, description: 'empty objectType', shouldError: true }, 232 | { args: { objectType: 'NonExistentObject' }, description: 'unknown object', shouldError: false }, // Fallback response 233 | { args: { objectType: 'Product123' }, description: 'object with numbers', shouldError: false }, // Fallback response 234 | ]; 235 | 236 | for (const testCase of invalidCases) { 237 | const result = await client.callTool('get_system_object_definition', testCase.args); 238 | 239 | if (testCase.shouldError) { 240 | assert.equal(result.isError, true, 241 | `Should return error for ${testCase.description}`); 242 | assert.ok(result.content[0].text.includes('Error'), 243 | `Error message should contain 'Error' for ${testCase.description}`); 244 | } else { 245 | // Fallback responses are acceptable for unknown object types 246 | if (result.isError) { 247 | assert.ok(result.content[0].text.includes('Error'), 248 | `Should have error message for ${testCase.description}`); 249 | } else { 250 | const data = JSON.parse(result.content[0].text); 251 | assert.ok(data.object_type, 'Should have fallback object type'); 252 | } 253 | } 254 | } 255 | }); 256 | 257 | test('should maintain performance and reliability under moderate load', async () => { 258 | const testObjects = ['Product', 'Customer', 'Order']; 259 | const results = []; 260 | const startTime = Date.now(); 261 | 262 | // Streamlined stress test (reduced from 10 to 5 iterations) 263 | for (let i = 0; i < 5; i++) { 264 | const objectType = testObjects[i % testObjects.length]; 265 | const iterationStart = Date.now(); 266 | 267 | const result = await client.callTool('get_system_object_definition', { objectType }); 268 | const iterationTime = Date.now() - iterationStart; 269 | 270 | results.push({ 271 | iteration: i, 272 | objectType, 273 | success: !result.isError, 274 | responseTime: iterationTime 275 | }); 276 | 277 | assert.equal(result.isError, false, 278 | `Iteration ${i} should succeed for ${objectType}`); 279 | } 280 | 281 | const totalTime = Date.now() - startTime; 282 | const successRate = results.filter(r => r.success).length / results.length; 283 | 284 | // Functional validation focused on reliability 285 | assert.equal(successRate, 1.0, 'All stress test iterations should succeed'); 286 | assert.ok(totalTime < 20000, 'Load test should complete within 20 seconds'); 287 | 288 | // Validate response consistency 289 | const responseTimes = results.map(r => r.responseTime); 290 | const avgResponseTime = responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length; 291 | assert.ok(avgResponseTime < 3000, 'Average response time should be reasonable'); 292 | }); 293 | }); 294 | 295 | // ================================================================================== 296 | // FOCUSED INTEGRATION VALIDATION 297 | // ================================================================================== 298 | 299 | describe('Integration and Context Validation', () => { 300 | test('should validate mock server integration and SFCC API compliance', async () => { 301 | const result = await client.callTool('get_system_object_definition', { objectType: 'Product' }); 302 | const data = JSON.parse(result.content[0].text); 303 | 304 | // Validate SFCC API compliance (essential checks only) 305 | assert.ok(data._v && data._v.match(/^\d+\.\d+$/), 'Should have valid API version format'); 306 | assert.ok(data.link && data.link.includes('/dw/data/'), 'Should have SFCC-style API link'); 307 | 308 | // Validate realistic data structure 309 | assert.ok(!isNaN(Date.parse(data.creation_date)), 'Should have valid creation date'); 310 | assert.ok(!isNaN(Date.parse(data.last_modified)), 'Should have valid last modified date'); 311 | assert.ok(data.attribute_definition_count >= 0, 'Should have realistic attribute count'); 312 | assert.ok(data.attribute_group_count >= 0, 'Should have realistic group count'); 313 | }); 314 | 315 | test('should validate cross-tool integration consistency', async () => { 316 | // Single object definition 317 | const singleResult = await client.callTool('get_system_object_definition', { objectType: 'Customer' }); 318 | const singleData = JSON.parse(singleResult.content[0].text); 319 | 320 | // Bulk object definitions (for context) 321 | const allResult = await client.callTool('get_system_object_definitions', {}); 322 | const allData = JSON.parse(allResult.content[0].text); 323 | 324 | // Validate integration consistency 325 | assert.equal(singleData.object_type, 'Customer', 'Should access Customer directly'); 326 | assert.ok(Array.isArray(allData.data), 'Bulk tool should return array'); 327 | 328 | // Find Customer in bulk results if present (pagination may affect this) 329 | const customerFromAll = allData.data.find(obj => obj.object_type === 'Customer'); 330 | if (customerFromAll) { 331 | assert.equal(singleData.object_type, customerFromAll.object_type, 332 | 'Object type should match between single and bulk operations'); 333 | } 334 | }); 335 | }); 336 | 337 | // ================================================================================== 338 | // HELPER FUNCTIONS 339 | // ================================================================================== 340 | 341 | function assertValidObjectDefinition(data, objectType) { 342 | assert.equal(data._type, 'object_type_definition', 'Should have correct type'); 343 | assert.equal(data.object_type, objectType, 'Should match requested object type'); 344 | assert.ok(data.display_name, 'Should have display name'); 345 | assert.ok(data.description, 'Should have description'); 346 | assert.ok(typeof data.attribute_definition_count === 'number', 'Should have attribute count'); 347 | assert.ok(typeof data.attribute_group_count === 'number', 'Should have group count'); 348 | assert.ok(typeof data.content_object === 'boolean', 'Should have content_object flag'); 349 | assert.ok(typeof data.queryable === 'boolean', 'Should have queryable flag'); 350 | assert.ok(typeof data.read_only === 'boolean', 'Should have read_only flag'); 351 | } 352 | 353 | function validateLocalizationStructure(localizationObject, fieldName) { 354 | assert.ok(localizationObject, `Should have ${fieldName}`); 355 | assert.ok(typeof localizationObject === 'object', `${fieldName} should be object`); 356 | assert.ok(localizationObject.default, `${fieldName} should have default value`); 357 | 358 | const locales = Object.keys(localizationObject); 359 | assert.ok(locales.length >= 1, `${fieldName} should have at least default locale`); 360 | 361 | locales.forEach(locale => { 362 | assert.ok(typeof localizationObject[locale] === 'string', 363 | `${fieldName}.${locale} should be string`); 364 | assert.ok(localizationObject[locale].length > 0, 365 | `${fieldName}.${locale} should not be empty`); 366 | }); 367 | } 368 | }); 369 | 370 | // ================================================================================== 371 | // OPTIMIZATION NOTES 372 | // ================================================================================== 373 | // This programmatic test suite has been optimized for focused, essential testing. 374 | // 375 | // OPTIMIZATIONS APPLIED: 376 | // - Consolidated tool discovery tests (2 tests → 1 comprehensive test) 377 | // - Streamlined business logic validation (3 tests → 2 focused tests) 378 | // - Simplified multi-step workflows (3 tests → 2 essential workflows) 379 | // - Reduced stress testing iterations (10 → 5 iterations) 380 | // - Consolidated error handling patterns (3 tests → 2 comprehensive tests) 381 | // - Focused integration testing (3 tests → 2 targeted tests) 382 | // - Removed mock-data-specific assertions for resilience 383 | // - Reduced object type testing (5 objects → 3 core objects) 384 | // 385 | // TOTAL REDUCTION: 14 tests → 9 focused tests 386 | // FOCUS: Tool functionality, critical workflows, error handling 387 | // RESILIENCE: Tests adapt to mock data changes 388 | // EFFICIENCY: Reduced API calls while maintaining comprehensive coverage 389 | // ================================================================================== ``` -------------------------------------------------------------------------------- /docs/dw_customer/Profile.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.customer 2 | 3 | # Class Profile 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.PersistentObject 9 | - dw.object.ExtensibleObject 10 | - dw.customer.EncryptedObject 11 | - dw.customer.Profile 12 | 13 | ## Description 14 | 15 | The class represents a customer profile. It also provides access to the customers address book and credentials. Note: this class handles sensitive security-related data. Pay special attention to PCI DSS v3. requirements 2, 4, and 12. 16 | 17 | ## Properties 18 | 19 | ### addressBook 20 | 21 | **Type:** AddressBook (Read Only) 22 | 23 | The customer's address book. 24 | 25 | ### birthday 26 | 27 | **Type:** Date 28 | 29 | The customer's birthday as a date. 30 | 31 | ### companyName 32 | 33 | **Type:** String 34 | 35 | The customer's company name. 36 | 37 | ### credentials 38 | 39 | **Type:** Credentials (Read Only) 40 | 41 | The customer's credentials. 42 | 43 | ### customer 44 | 45 | **Type:** Customer (Read Only) 46 | 47 | The customer object related to this profile. 48 | 49 | ### customerNo 50 | 51 | **Type:** String (Read Only) 52 | 53 | The customer's number, which is a number used to identify the Customer. 54 | 55 | ### email 56 | 57 | **Type:** String 58 | 59 | The customer's email address. 60 | 61 | ### fax 62 | 63 | **Type:** String 64 | 65 | The fax number to use for the customer. 66 | The length is restricted to 32 characters. 67 | 68 | ### female 69 | 70 | **Type:** boolean (Read Only) 71 | 72 | Indicates that the customer is female when set to true. 73 | 74 | ### firstName 75 | 76 | **Type:** String 77 | 78 | The customer's first name. 79 | 80 | ### gender 81 | 82 | **Type:** EnumValue 83 | 84 | The customer's gender. 85 | 86 | ### jobTitle 87 | 88 | **Type:** String 89 | 90 | The customer's job title. 91 | 92 | ### lastLoginTime 93 | 94 | **Type:** Date (Read Only) 95 | 96 | The last login time of the customer. 97 | 98 | ### lastName 99 | 100 | **Type:** String 101 | 102 | The customer's last name. 103 | 104 | ### lastVisitTime 105 | 106 | **Type:** Date (Read Only) 107 | 108 | The last visit time of the customer. 109 | 110 | ### male 111 | 112 | **Type:** boolean (Read Only) 113 | 114 | Indicates that the customer is male when set to true. 115 | 116 | ### nextBirthday 117 | 118 | **Type:** Date (Read Only) 119 | 120 | The upcoming customer's birthday as a date. 121 | If the customer already had birthday this year the method returns the birthday of the next year. 122 | Otherwise its birthday in this year. 123 | If the customer has not set a birthday this method returns null. 124 | 125 | ### phoneBusiness 126 | 127 | **Type:** String 128 | 129 | The business phone number to use for the customer. 130 | 131 | ### phoneHome 132 | 133 | **Type:** String 134 | 135 | The phone number to use for the customer. 136 | 137 | ### phoneMobile 138 | 139 | **Type:** String 140 | 141 | The mobile phone number to use for the customer. 142 | 143 | ### preferredLocale 144 | 145 | **Type:** String 146 | 147 | The customer's preferred locale. 148 | 149 | ### previousLoginTime 150 | 151 | **Type:** Date (Read Only) 152 | 153 | The time the customer logged in prior to the current login. 154 | 155 | ### previousVisitTime 156 | 157 | **Type:** Date (Read Only) 158 | 159 | The time the customer visited the store prior to the current visit. 160 | 161 | ### salutation 162 | 163 | **Type:** String 164 | 165 | The salutation to use for the customer. 166 | 167 | ### secondName 168 | 169 | **Type:** String 170 | 171 | The customer's second name. 172 | 173 | ### suffix 174 | 175 | **Type:** String 176 | 177 | The customer's suffix, such as "Jr." or "Sr.". 178 | 179 | ### taxID 180 | 181 | **Type:** String 182 | 183 | The tax ID value. The value is returned either plain 184 | text if the current context allows plain text access, or 185 | if it's not allowed, the ID value will be returned masked. 186 | The following criteria must be met in order to have plain text access: 187 | 188 | 189 | the method call must happen in the context of a storefront request; 190 | the current customer must be registered and authenticated; 191 | it is the profile of the current customer; 192 | and the current protocol is HTTPS. 193 | 194 | ### taxIDMasked 195 | 196 | **Type:** String (Read Only) 197 | 198 | The masked value of the tax ID. 199 | 200 | ### taxIDType 201 | 202 | **Type:** EnumValue 203 | 204 | The tax ID type. 205 | 206 | ### title 207 | 208 | **Type:** String 209 | 210 | The customer's title, such as "Mrs" or "Mr". 211 | 212 | ### wallet 213 | 214 | **Type:** Wallet (Read Only) 215 | 216 | The wallet of this customer. 217 | 218 | ## Constructor Summary 219 | 220 | ## Method Summary 221 | 222 | ### getAddressBook 223 | 224 | **Signature:** `getAddressBook() : AddressBook` 225 | 226 | Returns the customer's address book. 227 | 228 | ### getBirthday 229 | 230 | **Signature:** `getBirthday() : Date` 231 | 232 | Returns the customer's birthday as a date. 233 | 234 | ### getCompanyName 235 | 236 | **Signature:** `getCompanyName() : String` 237 | 238 | Returns the customer's company name. 239 | 240 | ### getCredentials 241 | 242 | **Signature:** `getCredentials() : Credentials` 243 | 244 | Returns the customer's credentials. 245 | 246 | ### getCustomer 247 | 248 | **Signature:** `getCustomer() : Customer` 249 | 250 | Returns the customer object related to this profile. 251 | 252 | ### getCustomerNo 253 | 254 | **Signature:** `getCustomerNo() : String` 255 | 256 | Returns the customer's number, which is a number used to identify the Customer. 257 | 258 | ### getEmail 259 | 260 | **Signature:** `getEmail() : String` 261 | 262 | Returns the customer's email address. 263 | 264 | ### getFax 265 | 266 | **Signature:** `getFax() : String` 267 | 268 | Returns the fax number to use for the customer. 269 | 270 | ### getFirstName 271 | 272 | **Signature:** `getFirstName() : String` 273 | 274 | Returns the customer's first name. 275 | 276 | ### getGender 277 | 278 | **Signature:** `getGender() : EnumValue` 279 | 280 | Returns the customer's gender. 281 | 282 | ### getJobTitle 283 | 284 | **Signature:** `getJobTitle() : String` 285 | 286 | Returns the customer's job title. 287 | 288 | ### getLastLoginTime 289 | 290 | **Signature:** `getLastLoginTime() : Date` 291 | 292 | Returns the last login time of the customer. 293 | 294 | ### getLastName 295 | 296 | **Signature:** `getLastName() : String` 297 | 298 | Returns the customer's last name. 299 | 300 | ### getLastVisitTime 301 | 302 | **Signature:** `getLastVisitTime() : Date` 303 | 304 | Returns the last visit time of the customer. 305 | 306 | ### getNextBirthday 307 | 308 | **Signature:** `getNextBirthday() : Date` 309 | 310 | Returns the upcoming customer's birthday as a date. 311 | 312 | ### getPhoneBusiness 313 | 314 | **Signature:** `getPhoneBusiness() : String` 315 | 316 | Returns the business phone number to use for the customer. 317 | 318 | ### getPhoneHome 319 | 320 | **Signature:** `getPhoneHome() : String` 321 | 322 | Returns the phone number to use for the customer. 323 | 324 | ### getPhoneMobile 325 | 326 | **Signature:** `getPhoneMobile() : String` 327 | 328 | Returns the mobile phone number to use for the customer. 329 | 330 | ### getPreferredLocale 331 | 332 | **Signature:** `getPreferredLocale() : String` 333 | 334 | Returns the customer's preferred locale. 335 | 336 | ### getPreviousLoginTime 337 | 338 | **Signature:** `getPreviousLoginTime() : Date` 339 | 340 | Returns the time the customer logged in prior to the current login. 341 | 342 | ### getPreviousVisitTime 343 | 344 | **Signature:** `getPreviousVisitTime() : Date` 345 | 346 | Returns the time the customer visited the store prior to the current visit. 347 | 348 | ### getSalutation 349 | 350 | **Signature:** `getSalutation() : String` 351 | 352 | Returns the salutation to use for the customer. 353 | 354 | ### getSecondName 355 | 356 | **Signature:** `getSecondName() : String` 357 | 358 | Returns the customer's second name. 359 | 360 | ### getSuffix 361 | 362 | **Signature:** `getSuffix() : String` 363 | 364 | Returns the customer's suffix, such as "Jr." or "Sr.". 365 | 366 | ### getTaxID 367 | 368 | **Signature:** `getTaxID() : String` 369 | 370 | Returns the tax ID value. 371 | 372 | ### getTaxIDMasked 373 | 374 | **Signature:** `getTaxIDMasked() : String` 375 | 376 | Returns the masked value of the tax ID. 377 | 378 | ### getTaxIDType 379 | 380 | **Signature:** `getTaxIDType() : EnumValue` 381 | 382 | Returns the tax ID type. 383 | 384 | ### getTitle 385 | 386 | **Signature:** `getTitle() : String` 387 | 388 | Returns the customer's title, such as "Mrs" or "Mr". 389 | 390 | ### getWallet 391 | 392 | **Signature:** `getWallet() : Wallet` 393 | 394 | Returns the wallet of this customer. 395 | 396 | ### isFemale 397 | 398 | **Signature:** `isFemale() : boolean` 399 | 400 | Indicates that the customer is female when set to true. 401 | 402 | ### isMale 403 | 404 | **Signature:** `isMale() : boolean` 405 | 406 | Indicates that the customer is male when set to true. 407 | 408 | ### setBirthday 409 | 410 | **Signature:** `setBirthday(aValue : Date) : void` 411 | 412 | Sets the customer's birthday as a date. 413 | 414 | ### setCompanyName 415 | 416 | **Signature:** `setCompanyName(aValue : String) : void` 417 | 418 | Sets the customer's company name. 419 | 420 | ### setEmail 421 | 422 | **Signature:** `setEmail(aValue : String) : void` 423 | 424 | Sets the customer's email address. 425 | 426 | ### setFax 427 | 428 | **Signature:** `setFax(number : String) : void` 429 | 430 | Sets the fax number to use for the customer. 431 | 432 | ### setFirstName 433 | 434 | **Signature:** `setFirstName(aValue : String) : void` 435 | 436 | Sets the customer's first name. 437 | 438 | ### setGender 439 | 440 | **Signature:** `setGender(aValue : Number) : void` 441 | 442 | Sets the customer's gender. 443 | 444 | ### setJobTitle 445 | 446 | **Signature:** `setJobTitle(aValue : String) : void` 447 | 448 | Sets the customer's job title. 449 | 450 | ### setLastName 451 | 452 | **Signature:** `setLastName(aValue : String) : void` 453 | 454 | Sets the customer's last name. 455 | 456 | ### setPhoneBusiness 457 | 458 | **Signature:** `setPhoneBusiness(number : String) : void` 459 | 460 | Sets the business phone number to use for the customer. 461 | 462 | ### setPhoneHome 463 | 464 | **Signature:** `setPhoneHome(number : String) : void` 465 | 466 | Sets the phone number to use for the customer. 467 | 468 | ### setPhoneMobile 469 | 470 | **Signature:** `setPhoneMobile(number : String) : void` 471 | 472 | Sets the mobile phone number to use for the customer. 473 | 474 | ### setPreferredLocale 475 | 476 | **Signature:** `setPreferredLocale(aValue : String) : void` 477 | 478 | Sets the customer's preferred locale. 479 | 480 | ### setSaluation 481 | 482 | **Signature:** `setSaluation(salutation : String) : void` 483 | 484 | Sets the salutation to use for the customer. 485 | 486 | ### setSalutation 487 | 488 | **Signature:** `setSalutation(salutation : String) : void` 489 | 490 | Sets the salutation to use for the customer. 491 | 492 | ### setSecondName 493 | 494 | **Signature:** `setSecondName(aValue : String) : void` 495 | 496 | Sets the customer's second name. 497 | 498 | ### setSuffix 499 | 500 | **Signature:** `setSuffix(aValue : String) : void` 501 | 502 | Sets the the customer's suffix. 503 | 504 | ### setTaxID 505 | 506 | **Signature:** `setTaxID(taxID : String) : void` 507 | 508 | Sets the tax ID value. 509 | 510 | ### setTaxIDType 511 | 512 | **Signature:** `setTaxIDType(taxIdType : String) : void` 513 | 514 | Sets the tax ID type. 515 | 516 | ### setTitle 517 | 518 | **Signature:** `setTitle(aValue : String) : void` 519 | 520 | Sets the customer's title. 521 | 522 | ## Method Detail 523 | 524 | ## Method Details 525 | 526 | ### getAddressBook 527 | 528 | **Signature:** `getAddressBook() : AddressBook` 529 | 530 | **Description:** Returns the customer's address book. 531 | 532 | **Returns:** 533 | 534 | the customer's address book. 535 | 536 | --- 537 | 538 | ### getBirthday 539 | 540 | **Signature:** `getBirthday() : Date` 541 | 542 | **Description:** Returns the customer's birthday as a date. 543 | 544 | **Returns:** 545 | 546 | the customer's birthday as a date. 547 | 548 | --- 549 | 550 | ### getCompanyName 551 | 552 | **Signature:** `getCompanyName() : String` 553 | 554 | **Description:** Returns the customer's company name. 555 | 556 | **Returns:** 557 | 558 | the customer's company name. 559 | 560 | --- 561 | 562 | ### getCredentials 563 | 564 | **Signature:** `getCredentials() : Credentials` 565 | 566 | **Description:** Returns the customer's credentials. 567 | 568 | **Returns:** 569 | 570 | the customer's credentials. 571 | 572 | --- 573 | 574 | ### getCustomer 575 | 576 | **Signature:** `getCustomer() : Customer` 577 | 578 | **Description:** Returns the customer object related to this profile. 579 | 580 | **Returns:** 581 | 582 | customer object related to profile. 583 | 584 | --- 585 | 586 | ### getCustomerNo 587 | 588 | **Signature:** `getCustomerNo() : String` 589 | 590 | **Description:** Returns the customer's number, which is a number used to identify the Customer. 591 | 592 | **Returns:** 593 | 594 | the customer's number. 595 | 596 | --- 597 | 598 | ### getEmail 599 | 600 | **Signature:** `getEmail() : String` 601 | 602 | **Description:** Returns the customer's email address. 603 | 604 | **Returns:** 605 | 606 | the customer's email address. 607 | 608 | --- 609 | 610 | ### getFax 611 | 612 | **Signature:** `getFax() : String` 613 | 614 | **Description:** Returns the fax number to use for the customer. The length is restricted to 32 characters. 615 | 616 | **Returns:** 617 | 618 | the fax mobile phone number to use for the customer. 619 | 620 | --- 621 | 622 | ### getFirstName 623 | 624 | **Signature:** `getFirstName() : String` 625 | 626 | **Description:** Returns the customer's first name. 627 | 628 | **Returns:** 629 | 630 | the customer's first name. 631 | 632 | --- 633 | 634 | ### getGender 635 | 636 | **Signature:** `getGender() : EnumValue` 637 | 638 | **Description:** Returns the customer's gender. 639 | 640 | **Returns:** 641 | 642 | the customer's gender. 643 | 644 | --- 645 | 646 | ### getJobTitle 647 | 648 | **Signature:** `getJobTitle() : String` 649 | 650 | **Description:** Returns the customer's job title. 651 | 652 | **Returns:** 653 | 654 | the customer's job title. 655 | 656 | --- 657 | 658 | ### getLastLoginTime 659 | 660 | **Signature:** `getLastLoginTime() : Date` 661 | 662 | **Description:** Returns the last login time of the customer. 663 | 664 | **Returns:** 665 | 666 | the time, when the customer was last logged in. 667 | 668 | --- 669 | 670 | ### getLastName 671 | 672 | **Signature:** `getLastName() : String` 673 | 674 | **Description:** Returns the customer's last name. 675 | 676 | **Returns:** 677 | 678 | the customer's last name. 679 | 680 | --- 681 | 682 | ### getLastVisitTime 683 | 684 | **Signature:** `getLastVisitTime() : Date` 685 | 686 | **Description:** Returns the last visit time of the customer. 687 | 688 | **Returns:** 689 | 690 | the time, when the customer has visited the storefront the last time (with enabled remember me functionality). 691 | 692 | --- 693 | 694 | ### getNextBirthday 695 | 696 | **Signature:** `getNextBirthday() : Date` 697 | 698 | **Description:** Returns the upcoming customer's birthday as a date. If the customer already had birthday this year the method returns the birthday of the next year. Otherwise its birthday in this year. If the customer has not set a birthday this method returns null. 699 | 700 | **Returns:** 701 | 702 | the customer's next birthday as a date. 703 | 704 | --- 705 | 706 | ### getPhoneBusiness 707 | 708 | **Signature:** `getPhoneBusiness() : String` 709 | 710 | **Description:** Returns the business phone number to use for the customer. 711 | 712 | **Returns:** 713 | 714 | the business phone number to use for the customer. 715 | 716 | --- 717 | 718 | ### getPhoneHome 719 | 720 | **Signature:** `getPhoneHome() : String` 721 | 722 | **Description:** Returns the phone number to use for the customer. 723 | 724 | **Returns:** 725 | 726 | the phone number to use for the customer. 727 | 728 | --- 729 | 730 | ### getPhoneMobile 731 | 732 | **Signature:** `getPhoneMobile() : String` 733 | 734 | **Description:** Returns the mobile phone number to use for the customer. 735 | 736 | **Returns:** 737 | 738 | the mobile phone number to use for the customer. 739 | 740 | --- 741 | 742 | ### getPreferredLocale 743 | 744 | **Signature:** `getPreferredLocale() : String` 745 | 746 | **Description:** Returns the customer's preferred locale. 747 | 748 | **Returns:** 749 | 750 | the customer's preferred locale. 751 | 752 | --- 753 | 754 | ### getPreviousLoginTime 755 | 756 | **Signature:** `getPreviousLoginTime() : Date` 757 | 758 | **Description:** Returns the time the customer logged in prior to the current login. 759 | 760 | **Returns:** 761 | 762 | the time the customer logged in prior to the current login. 763 | 764 | --- 765 | 766 | ### getPreviousVisitTime 767 | 768 | **Signature:** `getPreviousVisitTime() : Date` 769 | 770 | **Description:** Returns the time the customer visited the store prior to the current visit. 771 | 772 | **Returns:** 773 | 774 | the time the customer visited the store prior to the current visit. 775 | 776 | --- 777 | 778 | ### getSalutation 779 | 780 | **Signature:** `getSalutation() : String` 781 | 782 | **Description:** Returns the salutation to use for the customer. 783 | 784 | **Returns:** 785 | 786 | the salutation to use for the customer. 787 | 788 | --- 789 | 790 | ### getSecondName 791 | 792 | **Signature:** `getSecondName() : String` 793 | 794 | **Description:** Returns the customer's second name. 795 | 796 | **Returns:** 797 | 798 | the customer's second name. 799 | 800 | --- 801 | 802 | ### getSuffix 803 | 804 | **Signature:** `getSuffix() : String` 805 | 806 | **Description:** Returns the customer's suffix, such as "Jr." or "Sr.". 807 | 808 | **Returns:** 809 | 810 | the customer's suffix. 811 | 812 | --- 813 | 814 | ### getTaxID 815 | 816 | **Signature:** `getTaxID() : String` 817 | 818 | **Description:** Returns the tax ID value. The value is returned either plain text if the current context allows plain text access, or if it's not allowed, the ID value will be returned masked. The following criteria must be met in order to have plain text access: the method call must happen in the context of a storefront request; the current customer must be registered and authenticated; it is the profile of the current customer; and the current protocol is HTTPS. 819 | 820 | **Returns:** 821 | 822 | the tax ID value 823 | 824 | --- 825 | 826 | ### getTaxIDMasked 827 | 828 | **Signature:** `getTaxIDMasked() : String` 829 | 830 | **Description:** Returns the masked value of the tax ID. 831 | 832 | **Returns:** 833 | 834 | the masked value of the tax ID 835 | 836 | --- 837 | 838 | ### getTaxIDType 839 | 840 | **Signature:** `getTaxIDType() : EnumValue` 841 | 842 | **Description:** Returns the tax ID type. 843 | 844 | **Returns:** 845 | 846 | the tax ID type 847 | 848 | --- 849 | 850 | ### getTitle 851 | 852 | **Signature:** `getTitle() : String` 853 | 854 | **Description:** Returns the customer's title, such as "Mrs" or "Mr". 855 | 856 | **Returns:** 857 | 858 | the customer's title. 859 | 860 | --- 861 | 862 | ### getWallet 863 | 864 | **Signature:** `getWallet() : Wallet` 865 | 866 | **Description:** Returns the wallet of this customer. 867 | 868 | **Returns:** 869 | 870 | the wallet of this customer. 871 | 872 | --- 873 | 874 | ### isFemale 875 | 876 | **Signature:** `isFemale() : boolean` 877 | 878 | **Description:** Indicates that the customer is female when set to true. 879 | 880 | **Returns:** 881 | 882 | true if the customer is a female, false otherwise. 883 | 884 | --- 885 | 886 | ### isMale 887 | 888 | **Signature:** `isMale() : boolean` 889 | 890 | **Description:** Indicates that the customer is male when set to true. 891 | 892 | **Returns:** 893 | 894 | true if the customer is a male, false otherwise. 895 | 896 | --- 897 | 898 | ### setBirthday 899 | 900 | **Signature:** `setBirthday(aValue : Date) : void` 901 | 902 | **Description:** Sets the customer's birthday as a date. 903 | 904 | **Parameters:** 905 | 906 | - `aValue`: the customer's birthday as a date. 907 | 908 | --- 909 | 910 | ### setCompanyName 911 | 912 | **Signature:** `setCompanyName(aValue : String) : void` 913 | 914 | **Description:** Sets the customer's company name. 915 | 916 | **Parameters:** 917 | 918 | - `aValue`: the customer's company name. 919 | 920 | --- 921 | 922 | ### setEmail 923 | 924 | **Signature:** `setEmail(aValue : String) : void` 925 | 926 | **Description:** Sets the customer's email address. 927 | 928 | **Parameters:** 929 | 930 | - `aValue`: the customer's email address. 931 | 932 | --- 933 | 934 | ### setFax 935 | 936 | **Signature:** `setFax(number : String) : void` 937 | 938 | **Description:** Sets the fax number to use for the customer. The length is restricted to 32 characters. 939 | 940 | **Parameters:** 941 | 942 | - `number`: the fax number to use for the customer. 943 | 944 | --- 945 | 946 | ### setFirstName 947 | 948 | **Signature:** `setFirstName(aValue : String) : void` 949 | 950 | **Description:** Sets the customer's first name. 951 | 952 | **Parameters:** 953 | 954 | - `aValue`: the customer's first name. 955 | 956 | --- 957 | 958 | ### setGender 959 | 960 | **Signature:** `setGender(aValue : Number) : void` 961 | 962 | **Description:** Sets the customer's gender. 963 | 964 | **Parameters:** 965 | 966 | - `aValue`: the customer's gender. 967 | 968 | --- 969 | 970 | ### setJobTitle 971 | 972 | **Signature:** `setJobTitle(aValue : String) : void` 973 | 974 | **Description:** Sets the customer's job title. 975 | 976 | **Parameters:** 977 | 978 | - `aValue`: the customer's job title. 979 | 980 | --- 981 | 982 | ### setLastName 983 | 984 | **Signature:** `setLastName(aValue : String) : void` 985 | 986 | **Description:** Sets the customer's last name. 987 | 988 | **Parameters:** 989 | 990 | - `aValue`: the customer's last name. 991 | 992 | --- 993 | 994 | ### setPhoneBusiness 995 | 996 | **Signature:** `setPhoneBusiness(number : String) : void` 997 | 998 | **Description:** Sets the business phone number to use for the customer. The length is restricted to 32 characters. 999 | 1000 | **Parameters:** 1001 | 1002 | - `number`: the business phone number to use for the customer. 1003 | 1004 | --- 1005 | 1006 | ### setPhoneHome 1007 | 1008 | **Signature:** `setPhoneHome(number : String) : void` 1009 | 1010 | **Description:** Sets the phone number to use for the customer. The length is restricted to 32 characters. 1011 | 1012 | **Parameters:** 1013 | 1014 | - `number`: the phone number to use for the customer. 1015 | 1016 | --- 1017 | 1018 | ### setPhoneMobile 1019 | 1020 | **Signature:** `setPhoneMobile(number : String) : void` 1021 | 1022 | **Description:** Sets the mobile phone number to use for the customer. The length is restricted to 32 characters. 1023 | 1024 | **Parameters:** 1025 | 1026 | - `number`: the mobile phone number to use for the customer. 1027 | 1028 | --- 1029 | 1030 | ### setPreferredLocale 1031 | 1032 | **Signature:** `setPreferredLocale(aValue : String) : void` 1033 | 1034 | **Description:** Sets the customer's preferred locale. 1035 | 1036 | **Parameters:** 1037 | 1038 | - `aValue`: the customer's preferred locale. 1039 | 1040 | --- 1041 | 1042 | ### setSaluation 1043 | 1044 | **Signature:** `setSaluation(salutation : String) : void` 1045 | 1046 | **Description:** Sets the salutation to use for the customer. 1047 | 1048 | **Deprecated:** 1049 | 1050 | Use setSalutation(String) 1051 | 1052 | **Parameters:** 1053 | 1054 | - `salutation`: the salutation to use for the customer. 1055 | 1056 | --- 1057 | 1058 | ### setSalutation 1059 | 1060 | **Signature:** `setSalutation(salutation : String) : void` 1061 | 1062 | **Description:** Sets the salutation to use for the customer. 1063 | 1064 | **Parameters:** 1065 | 1066 | - `salutation`: the salutation to use for the customer. 1067 | 1068 | --- 1069 | 1070 | ### setSecondName 1071 | 1072 | **Signature:** `setSecondName(aValue : String) : void` 1073 | 1074 | **Description:** Sets the customer's second name. 1075 | 1076 | **Parameters:** 1077 | 1078 | - `aValue`: the customer's second name. 1079 | 1080 | --- 1081 | 1082 | ### setSuffix 1083 | 1084 | **Signature:** `setSuffix(aValue : String) : void` 1085 | 1086 | **Description:** Sets the the customer's suffix. 1087 | 1088 | **Parameters:** 1089 | 1090 | - `aValue`: the customer's suffix. 1091 | 1092 | --- 1093 | 1094 | ### setTaxID 1095 | 1096 | **Signature:** `setTaxID(taxID : String) : void` 1097 | 1098 | **Description:** Sets the tax ID value. The value can be set if the current context allows write access. The current context allows write access if the currently logged in user owns this profile and the connection is secured. 1099 | 1100 | **Parameters:** 1101 | 1102 | - `taxID`: the tax ID value to set 1103 | 1104 | --- 1105 | 1106 | ### setTaxIDType 1107 | 1108 | **Signature:** `setTaxIDType(taxIdType : String) : void` 1109 | 1110 | **Description:** Sets the tax ID type. 1111 | 1112 | **Parameters:** 1113 | 1114 | - `taxIdType`: the tax ID type to set 1115 | 1116 | --- 1117 | 1118 | ### setTitle 1119 | 1120 | **Signature:** `setTitle(aValue : String) : void` 1121 | 1122 | **Description:** Sets the customer's title. 1123 | 1124 | **Parameters:** 1125 | 1126 | - `aValue`: the customer's title. 1127 | 1128 | --- ``` -------------------------------------------------------------------------------- /docs-site/pages/FeaturesPage.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import SEO from '../components/SEO'; 4 | import BreadcrumbSchema from '../components/BreadcrumbSchema'; 5 | import StructuredData from '../components/StructuredData'; 6 | import { H1, PageSubtitle, H2, H3 } from '../components/Typography'; 7 | import { Collapsible } from '../components/Collapsible'; 8 | import { SITE_DATES } from '../constants'; 9 | 10 | const badge = (label: string) => ( 11 | <span className="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium mr-2 mb-2 bg-slate-100 text-slate-700 border border-slate-200">{label}</span> 12 | ); 13 | 14 | const FeaturesPage: React.FC = () => { 15 | const featuresStructuredData = { 16 | "@context": "https://schema.org", 17 | "@type": "TechArticle", 18 | "headline": "Features & Capabilities - SFCC Development MCP Server", 19 | "description": "Comprehensive overview of SFCC Development MCP Server features: documentation access, real-time log & job log analysis, system & custom object exploration, site preferences, cartridge generation, best practices, and AI-powered development tools.", 20 | "author": { 21 | "@type": "Person", 22 | "name": "Thomas Theunen" 23 | }, 24 | "publisher": { 25 | "@type": "Person", 26 | "name": "Thomas Theunen" 27 | }, 28 | "datePublished": SITE_DATES.PUBLISHED, 29 | "dateModified": SITE_DATES.MODIFIED, 30 | "url": "https://sfcc-mcp-dev.rhino-inquisitor.com/features/", 31 | "about": [ 32 | { 33 | "@type": "SoftwareApplication", 34 | "name": "SFCC Development MCP Server", 35 | "applicationCategory": "DeveloperApplication", 36 | "operatingSystem": "Node.js", 37 | "offers": { 38 | "@type": "Offer", 39 | "price": "0", 40 | "priceCurrency": "USD", 41 | "availability": "https://schema.org/InStock" 42 | } 43 | } 44 | ], 45 | "mainEntity": { 46 | "@type": "SoftwareApplication", 47 | "name": "SFCC Development MCP Server", 48 | "applicationCategory": "DeveloperApplication", 49 | "operatingSystem": "Node.js", 50 | "offers": { 51 | "@type": "Offer", 52 | "price": "0", 53 | "priceCurrency": "USD", 54 | "availability": "https://schema.org/InStock" 55 | }, 56 | "featureList": [ 57 | "SFCC API Documentation Access", 58 | "Real-time Log & Job Log Analysis", 59 | "System & Custom Object Exploration", 60 | "Cartridge Generation", 61 | "Best Practices Guides", 62 | "AI Assistant Integration" 63 | ] 64 | } 65 | }; 66 | 67 | return ( 68 | <div className="max-w-6xl mx-auto px-6 py-12"> 69 | <SEO 70 | title="Features & Capabilities" 71 | description="Comprehensive overview of SFCC Development MCP Server features: documentation access, real-time log & job log analysis, system & custom object exploration, site preferences, cartridge generation, best practices, and AI-powered development tools." 72 | keywords="SFCC MCP features, Commerce Cloud development tools, SFCC documentation access, log analysis tools, system object tools, cartridge generation, SFCC best practices, AI development features" 73 | canonical="/features/" 74 | ogType="article" 75 | /> 76 | <BreadcrumbSchema items={[ 77 | { name: "Home", url: "/" }, 78 | { name: "Features", url: "/features/" } 79 | ]} /> 80 | <StructuredData structuredData={featuresStructuredData} /> 81 | 82 | <header className="mb-14 text-center"> 83 | <div className="inline-flex items-center gap-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white px-4 py-2 rounded-full text-sm font-medium mb-6"> 84 | <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd"/></svg> 85 | Feature Surface 86 | </div> 87 | <H1 id="features" className="text-5xl md:text-6xl font-extrabold bg-gradient-to-r from-gray-900 via-blue-900 to-purple-900 bg-clip-text text-transparent mb-6">Discover & Apply Faster</H1> 88 | <PageSubtitle className="text-xl md:text-2xl text-gray-600 max-w-4xl mx-auto leading-relaxed"> 89 | Lean overview of every capability—expand only what you need. Optimized for AI-assisted flows and rapid onboarding. 90 | </PageSubtitle> 91 | 92 | </header> 93 | <div className="flex flex-wrap gap-2 mt-2"> 94 | {badge('Docs')} 95 | {badge('SFRA')} 96 | {badge('Best Practices')} 97 | {badge('Logs')} 98 | {badge('Jobs')} 99 | {badge('Objects')} 100 | {badge('Code Versions')} 101 | {badge('Security')} 102 | </div> 103 | <div className="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4 text-sm text-blue-800"> 104 | <strong className="block font-semibold text-blue-900">Getting started</strong> 105 | <ul className="mt-2 list-disc pl-5 space-y-1"> 106 | <li>Generate a cartridge with <code>generate_cartridge_structure</code></li> 107 | <li>Or ask your AI assistant: <em>"Show me dw.catalog.Product methods"</em></li> 108 | </ul> 109 | <p className="mt-3 m-0 text-blue-800">Ready for deeper insight? Expand the sections below.</p> 110 | </div> 111 | 112 | 113 | {/* DEEP DIVES */} 114 | <section className="space-y-8" aria-label="Feature deep dives"> 115 | <Collapsible id="cartridge-generation" title="🚀 Cartridge Generation" defaultOpen> 116 | <p className="text-sm">Create a production-ready SFCC cartridge (or full project) in seconds via <code>generate_cartridge_structure</code>.</p> 117 | <ul className="list-disc pl-5 text-sm space-y-1"> 118 | <li>Controllers, models, ISML templates, static assets, config, lint, build</li> 119 | <li>Full project mode adds package.json, Webpack, linting, scripts</li> 120 | <li>Direct file creation in specified target path (no manual copy)</li> 121 | <li>Consistent patterns for AI to reason about project shape</li> 122 | </ul> 123 | <div className="mt-4 text-xs text-slate-600"> 124 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 125 | <ul className="list-disc pl-4 space-y-1"> 126 | <li>"Generate a cartridge named <em>training_core</em> and show the created directories."</li> 127 | <li>"What files does generate_cartridge_structure create in full project mode?"</li> 128 | <li>"Add a controller skeleton to my generated cartridge for listing products."</li> 129 | </ul> 130 | </div> 131 | </Collapsible> 132 | 133 | <Collapsible id="sfcc-best-practices-guides" title="📚 Best Practices, Guides & Hook References"> 134 | <p className="text-sm mb-2">Structured guidance across 13 core development domains plus searchable OCAPI / SCAPI hook reference tables with extension point signatures.</p> 135 | <div className="grid sm:grid-cols-2 gap-4 text-sm"> 136 | <div> 137 | <h4 className="font-semibold mb-1">Guides</h4> 138 | <ul className="list-disc pl-4 space-y-1"> 139 | <li>Cartridge creation & architecture</li> 140 | <li>ISML templates (security & performance)</li> 141 | <li>Job framework patterns</li> 142 | <li>LocalServiceRegistry integrations</li> 143 | <li>SFRA controllers & models</li> 144 | <li>SFRA client-side JavaScript architecture</li> 145 | <li>SFRA SCSS theming & override discipline</li> 146 | <li>Custom SCAPI endpoints</li> 147 | </ul> 148 | </div> 149 | <div> 150 | <h4 className="font-semibold mb-1">Also Covers</h4> 151 | <ul className="list-disc pl-4 space-y-1"> 152 | <li>OCAPI & SCAPI hooks</li> 153 | <li>Security hardening (OWASP)</li> 154 | <li>Performance & scalability tactics</li> 155 | <li>Search across all guides</li> 156 | <li>Hook reference tables (OCAPI / SCAPI)</li> 157 | </ul> 158 | </div> 159 | </div> 160 | <div className="mt-4 text-xs text-slate-600"> 161 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 162 | <ul className="list-disc pl-4 space-y-1"> 163 | <li>"Show me security recommendations for ISML templates."</li> 164 | <li>"List OCAPI hook points for product import and explain when to use each."</li> 165 | <li>"Summarize performance best practices relevant to checkout."</li> 166 | </ul> 167 | </div> 168 | </Collapsible> 169 | 170 | <Collapsible id="sfcc-documentation" title="🔍 SFCC API Documentation"> 171 | <p className="text-sm">Query the entire <code>dw.*</code> namespace surface area.</p> 172 | <ul className="list-disc pl-5 text-sm space-y-1"> 173 | <li>Get class info (properties, methods, descriptions)</li> 174 | <li>Search classes & methods with partial matching</li> 175 | <li>List all classes for discovery</li> 176 | <li>Retrieve raw markdown documentation</li> 177 | </ul> 178 | <div className="mt-4 text-xs text-slate-600"> 179 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 180 | <ul className="list-disc pl-4 space-y-1"> 181 | <li>"What methods does dw.catalog.Product have for pricing?"</li> 182 | <li>"Search SFCC classes related to inventory."</li> 183 | <li>"Show raw documentation for dw.system.Transaction."</li> 184 | </ul> 185 | </div> 186 | </Collapsible> 187 | 188 | <Collapsible id="sfra-docs" title="🏗️ Enhanced SFRA Documentation"> 189 | <p className="text-sm">26+ documents with smart categorization (core, product, order, customer, pricing, store, other).</p> 190 | <ul className="list-disc pl-5 text-sm space-y-1"> 191 | <li>Relevance-scored search with highlighted context</li> 192 | <li>Category filtering and model coverage</li> 193 | <li>Core classes: server, request, response, querystring, render</li> 194 | <li>Extensive product/order/customer/pricing/store models</li> 195 | </ul> 196 | <div className="mt-4 text-xs text-slate-600"> 197 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 198 | <ul className="list-disc pl-4 space-y-1"> 199 | <li>"Search SFRA docs for middleware examples."</li> 200 | <li>"Show differences between product-full and product-tile models."</li> 201 | <li>"List order-related SFRA documents and summarize each."</li> 202 | </ul> 203 | </div> 204 | </Collapsible> 205 | 206 | <Collapsible id="logs" title="📊 Log & Job Log Analysis"> 207 | <p className="text-sm mb-2">Real-time visibility into runtime behaviour plus deep job execution insight (multi-level logs in single files).</p> 208 | <div className="grid sm:grid-cols-2 gap-4 text-sm"> 209 | <div> 210 | <h4 className="font-semibold mb-1">Runtime Logs</h4> 211 | <ul className="list-disc pl-4 space-y-1"> 212 | <li>Latest error / warn / info / debug</li> 213 | <li>Pattern search & daily summarization</li> 214 | <li>Full file listing & tail reads</li> 215 | <li>Health & recurrence analysis</li> 216 | </ul> 217 | </div> 218 | <div> 219 | <h4 className="font-semibold mb-1">Job Logs</h4> 220 | <ul className="list-disc pl-4 space-y-1"> 221 | <li>Latest job log files</li> 222 | <li>Search by job name or pattern</li> 223 | <li>Execution summary (steps, timings)</li> 224 | <li>Unified multi-level parsing</li> 225 | </ul> 226 | </div> 227 | </div> 228 | <div className="mt-3 text-xs text-amber-800 bg-amber-50 border border-amber-200 rounded px-3 py-2"> 229 | <strong className="font-semibold">Requires full mode:</strong> WebDAV / log tools need sandbox <code>username</code> & <code>password</code> (basic auth) plus any required realm access. 230 | </div> 231 | <div className="mt-4 text-xs text-slate-600"> 232 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 233 | <ul className="list-disc pl-4 space-y-1"> 234 | <li>"Show the latest 10 error log entries containing 'OCAPI'."</li> 235 | <li>"Summarize today’s log health and top recurring issues."</li> 236 | <li>"Get the latest job execution summary for ProductFeedJob."</li> 237 | </ul> 238 | </div> 239 | </Collapsible> 240 | 241 | <Collapsible id="system-objects" title="⚙️ System & Custom Objects / Data Model"> 242 | <p className="text-sm">Explore system & custom object schemas, attributes, groups and site preferences with advanced querying.</p> 243 | <ul className="list-disc pl-5 text-sm space-y-1"> 244 | <li>List all system objects with metadata</li> 245 | <li>Attribute & group searches (boolean, text, sort)</li> 246 | <li>Custom object attribute discovery (targeted or match-all queries)</li> 247 | <li>Site preference group & value exploration</li> 248 | </ul> 249 | <div className="mt-3 text-xs text-amber-800 bg-amber-50 border border-amber-200 rounded px-3 py-2"> 250 | <strong className="font-semibold">Requires full mode:</strong> OCAPI system object & preference tools need <code>client_id</code> and <code>client_secret</code> with proper scopes (Data API + Shop as applicable). 251 | </div> 252 | <div className="mt-4 text-xs text-slate-600"> 253 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 254 | <ul className="list-disc pl-4 space-y-1"> 255 | <li>"List custom attributes for Product containing 'brand'."</li> 256 | <li>"Search site preferences in the SEO group for description fields."</li> 257 | <li>"Show attribute definitions for custom object Global_String matching 'locale'."</li> 258 | </ul> 259 | </div> 260 | </Collapsible> 261 | 262 | <Collapsible id="code-versions" title="🔄 Code Version Management"> 263 | <p className="text-sm">Manage deployment states directly via MCP.</p> 264 | <ul className="list-disc pl-5 text-sm space-y-1"> 265 | <li>List available code versions</li> 266 | <li>Activate version to resolve endpoint or job issues</li> 267 | <li>AI can propose switches based on log diagnostics</li> 268 | </ul> 269 | <div className="mt-3 text-xs text-amber-800 bg-amber-50 border border-amber-200 rounded px-3 py-2"> 270 | <strong className="font-semibold">Requires full mode:</strong> Code version tools require OCAPI <code>client_id</code> and <code>client_secret</code> authorized for code/version management. 271 | </div> 272 | <div className="mt-4 text-xs text-slate-600"> 273 | <p className="font-semibold mb-1 text-slate-700">Try asking:</p> 274 | <ul className="list-disc pl-4 space-y-1"> 275 | <li>"List code versions and highlight the active one."</li> 276 | <li>"Activate code version <em>int_release_2025_09</em>."</li> 277 | <li>"Which inactive code versions look safe to remove?"</li> 278 | </ul> 279 | </div> 280 | </Collapsible> 281 | 282 | <Collapsible id="security-performance" title="🛡️ Security & Performance"> 283 | <div className="grid sm:grid-cols-2 gap-6 text-sm"> 284 | <div> 285 | <h4 className="font-semibold mb-1">Security</h4> 286 | <ul className="list-disc pl-4 space-y-1"> 287 | <li>Credential isolation & never persisted plaintext</li> 288 | <li>Path traversal & input validation guards</li> 289 | <li>Scoped tool surface (principle of least privilege)</li> 290 | <li>Structured error sanitization</li> 291 | </ul> 292 | </div> 293 | <div> 294 | <h4 className="font-semibold mb-1">Performance</h4> 295 | <ul className="list-disc pl-4 space-y-1"> 296 | <li>Layered caching & deduplicated requests</li> 297 | <li>Chunked log tailing (range reads)</li> 298 | <li>Lazy loading of heavy docs</li> 299 | <li>Resource bounding for single-dev usage</li> 300 | </ul> 301 | </div> 302 | </div> 303 | </Collapsible> 304 | 305 | <Collapsible id="ai-integration" title="🤖 AI Integration Rationale"> 306 | <p className="text-sm">Designed so assistants like GitHub Copilot, Claude, and Cursor can produce higher-quality SFCC code with deterministic tool surfaces and enriched local context.</p> 307 | <ul className="list-disc pl-5 text-sm space-y-1"> 308 | <li>Deterministic tool naming & argument shapes</li> 309 | <li>High-signal, low-noise responses (agent friendly)</li> 310 | <li>Semantic grouping reduces prompt tokens</li> 311 | <li>Guides & raw docs unify knowledge surface</li> 312 | </ul> 313 | </Collapsible> 314 | </section> 315 | 316 | {/* NEXT STEPS (aligned with home/config CTA style) */} 317 | <section className="mt-20 mb-12 text-center" aria-labelledby="next-steps"> 318 | <H2 id="next-steps" className="text-3xl font-bold mb-4">🔗 Next Steps</H2> 319 | <p className="text-sm md:text-base text-gray-600 max-w-2xl mx-auto mb-8">Pick a direction—inspect the precise tool surface first or jump straight into multi-step usage patterns.</p> 320 | <div className="flex flex-col sm:flex-row gap-4 justify-center mb-10"> 321 | <NavLink 322 | to="/tools/" 323 | className="group bg-gradient-to-r from-blue-600 to-purple-600 text-white px-8 py-4 rounded-xl font-semibold text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 no-underline hover:no-underline focus:no-underline" 324 | > 325 | Browse Tools 326 | <span className="ml-2 group-hover:translate-x-1 inline-block transition-transform">→</span> 327 | </NavLink> 328 | <NavLink 329 | to="/examples/" 330 | className="border-2 border-gray-300 text-gray-700 px-8 py-4 rounded-xl font-semibold text-lg hover:border-blue-500 hover:text-blue-600 transition-all duration-300 no-underline hover:no-underline focus:no-underline" 331 | > 332 | Examples & Use Cases 333 | </NavLink> 334 | </div> 335 | <div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto mb-8 text-left"> 336 | <div className="rounded-xl border border-gray-200 bg-white p-5"> 337 | <h3 className="font-semibold text-sm mb-2">Need exact tool names?</h3> 338 | <p className="text-xs text-gray-600">The tools catalog lists arguments & intent so you can craft precise, low-token prompts.</p> 339 | </div> 340 | <div className="rounded-xl border border-blue-200 bg-blue-50 p-5"> 341 | <h3 className="font-semibold text-sm mb-2 text-blue-800">Prefer guided flows?</h3> 342 | <p className="text-xs text-blue-800">Example sequences show chained usage (docs → logs → versions) ready to reuse.</p> 343 | </div> 344 | </div> 345 | <div className="mx-auto max-w-xl p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-800"> 346 | 💡 <strong>Tip:</strong> Ask: <em>"Suggest a workflow to inspect a product attribute then trace related log errors using available tools."</em> 347 | </div> 348 | </section> 349 | </div> 350 | ); 351 | }; 352 | 353 | export default FeaturesPage; 354 | ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-available-best-practice-guides.docs-only.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Programmatic tests for get_available_best_practice_guides tool 3 | * 4 | * These tests provide advanced verification capabilities beyond YAML pattern matching, 5 | * including dynamic validation, comprehensive content analysis, 6 | * and advanced error categorization for the SFCC best practice guides functionality. 7 | * 8 | * Response format discovered via aegis query: 9 | * - Success: { content: [{ type: "text", text: "[{\"name\":\"guide_name\",\"title\":\"Guide Title\",\"description\":\"...\"}]" }], isError: false } 10 | * - Always successful: No isError field or error conditions in docs-only mode 11 | * - Ignores extra parameters: Gracefully handles unexpected parameters 12 | * - Comprehensive: Returns 13 best practice guides across SFCC development areas 13 | */ 14 | 15 | import { test, describe, before, after, beforeEach } from 'node:test'; 16 | import { strict as assert } from 'node:assert'; 17 | import { connect } from 'mcp-aegis'; 18 | 19 | /** 20 | * Content analysis utility for SFCC best practice guides validation 21 | */ 22 | class BestPracticeAnalyzer { 23 | constructor() { 24 | // Expected best practice guides based on project structure 25 | this.expectedGuides = [ 26 | 'cartridge_creation', 27 | 'isml_templates', 28 | 'job_framework', 29 | 'localserviceregistry', 30 | 'ocapi_hooks', 31 | 'scapi_hooks', 32 | 'scapi_custom_endpoint', 33 | 'sfra_controllers', 34 | 'sfra_models', 35 | 'sfra_client_side_js', 36 | 'sfra_scss', 37 | 'performance', 38 | 'security' 39 | ]; 40 | 41 | // Critical guides that must be present 42 | this.criticalGuides = [ 43 | 'cartridge_creation', 44 | 'sfra_controllers', 45 | 'ocapi_hooks', 46 | 'scapi_hooks', 47 | 'sfra_client_side_js', 48 | 'sfra_scss', 49 | 'security', 50 | 'performance' 51 | ]; 52 | 53 | // Guide categories for validation 54 | this.guideCategories = { 55 | core: ['cartridge_creation', 'isml_templates', 'job_framework'], 56 | sfra: ['sfra_controllers', 'sfra_models', 'sfra_client_side_js', 'sfra_scss'], 57 | api_hooks: ['ocapi_hooks', 'scapi_hooks', 'scapi_custom_endpoint'], 58 | services: ['localserviceregistry'], 59 | quality: ['performance', 'security'] 60 | }; 61 | 62 | // Required fields for each guide 63 | this.requiredFields = ['name', 'title', 'description']; 64 | } 65 | 66 | analyzeGuideList(guideArray) { 67 | const analysis = { 68 | totalGuides: guideArray.length, 69 | foundGuides: new Set(), 70 | missingGuides: [], 71 | missingCriticalGuides: [], 72 | foundCriticalGuides: [], 73 | invalidGuides: [], 74 | duplicates: [], 75 | guidesByCategory: {}, 76 | validationErrors: [] 77 | }; 78 | 79 | // Initialize category counters 80 | Object.keys(this.guideCategories).forEach(category => { 81 | analysis.guidesByCategory[category] = []; 82 | }); 83 | 84 | // Analyze each guide 85 | const seenGuides = new Set(); 86 | guideArray.forEach((guide, index) => { 87 | // Validate guide structure 88 | const structureErrors = this.validateGuideStructure(guide, index); 89 | if (structureErrors.length > 0) { 90 | analysis.validationErrors.push(...structureErrors); 91 | analysis.invalidGuides.push(guide); 92 | return; 93 | } 94 | 95 | const guideName = guide.name; 96 | 97 | // Check for duplicates 98 | if (seenGuides.has(guideName)) { 99 | analysis.duplicates.push(guideName); 100 | return; 101 | } 102 | seenGuides.add(guideName); 103 | analysis.foundGuides.add(guideName); 104 | 105 | // Check critical guides 106 | if (this.criticalGuides.includes(guideName)) { 107 | analysis.foundCriticalGuides.push(guideName); 108 | } 109 | 110 | // Categorize guide 111 | let categorized = false; 112 | Object.entries(this.guideCategories).forEach(([category, guides]) => { 113 | if (guides.includes(guideName)) { 114 | analysis.guidesByCategory[category].push(guideName); 115 | categorized = true; 116 | } 117 | }); 118 | 119 | if (!categorized) { 120 | if (!analysis.guidesByCategory.other) { 121 | analysis.guidesByCategory.other = []; 122 | } 123 | analysis.guidesByCategory.other.push(guideName); 124 | } 125 | }); 126 | 127 | // Find missing guides 128 | analysis.missingGuides = this.expectedGuides.filter( 129 | guide => !analysis.foundGuides.has(guide) 130 | ); 131 | 132 | // Find missing critical guides 133 | analysis.missingCriticalGuides = this.criticalGuides.filter( 134 | guide => !analysis.foundCriticalGuides.includes(guide) 135 | ); 136 | 137 | return analysis; 138 | } 139 | 140 | validateGuideStructure(guide, index) { 141 | const errors = []; 142 | 143 | if (typeof guide !== 'object' || guide === null) { 144 | errors.push(`Guide at index ${index} is not an object`); 145 | return errors; 146 | } 147 | 148 | // Check required fields 149 | this.requiredFields.forEach(field => { 150 | if (!Object.prototype.hasOwnProperty.call(guide, field)) { 151 | errors.push(`Guide at index ${index} missing required field: ${field}`); 152 | } else if (typeof guide[field] !== 'string') { 153 | errors.push(`Guide at index ${index} field ${field} should be string, got ${typeof guide[field]}`); 154 | } else if (guide[field].trim().length === 0) { 155 | errors.push(`Guide at index ${index} field ${field} is empty`); 156 | } 157 | }); 158 | 159 | // Validate name format (should be lowercase with underscores) 160 | if (guide.name && !/^[a-z][a-z0-9_]*$/.test(guide.name)) { 161 | errors.push(`Guide at index ${index} has invalid name format: ${guide.name}`); 162 | } 163 | 164 | // Validate title format (should contain "Best Practices") 165 | if (guide.title && !guide.title.includes('Best Practices')) { 166 | errors.push(`Guide at index ${index} title should contain "Best Practices": ${guide.title}`); 167 | } 168 | 169 | // Validate description length (should be meaningful) 170 | if (guide.description && guide.description.length < 20) { 171 | errors.push(`Guide at index ${index} description too short: ${guide.description.length} chars`); 172 | } 173 | 174 | return errors; 175 | } 176 | 177 | validateCompleteness(analysis) { 178 | const issues = []; 179 | 180 | if (analysis.totalGuides < 13) { 181 | issues.push(`Guide count too low: ${analysis.totalGuides} (expected 13+)`); 182 | } 183 | 184 | if (analysis.missingGuides.length > 0) { 185 | issues.push(`Missing guides: ${analysis.missingGuides.join(', ')}`); 186 | } 187 | 188 | if (analysis.missingCriticalGuides.length > 0) { 189 | issues.push(`Missing critical guides: ${analysis.missingCriticalGuides.join(', ')}`); 190 | } 191 | 192 | if (analysis.duplicates.length > 0) { 193 | issues.push(`Duplicate guides found: ${analysis.duplicates.length}`); 194 | } 195 | 196 | if (analysis.invalidGuides.length > 0) { 197 | issues.push(`Invalid guides found: ${analysis.invalidGuides.length}`); 198 | } 199 | 200 | if (analysis.validationErrors.length > 0) { 201 | issues.push(`Validation errors: ${analysis.validationErrors.length}`); 202 | } 203 | 204 | return issues; 205 | } 206 | } 207 | 208 | /** 209 | * Helper function to validate MCP response structure 210 | */ 211 | function assertValidMCPResponse(result) { 212 | assert.ok(result.content, 'Response should have content'); 213 | assert.ok(Array.isArray(result.content), 'Content should be array'); 214 | assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); 215 | } 216 | 217 | describe('get_available_best_practice_guides Programmatic Tests', () => { 218 | let client; 219 | const bestPracticeAnalyzer = new BestPracticeAnalyzer(); 220 | 221 | before(async () => { 222 | client = await connect('./aegis.config.docs-only.json'); 223 | }); 224 | 225 | after(async () => { 226 | if (client?.connected) { 227 | await client.disconnect(); 228 | } 229 | }); 230 | 231 | beforeEach(() => { 232 | client.clearAllBuffers(); 233 | }); 234 | 235 | describe('Protocol Compliance', () => { 236 | test('should be properly connected to MCP server', async () => { 237 | assert.ok(client.connected, 'Client should be connected'); 238 | }); 239 | 240 | test('should have get_available_best_practice_guides tool available', async () => { 241 | const tools = await client.listTools(); 242 | const guidesTool = tools.find(tool => tool.name === 'get_available_best_practice_guides'); 243 | 244 | assert.ok(guidesTool, 'get_available_best_practice_guides tool should be available'); 245 | assert.equal(guidesTool.name, 'get_available_best_practice_guides'); 246 | assert.ok(guidesTool.description, 'Tool should have description'); 247 | assert.ok(guidesTool.inputSchema, 'Tool should have input schema'); 248 | }); 249 | 250 | test('should have correct tool input schema', async () => { 251 | const tools = await client.listTools(); 252 | const guidesTool = tools.find(tool => tool.name === 'get_available_best_practice_guides'); 253 | 254 | assert.equal(guidesTool.inputSchema.type, 'object'); 255 | // get_available_best_practice_guides takes no required parameters 256 | assert.ok(!guidesTool.inputSchema.required || guidesTool.inputSchema.required.length === 0); 257 | }); 258 | }); 259 | 260 | describe('Basic Functionality', () => { 261 | test('should execute successfully with empty parameters', async () => { 262 | const result = await client.callTool('get_available_best_practice_guides', {}); 263 | 264 | assertValidMCPResponse(result); 265 | assert.equal(result.isError, false, 'Should not return error'); 266 | assert.equal(result.content.length, 1, 'Should return single content item'); 267 | assert.equal(result.content[0].type, 'text', 'Content type should be text'); 268 | }); 269 | 270 | test('should return valid JSON array in response', async () => { 271 | const result = await client.callTool('get_available_best_practice_guides', {}); 272 | 273 | assertValidMCPResponse(result); 274 | const responseText = result.content[0].text; 275 | 276 | // Should be valid JSON 277 | let guideArray; 278 | assert.doesNotThrow(() => { 279 | guideArray = JSON.parse(responseText); 280 | }, 'Response should be valid JSON'); 281 | 282 | assert.ok(Array.isArray(guideArray), 'Response should be JSON array'); 283 | assert.ok(guideArray.length > 0, 'Guide array should not be empty'); 284 | }); 285 | 286 | test('should ignore additional parameters gracefully', async () => { 287 | const result = await client.callTool('get_available_best_practice_guides', { 288 | unexpectedParam: 'should be ignored', 289 | anotherParam: 123, 290 | objectParam: { nested: 'value' } 291 | }); 292 | 293 | assertValidMCPResponse(result); 294 | assert.equal(result.isError, false, 'Should handle extra params gracefully'); 295 | 296 | // Result should be identical to empty params call 297 | const baselineResult = await client.callTool('get_available_best_practice_guides', {}); 298 | assert.equal(result.content[0].text, baselineResult.content[0].text, 299 | 'Result should be identical regardless of extra params'); 300 | }); 301 | }); 302 | 303 | describe('Content Quality and Completeness', () => { 304 | test('should return comprehensive best practice guide coverage', async () => { 305 | const result = await client.callTool('get_available_best_practice_guides', {}); 306 | const guideArray = JSON.parse(result.content[0].text); 307 | const analysis = bestPracticeAnalyzer.analyzeGuideList(guideArray); 308 | 309 | // Validate completeness 310 | const issues = bestPracticeAnalyzer.validateCompleteness(analysis); 311 | assert.equal(issues.length, 0, `Content issues found: ${issues.join('; ')}`); 312 | 313 | // Verify substantial guide count 314 | assert.ok(analysis.totalGuides >= 13, 315 | `Should have substantial guide count (got ${analysis.totalGuides})`); 316 | }); 317 | 318 | test('should include all critical best practice guides', async () => { 319 | const result = await client.callTool('get_available_best_practice_guides', {}); 320 | const guideArray = JSON.parse(result.content[0].text); 321 | const analysis = bestPracticeAnalyzer.analyzeGuideList(guideArray); 322 | 323 | bestPracticeAnalyzer.criticalGuides.forEach(guideName => { 324 | assert.ok(analysis.foundGuides.has(guideName), 325 | `Missing critical guide: ${guideName}`); 326 | }); 327 | 328 | // All critical guides should be found 329 | assert.equal(analysis.missingCriticalGuides.length, 0, 330 | `Missing critical guides: ${analysis.missingCriticalGuides.join(', ')}`); 331 | }); 332 | 333 | test('should have proper guide structure for all guides', async () => { 334 | const result = await client.callTool('get_available_best_practice_guides', {}); 335 | const guideArray = JSON.parse(result.content[0].text); 336 | 337 | guideArray.forEach((guide, index) => { 338 | // Validate required fields 339 | bestPracticeAnalyzer.requiredFields.forEach(field => { 340 | assert.ok(Object.prototype.hasOwnProperty.call(guide, field), 341 | `Guide ${index} missing field: ${field}`); 342 | assert.equal(typeof guide[field], 'string', 343 | `Guide ${index} field ${field} should be string`); 344 | assert.ok(guide[field].trim().length > 0, 345 | `Guide ${index} field ${field} should not be empty`); 346 | }); 347 | 348 | // Validate name format 349 | assert.ok(/^[a-z][a-z0-9_]*$/.test(guide.name), 350 | `Guide ${index} has invalid name format: ${guide.name}`); 351 | 352 | // Validate title contains "Best Practices" 353 | assert.ok(guide.title.includes('Best Practices'), 354 | `Guide ${index} title should contain "Best Practices": ${guide.title}`); 355 | 356 | // Validate description length 357 | assert.ok(guide.description.length >= 20, 358 | `Guide ${index} description too short: ${guide.description}`); 359 | }); 360 | }); 361 | 362 | test('should cover all major SFCC development areas', async () => { 363 | const result = await client.callTool('get_available_best_practice_guides', {}); 364 | const guideArray = JSON.parse(result.content[0].text); 365 | const analysis = bestPracticeAnalyzer.analyzeGuideList(guideArray); 366 | 367 | // Check each category has coverage 368 | Object.entries(bestPracticeAnalyzer.guideCategories).forEach(([category, expectedGuides]) => { 369 | const foundInCategory = analysis.guidesByCategory[category] || []; 370 | assert.ok(foundInCategory.length > 0, 371 | `Category ${category} should have at least one guide`); 372 | 373 | // Check that most expected guides in category are present 374 | const coverageRatio = foundInCategory.length / expectedGuides.length; 375 | assert.ok(coverageRatio >= 0.5, 376 | `Category ${category} should have good coverage (${foundInCategory.length}/${expectedGuides.length})`); 377 | }); 378 | }); 379 | 380 | test('should not have duplicate or invalid guides', async () => { 381 | const result = await client.callTool('get_available_best_practice_guides', {}); 382 | const guideArray = JSON.parse(result.content[0].text); 383 | const analysis = bestPracticeAnalyzer.analyzeGuideList(guideArray); 384 | 385 | assert.equal(analysis.duplicates.length, 0, 386 | `Found duplicate guides: ${analysis.duplicates.join(', ')}`); 387 | 388 | assert.equal(analysis.invalidGuides.length, 0, 389 | `Found invalid guides: ${analysis.invalidGuides.length}`); 390 | 391 | assert.equal(analysis.validationErrors.length, 0, 392 | `Validation errors: ${analysis.validationErrors.join('; ')}`); 393 | }); 394 | }); 395 | 396 | describe('Content Validation', () => { 397 | test('should include expected core guides', async () => { 398 | const result = await client.callTool('get_available_best_practice_guides', {}); 399 | const guideArray = JSON.parse(result.content[0].text); 400 | const guideNames = guideArray.map(guide => guide.name); 401 | 402 | // Check specific expected guides 403 | const expectedCoreGuides = [ 404 | 'cartridge_creation', 405 | 'sfra_controllers', 406 | 'sfra_models', 407 | 'sfra_client_side_js', 408 | 'sfra_scss', 409 | 'ocapi_hooks', 410 | 'scapi_hooks', 411 | 'security', 412 | 'performance' 413 | ]; 414 | 415 | expectedCoreGuides.forEach(guideName => { 416 | assert.ok(guideNames.includes(guideName), 417 | `Should include core guide: ${guideName}`); 418 | }); 419 | }); 420 | 421 | test('should have meaningful guide descriptions', async () => { 422 | const result = await client.callTool('get_available_best_practice_guides', {}); 423 | const guideArray = JSON.parse(result.content[0].text); 424 | 425 | guideArray.forEach(guide => { 426 | // Description should be substantial 427 | assert.ok(guide.description.length >= 50, 428 | `Guide ${guide.name} description should be substantial: ${guide.description.length} chars`); 429 | 430 | // Description should be related to the guide name 431 | const nameWords = guide.name.split('_'); 432 | const hasRelatedContent = nameWords.some(word => 433 | guide.description.toLowerCase().includes(word.toLowerCase()) 434 | ); 435 | assert.ok(hasRelatedContent || guide.description.toLowerCase().includes('sfcc') || 436 | guide.description.toLowerCase().includes('commerce'), 437 | `Guide ${guide.name} description should be related to guide name or SFCC`); 438 | }); 439 | }); 440 | 441 | test('should provide guides for different skill levels', async () => { 442 | const result = await client.callTool('get_available_best_practice_guides', {}); 443 | const guideArray = JSON.parse(result.content[0].text); 444 | 445 | // Should have guides for beginners (cartridge creation) 446 | const beginnerGuides = guideArray.filter(guide => 447 | guide.name.includes('cartridge') || 448 | guide.description.toLowerCase().includes('introduction') || 449 | guide.description.toLowerCase().includes('getting started') 450 | ); 451 | 452 | // Should have advanced guides (security, performance) 453 | const advancedGuides = guideArray.filter(guide => 454 | guide.name.includes('security') || 455 | guide.name.includes('performance') || 456 | guide.description.toLowerCase().includes('advanced') || 457 | guide.description.toLowerCase().includes('optimization') 458 | ); 459 | 460 | assert.ok(beginnerGuides.length > 0, 'Should have guides for beginners'); 461 | assert.ok(advancedGuides.length > 0, 'Should have guides for advanced users'); 462 | }); 463 | }); 464 | 465 | describe('Error Handling and Edge Cases', () => { 466 | test('should handle malformed parameters gracefully', async () => { 467 | // Test with various edge case parameters that are still valid objects 468 | const edgeCases = [ 469 | {}, // Empty object (valid) 470 | { malformed: 'data' }, 471 | { nested: { deeply: { malformed: 'data' } } }, 472 | { randomField: null }, 473 | { arrayField: [1, 2, 3] }, 474 | { booleanField: true } 475 | ]; 476 | 477 | for (const params of edgeCases) { 478 | const result = await client.callTool('get_available_best_practice_guides', params); 479 | 480 | assertValidMCPResponse(result); 481 | assert.equal(result.isError, false, 482 | `Should handle edge case gracefully: ${JSON.stringify(params)}`); 483 | 484 | // Result should still be valid 485 | const guideArray = JSON.parse(result.content[0].text); 486 | assert.ok(Array.isArray(guideArray), 'Should still return valid array'); 487 | assert.ok(guideArray.length > 0, 'Should still return guides'); 488 | } 489 | }); 490 | 491 | test('should maintain functionality under rapid consecutive calls', async () => { 492 | const rapidCalls = 10; 493 | const promises = Array.from({ length: rapidCalls }, () => 494 | client.callTool('get_available_best_practice_guides', {}) 495 | ); 496 | 497 | const results = await Promise.all(promises); 498 | 499 | results.forEach((result, index) => { 500 | assertValidMCPResponse(result); 501 | assert.equal(result.isError, false, `Rapid call ${index} should succeed`); 502 | 503 | const guideArray = JSON.parse(result.content[0].text); 504 | assert.ok(Array.isArray(guideArray), `Rapid call ${index} should return array`); 505 | assert.ok(guideArray.length > 0, `Rapid call ${index} should return guides`); 506 | }); 507 | 508 | // All results should be identical 509 | const firstResponse = results[0].content[0].text; 510 | results.forEach((result, index) => { 511 | assert.equal(result.content[0].text, firstResponse, 512 | `Rapid call ${index} result should be identical`); 513 | }); 514 | }); 515 | }); 516 | }); 517 | ``` -------------------------------------------------------------------------------- /docs/best-practices/sfra_scss.md: -------------------------------------------------------------------------------- ```markdown 1 | ## SFRA SCSS Best Practices (AI-Agent Optimized) 2 | 3 | Purpose: Provide a concise, implementation‑ready reference for styling and theming Salesforce B2C Commerce SFRA storefronts using SCSS. Optimized for automated reasoning, safe overrides, upgrade resilience, and performant output. 4 | 5 | --- 6 | ## 1. Core Principles (Never Skip) 7 | | Principle | Rule | Why It Exists | Action Pattern | 8 | |-----------|------|---------------|----------------| 9 | | Cartridge Overlay | Never edit `app_storefront_base` | Preserve upgrade path | Create mirror path in custom cartridge + import base first | 10 | | Import First, Then Override | Always `@import '~base/...';` at top | Keeps base layout + utilities | File header snippet template | 11 | | Single Source of Truth | Centralize tokens in `abstracts/_variables.scss` | Global theming + diffable changes | Import base → redefine only changed vars | 12 | | Shallow Specificity | Max selector depth 2–3 | Predictable cascade | Use BEM instead of nested HTML chains | 13 | | Mobile‑First | Base = smallest viewport | Less override churn | Add enhancements with `min-width` media mixin | 14 | | Immutable Generated CSS | Never hand edit `/static/default/css` | Prevent drift | Recompile via build scripts only | 15 | | Deterministic Build | Same inputs ⇒ same CSS | Debug + CI parity | Lock dependency versions | 16 | 17 | Red flag triggers for re‑evaluation: deep nesting (>3), repeated hardcoded colors, `!important`, duplicated breakpoint literals, editing compiled CSS, or missing initial `@import`. 18 | 19 | AI Guardrail (fast reject): If asked to "change base SCSS" directly, respond with an override strategy instead of editing base source. 20 | 21 | --- 22 | ## 2. Minimal Override Workflow (Algorithm) 23 | 1. Inspect element in browser → capture compiled CSS filename (e.g. `product/detail.css`) & selector(s). 24 | 2. Locate source in base: `app_storefront_base/cartridge/client/default/scss/<path>.scss`. 25 | 3. Recreate identical relative path in custom cartridge under `cartridge/client/default/scss/`. 26 | 4. Add header: `@import '~base/<path>';` (must be line 1; no preceding BOM/comment). 27 | 5. Append scoped overrides (BEM, tokens, mixins only—no raw hex unless introducing new token). 28 | 6. Run `npm run compile:scss` (or project alias) → deploy → verify cascade diff. 29 | 7. If change is global (color, spacing, radius) prefer variable override in `_variables.scss` before component override. 30 | 31 | Decision Tree: 32 | | Goal | Location to Change First | Fallback If Not Available | 33 | |------|--------------------------|---------------------------| 34 | | Color / Font / Spacing shift | `_variables.scss` | Component partial override | 35 | | Layout structure | ISML + classes | New component partial | 36 | | Responsive tweak | Mixin breakpoints | Local media query (still via mixin) | 37 | | Reusable visual pattern | New mixin | Placeholder + `@extend` | 38 | | Plugin override | `vendors/` override partial | Scoped component patch | 39 | 40 | Plugin Extension Nuances (Observed from `plugin_wishlists`): 41 | * Keep plugin additions minimal: import base global first inside plugin `global.scss` (`@import "~base/global";`) then only plugin-specific partials. 42 | * Do NOT re-import `variables` inside multiple plugin partials unless you need local compilation context—prefer central import in the entry file to avoid duplication. 43 | * Avoid redefining spacing tokens mid-file (e.g. `$spacer` inside a feature file) unless intentionally creating a component-scoped token; document with a comment `// local token`. 44 | * Consolidate repeated keyframes: If multiple alerts use identical fade animation, define it once in a shared partial (e.g. `components/_animations.scss`) instead of inline per component. 45 | * When plugin needs base mixins (e.g. Bootstrap breakpoints) prefer `@import "~base/global";` rather than directly importing bootstrap again to reduce risk of version drift. 46 | 47 | --- 48 | ## 3. Canonical SCSS Directory (7‑1 Adapted) 49 | ``` 50 | scss/ 51 | abstracts/ (_variables.scss, _mixins.scss, _functions.scss) 52 | base/ (_reset.scss, _typography.scss, _base.scss) 53 | layout/ (_header.scss, _footer.scss, _navigation.scss, _grid.scss) 54 | components/ (_buttons.scss, _product-tile.scss, _carousel.scss, ...) 55 | pages/ (_home.scss, _checkout.scss, _pdp.scss, ...) 56 | vendors/ (_bootstrap-overrides.scss, _plugin-X.scss) 57 | global.scss (imports only – no rules) 58 | ``` 59 | 60 | Import Order (global.scss): 61 | 1. abstracts (variables → mixins → functions) 62 | 2. vendors 63 | 3. base 64 | 4. layout 65 | 5. components 66 | 6. pages 67 | 68 | SFRA Reality Snapshot: 69 | The core `global.scss` in `app_storefront_base` imports: `variables`, `skin/skinVariables`, bootstrap custom, overrides, utilities, icon libraries, then individual component partials. AI agents should treat this as the authoritative ordering when composing custom `global.scss` overlays: always import base `global` first in plugin/global contexts, then plugin additions. 70 | 71 | Rule: No CSS selectors in `global.scss`; only `@import`. 72 | 73 | --- 74 | ## 4. Naming & Specificity Strategy (BEM + SFRA Conventions) 75 | | Aspect | Rule | Example | 76 | |--------|------|---------| 77 | | Block | Semantic, domain concept | `.product-tile` | 78 | | Element | `__` double underscore | `.product-tile__price` | 79 | | Modifier | `--` double hyphen | `.product-tile--featured` | 80 | | State (JS) | `is-` prefix (transient) | `.is-loading` | 81 | | Utility | `u-` + purpose | `.u-hidden` | 82 | 83 | Guidelines: 84 | * Avoid HTML element chaining: prefer `.mini-cart__count` not `header .mini-cart span.count`. 85 | * Never exceed 3 levels of specificity (block + element + modifier/state). 86 | * Do not style IDs; reserve for scripting anchor only if necessary. 87 | * Existing SFRA partials sometimes nest deeper (e.g. `.product-tile .tile-body .price .tiered .value`). When extending these, do NOT replicate entire nesting; extract just the block root with a modifier class you control (e.g. `.product-tile--compact`). 88 | * For legacy classes not following BEM (e.g. `.wishlistTile`), retain original naming but annotate exception with justification comment to aid future refactor and silence lint selectively—in code: `/* stylelint-disable-line */`. 89 | 90 | --- 91 | ## 5. Token & Theme Management 92 | Override pattern (`abstracts/_variables.scss`): 93 | ```scss 94 | @import '~base/variables'; // must stay first 95 | // Theme – Colors 96 | $primary: #0B5FFF; // Brand blue 97 | $secondary: #00A676; 98 | $body-bg: #F8F9FA; 99 | $body-color: #212529; 100 | // Typography 101 | $font-family-sans-serif: 'Inter', 'Helvetica Neue', Arial, sans-serif; 102 | $font-family-serif: 'Merriweather', Georgia, serif; 103 | // Radii / Spacing 104 | $border-radius: 2px; 105 | $btn-padding-y: 0.5rem; 106 | $btn-padding-x: 1.125rem; 107 | ``` 108 | Rules: 109 | * Only override variables you intentionally change. 110 | * Introduce new tokens with consistent naming (`$color-*`, `$space-*`, `$z-*`). 111 | * Never redefine variables in component partials—centralize. 112 | * If a plugin must create a small local scalar (e.g. `$spacer` for a one-off layout tweak), prefix with component context when possible (`$wishlist-spacer`) to avoid collision with global tokens. 113 | * Avoid re-importing base variables in each partial—imports are concatenated; repeated imports increase compile time and risk inconsistent overrides if order diverges. 114 | 115 | --- 116 | ## 6. Reuse Mechanisms (Decision Matrix) 117 | | Need | Use | Why | Avoid If | 118 | |------|-----|-----|----------| 119 | | Static property bundle reused widely | `%placeholder` + `@extend` | Single output selector group | Needs argumentization | 120 | | Dynamic pattern w/ params | `@mixin` | Flexible + readable | Output duplication causes bloat | 121 | | Global theming value | `$variable` | Single update point | Represents multi-rule semantic block | 122 | | Conditional computed value | `@function` | Returns scalar for calc chains | Can be plain variable | 123 | 124 | Responsive Mixin: 125 | ```scss 126 | $breakpoints: ( 127 | sm: 576px, 128 | md: 768px, 129 | lg: 992px, 130 | xl: 1200px 131 | ) !default; 132 | 133 | @mixin mq($bp) { 134 | @if map-has-key($breakpoints, $bp) { 135 | @media (min-width: map-get($breakpoints, $bp)) { @content; } 136 | } @else { @warn 'Unknown breakpoint: #{$bp}'; } 137 | } 138 | ``` 139 | 140 | Usage: 141 | ```scss 142 | .product-tile { width: 100%; @include mq(md) { width: 50%; } } 143 | ``` 144 | Bootstrap Breakpoints Interop: 145 | SFRA base uses Bootstrap's `media-breakpoint-*` mixins. When generating code referencing base breakpoints, prefer those existing mixins (`@include media-breakpoint-down(md) { ... }`) if the base already depends on Bootstrap, instead of introducing a parallel custom map to avoid divergence. Use the custom `$breakpoints` + `mq()` pattern only if project intentionally decouples from Bootstrap. 146 | 147 | --- 148 | ## 7. Example Override (PDP Title) 149 | File: `product/detail.scss` 150 | ```scss 151 | @import '~base/product/detail'; // inherit base 152 | 153 | .product-detail__name { 154 | font-family: $font-family-serif; 155 | font-size: 2.4rem; 156 | color: $primary; 157 | } 158 | ``` 159 | Compiled file resolved via `assets.addCss('/css/product/detail.css')` with cartridge path precedence—no ISML changes needed. 160 | AI Validation Step: After generation, assert snippet begins with base import and contains only new selectors or modified declarations—no duplication of entire base blocks. 161 | 162 | --- 163 | ## 8. Component Patterns Library (Sample Snippets) 164 | Buttons (`components/_buttons.scss`): 165 | ```scss 166 | @import '~base/components/buttons'; 167 | 168 | .btn--pill { border-radius: 999rem; } 169 | .btn--outline-secondary { 170 | color: $secondary; background: transparent; border: 1px solid $secondary; 171 | &:hover { background: $secondary; color: #fff; } 172 | } 173 | ``` 174 | 175 | Product Tile (`components/_product-tile.scss`): 176 | ```scss 177 | @import '~base/components/product-tile'; 178 | 179 | .product-tile { 180 | border: 1px solid transparent; transition: box-shadow .2s; 181 | &:hover { box-shadow: 0 4px 12px rgba(0,0,0,.1); border-color: $gray-300; } 182 | &__price--sale { color: $danger; font-weight: 600; } 183 | } 184 | 185 | Refactor Tip: Instead of deeply nested overrides inside `.product-tile .tile-body .price .tiered .value`, target a purposeful modifier class (`.product-tile__price--tiered-value`) you add in ISML for long-term maintainability. 186 | ``` 187 | 188 | Checkout (`pages/_checkout.scss`): 189 | ```scss 190 | @import '~base/pages/checkout'; 191 | .checkout-nav .nav-link { background:$gray-200; color:$gray-600; 192 | &.active { background:$primary; color:#fff; } 193 | } 194 | ``` 195 | 196 | --- 197 | ## 9. Responsive Strategy 198 | | Rule | Implementation | 199 | |------|----------------| 200 | | Mobile‑first | Base styles = small viewport | 201 | | Progressive enhancement | `@include mq(md){...}` not `max-width` overrides | 202 | | Centralized breakpoints | `$breakpoints` map only | 203 | | Avoid pixel drift | Use tokens, not ad‑hoc numbers | 204 | 205 | Anti‑Pattern: Desktop CSS first + mobile overrides via `@media (max-width)` – leads to override churn. 206 | Plugin Reality: Wishlist plugin currently mixes `media-breakpoint-up` and `media-breakpoint-down` usage; maintain consistency—choose mobile-first pattern (prefer `-up` for enhancements) and refactor legacy `-down` only when safe. 207 | 208 | --- 209 | ## 10. Accessibility & Inclusive Styling 210 | | Concern | Checklist | 211 | |---------|-----------| 212 | | Color Contrast | Ensure WCAG AA (> 4.5:1 body, 3:1 large text). Token review before merge. | 213 | | Focus States | Visible focus ring; never remove outline without replacement. | 214 | | Reduced Motion | Gate large transitions with `@media (prefers-reduced-motion: reduce)` | 215 | | Hidden Content | Use utility `.u-visually-hidden` for screen-reader only text; avoid `display:none` for needed ARIA live content. | 216 | | Touch Targets | Minimum 44px height for interactive elements (buttons, filters). | 217 | 218 | Utility Example: 219 | ```scss 220 | .u-visually-hidden { position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; } 221 | ``` 222 | Animation Respect: 223 | Provide a reduced-motion alternative when introducing keyframe animations (e.g. fading alerts). Centralize keyframes and wrap any large movement inside `@media (prefers-reduced-motion: no-preference)`; fallback: immediate end state. 224 | 225 | --- 226 | ## 11. Performance Practices 227 | | Optimization | Action | Tooling | 228 | |--------------|--------|---------| 229 | | Critical CSS | Generate subset for key templates → inline via `store-skin` asset | `npm i critical` | 230 | | Prune Unused CSS | Integrate PurgeCSS scanning ISML & JS | PurgeCSS plugin | 231 | | Minification | Ensure prod build via `sgmf-scripts` produces minified bundle | CI check size | 232 | | Source Maps (dev only) | Enable for DX; disable prod | Sass compiler flag | 233 | | Font Strategy | Use WOFF2 + subsetting + `font-display: swap` | Font tooling | 234 | 235 | Suggested CI Budget: Global CSS ≤ 250KB uncompressed (enterprise baseline) – fail build if regression > +15% week‑over‑week. 236 | Duplicate Import Scan: Add a CI grep step to detect repeated `@import "~base/variables";` inside plugin component partials—emit warning to consolidate. 237 | 238 | --- 239 | ## 12. Linting & Quality Automation 240 | Install: 241 | ``` 242 | npm i -D stylelint stylelint-config-standard-scss stylelint-declaration-strict-value 243 | ``` 244 | Config (`.stylelintrc.json`): 245 | ```json 246 | { 247 | "extends": "stylelint-config-standard-scss", 248 | "plugins": ["stylelint-declaration-strict-value"], 249 | "rules": { 250 | "selector-class-pattern": ["^([a-z][a-z0-9]*)(__(?:[a-z0-9]+))?(--[a-z0-9-]+)?$", {"message": "Use BEM: block__element--modifier"}], 251 | "max-nesting-depth": 2, 252 | "declaration-no-important": true, 253 | "scale-unlimited/declaration-strict-value": [["color", "background-color", "border-color", "font-size"], {"ignoreValues": ["inherit", "transparent", "currentColor"]}] 254 | } 255 | } 256 | ``` 257 | Script: 258 | ```json 259 | "scripts": { "lint:scss": "stylelint 'cartridges/**/*.scss' --cache" } 260 | ``` 261 | Optional: pre‑commit hook with Husky + lint-staged. 262 | 263 | AI Generation Rule Set: 264 | When asked to produce SCSS: 265 | 1. Always start with required base import (unless file is `abstracts/_variables.scss`). 266 | 2. Do not inline raw colors already expressible as existing tokens—prefer token reference. 267 | 3. Replace any suggested `!important` with a cascade adjustment strategy comment. 268 | 4. Provide at most one new local token; advise moving to global if reused >2 times. 269 | 5. Add a 2-line header comment with PURPOSE + SAFE REMOVE classification. 270 | 271 | --- 272 | ## 13. Anti‑Patterns (Refactor Immediately) 273 | | Smell | Why Dangerous | Fix | 274 | |-------|---------------|-----| 275 | | Editing compiled CSS | Lost on next build | Modify SCSS source | 276 | | Missing base import | Breaks structural inheritance | Add `@import '~base/...';` line 1 | 277 | | Deep selector chains | Specificity lock-in | Convert to BEM blocks/elements | 278 | | Repeated hex codes | Inconsistent theming | Promote to variable | 279 | | `!important` usage | Overrides cascade discipline | Reduce specificity / re-order imports | 280 | | Inline media queries w/ literals | Hard to refactor | Use `mq()` mixin | 281 | | Component partial modifies unrelated block | Hidden coupling | Create new partial / utility | 282 | | Re-importing base variables in every plugin partial | Build bloat / risk inconsistent overrides | Import once at entry (e.g. plugin `global.scss`) | 283 | | Inline duplicated keyframes | Larger bundle & maintenance overhead | Centralize in shared `components/_animations.scss` | 284 | 285 | --- 286 | ## 14. Troubleshooting Matrix 287 | | Symptom | Likely Cause | Diagnostic | Resolution | 288 | |---------|--------------|-----------|-----------| 289 | | Style not applied | Not in compiled CSS | Search compiled file for selector | Check import order / rebuild | 290 | | Base style lost | Missing base import | Inspect diff of partial | Add import + revert overrides | 291 | | Wrong theme color persisting | Variable shadowed locally | Grep for variable redefinition | Centralize in `_variables.scss` | 292 | | Breakpoint shift inconsistent | Hardcoded px values | `grep -R "992px"` | Replace with token map | 293 | | Layout jumps FOUC | Critical CSS missing | Lighthouse trace | Implement store-skin inline CSS | 294 | | Repeated variable override ignored | Later import overrides earlier | Trace import graph order | Consolidate variable overrides at earliest import point | 295 | 296 | --- 297 | ## 15. Promptable Action Snippets (For AI Agents) 298 | | Intent | Prompt Template | 299 | |--------|-----------------| 300 | | Create component override | "Generate SFRA SCSS override for `<component>` (base path `<path>`). Include base import and BEM modifiers for states: loading, error." | 301 | | Introduce new theme color | "Add semantic token for highlight color with accessible contrast against `$body-bg` and update button hover style." | 302 | | Refactor deep selectors | "Refactor selector `<selector>` into BEM with max depth 2; preserve semantics." | 303 | | Add responsive variant | "Extend `.product-tile` to show 2 columns at md, 4 at lg via existing breakpoint mixin." | 304 | | Performance audit | "List top 10 heaviest SCSS partials by compiled size estimate and suggest consolidation." | 305 | | Plugin extension | "Generate wishlist plugin SCSS override importing '~base/global' then add toast animation using existing base token palette; avoid duplicating keyframes if fade already exists." | 306 | 307 | --- 308 | ## 16. Migration Checklist (Before Deploy) 309 | [] All overrides start with base import 310 | [] No direct edits to `app_storefront_base` 311 | [] Variables overridden centrally only 312 | [] Lint passes (no warnings in CI) 313 | [] No `!important` (or justified + documented) 314 | [] CSS bundle size budget respected 315 | [] Critical CSS updated for homepage + PDP 316 | [] Source maps disabled for production 317 | [] New utilities documented (inline comment header) 318 | [] No duplicate keyframe blocks with identical declarations 319 | [] Plugin global includes base global exactly once 320 | 321 | --- 322 | ## 17. Reference Quick Commands 323 | ``` 324 | # Compile SCSS 325 | npm run compile:scss 326 | 327 | # Lint SCSS 328 | npm run lint:scss 329 | 330 | # Find hardcoded colors (non-variable) 331 | // in app_custom/cartridge/client/default/scss/abstracts/_variables.scss 332 | 333 | # Estimate partial usage (appearance in compiled) 334 | @import '~base/variables'; 335 | ``` 336 | 337 | CI Grep Helpers (Optional): 338 | ``` 339 | grep -R "@import \"~base/variables\"" cartridges/plugin_* | sort 340 | grep -R "@keyframes fade" cartridges/ | wc -l 341 | ``` 342 | 343 | --- 344 | ## 18. Secure & Maintainable Patterns 345 | | Concern | Guidance | 346 | |---------|----------| 347 | | Multi-brand scaling | Abstract brand diffs to dedicated `_brand-<id>.scss` imported conditionally via build flag | 348 | | A/B testing CSS | Isolate experiment layer in `components/experiments/` with clear removal date comment | 349 | | Plugin overrides | Keep each plugin override in `vendors/_<plugin>-overrides.scss` with upstream version tag | 350 | | Future SFRA upgrade | Diff only variable + override partials; never fork base files wholesale | 351 | 352 | Header Annotation Block (optional for generated partial): 353 | ```scss 354 | // PURPOSE: Override of base product tile for brand visual refinement 355 | // DEPENDS: ~base/components/product-tile 356 | // SAFE REMOVE: Yes (reverts to base visual style) 357 | ``` 358 | Classification Legend: 359 | * SAFE REMOVE: Removing file reverts to acceptable base styling. 360 | * CONDITIONAL: Removal affects brand contract (document impact). 361 | * CRITICAL: Provides accessibility or functional styling (never remove without replacement). 362 | 363 | --- 364 | ## 19. When NOT to Override 365 | | Scenario | Better Option | 366 | |----------|---------------| 367 | | Structural HTML limitation | Adjust ISML markup first | 368 | | Repeated spacing hacks | Introduce spacing scale utility classes | 369 | | Overriding grid internals | Use flexbox utilities / custom wrapper | 370 | | One-off promotional layout | Page partial (`pages/_promo-<slug>.scss`) + sunset note | 371 | | Behavior actually JS-driven (e.g. show/hide) | Adjust JS or data attributes; keep CSS purely presentational | 372 | 373 | --- 374 | 375 | ## 20. AI Generation Quick Checklist (Inline in Responses) 376 | Provide along with any generated SCSS so humans can validate rapidly: 377 | 1. Base Import Present? (Yes/No) 378 | 2. Variable Overrides? (List or None) 379 | 3. New Tokens Introduced? (List or None) 380 | 4. Max Nesting Depth (#) 381 | 5. Uses Existing Breakpoint Mixins? (Yes/No) 382 | 6. Any Raw Hex Values? (List or None + justification) 383 | 7. Keyframes Added? (Name or None; already exists?) 384 | 8. Accessibility Considerations? (Focus/contrast/motion) 385 | 9. Removable Classification (SAFE REMOVE / CONDITIONAL / CRITICAL) 386 | 10. Lint‑Sensitive Areas (Exceptions with reason) 387 | 388 | If any answer violates a stated rule, propose an auto-correction diff, not just a warning. 389 | ``` -------------------------------------------------------------------------------- /tests/mcp/node/get-latest-warn.full-mode.programmatic.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { test, describe, before, after, beforeEach } from 'node:test'; 2 | import { strict as assert } from 'node:assert'; 3 | import { connect } from 'mcp-aegis'; 4 | 5 | describe('get_latest_warn - Full Mode Programmatic Tests', () => { 6 | let client; 7 | 8 | before(async () => { 9 | client = await connect('./aegis.config.with-dw.json'); 10 | }); 11 | 12 | after(async () => { 13 | if (client?.connected) { 14 | await client.disconnect(); 15 | } 16 | }); 17 | 18 | beforeEach(() => { 19 | // CRITICAL: Clear all buffers to prevent leaking into next tests 20 | client.clearAllBuffers(); // Recommended - comprehensive protection 21 | }); 22 | 23 | // Helper functions for common validations 24 | function assertValidMCPResponse(result) { 25 | assert.ok(result.content, 'Should have content'); 26 | assert.ok(Array.isArray(result.content), 'Content should be array'); 27 | assert.equal(typeof result.isError, 'boolean', 'isError should be boolean'); 28 | } 29 | 30 | function assertErrorResponse(result, expectedErrorText) { 31 | assertValidMCPResponse(result); 32 | assert.equal(result.isError, true, 'Should be an error response'); 33 | assert.equal(result.content[0].type, 'text'); 34 | assert.ok(result.content[0].text.includes(expectedErrorText), 35 | `Expected error text "${expectedErrorText}" in "${result.content[0].text}"`); 36 | } 37 | 38 | function assertSuccessResponse(result) { 39 | assertValidMCPResponse(result); 40 | assert.equal(result.isError, false, 'Should not be an error response'); 41 | assert.equal(result.content[0].type, 'text'); 42 | } 43 | 44 | function assertLogFormat(result, expectedLimit) { 45 | assertSuccessResponse(result); 46 | const text = result.content[0].text; 47 | 48 | // Should contain the expected limit message 49 | assert.ok(text.includes(`Latest ${expectedLimit} warn messages`), 50 | `Should mention "${expectedLimit}" warn messages`); 51 | 52 | // Should contain log file name pattern 53 | assert.ok(/warn-blade-\d{8}-\d{6}\.log/.test(text), 54 | 'Should contain warn log file name pattern'); 55 | 56 | // Should contain WARN level entries 57 | assert.ok(text.includes('WARN'), 'Should contain WARN level entries'); 58 | 59 | // Should contain GMT timestamps 60 | assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} GMT/.test(text), 61 | 'Should contain GMT timestamp pattern'); 62 | } 63 | 64 | // Helper function to get current date in YYYYMMDD format 65 | function getCurrentDateString() { 66 | const now = new Date(); 67 | const year = now.getFullYear(); 68 | const month = String(now.getMonth() + 1).padStart(2, '0'); 69 | const day = String(now.getDate()).padStart(2, '0'); 70 | return `${year}${month}${day}`; 71 | } 72 | 73 | // Core functionality tests - focused on programmatic strengths 74 | describe('Core Functionality', () => { 75 | test('should retrieve latest warn messages with default parameters', async () => { 76 | const result = await client.callTool('get_latest_warn', {}); 77 | 78 | assertLogFormat(result, 10); // Default limit is 10 79 | 80 | // Should contain SFCC-specific patterns 81 | const text = result.content[0].text; 82 | assert.ok(/PipelineCallServlet|SystemJobThread/.test(text), 83 | 'Should contain SFCC thread patterns'); 84 | assert.ok(text.includes('Sites-'), 'Should contain Sites information'); 85 | }); 86 | 87 | test('should handle comprehensive parameter combinations', async () => { 88 | const paramCombinations = [ 89 | { limit: 3 }, 90 | { date: getCurrentDateString(), limit: 2 }, 91 | { limit: 50 }, 92 | {} // default parameters 93 | ]; 94 | 95 | const results = []; 96 | 97 | // Execute calls sequentially (never use Promise.all with MCP!) 98 | for (const params of paramCombinations) { 99 | const result = await client.callTool('get_latest_warn', params); 100 | results.push({ params, result }); 101 | } 102 | 103 | // All should have consistent structure 104 | results.forEach(({ params, result }, index) => { 105 | assertValidMCPResponse(result); 106 | assert.equal(result.isError, false, `Call ${index} should not be error`); 107 | assert.equal(result.content[0].type, 'text', `Call ${index} should have text content`); 108 | 109 | const expectedLimit = params.limit || 10; 110 | assert.ok(result.content[0].text.includes(`Latest ${expectedLimit} warn messages`), 111 | `Call ${index} should contain 'Latest ${expectedLimit}' in response`); 112 | }); 113 | }); 114 | }); 115 | 116 | // Error handling and edge cases - key validation scenarios 117 | describe('Error Handling and Edge Cases', () => { 118 | test('should handle parameter validation errors correctly', async () => { 119 | const errorCases = [ 120 | { params: { limit: '5' }, expectedError: 'Invalid limit \'5\' for get_latest_warn. Must be a valid number' }, 121 | { params: { limit: 0 }, expectedError: 'Invalid limit \'0\' for get_latest_warn. Must be between 1 and 1000' }, 122 | { params: { limit: -5 }, expectedError: 'Invalid limit \'-5\'' }, 123 | { params: { limit: 9999 }, expectedError: 'Invalid limit' } 124 | ]; 125 | 126 | for (const { params, expectedError } of errorCases) { 127 | const result = await client.callTool('get_latest_warn', params); 128 | assertErrorResponse(result, expectedError); 129 | } 130 | }); 131 | 132 | test('should handle edge cases without breaking server state', async () => { 133 | const edgeCases = [ 134 | { date: '' }, // Empty date 135 | { date: '2024-01-01' }, // Invalid format 136 | { date: '20251231' }, // Future date 137 | { invalid: 'param' } // Invalid parameter name 138 | ]; 139 | 140 | // Test all edge cases sequentially 141 | for (const testCase of edgeCases) { 142 | const result = await client.callTool('get_latest_warn', testCase); 143 | assertValidMCPResponse(result); 144 | // Some may succeed with no data, some may have default behavior - but none should crash 145 | } 146 | 147 | // Verify tool still works after edge cases 148 | const finalResult = await client.callTool('get_latest_warn', { limit: 1 }); 149 | assertSuccessResponse(finalResult); 150 | }); 151 | }); 152 | 153 | // Advanced content analysis - leveraging programmatic strengths 154 | describe('SFCC Content Analysis', () => { 155 | test('should contain and analyze SFCC-specific warning patterns', async () => { 156 | const result = await client.callTool('get_latest_warn', { limit: 5 }); 157 | 158 | assertSuccessResponse(result); 159 | const text = result.content[0].text; 160 | 161 | // SFCC-specific validation patterns for warnings 162 | const sfccWarningPatterns = [ 163 | /Sites-\w+/, // Sites names 164 | /PipelineCallServlet|SystemJobThread/, // Thread types 165 | /\|\d+\|/, // Thread IDs 166 | /custom \[\]/, // Custom category 167 | /inventory low|Content asset|offline/i // Common warning types 168 | ]; 169 | 170 | const matchedPatterns = sfccWarningPatterns.filter(pattern => pattern.test(text)); 171 | assert.ok(matchedPatterns.length >= 2, 172 | `Should match at least 2 SFCC warning patterns. Matched: ${matchedPatterns.length}`); 173 | 174 | // Analyze log structure elements 175 | assert.ok(/warn-blade-.*\.log/.test(text), 'Should include log file name'); 176 | assert.ok(/\d{4}-\d{2}-\d{2}/.test(text), 'Should include timestamp'); 177 | assert.ok(/GMT/.test(text), 'Should include GMT timezone'); 178 | assert.ok(/WARN PipelineCallServlet/.test(text), 'Should include servlet context'); 179 | 180 | // Count separators - should have appropriate separators for multiple entries 181 | const separators = text.match(/---/g); 182 | assert.ok(separators && separators.length >= 1, 183 | `Should have separators for multiple entries. Found: ${separators?.length || 0}`); 184 | }); 185 | 186 | test('should validate warning content quality and context', async () => { 187 | const result = await client.callTool('get_latest_warn', { limit: 3 }); 188 | assertSuccessResponse(result); 189 | 190 | const text = result.content[0].text; 191 | const lines = text.split('\n').filter(line => line.trim().length > 0); 192 | 193 | // Dynamic validation based on actual content 194 | const warnLines = lines.filter(line => line.includes('WARN')); 195 | assert.ok(warnLines.length > 0, 'Should have at least one WARN line'); 196 | 197 | // Each warn line should have proper structure 198 | warnLines.forEach((line, index) => { 199 | assert.ok(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(line), 200 | `Warn line ${index} should contain timestamp: ${line}`); 201 | }); 202 | 203 | // Warning-specific content analysis 204 | const warningIndicators = [ 205 | 'inventory low', 206 | 'Content asset', 207 | 'offline', 208 | 'deprecated' 209 | ]; 210 | 211 | const foundIndicators = warningIndicators.filter(indicator => 212 | text.toLowerCase().includes(indicator.toLowerCase()) 213 | ); 214 | assert.ok(foundIndicators.length > 0, 215 | `Should contain warning indicators. Found: ${foundIndicators.join(', ')}`); 216 | }); 217 | }); 218 | 219 | // Multi-step workflows - where programmatic tests excel 220 | describe('Multi-Step Workflows and State Management', () => { 221 | test('should support comprehensive warning analysis workflow', async () => { 222 | // Step 1: Get recent warnings with small limit 223 | const recentWarnings = await client.callTool('get_latest_warn', { limit: 2 }); 224 | assertSuccessResponse(recentWarnings); 225 | 226 | // Step 2: Get more comprehensive warning list 227 | const comprehensiveWarnings = await client.callTool('get_latest_warn', { limit: 10 }); 228 | assertSuccessResponse(comprehensiveWarnings); 229 | 230 | // Step 3: Get warnings for specific date 231 | const dateSpecificWarnings = await client.callTool('get_latest_warn', { 232 | date: getCurrentDateString(), 233 | limit: 5 234 | }); 235 | assertSuccessResponse(dateSpecificWarnings); 236 | 237 | // Cross-analysis: Verify pattern consistency across different scopes 238 | const recentText = recentWarnings.content[0].text; 239 | const comprehensiveText = comprehensiveWarnings.content[0].text; 240 | const dateText = dateSpecificWarnings.content[0].text; 241 | 242 | [recentText, comprehensiveText, dateText].forEach((text, index) => { 243 | assert.ok(text.includes('WARN'), `Analysis ${index} should contain WARN level`); 244 | assert.ok(/Sites-/.test(text), `Analysis ${index} should contain Sites information`); 245 | assert.ok(/PipelineCallServlet/.test(text), `Analysis ${index} should contain servlet context`); 246 | }); 247 | 248 | // Advanced: Verify scope scaling 249 | const recentCount = (recentText.match(/WARN/g) || []).length; 250 | const comprehensiveCount = (comprehensiveText.match(/WARN/g) || []).length; 251 | assert.ok(comprehensiveCount >= recentCount, 252 | 'Comprehensive analysis should include at least as many warnings as recent analysis'); 253 | }); 254 | 255 | test('should maintain server state integrity across sequential operations', async () => { 256 | const operationSequence = [ 257 | { params: { limit: 1 }, description: 'Single warning check' }, 258 | { params: { limit: 20 }, description: 'Large batch analysis' }, 259 | { params: { limit: 0 }, description: 'Error condition', expectError: true }, 260 | { params: { limit: 5 }, description: 'Recovery operation' }, 261 | { params: { date: getCurrentDateString(), limit: 3 }, description: 'Date-specific analysis' } 262 | ]; 263 | 264 | const results = []; 265 | 266 | // Execute operations sequentially to test state management 267 | for (const operation of operationSequence) { 268 | const result = await client.callTool('get_latest_warn', operation.params); 269 | results.push({ 270 | operation: operation.description, 271 | params: operation.params, 272 | success: !result.isError, 273 | expectedError: operation.expectError || false, 274 | result 275 | }); 276 | } 277 | 278 | // Analyze operation results for state integrity 279 | results.forEach((opResult, index) => { 280 | if (opResult.expectedError) { 281 | assert.equal(opResult.success, false, 282 | `Operation ${index} (${opResult.operation}) should have failed as expected`); 283 | } else { 284 | assert.equal(opResult.success, true, 285 | `Operation ${index} (${opResult.operation}) should have succeeded`); 286 | 287 | // Verify state consistency for successful operations 288 | const text = opResult.result.content[0].text; 289 | const expectedLimit = opResult.params.limit || 10; 290 | assert.ok(text.includes(`Latest ${expectedLimit} warn messages`), 291 | `Operation ${index} should show correct limit: ${expectedLimit}`); 292 | } 293 | }); 294 | 295 | // Final verification: Server should still be fully functional 296 | const finalCheck = await client.callTool('get_latest_warn', { limit: 1 }); 297 | assertSuccessResponse(finalCheck); 298 | }); 299 | }); 300 | 301 | // Operational monitoring and resilience - complex business logic validation 302 | describe('Operational Monitoring and Resilience', () => { 303 | test('should provide comprehensive monitoring capabilities', async () => { 304 | // Simulate different monitoring scenarios 305 | const monitoringScenarios = [ 306 | { limit: 1, description: 'Latest warning check', alertLevel: 'immediate' }, 307 | { limit: 10, description: 'Recent warnings review', alertLevel: 'hourly' }, 308 | { limit: 50, description: 'Comprehensive warning analysis', alertLevel: 'daily' } 309 | ]; 310 | 311 | const monitoringResults = []; 312 | 313 | for (const scenario of monitoringScenarios) { 314 | const result = await client.callTool('get_latest_warn', { limit: scenario.limit }); 315 | 316 | const analysisResult = { 317 | scenario: scenario.description, 318 | alertLevel: scenario.alertLevel, 319 | limit: scenario.limit, 320 | success: !result.isError, 321 | warningCount: 0, 322 | hasWarnings: false, 323 | sfccPatterns: 0 324 | }; 325 | 326 | if (!result.isError) { 327 | const text = result.content[0].text; 328 | analysisResult.hasWarnings = text.includes('WARN'); 329 | analysisResult.warningCount = (text.match(/WARN/g) || []).length; 330 | 331 | // Count SFCC-specific patterns for monitoring quality 332 | const sfccPatterns = [/Sites-/, /PipelineCallServlet/, /custom \[\]/]; 333 | analysisResult.sfccPatterns = sfccPatterns.filter(pattern => pattern.test(text)).length; 334 | } 335 | 336 | monitoringResults.push(analysisResult); 337 | } 338 | 339 | // Validate monitoring effectiveness 340 | monitoringResults.forEach(result => { 341 | assert.ok(result.success, `${result.scenario} should succeed`); 342 | assert.ok(result.hasWarnings, `${result.scenario} should contain warnings`); 343 | assert.ok(result.sfccPatterns >= 1, `${result.scenario} should contain SFCC patterns`); 344 | }); 345 | 346 | // Verify monitoring scope increases with limit 347 | assert.ok(monitoringResults[0].limit < monitoringResults[1].limit, 348 | 'Monitoring scope should increase'); 349 | assert.ok(monitoringResults[1].limit < monitoringResults[2].limit, 350 | 'Comprehensive analysis should have largest scope'); 351 | }); 352 | 353 | test('should handle resilience scenarios and error recovery', async () => { 354 | // Test normal operation baseline 355 | const baselineResult = await client.callTool('get_latest_warn', { limit: 3 }); 356 | assertSuccessResponse(baselineResult); 357 | 358 | // Test various failure scenarios and recovery 359 | const resilienceTests = [ 360 | { params: { limit: 0 }, shouldFail: true, description: 'Zero limit validation' }, 361 | { params: { limit: '1' }, shouldFail: true, description: 'Type validation' }, 362 | { params: { limit: 99999 }, shouldFail: true, description: 'Range validation' }, 363 | { params: { date: '' }, shouldFail: false, description: 'Empty date handling' }, 364 | { params: { invalid: 'param' }, shouldFail: false, description: 'Unknown parameter handling' } 365 | ]; 366 | 367 | let failureCount = 0; 368 | let recoveryCount = 0; 369 | 370 | for (const resilienceTest of resilienceTests) { 371 | const result = await client.callTool('get_latest_warn', resilienceTest.params); 372 | 373 | if (resilienceTest.shouldFail) { 374 | assert.equal(result.isError, true, 375 | `${resilienceTest.description} should fail as expected`); 376 | failureCount++; 377 | } else { 378 | assert.equal(result.isError, false, 379 | `${resilienceTest.description} should succeed`); 380 | } 381 | 382 | // Test recovery after each scenario 383 | const recoveryResult = await client.callTool('get_latest_warn', { limit: 1 }); 384 | assert.equal(recoveryResult.isError, false, 385 | `Recovery after ${resilienceTest.description} should work`); 386 | recoveryCount++; 387 | } 388 | 389 | // Verify resilience metrics 390 | assert.ok(failureCount >= 3, 'Should have tested multiple failure scenarios'); 391 | assert.equal(recoveryCount, resilienceTests.length, 'All recovery tests should pass'); 392 | 393 | // Final comprehensive recovery test 394 | const finalResult = await client.callTool('get_latest_warn', { limit: 5 }); 395 | assertSuccessResponse(finalResult); 396 | 397 | const finalText = finalResult.content[0].text; 398 | assert.ok(finalText.includes('Latest 5 warn messages'), 'Final recovery should work correctly'); 399 | assert.ok(finalText.includes('WARN'), 'Final recovery should contain valid warning data'); 400 | }); 401 | 402 | test('should provide detailed data quality analysis across different parameters', async () => { 403 | const qualityTests = [ 404 | { params: { limit: 2 }, minWarnings: 1, description: 'Small sample quality' }, 405 | { params: { limit: 10 }, minWarnings: 3, description: 'Standard sample quality' }, 406 | { params: { date: getCurrentDateString(), limit: 5 }, minWarnings: 1, description: 'Date-specific quality' } 407 | ]; 408 | 409 | const qualityResults = []; 410 | 411 | for (const qualityTest of qualityTests) { 412 | const result = await client.callTool('get_latest_warn', qualityTest.params); 413 | assertSuccessResponse(result); 414 | 415 | const text = result.content[0].text; 416 | 417 | // Quality metrics 418 | const qualityMetrics = { 419 | hasLogFileName: /warn-blade-.*\.log/.test(text), 420 | hasTimestamp: /\d{4}-\d{2}-\d{2}/.test(text), 421 | hasSiteInfo: /Sites-/.test(text), 422 | hasWarnLevel: text.includes('WARN'), 423 | hasGMTTimezone: text.includes('GMT'), 424 | warningCount: (text.match(/WARN/g) || []).length, 425 | separatorCount: (text.match(/---/g) || []).length 426 | }; 427 | 428 | qualityResults.push({ 429 | description: qualityTest.description, 430 | params: qualityTest.params, 431 | metrics: qualityMetrics, 432 | passedChecks: Object.values(qualityMetrics).filter(Boolean).length 433 | }); 434 | 435 | // Validate quality requirements 436 | assert.ok(qualityMetrics.hasLogFileName, `${qualityTest.description}: Should have log file name`); 437 | assert.ok(qualityMetrics.hasTimestamp, `${qualityTest.description}: Should have timestamp`); 438 | assert.ok(qualityMetrics.hasWarnLevel, `${qualityTest.description}: Should have WARN level`); 439 | assert.ok(qualityMetrics.warningCount >= qualityTest.minWarnings, 440 | `${qualityTest.description}: Should have at least ${qualityTest.minWarnings} warnings`); 441 | } 442 | 443 | // Cross-quality analysis 444 | const avgQualityScore = qualityResults.reduce((sum, result) => sum + result.passedChecks, 0) / qualityResults.length; 445 | assert.ok(avgQualityScore >= 6, `Average quality score should be high (${avgQualityScore})`); 446 | 447 | // Consistency check across different parameter sets 448 | qualityResults.forEach(result => { 449 | assert.ok(result.passedChecks >= 6, 450 | `${result.description} should pass most quality checks (${result.passedChecks})`); 451 | }); 452 | }); 453 | }); 454 | }); 455 | ```