@lowdefy/server-dev 6.1.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +2 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +222 -36
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/describeTargetKey.js +29 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +133 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +83 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +81 -7
- package/lib/docs/getBuildStatus.test.mjs +229 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +92 -0
- package/lib/server/applyDataSetRedirect.test.mjs +446 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{session.js → getStrategies.js} +17 -16
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +168 -41
- package/lib/server/createLowdefyContext.test.mjs +331 -8
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +15 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +25 -3
- package/manager/processes/initialBuild.mjs +17 -1
- package/manager/processes/initialBuild.test.mjs +104 -0
- package/manager/processes/lowdefyBuild.mjs +31 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +60 -47
- package/package.original.json +60 -47
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +173 -3
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +3 -1
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- package/src/routes/apiPage.js +0 -37
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { callEndpoint, getEndpointConfig, runDetachedEndpoint } from '@lowdefy/api';
|
|
18
|
+
import { ConfigError } from '@lowdefy/errors';
|
|
19
|
+
import { type } from '@lowdefy/helpers';
|
|
20
|
+
|
|
21
|
+
import isWriteRequestsAllowed from './isWriteRequestsAllowed.js';
|
|
22
|
+
import fitResponse from './fitResponse.js';
|
|
23
|
+
import resolveToolCaller from './resolveToolCaller.js';
|
|
24
|
+
import withDataSession from './withDataSession.js';
|
|
25
|
+
|
|
26
|
+
// Executes an Api endpoint routine the same way POST /api/endpoints/<endpointId>
|
|
27
|
+
// does (src/routes/endpoints.js), but gated for agent use. Endpoints are not
|
|
28
|
+
// classified read/write - a routine has no checkWrite meta and a single routine
|
|
29
|
+
// can read, write and call other endpoints - so running one always needs the
|
|
30
|
+
// app's explicit opt-in via lowdefy.yaml's cli.agentTools.allowWriteRequests.
|
|
31
|
+
// Never throws for outcomes an agent should reason about: refusals, a :reject
|
|
32
|
+
// or :throw in the routine, and faults that escape callEndpoint (auth
|
|
33
|
+
// refusals, missing connections, InternalApi endpoints) all come back as data.
|
|
34
|
+
// Only malformed input throws, as a ConfigError.
|
|
35
|
+
//
|
|
36
|
+
// system: true runs the routine through runDetachedEndpoint instead - the same
|
|
37
|
+
// entry point POST /api/detached uses - so it executes as a system context
|
|
38
|
+
// exactly like a cron or detached run: no session, no user, `_user` undefined,
|
|
39
|
+
// endpoint auth not checked and InternalApi endpoints callable. That is the
|
|
40
|
+
// only local way to exercise a schedules-only InternalApi routine without
|
|
41
|
+
// CRON_SECRET. Nested CallApi steps with detached: true are not faked: they
|
|
42
|
+
// still dispatch over HTTP against the request origin and need CRON_SECRET.
|
|
43
|
+
//
|
|
44
|
+
// `user` and `data` name the caller as a journey's do (see resolveToolCaller):
|
|
45
|
+
// a user object, "none" (signed out) or a data set user's name. A data set
|
|
46
|
+
// runs the routine on that data set's own database.
|
|
47
|
+
async function runEndpoint({
|
|
48
|
+
endpointId,
|
|
49
|
+
payload = {},
|
|
50
|
+
user,
|
|
51
|
+
data,
|
|
52
|
+
system = false,
|
|
53
|
+
saveResponse = false,
|
|
54
|
+
honoContext,
|
|
55
|
+
}) {
|
|
56
|
+
if (type.isUndefined(endpointId) || !type.isString(endpointId)) {
|
|
57
|
+
throw new ConfigError(
|
|
58
|
+
`run_endpoint requires an "endpointId" string. Received ${JSON.stringify(endpointId)}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!type.isBoolean(saveResponse)) {
|
|
63
|
+
throw new ConfigError(
|
|
64
|
+
`run_endpoint "saveResponse" must be a boolean. Received ${JSON.stringify(saveResponse)}.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!type.isNone(system) && !type.isBoolean(system)) {
|
|
69
|
+
throw new ConfigError(
|
|
70
|
+
`run_endpoint "system" must be a boolean. Received ${JSON.stringify(system)}.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (system === true && !type.isNone(user)) {
|
|
75
|
+
throw new ConfigError(
|
|
76
|
+
'run_endpoint "system" cannot be combined with "user": a system run has no user (_user is undefined).'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const caller = await resolveToolCaller({ user, data });
|
|
81
|
+
if (!type.isUndefined(caller.error)) {
|
|
82
|
+
throw new ConfigError(`run_endpoint: ${caller.error}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const allowed = await isWriteRequestsAllowed();
|
|
86
|
+
if (!allowed) {
|
|
87
|
+
return {
|
|
88
|
+
refused: true,
|
|
89
|
+
reason:
|
|
90
|
+
'Api endpoint routines are not classified read-only — a routine can write, call other endpoints and send notifications — so running one needs agent write access.',
|
|
91
|
+
howToEnable: 'Set cli.agentTools.allowWriteRequests: true in lowdefy.yaml (dev only).',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ran = await withDataSession({
|
|
96
|
+
dataSet: caller.dataSet,
|
|
97
|
+
task: ({ dataSession }) =>
|
|
98
|
+
runEndpointInContext({
|
|
99
|
+
endpointId,
|
|
100
|
+
payload,
|
|
101
|
+
user: caller.user,
|
|
102
|
+
system,
|
|
103
|
+
saveResponse,
|
|
104
|
+
honoContext,
|
|
105
|
+
dataSession,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
// A data set that failed to load never ran the routine.
|
|
109
|
+
if (type.isString(ran.error)) {
|
|
110
|
+
return { refused: true, reason: ran.error };
|
|
111
|
+
}
|
|
112
|
+
return ran;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The part of runEndpoint that runs in a Lowdefy context, on the data set's
|
|
116
|
+
// database when the call named one.
|
|
117
|
+
async function runEndpointInContext({
|
|
118
|
+
endpointId,
|
|
119
|
+
payload,
|
|
120
|
+
user,
|
|
121
|
+
system,
|
|
122
|
+
saveResponse,
|
|
123
|
+
honoContext,
|
|
124
|
+
dataSession,
|
|
125
|
+
}) {
|
|
126
|
+
// Deferred import: createLowdefyContext statically imports build/plugins/*
|
|
127
|
+
// artifacts, which only exist in a running server directory - importing it
|
|
128
|
+
// at module load would break every consumer of this module (e.g. the MCP
|
|
129
|
+
// server) in environments without a full build.
|
|
130
|
+
const { default: createLowdefyContext } = await import('../server/createLowdefyContext.js');
|
|
131
|
+
// "none" injects no caller: the app's own auth resolves this call, which
|
|
132
|
+
// carries no session, so it runs signed out.
|
|
133
|
+
const context = await createLowdefyContext({
|
|
134
|
+
c: honoContext,
|
|
135
|
+
user: user === 'none' ? undefined : user,
|
|
136
|
+
dataSession,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// getEndpointConfig needs the context's readConfigFile, so the endpoint is
|
|
140
|
+
// resolved after the context is built. Its not-found ConfigError is answered
|
|
141
|
+
// as a refusal rather than allowed to escape.
|
|
142
|
+
try {
|
|
143
|
+
await getEndpointConfig(context, { endpointId });
|
|
144
|
+
} catch {
|
|
145
|
+
return {
|
|
146
|
+
refused: true,
|
|
147
|
+
reason:
|
|
148
|
+
`Endpoint "${endpointId}" was not found. ` +
|
|
149
|
+
`See GET /lowdefy-docs/app-map for the endpoints that exist.`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
context.logger.info({ event: 'agent_run_endpoint', endpointId, user, system });
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// callEndpoint refuses InternalApi endpoints and enforces the endpoint's
|
|
157
|
+
// auth and payloadSchema exactly as the HTTP route does. A :reject or
|
|
158
|
+
// :throw resolves normally with success: false and the routine's own
|
|
159
|
+
// error, so neither reaches the catch below.
|
|
160
|
+
// The agent wrote the payload, so a payload the endpoint's payloadSchema
|
|
161
|
+
// refuses is the agent's to correct (outsideCaller), not a config fault.
|
|
162
|
+
const result = system
|
|
163
|
+
? await runDetachedEndpoint(context, { endpointId, outsideCaller: true, payload })
|
|
164
|
+
: await callEndpoint(context, {
|
|
165
|
+
blockId: undefined,
|
|
166
|
+
endpointId,
|
|
167
|
+
outsideCaller: true,
|
|
168
|
+
pageId: undefined,
|
|
169
|
+
payload,
|
|
170
|
+
});
|
|
171
|
+
return { refused: false, ...fitResponse({ result, name: endpointId, saveResponse }) };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return {
|
|
174
|
+
refused: false,
|
|
175
|
+
error: {
|
|
176
|
+
name: error.name,
|
|
177
|
+
message: error.message,
|
|
178
|
+
source: error.source,
|
|
179
|
+
configKey: error.configKey,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default runEndpoint;
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
import { jest } from '@jest/globals';
|
|
22
|
+
|
|
23
|
+
// runEndpoint reads lowdefy.yaml, builds a Lowdefy context and calls into
|
|
24
|
+
// @lowdefy/api - all of which only exist in a running server directory, so mock
|
|
25
|
+
// them and leave runEndpoint's own validation, gating and pass-through under
|
|
26
|
+
// test.
|
|
27
|
+
const mockCallEndpoint = jest.fn();
|
|
28
|
+
const mockRunDetachedEndpoint = jest.fn();
|
|
29
|
+
const mockGetEndpointConfig = jest.fn();
|
|
30
|
+
const mockIsWriteRequestsAllowed = jest.fn();
|
|
31
|
+
const mockCreateLowdefyContext = jest.fn();
|
|
32
|
+
const mockLoggerInfo = jest.fn();
|
|
33
|
+
|
|
34
|
+
jest.unstable_mockModule('@lowdefy/api', () => ({
|
|
35
|
+
callEndpoint: mockCallEndpoint,
|
|
36
|
+
getEndpointConfig: mockGetEndpointConfig,
|
|
37
|
+
runDetachedEndpoint: mockRunDetachedEndpoint,
|
|
38
|
+
}));
|
|
39
|
+
jest.unstable_mockModule('./isWriteRequestsAllowed.js', () => ({
|
|
40
|
+
default: mockIsWriteRequestsAllowed,
|
|
41
|
+
}));
|
|
42
|
+
jest.unstable_mockModule('../server/createLowdefyContext.js', () => ({
|
|
43
|
+
default: mockCreateLowdefyContext,
|
|
44
|
+
}));
|
|
45
|
+
// A data set caller loads a data set into the dev server's memory store; both
|
|
46
|
+
// are mocked so the test checks who the routine runs as and on which database.
|
|
47
|
+
const mockReadDataSet = jest.fn();
|
|
48
|
+
const mockOpenDataSession = jest.fn();
|
|
49
|
+
jest.unstable_mockModule('./dataSets/readDataSet.js', () => ({ default: mockReadDataSet }));
|
|
50
|
+
jest.unstable_mockModule('./dataSets/getDataStore.js', () => ({ default: async () => ({}) }));
|
|
51
|
+
jest.unstable_mockModule('./dataSets/openDataSession.js', () => ({
|
|
52
|
+
default: mockOpenDataSession,
|
|
53
|
+
}));
|
|
54
|
+
jest.unstable_mockModule('./readDevAuthMode.js', () => ({
|
|
55
|
+
default: () => ({ authConfigured: true, mockUserActive: false }),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const { ConfigError } = await import('@lowdefy/errors');
|
|
59
|
+
const { MAX_INLINE_RESPONSE_CHARS } = await import('./fitResponse.js');
|
|
60
|
+
const { default: runEndpoint } = await import('./runEndpoint.js');
|
|
61
|
+
|
|
62
|
+
const honoContext = { req: { path: '/lowdefy-docs/run-endpoint' } };
|
|
63
|
+
const context = { logger: { info: mockLoggerInfo } };
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
jest.clearAllMocks();
|
|
67
|
+
mockIsWriteRequestsAllowed.mockResolvedValue(true);
|
|
68
|
+
mockCreateLowdefyContext.mockResolvedValue(context);
|
|
69
|
+
mockGetEndpointConfig.mockResolvedValue({ id: 'endpoint:create_order', type: 'Api' });
|
|
70
|
+
mockCallEndpoint.mockResolvedValue({
|
|
71
|
+
error: null,
|
|
72
|
+
response: { orderId: 'o_1' },
|
|
73
|
+
status: 'success',
|
|
74
|
+
success: true,
|
|
75
|
+
});
|
|
76
|
+
mockRunDetachedEndpoint.mockResolvedValue({
|
|
77
|
+
error: null,
|
|
78
|
+
response: { purged: 3 },
|
|
79
|
+
status: 'success',
|
|
80
|
+
success: true,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('runEndpoint throws a ConfigError when endpointId is missing', async () => {
|
|
85
|
+
await expect(runEndpoint({ honoContext })).rejects.toThrow(ConfigError);
|
|
86
|
+
await expect(runEndpoint({ honoContext })).rejects.toThrow(
|
|
87
|
+
'run_endpoint requires an "endpointId" string. Received undefined.'
|
|
88
|
+
);
|
|
89
|
+
expect(mockIsWriteRequestsAllowed).not.toHaveBeenCalled();
|
|
90
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('runEndpoint throws a ConfigError when endpointId is not a string', async () => {
|
|
94
|
+
await expect(runEndpoint({ endpointId: 42, honoContext })).rejects.toThrow(
|
|
95
|
+
'run_endpoint requires an "endpointId" string. Received 42.'
|
|
96
|
+
);
|
|
97
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('runEndpoint throws a ConfigError when user is neither an object nor a name', async () => {
|
|
101
|
+
await expect(
|
|
102
|
+
runEndpoint({ endpointId: 'create_order', user: ['admin'], honoContext })
|
|
103
|
+
).rejects.toThrow(ConfigError);
|
|
104
|
+
await expect(
|
|
105
|
+
runEndpoint({ endpointId: 'create_order', user: ['admin'], honoContext })
|
|
106
|
+
).rejects.toThrow(/run_endpoint: "user" should be a user object/);
|
|
107
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('runEndpoint throws a ConfigError for a data set user name without data', async () => {
|
|
111
|
+
await expect(
|
|
112
|
+
runEndpoint({ endpointId: 'create_order', user: 'member', honoContext })
|
|
113
|
+
).rejects.toThrow(
|
|
114
|
+
'run_endpoint: The call\'s user "member" names a data set user, but the call has no "data".'
|
|
115
|
+
);
|
|
116
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("runEndpoint runs as a data set user, on the data set's own database", async () => {
|
|
120
|
+
const member = { id: 'u_member', roles: ['member'] };
|
|
121
|
+
const dataSet = { name: 'crm', users: { member } };
|
|
122
|
+
const session = { id: 'session1', state: 'open' };
|
|
123
|
+
const close = jest.fn(async () => {});
|
|
124
|
+
mockReadDataSet.mockResolvedValue(dataSet);
|
|
125
|
+
mockOpenDataSession.mockResolvedValue({ cookie: 'session1', session, close });
|
|
126
|
+
|
|
127
|
+
const result = await runEndpoint({
|
|
128
|
+
endpointId: 'create_order',
|
|
129
|
+
user: 'member',
|
|
130
|
+
data: 'crm',
|
|
131
|
+
honoContext,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(result.refused).toBe(false);
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
expect(mockOpenDataSession).toHaveBeenCalledWith({ dataSet });
|
|
137
|
+
expect(mockCreateLowdefyContext).toHaveBeenCalledWith({
|
|
138
|
+
c: honoContext,
|
|
139
|
+
user: member,
|
|
140
|
+
dataSession: session,
|
|
141
|
+
});
|
|
142
|
+
expect(close).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('runEndpoint closes the data session when the routine throws', async () => {
|
|
146
|
+
const close = jest.fn(async () => {});
|
|
147
|
+
mockReadDataSet.mockResolvedValue({ name: 'crm', users: { member: { id: 'u_member' } } });
|
|
148
|
+
mockOpenDataSession.mockResolvedValue({ cookie: 'session1', session: {}, close });
|
|
149
|
+
mockCallEndpoint.mockRejectedValue(new Error('boom'));
|
|
150
|
+
|
|
151
|
+
const result = await runEndpoint({
|
|
152
|
+
endpointId: 'create_order',
|
|
153
|
+
user: 'member',
|
|
154
|
+
data: 'crm',
|
|
155
|
+
honoContext,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(result.error.message).toEqual('boom');
|
|
159
|
+
expect(close).toHaveBeenCalledTimes(1);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('runEndpoint refuses a data set that fails to load without running the routine', async () => {
|
|
163
|
+
mockReadDataSet.mockResolvedValue({ name: 'crm', users: { member: { id: 'u_member' } } });
|
|
164
|
+
mockOpenDataSession.mockRejectedValue(new Error('Data set "crm": fixture orders is not valid.'));
|
|
165
|
+
|
|
166
|
+
const result = await runEndpoint({
|
|
167
|
+
endpointId: 'create_order',
|
|
168
|
+
user: 'member',
|
|
169
|
+
data: 'crm',
|
|
170
|
+
honoContext,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result).toEqual({
|
|
174
|
+
refused: true,
|
|
175
|
+
reason: 'Data set "crm": fixture orders is not valid.',
|
|
176
|
+
});
|
|
177
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('runEndpoint runs signed out for user "none", with no injected caller', async () => {
|
|
181
|
+
await runEndpoint({ endpointId: 'create_order', user: 'none', honoContext });
|
|
182
|
+
|
|
183
|
+
expect(mockCreateLowdefyContext).toHaveBeenCalledWith({
|
|
184
|
+
c: honoContext,
|
|
185
|
+
user: undefined,
|
|
186
|
+
dataSession: undefined,
|
|
187
|
+
});
|
|
188
|
+
expect(mockOpenDataSession).not.toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('runEndpoint refuses every endpoint when agent write access is disabled', async () => {
|
|
192
|
+
mockIsWriteRequestsAllowed.mockResolvedValue(false);
|
|
193
|
+
|
|
194
|
+
const result = await runEndpoint({ endpointId: 'create_order', honoContext });
|
|
195
|
+
|
|
196
|
+
expect(result).toEqual({
|
|
197
|
+
refused: true,
|
|
198
|
+
reason:
|
|
199
|
+
'Api endpoint routines are not classified read-only — a routine can write, call other endpoints and send notifications — so running one needs agent write access.',
|
|
200
|
+
howToEnable: 'Set cli.agentTools.allowWriteRequests: true in lowdefy.yaml (dev only).',
|
|
201
|
+
});
|
|
202
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
203
|
+
expect(mockGetEndpointConfig).not.toHaveBeenCalled();
|
|
204
|
+
expect(mockCallEndpoint).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('runEndpoint refuses a write-gated endpoint for an impersonated caller too', async () => {
|
|
208
|
+
mockIsWriteRequestsAllowed.mockResolvedValue(false);
|
|
209
|
+
|
|
210
|
+
const result = await runEndpoint({
|
|
211
|
+
endpointId: 'create_order',
|
|
212
|
+
user: { roles: ['admin'] },
|
|
213
|
+
honoContext,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result.refused).toBe(true);
|
|
217
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('runEndpoint returns a not-found refusal when the endpoint does not exist', async () => {
|
|
221
|
+
mockGetEndpointConfig.mockRejectedValue(
|
|
222
|
+
new ConfigError('API Endpoint "missing_endpoint" does not exist.')
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const result = await runEndpoint({ endpointId: 'missing_endpoint', honoContext });
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual({
|
|
228
|
+
refused: true,
|
|
229
|
+
reason:
|
|
230
|
+
'Endpoint "missing_endpoint" was not found. See GET /lowdefy-docs/app-map for the endpoints that exist.',
|
|
231
|
+
});
|
|
232
|
+
expect(mockGetEndpointConfig).toHaveBeenCalledWith(context, { endpointId: 'missing_endpoint' });
|
|
233
|
+
expect(mockCallEndpoint).not.toHaveBeenCalled();
|
|
234
|
+
expect(mockLoggerInfo).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('runEndpoint runs a successful routine and returns its result', async () => {
|
|
238
|
+
const result = await runEndpoint({
|
|
239
|
+
endpointId: 'create_order',
|
|
240
|
+
payload: { sku: 'A1' },
|
|
241
|
+
honoContext,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(mockCreateLowdefyContext).toHaveBeenCalledWith({ c: honoContext, user: undefined });
|
|
245
|
+
expect(mockCallEndpoint).toHaveBeenCalledWith(context, {
|
|
246
|
+
blockId: undefined,
|
|
247
|
+
endpointId: 'create_order',
|
|
248
|
+
outsideCaller: true,
|
|
249
|
+
pageId: undefined,
|
|
250
|
+
payload: { sku: 'A1' },
|
|
251
|
+
});
|
|
252
|
+
expect(result).toEqual({
|
|
253
|
+
refused: false,
|
|
254
|
+
error: null,
|
|
255
|
+
response: { orderId: 'o_1' },
|
|
256
|
+
status: 'success',
|
|
257
|
+
success: true,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('runEndpoint defaults the payload to an empty object', async () => {
|
|
262
|
+
await runEndpoint({ endpointId: 'create_order', honoContext });
|
|
263
|
+
|
|
264
|
+
expect(mockCallEndpoint).toHaveBeenCalledWith(context, expect.objectContaining({ payload: {} }));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('runEndpoint passes the user to createLowdefyContext and logs the run', async () => {
|
|
268
|
+
const user = { roles: ['admin'], organization_id: 'org_1' };
|
|
269
|
+
|
|
270
|
+
await runEndpoint({ endpointId: 'create_order', user, honoContext });
|
|
271
|
+
|
|
272
|
+
expect(mockCreateLowdefyContext).toHaveBeenCalledWith({ c: honoContext, user });
|
|
273
|
+
expect(mockLoggerInfo).toHaveBeenCalledWith({
|
|
274
|
+
event: 'agent_run_endpoint',
|
|
275
|
+
endpointId: 'create_order',
|
|
276
|
+
user,
|
|
277
|
+
system: false,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('runEndpoint with system: true runs through runDetachedEndpoint instead of callEndpoint', async () => {
|
|
282
|
+
const result = await runEndpoint({
|
|
283
|
+
endpointId: 'purge_stale',
|
|
284
|
+
payload: { mode: 'full' },
|
|
285
|
+
system: true,
|
|
286
|
+
honoContext,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(mockCreateLowdefyContext).toHaveBeenCalledWith({ c: honoContext, user: undefined });
|
|
290
|
+
expect(mockRunDetachedEndpoint).toHaveBeenCalledWith(context, {
|
|
291
|
+
endpointId: 'purge_stale',
|
|
292
|
+
outsideCaller: true,
|
|
293
|
+
payload: { mode: 'full' },
|
|
294
|
+
});
|
|
295
|
+
expect(mockCallEndpoint).not.toHaveBeenCalled();
|
|
296
|
+
expect(mockLoggerInfo).toHaveBeenCalledWith({
|
|
297
|
+
event: 'agent_run_endpoint',
|
|
298
|
+
endpointId: 'purge_stale',
|
|
299
|
+
user: undefined,
|
|
300
|
+
system: true,
|
|
301
|
+
});
|
|
302
|
+
expect(result).toEqual({
|
|
303
|
+
refused: false,
|
|
304
|
+
error: null,
|
|
305
|
+
response: { purged: 3 },
|
|
306
|
+
status: 'success',
|
|
307
|
+
success: true,
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('runEndpoint with system: true still needs agent write access', async () => {
|
|
312
|
+
mockIsWriteRequestsAllowed.mockResolvedValue(false);
|
|
313
|
+
|
|
314
|
+
const result = await runEndpoint({ endpointId: 'purge_stale', system: true, honoContext });
|
|
315
|
+
|
|
316
|
+
expect(result.refused).toBe(true);
|
|
317
|
+
expect(mockRunDetachedEndpoint).not.toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('runEndpoint with system: true returns a :reject as data', async () => {
|
|
321
|
+
mockRunDetachedEndpoint.mockResolvedValue({
|
|
322
|
+
error: { name: 'Error', message: 'Nothing to purge.' },
|
|
323
|
+
response: null,
|
|
324
|
+
status: 'reject',
|
|
325
|
+
success: false,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const result = await runEndpoint({ endpointId: 'purge_stale', system: true, honoContext });
|
|
329
|
+
|
|
330
|
+
expect(result).toEqual({
|
|
331
|
+
refused: false,
|
|
332
|
+
error: { name: 'Error', message: 'Nothing to purge.' },
|
|
333
|
+
response: null,
|
|
334
|
+
status: 'reject',
|
|
335
|
+
success: false,
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('runEndpoint with system: true returns faults that escape runDetachedEndpoint as an error object', async () => {
|
|
340
|
+
const fault = new ConfigError('CallApi detached requires CRON_SECRET.', { configKey: 'k2' });
|
|
341
|
+
mockRunDetachedEndpoint.mockRejectedValue(fault);
|
|
342
|
+
|
|
343
|
+
const result = await runEndpoint({ endpointId: 'purge_stale', system: true, honoContext });
|
|
344
|
+
|
|
345
|
+
expect(result).toEqual({
|
|
346
|
+
refused: false,
|
|
347
|
+
error: {
|
|
348
|
+
name: 'ConfigError',
|
|
349
|
+
message: 'CallApi detached requires CRON_SECRET.',
|
|
350
|
+
source: null,
|
|
351
|
+
configKey: 'k2',
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('runEndpoint throws a ConfigError when system is combined with user', async () => {
|
|
357
|
+
await expect(
|
|
358
|
+
runEndpoint({
|
|
359
|
+
endpointId: 'purge_stale',
|
|
360
|
+
system: true,
|
|
361
|
+
user: { roles: ['admin'] },
|
|
362
|
+
honoContext,
|
|
363
|
+
})
|
|
364
|
+
).rejects.toThrow(ConfigError);
|
|
365
|
+
await expect(
|
|
366
|
+
runEndpoint({
|
|
367
|
+
endpointId: 'purge_stale',
|
|
368
|
+
system: true,
|
|
369
|
+
user: { roles: ['admin'] },
|
|
370
|
+
honoContext,
|
|
371
|
+
})
|
|
372
|
+
).rejects.toThrow('run_endpoint "system" cannot be combined with "user"');
|
|
373
|
+
expect(mockIsWriteRequestsAllowed).not.toHaveBeenCalled();
|
|
374
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('runEndpoint throws a ConfigError when system is not a boolean', async () => {
|
|
378
|
+
await expect(
|
|
379
|
+
runEndpoint({ endpointId: 'purge_stale', system: 'yes', honoContext })
|
|
380
|
+
).rejects.toThrow('run_endpoint "system" must be a boolean. Received "yes".');
|
|
381
|
+
expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('runEndpoint with system: false runs through callEndpoint as before', async () => {
|
|
385
|
+
await runEndpoint({ endpointId: 'create_order', system: false, honoContext });
|
|
386
|
+
|
|
387
|
+
expect(mockCallEndpoint).toHaveBeenCalled();
|
|
388
|
+
expect(mockRunDetachedEndpoint).not.toHaveBeenCalled();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('runEndpoint returns a :reject as data rather than throwing', async () => {
|
|
392
|
+
mockCallEndpoint.mockResolvedValue({
|
|
393
|
+
error: { name: 'Error', message: 'Out of stock.' },
|
|
394
|
+
response: null,
|
|
395
|
+
status: 'reject',
|
|
396
|
+
success: false,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const result = await runEndpoint({ endpointId: 'create_order', honoContext });
|
|
400
|
+
|
|
401
|
+
expect(result).toEqual({
|
|
402
|
+
refused: false,
|
|
403
|
+
error: { name: 'Error', message: 'Out of stock.' },
|
|
404
|
+
response: null,
|
|
405
|
+
status: 'reject',
|
|
406
|
+
success: false,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('runEndpoint returns a :throw as data with status error', async () => {
|
|
411
|
+
mockCallEndpoint.mockResolvedValue({
|
|
412
|
+
error: { name: 'Error', message: 'Payment provider down.' },
|
|
413
|
+
response: null,
|
|
414
|
+
status: 'error',
|
|
415
|
+
success: false,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const result = await runEndpoint({ endpointId: 'create_order', honoContext });
|
|
419
|
+
|
|
420
|
+
expect(result.refused).toBe(false);
|
|
421
|
+
expect(result.success).toBe(false);
|
|
422
|
+
expect(result.status).toBe('error');
|
|
423
|
+
expect(result.error.message).toBe('Payment provider down.');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('runEndpoint returns the routine error of a failed endpoint, which has no response', async () => {
|
|
427
|
+
// callEndpoint's result for a failed routine: the wire error with the full error
|
|
428
|
+
// beside it as devError, and response undefined.
|
|
429
|
+
const endpointError = {
|
|
430
|
+
'~e': { name: 'ServiceError', message: 'Something went wrong.', configKey: 'k9' },
|
|
431
|
+
devError: {
|
|
432
|
+
'~e': {
|
|
433
|
+
name: 'ServiceError',
|
|
434
|
+
message: 'MongoDB: MongoDB rejected the MongoDBFind on collection "docs" (code 27).',
|
|
435
|
+
source: 'api/search.yaml:40',
|
|
436
|
+
configKey: 'k9',
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
mockCallEndpoint.mockResolvedValue({
|
|
441
|
+
error: endpointError,
|
|
442
|
+
response: undefined,
|
|
443
|
+
status: 'error',
|
|
444
|
+
success: false,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const result = await runEndpoint({ endpointId: 'search', honoContext });
|
|
448
|
+
|
|
449
|
+
expect(result).toEqual({
|
|
450
|
+
refused: false,
|
|
451
|
+
error: endpointError,
|
|
452
|
+
response: undefined,
|
|
453
|
+
status: 'error',
|
|
454
|
+
success: false,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('runEndpoint returns faults that escape callEndpoint as an error object', async () => {
|
|
459
|
+
const fault = new ConfigError('API Endpoint "internal_only" does not exist.', {
|
|
460
|
+
configKey: 'k1',
|
|
461
|
+
});
|
|
462
|
+
fault.source = 'api/internal.yaml:3';
|
|
463
|
+
mockCallEndpoint.mockRejectedValue(fault);
|
|
464
|
+
|
|
465
|
+
const result = await runEndpoint({ endpointId: 'internal_only', honoContext });
|
|
466
|
+
|
|
467
|
+
expect(result).toEqual({
|
|
468
|
+
refused: false,
|
|
469
|
+
error: {
|
|
470
|
+
name: 'ConfigError',
|
|
471
|
+
message: 'API Endpoint "internal_only" does not exist.',
|
|
472
|
+
source: 'api/internal.yaml:3',
|
|
473
|
+
configKey: 'k1',
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('runEndpoint writes an oversized response to a file', async () => {
|
|
479
|
+
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-run-endpoint-'));
|
|
480
|
+
process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
|
|
481
|
+
const response = 'x'.repeat(MAX_INLINE_RESPONSE_CHARS + 10);
|
|
482
|
+
mockCallEndpoint.mockResolvedValue({
|
|
483
|
+
error: null,
|
|
484
|
+
response,
|
|
485
|
+
status: 'success',
|
|
486
|
+
success: true,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
const result = await runEndpoint({ endpointId: 'create_order', honoContext });
|
|
491
|
+
|
|
492
|
+
expect(result.refused).toBe(false);
|
|
493
|
+
expect(result.response).toBeUndefined();
|
|
494
|
+
expect(result.responseChars).toBe(MAX_INLINE_RESPONSE_CHARS + 12);
|
|
495
|
+
expect(path.dirname(result.responseFile)).toBe(
|
|
496
|
+
path.join(configDirectory, '.lowdefy', 'responses')
|
|
497
|
+
);
|
|
498
|
+
expect(JSON.parse(fs.readFileSync(result.responseFile, 'utf8'))).toBe(response);
|
|
499
|
+
expect(result.status).toBe('success');
|
|
500
|
+
} finally {
|
|
501
|
+
delete process.env.LOWDEFY_DIRECTORY_CONFIG;
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('runEndpoint throws a ConfigError when saveResponse is not a boolean', async () => {
|
|
506
|
+
await expect(
|
|
507
|
+
runEndpoint({ endpointId: 'create_order', saveResponse: 'yes', honoContext })
|
|
508
|
+
).rejects.toThrow(ConfigError);
|
|
509
|
+
});
|