@lowdefy/server-dev 6.0.0 → 7.0.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 +63 -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 +224 -39
- 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 +67 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -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/findCollectionCollisions.js +52 -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 +44 -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 +119 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -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 +68 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +220 -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/sweepOrphanDatabases.js +33 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -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 +54 -6
- package/lib/docs/getBuildStatus.test.mjs +133 -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 +83 -0
- package/lib/server/applyDataSetRedirect.test.mjs +340 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/getAuth.js +46 -0
- 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/{getAuthConfig.js → getStrategies.js} +12 -10
- 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 +174 -41
- package/lib/server/createLowdefyContext.test.mjs +437 -0
- 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 +77 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +112 -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/scrubSecrets.js +23 -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 +24 -2
- package/manager/processes/initialBuild.mjs +14 -0
- package/manager/processes/initialBuild.test.mjs +101 -0
- package/manager/processes/lowdefyBuild.mjs +30 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -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 +61 -49
- package/package.original.json +61 -49
- 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 +196 -14
- 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 +4 -2
- 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/lib/server/auth/session.js +0 -39
- 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,679 @@
|
|
|
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 { execFile } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { promisify } from 'node:util';
|
|
23
|
+
|
|
24
|
+
import { jest } from '@jest/globals';
|
|
25
|
+
import { MongoClient } from 'mongodb';
|
|
26
|
+
|
|
27
|
+
// Data set journeys end to end: a real Chromium drives journeys through the dev server's own
|
|
28
|
+
// journey route, API context, endpoint, detached and websocket handlers and the MongoDB connection
|
|
29
|
+
// plugin, over a build of the fixture app in test/app. Its connections, its module's connection and
|
|
30
|
+
// its auth adapter read the MONGODB_URI secret, pointed at a database of its own on the jest-mongodb
|
|
31
|
+
// memory server (MONGO_URL), which stands in for the developer's real database. Journeys on the
|
|
32
|
+
// "shop" data set must reach the data store's session database for every request, module
|
|
33
|
+
// connection, detached call and change stream, must never reach the auth engine, and must leave the
|
|
34
|
+
// stand-in exactly as they found it. The page each actor opens is a
|
|
35
|
+
// small HTML stand-in for a Lowdefy page (window.lowdefy state, blocks as #bl-<id>), so no client
|
|
36
|
+
// bundle is built. Skipped when no Chromium can be launched.
|
|
37
|
+
|
|
38
|
+
jest.setTimeout(240000);
|
|
39
|
+
|
|
40
|
+
const execFileAsync = promisify(execFile);
|
|
41
|
+
|
|
42
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const serverDevDirectory = path.resolve(dirname, '../../..');
|
|
44
|
+
const appDirectory = path.join(dirname, 'test', 'app');
|
|
45
|
+
const originalCwd = process.cwd();
|
|
46
|
+
// Other mongodb suites share the memory server and run alongside this one, so the stand-in is a
|
|
47
|
+
// database of its own, which the suite can snapshot without seeing their writes.
|
|
48
|
+
const standInDatabase = 'data_set_journeys';
|
|
49
|
+
const standInUrl = new URL(process.env.MONGO_URL);
|
|
50
|
+
standInUrl.pathname = `/${standInDatabase}`;
|
|
51
|
+
const standInUri = standInUrl.toString();
|
|
52
|
+
|
|
53
|
+
process.env.LOWDEFY_SECRET_MONGODB_URI = standInUri;
|
|
54
|
+
process.env.LOWDEFY_DIRECTORY_CONFIG = appDirectory;
|
|
55
|
+
process.env.LOWDEFY_LOG_LEVEL = 'error';
|
|
56
|
+
process.env.CRON_SECRET = 'data-set-journeys-cron-secret';
|
|
57
|
+
process.env.LOWDEFY_SECRET_BETTER_AUTH_SECRET = 'data-set-journeys-auth-secret-0123456789abcdef';
|
|
58
|
+
|
|
59
|
+
// createLowdefyContext imports the app's plugin maps from server-dev/build/plugins, which only a
|
|
60
|
+
// built server directory has: they are materialised for the import (like
|
|
61
|
+
// createLowdefyContext.test.mjs) and mocked with the real plugins the fixture app uses.
|
|
62
|
+
const pluginsDirectory = path.join(serverDevDirectory, 'build', 'plugins');
|
|
63
|
+
const pluginFiles = [
|
|
64
|
+
'agents.js',
|
|
65
|
+
path.join('auth', 'adapters.js'),
|
|
66
|
+
path.join('auth', 'providers.js'),
|
|
67
|
+
path.join('auth', 'strategies.js'),
|
|
68
|
+
'connections.js',
|
|
69
|
+
'notifications.js',
|
|
70
|
+
path.join('operators', 'server.js'),
|
|
71
|
+
path.join('operators', 'serverJsMap.js'),
|
|
72
|
+
'steps.js',
|
|
73
|
+
'websockets.js',
|
|
74
|
+
].map((file) => path.join(pluginsDirectory, file));
|
|
75
|
+
const createdPluginFiles = [];
|
|
76
|
+
pluginFiles.forEach((filePath) => {
|
|
77
|
+
if (!fs.existsSync(filePath)) {
|
|
78
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(filePath, 'export default {};\n');
|
|
80
|
+
createdPluginFiles.push(filePath);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const { MongoDBCollection } = await import('@lowdefy/connection-mongodb/connections');
|
|
84
|
+
const { MongoDBChangeStream } = await import('@lowdefy/connection-mongodb/websockets');
|
|
85
|
+
const { MongoDBAuthAdapter } = await import('@lowdefy/connection-mongodb/auth/adapters');
|
|
86
|
+
const { ListMembers } = await import('@lowdefy/plugin-better-auth/steps');
|
|
87
|
+
const serverOperators = await import('@lowdefy/operators-js/operators/server');
|
|
88
|
+
const pluginModules = {
|
|
89
|
+
[path.join('auth', 'adapters.js')]: { default: { MongoDBAuthAdapter } },
|
|
90
|
+
'connections.js': { default: { MongoDBCollection } },
|
|
91
|
+
'steps.js': { default: { ListMembers } },
|
|
92
|
+
'websockets.js': { default: { MongoDBChangeStream } },
|
|
93
|
+
[path.join('operators', 'server.js')]: { default: { ...serverOperators } },
|
|
94
|
+
'notifications.js': { default: {}, interpolateProperties: () => {}, renderEmail: () => {} },
|
|
95
|
+
};
|
|
96
|
+
pluginFiles.forEach((filePath) => {
|
|
97
|
+
const relative = path.relative(pluginsDirectory, filePath);
|
|
98
|
+
jest.unstable_mockModule(filePath, () => pluginModules[relative] ?? { default: {} });
|
|
99
|
+
});
|
|
100
|
+
// The journeys run on a stand-in page, not a built Lowdefy page, so there is no page build to
|
|
101
|
+
// check their step references against; the fixture suite covers the checks.
|
|
102
|
+
jest.unstable_mockModule('../checkStepReferences.js', () => ({
|
|
103
|
+
default: async () => {},
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
// The fixture app's build, in a temporary server directory: lib/build/*.js read build/*.json from
|
|
107
|
+
// the working directory when they are imported.
|
|
108
|
+
const serverDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-data-set-journeys-'));
|
|
109
|
+
await execFileAsync(process.execPath, [
|
|
110
|
+
path.join(dirname, 'test', 'buildTestApp.mjs'),
|
|
111
|
+
appDirectory,
|
|
112
|
+
serverDirectory,
|
|
113
|
+
]);
|
|
114
|
+
process.chdir(serverDirectory);
|
|
115
|
+
|
|
116
|
+
const { Hono } = await import('hono');
|
|
117
|
+
const { serve } = await import('@hono/node-server');
|
|
118
|
+
const { WebSocketServer } = await import('ws');
|
|
119
|
+
const { default: apiContext } = await import('../../../src/middleware/apiContext.js');
|
|
120
|
+
const { default: authMiddleware } = await import('../../../src/routes/auth.js');
|
|
121
|
+
const { default: createErrorHandler } = await import('../../../src/middleware/errorHandler.js');
|
|
122
|
+
const { default: detachedHandler } = await import('../../../src/routes/detached.js');
|
|
123
|
+
const { default: docsJourneyHandler } = await import('../../../src/routes/docs/journey.js');
|
|
124
|
+
const { default: endpointsHandler } = await import('../../../src/routes/endpoints.js');
|
|
125
|
+
const { default: websocketHandler } = await import('../../../src/routes/websocket.js');
|
|
126
|
+
const { handleWebSocketUpgrade } = await import('../../../src/websocket/devWebSocket.js');
|
|
127
|
+
const { getBrowser } = await import('../getBrowser.js');
|
|
128
|
+
// The connection plugin keeps one driver client per URI for the life of the process; the suite
|
|
129
|
+
// closes them so jest can exit. Imported by file: the package does not export it.
|
|
130
|
+
const { closeClients } = await import(
|
|
131
|
+
path.join(
|
|
132
|
+
serverDevDirectory,
|
|
133
|
+
'node_modules/@lowdefy/connection-mongodb/dist/connections/MongoDBCollection/getClient.js'
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
const { default: getDataStore } = await import('./getDataStore.js');
|
|
137
|
+
const { default: getBuildId } = await import('../getBuildId.js');
|
|
138
|
+
const { serializer } = await import('@lowdefy/helpers');
|
|
139
|
+
|
|
140
|
+
// A stand-in for a Lowdefy page: buttons call the fixture app's endpoints and keep the answer in
|
|
141
|
+
// page state, which journey expect steps read; "watch" subscribes to the change stream source.
|
|
142
|
+
function pageHtml() {
|
|
143
|
+
return `<!doctype html><html><head><title>tickets</title></head><body>
|
|
144
|
+
<div id="bl-create"><button onclick="callEndpoint('create_ticket', { id: 'new-1', title: 'Made' }, 'created')">Create</button></div>
|
|
145
|
+
<div id="bl-create_later"><button onclick="callEndpoint('create_ticket_later', { id: 'later-1' }, 'dispatched')">Later</button></div>
|
|
146
|
+
<div id="bl-list"><button onclick="callEndpoint('list_tickets', {}, 'listed')">List</button></div>
|
|
147
|
+
<div id="bl-watch"><button onclick="watch()">Watch</button></div>
|
|
148
|
+
<div id="bl-archive"><button onclick="callEndpoint('archive/archive_ticket', { id: 'arch-1' }, 'archived')">Archive</button></div>
|
|
149
|
+
<div id="bl-create_later_auth"><button onclick="callEndpoint('create_ticket_later_auth', {}, 'dispatchedAuth')">Later with auth</button></div>
|
|
150
|
+
<div id="bl-leave"><button onclick="location.href = 'http://localhost:' + location.port + '/tickets'">Leave</button></div>
|
|
151
|
+
<div id="bl-auth_probe"><button onclick="probeAuth()">Auth</button></div>
|
|
152
|
+
<div id="bl-create_later_logged"><button onclick="callEndpoint('create_ticket_later_logged', { id: 'later-logged' }, 'dispatchedLogged')">Later, logged</button></div>
|
|
153
|
+
<div id="bl-create_logged"><button onclick="callEndpoint('create_ticket_logged', { id: 'new-logged' }, 'createdLogged')">Create, logged</button></div>
|
|
154
|
+
<div id="bl-create_watched"><button onclick="callEndpoint('create_ticket', { id: 'watched-1', title: 'Seen' }, 'createdWatched')">Create watched</button></div>
|
|
155
|
+
<script>
|
|
156
|
+
window.lowdefy = {
|
|
157
|
+
pageId: 'tickets',
|
|
158
|
+
pageInstances: { tickets: ['page:tickets'] },
|
|
159
|
+
// The e2e-utils state helpers read the instance on screen through the path memory.
|
|
160
|
+
pathMemory: new Map([['tickets', { pageId: 'tickets', pathParams: {}, instanceKey: 'page:tickets' }]]),
|
|
161
|
+
contexts: {
|
|
162
|
+
'page:tickets': {
|
|
163
|
+
state: { changes: [] },
|
|
164
|
+
requests: {},
|
|
165
|
+
websockets: {},
|
|
166
|
+
_internal: { onInitDone: true, onInitAsyncDone: true, RootSlots: { map: {} } },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const state = window.lowdefy.contexts['page:tickets'].state;
|
|
171
|
+
async function callEndpoint(endpointId, payload, key) {
|
|
172
|
+
const response = await fetch('/api/endpoints/' + endpointId, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'content-type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({ payload }),
|
|
176
|
+
});
|
|
177
|
+
const body = await response.json();
|
|
178
|
+
state[key] = {
|
|
179
|
+
status: response.status,
|
|
180
|
+
success: body.success === true,
|
|
181
|
+
ids: Array.isArray(body.response) ? body.response.map((document) => document._id) : null,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function probeAuth() {
|
|
185
|
+
const signUp = await fetch('/api/auth/sign-up/email', {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: { 'content-type': 'application/json' },
|
|
188
|
+
body: JSON.stringify({ email: 'journey@example.com', password: 'journey-password', name: 'Journey' }),
|
|
189
|
+
});
|
|
190
|
+
const signIn = await fetch('/api/auth/sign-in/email', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { 'content-type': 'application/json' },
|
|
193
|
+
body: JSON.stringify({ email: 'developer@example.com', password: 'developer-password' }),
|
|
194
|
+
});
|
|
195
|
+
const session = await fetch('/api/auth/get-session');
|
|
196
|
+
const sessionBody = await session.json();
|
|
197
|
+
state.auth = {
|
|
198
|
+
signUp: signUp.status,
|
|
199
|
+
signIn: signIn.status,
|
|
200
|
+
session: session.status,
|
|
201
|
+
sessionUser: sessionBody.user ? sessionBody.user.id : null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function watch() {
|
|
205
|
+
const socket = new WebSocket('ws://' + location.host + '/api/websocket');
|
|
206
|
+
socket.onopen = () => {
|
|
207
|
+
socket.send(JSON.stringify({ type: 'subscribe', websocketId: 'ticket_changes', requestId: 'r1', payload: {} }));
|
|
208
|
+
};
|
|
209
|
+
socket.onmessage = (event) => {
|
|
210
|
+
const frame = JSON.parse(event.data);
|
|
211
|
+
if (frame.type === 'subscribed') state.subscribed = true;
|
|
212
|
+
const id = JSON.stringify(frame).match(/"documentKey":\\{"_id":"([^"]+)"/);
|
|
213
|
+
if (id !== null) state.changes = [...state.changes, id[1]];
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
</script></body></html>`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// The Host header of every request the server answers, so a test can tell that a request to the
|
|
220
|
+
// server's other host never arrived.
|
|
221
|
+
const seenHosts = [];
|
|
222
|
+
|
|
223
|
+
function createApp() {
|
|
224
|
+
const app = new Hono();
|
|
225
|
+
app.use('*', async (c, next) => {
|
|
226
|
+
seenHosts.push(c.req.header('host'));
|
|
227
|
+
await next();
|
|
228
|
+
});
|
|
229
|
+
app.post('/lowdefy-docs/journey', docsJourneyHandler);
|
|
230
|
+
app.use('/api/*', apiContext());
|
|
231
|
+
// As src/app.js mounts them: the get-session stub answers for injected callers, and every other
|
|
232
|
+
// auth route reaches the auth engine, or a 404 under a data session.
|
|
233
|
+
app.get('/api/auth/get-session', async (c, next) => {
|
|
234
|
+
const context = c.get('lowdefyContext');
|
|
235
|
+
if (context.user) return c.json({ session: { id: 'dev' }, user: context.user });
|
|
236
|
+
return next();
|
|
237
|
+
});
|
|
238
|
+
app.use('/api/auth/*', authMiddleware({ logger: { warn: () => {}, error: console.error } }));
|
|
239
|
+
app.all('/api/endpoints/*', endpointsHandler);
|
|
240
|
+
app.post('/api/detached/*', detachedHandler);
|
|
241
|
+
app.get('/api/websocket', websocketHandler);
|
|
242
|
+
app.get('/tickets', (c) => c.html(pageHtml()));
|
|
243
|
+
app.onError(createErrorHandler({ logger: { warn: () => {}, error: console.error } }));
|
|
244
|
+
return app;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let browser = null;
|
|
248
|
+
try {
|
|
249
|
+
browser = await getBrowser();
|
|
250
|
+
} catch {
|
|
251
|
+
browser = null;
|
|
252
|
+
}
|
|
253
|
+
const chromiumTest = browser === null ? test.skip : test;
|
|
254
|
+
|
|
255
|
+
let server;
|
|
256
|
+
let wss;
|
|
257
|
+
let origin;
|
|
258
|
+
let standIn;
|
|
259
|
+
let store;
|
|
260
|
+
let storeEvents;
|
|
261
|
+
let storeWatch;
|
|
262
|
+
let standInBefore;
|
|
263
|
+
|
|
264
|
+
// Every collection of the stand-in database (app data and the auth engine's user-* collections),
|
|
265
|
+
// with every document in it.
|
|
266
|
+
async function snapshotStandIn() {
|
|
267
|
+
const db = standIn.db(standInDatabase);
|
|
268
|
+
const collections = await db.listCollections({}, { nameOnly: true }).toArray();
|
|
269
|
+
const snapshot = {};
|
|
270
|
+
for (const { name } of collections.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
271
|
+
snapshot[name] = await db.collection(name).find({}).sort({ _id: 1 }).toArray();
|
|
272
|
+
}
|
|
273
|
+
return snapshot;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function standInSessionDatabases() {
|
|
277
|
+
const { databases } = await standIn.db('admin').admin().listDatabases({ nameOnly: true });
|
|
278
|
+
return databases.map((database) => database.name).filter((name) => name.startsWith('ld_'));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// The developer's own tab, with no journey cookie: it reaches the auth engine, which writes to the
|
|
282
|
+
// stand-in.
|
|
283
|
+
async function signUpWithoutCookies() {
|
|
284
|
+
const response = await fetch(`${origin}/api/auth/sign-up/email`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: { 'content-type': 'application/json', origin },
|
|
287
|
+
body: JSON.stringify({
|
|
288
|
+
email: 'developer@example.com',
|
|
289
|
+
password: 'developer-password',
|
|
290
|
+
name: 'Developer',
|
|
291
|
+
}),
|
|
292
|
+
});
|
|
293
|
+
return response.status;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
beforeAll(async () => {
|
|
297
|
+
standIn = new MongoClient(standInUri);
|
|
298
|
+
await standIn.connect();
|
|
299
|
+
await standIn.db(standInDatabase).dropDatabase();
|
|
300
|
+
await standIn
|
|
301
|
+
.db(standInDatabase)
|
|
302
|
+
.collection('tickets')
|
|
303
|
+
.insertOne({ _id: 'real-1', organization_id: 'org_a', title: 'The developer’s own' });
|
|
304
|
+
|
|
305
|
+
store = await getDataStore();
|
|
306
|
+
// Every write on the data store, in order, so a test can tell which session database a detached
|
|
307
|
+
// write reached and that it landed before the session dropped it.
|
|
308
|
+
storeEvents = [];
|
|
309
|
+
storeWatch = store.client.watch([], { fullDocument: 'default' });
|
|
310
|
+
storeWatch.on('change', (change) => {
|
|
311
|
+
storeEvents.push({
|
|
312
|
+
operationType: change.operationType,
|
|
313
|
+
db: change.ns?.db,
|
|
314
|
+
coll: change.ns?.coll,
|
|
315
|
+
id: change.documentKey?._id,
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
319
|
+
|
|
320
|
+
const app = createApp();
|
|
321
|
+
wss = new WebSocketServer({ noServer: true });
|
|
322
|
+
server = serve({ fetch: app.fetch, port: 0, hostname: '127.0.0.1' });
|
|
323
|
+
server.on('upgrade', (request, socket, head) => {
|
|
324
|
+
handleWebSocketUpgrade({ app, request, socket, head, wss });
|
|
325
|
+
});
|
|
326
|
+
await new Promise((resolve) => {
|
|
327
|
+
if (server.listening) resolve();
|
|
328
|
+
else server.once('listening', resolve);
|
|
329
|
+
});
|
|
330
|
+
origin = `http://127.0.0.1:${server.address().port}`;
|
|
331
|
+
|
|
332
|
+
// The auth engine is live for the developer's tab: its sign-up writes a user, a session and the
|
|
333
|
+
// organization the tenant policy mints. The snapshot is taken after it, so every auth collection
|
|
334
|
+
// on the stand-in already holds documents a journey could add to.
|
|
335
|
+
expect(await signUpWithoutCookies()).toBe(200);
|
|
336
|
+
standInBefore = await snapshotStandIn();
|
|
337
|
+
expect(standInBefore.users).toHaveLength(1);
|
|
338
|
+
expect(standInBefore['user-sessions']).toHaveLength(1);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
afterAll(async () => {
|
|
342
|
+
await browser?.close();
|
|
343
|
+
await storeWatch?.close();
|
|
344
|
+
wss?.close();
|
|
345
|
+
await new Promise((resolve) => (server ? server.close(resolve) : resolve()));
|
|
346
|
+
await closeClients();
|
|
347
|
+
await store?.stop();
|
|
348
|
+
await standIn?.db(standInDatabase).dropDatabase();
|
|
349
|
+
await standIn?.close();
|
|
350
|
+
process.chdir(originalCwd);
|
|
351
|
+
fs.rmSync(serverDirectory, { recursive: true, force: true });
|
|
352
|
+
createdPluginFiles.forEach((filePath) => fs.rmSync(filePath, { force: true }));
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
async function runJourney(body) {
|
|
356
|
+
const response = await fetch(`${origin}/lowdefy-docs/journey`, {
|
|
357
|
+
method: 'POST',
|
|
358
|
+
headers: { 'content-type': 'application/json' },
|
|
359
|
+
body: JSON.stringify({ pageId: 'tickets', timeout: 10000, state: true, ...body }),
|
|
360
|
+
});
|
|
361
|
+
return { status: response.status, result: await response.json() };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function standInIds() {
|
|
365
|
+
const documents = await standIn
|
|
366
|
+
.db(standInDatabase)
|
|
367
|
+
.collection('tickets')
|
|
368
|
+
.find({})
|
|
369
|
+
.sort({ _id: 1 })
|
|
370
|
+
.toArray();
|
|
371
|
+
return documents.map((document) => document._id);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function listAllWithoutCookies() {
|
|
375
|
+
const response = await fetch(`${origin}/api/endpoints/list_all_tickets`, {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
headers: { 'content-type': 'application/json' },
|
|
378
|
+
body: JSON.stringify({ payload: {} }),
|
|
379
|
+
});
|
|
380
|
+
const body = await response.json();
|
|
381
|
+
return body.response.map((document) => document._id);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
chromiumTest(
|
|
385
|
+
'a data-set journey writes to its own database, and the next journey on the data set does not see the write',
|
|
386
|
+
async () => {
|
|
387
|
+
const first = await runJourney({
|
|
388
|
+
data: 'shop',
|
|
389
|
+
user: 'owner',
|
|
390
|
+
steps: [
|
|
391
|
+
{ click: 'create' },
|
|
392
|
+
{ expect: { state: { path: 'created.success', equals: true } } },
|
|
393
|
+
{ click: 'list' },
|
|
394
|
+
{ expect: { state: { path: 'listed.ids', equals: ['new-1', 't-a1', 't-a2'] } } },
|
|
395
|
+
],
|
|
396
|
+
});
|
|
397
|
+
expect(first.status).toBe(200);
|
|
398
|
+
expect(first.result.passed).toBe(true);
|
|
399
|
+
expect(first.result.data).toEqual({ name: 'shop', loadMs: expect.any(Number), documents: 3 });
|
|
400
|
+
|
|
401
|
+
const second = await runJourney({
|
|
402
|
+
data: 'shop',
|
|
403
|
+
user: 'owner',
|
|
404
|
+
steps: [
|
|
405
|
+
{ click: 'list' },
|
|
406
|
+
{ expect: { state: { path: 'listed.ids', equals: ['t-a1', 't-a2'] } } },
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
expect(second.result.passed).toBe(true);
|
|
410
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
chromiumTest(
|
|
415
|
+
'a cookieless request to the same server reads the stand-in database while a data journey runs',
|
|
416
|
+
async () => {
|
|
417
|
+
const journey = runJourney({
|
|
418
|
+
data: 'shop',
|
|
419
|
+
user: 'owner',
|
|
420
|
+
steps: [
|
|
421
|
+
{ click: 'create' },
|
|
422
|
+
{ expect: { state: { path: 'created.success', equals: true } } },
|
|
423
|
+
{ wait: { ms: 1500 } },
|
|
424
|
+
{ click: 'list' },
|
|
425
|
+
{ expect: { state: { path: 'listed.ids', equals: ['new-1', 't-a1', 't-a2'] } } },
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
429
|
+
expect(await listAllWithoutCookies()).toEqual(['real-1']);
|
|
430
|
+
const { result } = await journey;
|
|
431
|
+
expect(result.passed).toBe(true);
|
|
432
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
chromiumTest('as: outsider acts as that data set user and sees only its own org', async () => {
|
|
437
|
+
const { result } = await runJourney({
|
|
438
|
+
data: 'shop',
|
|
439
|
+
user: 'owner',
|
|
440
|
+
steps: [
|
|
441
|
+
{ click: 'list' },
|
|
442
|
+
{ expect: { state: { path: 'listed.ids', equals: ['t-a1', 't-a2'] } } },
|
|
443
|
+
{ as: 'outsider' },
|
|
444
|
+
{ click: 'list' },
|
|
445
|
+
{ expect: { state: { path: 'listed.ids', equals: ['t-b1'] } } },
|
|
446
|
+
],
|
|
447
|
+
});
|
|
448
|
+
expect(result.passed).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
chromiumTest(
|
|
452
|
+
'a detached CallApi on the last step writes to the session database before the session drops it',
|
|
453
|
+
async () => {
|
|
454
|
+
storeEvents.length = 0;
|
|
455
|
+
const { result } = await runJourney({
|
|
456
|
+
data: 'shop',
|
|
457
|
+
user: 'owner',
|
|
458
|
+
steps: [
|
|
459
|
+
{ click: 'create_later' },
|
|
460
|
+
{ expect: { state: { path: 'dispatched.success', equals: true } } },
|
|
461
|
+
],
|
|
462
|
+
});
|
|
463
|
+
expect(result.passed).toBe(true);
|
|
464
|
+
// The session closed when the journey answered; by then the detached run had written.
|
|
465
|
+
const insert = storeEvents.find(
|
|
466
|
+
(event) => event.operationType === 'insert' && event.id === 'later-1'
|
|
467
|
+
);
|
|
468
|
+
expect(insert).toBeDefined();
|
|
469
|
+
expect(insert.db).toMatch(/^ld_[0-9a-f]{12}$/);
|
|
470
|
+
const insertIndex = storeEvents.indexOf(insert);
|
|
471
|
+
const dropIndex = storeEvents.findIndex(
|
|
472
|
+
(event) => event.operationType === 'dropDatabase' && event.db === insert.db
|
|
473
|
+
);
|
|
474
|
+
expect(dropIndex).toBeGreaterThan(insertIndex);
|
|
475
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
chromiumTest(
|
|
480
|
+
"a change stream source on a redirected connection delivers the session's changes and not the stand-in's",
|
|
481
|
+
async () => {
|
|
482
|
+
const journey = runJourney({
|
|
483
|
+
data: 'shop',
|
|
484
|
+
user: 'owner',
|
|
485
|
+
steps: [
|
|
486
|
+
{ click: 'watch' },
|
|
487
|
+
{ expect: { state: { path: 'subscribed', equals: true } } },
|
|
488
|
+
{ wait: { ms: 1500 } },
|
|
489
|
+
{ click: 'create_watched' },
|
|
490
|
+
{ expect: { state: { path: 'createdWatched.success', equals: true } } },
|
|
491
|
+
{ expect: { state: { path: 'changes', equals: ['watched-1'] } } },
|
|
492
|
+
{ wait: { ms: 1500 } },
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
// Written to the stand-in while the journey watches: its stream must not deliver it.
|
|
496
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
497
|
+
await standIn
|
|
498
|
+
.db(standInDatabase)
|
|
499
|
+
.collection('tickets')
|
|
500
|
+
.insertOne({ _id: 'real-2', organization_id: 'org_a' });
|
|
501
|
+
const { result } = await journey;
|
|
502
|
+
expect(result.passed).toBe(true);
|
|
503
|
+
expect(result.state.changes).toEqual(['watched-1']);
|
|
504
|
+
await standIn.db(standInDatabase).collection('tickets').deleteOne({ _id: 'real-2' });
|
|
505
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
chromiumTest(
|
|
510
|
+
'a journey that writes through a module connection writes to its session database',
|
|
511
|
+
async () => {
|
|
512
|
+
storeEvents.length = 0;
|
|
513
|
+
const { result } = await runJourney({
|
|
514
|
+
data: 'shop',
|
|
515
|
+
user: 'owner',
|
|
516
|
+
steps: [
|
|
517
|
+
{ click: 'archive' },
|
|
518
|
+
{ expect: { state: { path: 'archived.success', equals: true } } },
|
|
519
|
+
],
|
|
520
|
+
});
|
|
521
|
+
expect(result.passed).toBe(true);
|
|
522
|
+
const insert = storeEvents.find(
|
|
523
|
+
(event) => event.operationType === 'insert' && event.id === 'arch-1'
|
|
524
|
+
);
|
|
525
|
+
expect(insert).toBeDefined();
|
|
526
|
+
expect(insert.db).toMatch(/^ld_[0-9a-f]{12}$/);
|
|
527
|
+
expect(insert.coll).toEqual('archived');
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
chromiumTest(
|
|
532
|
+
"a data set journey's browser gets 404 from the auth routes and the get-session stub answers for its user",
|
|
533
|
+
async () => {
|
|
534
|
+
const { result } = await runJourney({
|
|
535
|
+
data: 'shop',
|
|
536
|
+
user: 'owner',
|
|
537
|
+
steps: [
|
|
538
|
+
{ click: 'auth_probe' },
|
|
539
|
+
{ expect: { state: { path: 'auth.session', equals: 200 } } },
|
|
540
|
+
],
|
|
541
|
+
});
|
|
542
|
+
expect(result.passed).toBe(true);
|
|
543
|
+
expect(result.state.auth).toEqual({
|
|
544
|
+
signUp: 404,
|
|
545
|
+
signIn: 404,
|
|
546
|
+
session: 200,
|
|
547
|
+
sessionUser: 'u_1',
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
chromiumTest(
|
|
553
|
+
'a detached run that reaches an auth step under a data session fails at that step',
|
|
554
|
+
async () => {
|
|
555
|
+
storeEvents.length = 0;
|
|
556
|
+
const { result } = await runJourney({
|
|
557
|
+
data: 'shop',
|
|
558
|
+
user: 'owner',
|
|
559
|
+
steps: [
|
|
560
|
+
{ click: 'create_later_auth' },
|
|
561
|
+
{ expect: { state: { path: 'dispatchedAuth.success', equals: true } } },
|
|
562
|
+
],
|
|
563
|
+
});
|
|
564
|
+
expect(result.passed).toBe(true);
|
|
565
|
+
// The session waited for the detached run before it closed: the step before the auth step
|
|
566
|
+
// wrote to the session database, and the step after it never ran.
|
|
567
|
+
const before = storeEvents.find(
|
|
568
|
+
(event) => event.operationType === 'insert' && event.id === 'later-auth-before'
|
|
569
|
+
);
|
|
570
|
+
expect(before).toBeDefined();
|
|
571
|
+
expect(before.db).toMatch(/^ld_[0-9a-f]{12}$/);
|
|
572
|
+
expect(storeEvents.some((event) => event.id === 'later-auth-after')).toBe(false);
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
chromiumTest(
|
|
577
|
+
"a data set journey that navigates to the dev server's other host fails, and the server never sees it",
|
|
578
|
+
async () => {
|
|
579
|
+
const port = new URL(origin).port;
|
|
580
|
+
// The other host reaches this server: without a journey's browser in the way, it answers.
|
|
581
|
+
const reachable = await fetch(`http://localhost:${port}/tickets`);
|
|
582
|
+
expect(reachable.status).toBe(200);
|
|
583
|
+
seenHosts.length = 0;
|
|
584
|
+
const { result } = await runJourney({
|
|
585
|
+
data: 'shop',
|
|
586
|
+
user: 'owner',
|
|
587
|
+
steps: [{ click: 'leave' }, { click: 'list' }],
|
|
588
|
+
});
|
|
589
|
+
expect(result.passed).toBe(false);
|
|
590
|
+
expect(result.failure.index).toBe(0);
|
|
591
|
+
expect(result.failure.message).toEqual(
|
|
592
|
+
`Journey left its origin ${origin} for http://localhost:${port}/tickets; a data set journey must stay on one host of the dev server.`
|
|
593
|
+
);
|
|
594
|
+
expect(result.steps.map((step) => step.status)).toEqual(['failed', 'skipped']);
|
|
595
|
+
expect(seenHosts.length).toBeGreaterThan(0);
|
|
596
|
+
expect(seenHosts.filter((host) => host.startsWith('localhost'))).toEqual([]);
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
// A drop-step mutant on an endpoint's "log" step, as `lowdefy journeys harden` sends it: the step's
|
|
601
|
+
// ~k in the build the server is serving.
|
|
602
|
+
function dropLogMutant(endpointId) {
|
|
603
|
+
const endpoint = serializer.deserializeFromString(
|
|
604
|
+
fs.readFileSync(path.join(serverDirectory, 'build', 'api', `${endpointId}.json`), 'utf8')
|
|
605
|
+
);
|
|
606
|
+
const log = endpoint.routine.find((step) => step.stepId === 'log');
|
|
607
|
+
return {
|
|
608
|
+
buildId: getBuildId(),
|
|
609
|
+
artifact: `api/${endpointId}.json`,
|
|
610
|
+
key: log['~k'],
|
|
611
|
+
arg: null,
|
|
612
|
+
operator: 'drop-step',
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
chromiumTest(
|
|
617
|
+
'a mutant on an endpoint reached only through a detached CallApi applies, and the detached target writes to the session database',
|
|
618
|
+
async () => {
|
|
619
|
+
storeEvents.length = 0;
|
|
620
|
+
const { status, result } = await runJourney({
|
|
621
|
+
data: 'shop',
|
|
622
|
+
user: 'owner',
|
|
623
|
+
mutant: dropLogMutant('insert_detached_logged'),
|
|
624
|
+
steps: [
|
|
625
|
+
{ click: 'create_later_logged' },
|
|
626
|
+
{ expect: { state: { path: 'dispatchedLogged.success', equals: true } } },
|
|
627
|
+
],
|
|
628
|
+
});
|
|
629
|
+
expect(status).toBe(200);
|
|
630
|
+
expect(result.passed).toBe(true);
|
|
631
|
+
// The detached request carried the mutant cookie: its read of the endpoint dropped "log".
|
|
632
|
+
expect(result.mutant.applied).toBeGreaterThanOrEqual(1);
|
|
633
|
+
expect(result.mutant.misses).toEqual([]);
|
|
634
|
+
// And the data cookie: its insert reached the session's database, not the stand-in.
|
|
635
|
+
const insert = storeEvents.find(
|
|
636
|
+
(event) => event.operationType === 'insert' && event.id === 'later-logged'
|
|
637
|
+
);
|
|
638
|
+
expect(insert).toBeDefined();
|
|
639
|
+
expect(insert.db).toMatch(/^ld_[0-9a-f]{12}$/);
|
|
640
|
+
expect(storeEvents.some((event) => event.id === 'log-detached')).toBe(false);
|
|
641
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
chromiumTest(
|
|
646
|
+
'two mutant runs of a data-set journey at once, as two harden workers run them, never see each other’s writes',
|
|
647
|
+
async () => {
|
|
648
|
+
const mutant = dropLogMutant('create_ticket_logged');
|
|
649
|
+
const journey = {
|
|
650
|
+
data: 'shop',
|
|
651
|
+
user: 'owner',
|
|
652
|
+
mutant,
|
|
653
|
+
steps: [
|
|
654
|
+
{ click: 'create_logged' },
|
|
655
|
+
{ expect: { state: { path: 'createdLogged.success', equals: true } } },
|
|
656
|
+
{ wait: { ms: 1000 } },
|
|
657
|
+
{ click: 'list' },
|
|
658
|
+
{ expect: { state: { path: 'listed.ids', equals: ['new-logged', 't-a1', 't-a2'] } } },
|
|
659
|
+
],
|
|
660
|
+
};
|
|
661
|
+
const runs = await Promise.all([runJourney(journey), runJourney(journey)]);
|
|
662
|
+
runs.forEach(({ status, result }) => {
|
|
663
|
+
expect(status).toBe(200);
|
|
664
|
+
// Each inserted new-logged into a database of its own: in a shared one the second insert of
|
|
665
|
+
// that _id would fail, and each list would show both runs' rows.
|
|
666
|
+
expect(result.passed).toBe(true);
|
|
667
|
+
expect(result.mutant.applied).toBeGreaterThanOrEqual(1);
|
|
668
|
+
});
|
|
669
|
+
expect(runs[0].result.mutant.id).not.toEqual(runs[1].result.mutant.id);
|
|
670
|
+
expect(await standInIds()).toEqual(['real-1']);
|
|
671
|
+
}
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
// Last, after every journey above: the stand-in, auth collections included, is exactly as the
|
|
675
|
+
// snapshot found it, and no session database ever landed on it.
|
|
676
|
+
chromiumTest('the data set journeys left the stand-in database unchanged', async () => {
|
|
677
|
+
expect(await snapshotStandIn()).toEqual(standInBefore);
|
|
678
|
+
expect(await standInSessionDatabases()).toEqual([]);
|
|
679
|
+
});
|