feat: test script and pre-request script wiring

This commit is contained in:
Andrew Bastin
2021-07-21 23:56:39 -04:00
parent 5f5f086dfc
commit 8597c04ff1
6 changed files with 193 additions and 16 deletions

View File

@@ -74,7 +74,7 @@
<ButtonPrimary
id="send"
class="rounded-none"
:label="!loading ? $t('send') : $('cancel')"
:label="!loading ? $t('send') : $t('cancel')"
:shortcuts="[getSpecialKey(), 'G']"
outline
@click.native="!loading ? newSendRequest() : cancelRequest()"
@@ -188,16 +188,13 @@
<script>
import {
updateRESTResponse,
restRequest$,
restEndpoint$,
setRESTEndpoint,
restMethod$,
updateRESTMethod,
} from "~/newstore/RESTSession"
import { createRESTNetworkRequestStream } from "~/helpers/network"
import { currentEnvironment$ } from "~/newstore/environments"
import { getEffectiveRESTRequestStream } from "~/helpers/utils/EffectiveURL"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { runRESTRequest$ } from "~/helpers/RequestRunner"
export default {
data() {
@@ -220,7 +217,6 @@ export default {
showCurlImportModal: false,
showCodegenModal: false,
navigatorShare: navigator.share,
effectiveStream$: null,
loading: false,
}
},
@@ -228,10 +224,6 @@ export default {
return {
newMethod$: restMethod$,
newEndpoint$: restEndpoint$,
effectiveStream$: getEffectiveRESTRequestStream(
restRequest$,
currentEnvironment$
),
}
},
watch: {
@@ -247,10 +239,7 @@ export default {
newSendRequest() {
this.loading = true
this.$subscribeTo(
createRESTNetworkRequestStream(
this.effectiveStream$,
currentEnvironment$
),
runRESTRequest$(),
(responseState) => {
console.log(responseState)
updateRESTResponse(responseState)

View File

@@ -79,12 +79,14 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { useTestScript } from "~/newstore/RESTSession"
import { useTestScript, restTestResults$ } from "~/newstore/RESTSession"
import { useReadonlyStream } from "~/helpers/utils/composables"
export default defineComponent({
setup() {
return {
testScript: useTestScript(),
testResults: useReadonlyStream(restTestResults$, null),
testReports: [],
}
},

147
helpers/RequestRunner.ts Normal file
View File

@@ -0,0 +1,147 @@
import { Observable } from "rxjs"
import { filter } from "rxjs/operators"
import getEnvironmentVariablesFromScript from "./preRequest"
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
import { HoppRESTResponse } from "./types/HoppRESTResponse"
import { createRESTNetworkRequestStream } from "./network"
import runTestScriptWithVariables from "./postwomanTesting"
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
/**
* Runs a REST network request along with all the
* other side processes (like running test scripts)
*/
export function runRESTRequest$(): Observable<HoppRESTResponse> {
const envs = getEnvironmentVariablesFromScript(
getRESTRequest().preRequestScript
)
const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
name: "Env",
variables: Object.keys(envs).map((key) => {
return {
key,
value: envs[key],
}
}),
})
const stream = createRESTNetworkRequestStream(effectiveRequest)
// Run Test Script when request ran successfully
const subscription = stream
.pipe(filter((res) => res.type === "success"))
.subscribe((res) => {
const testReport: {
report: "" // ¯\_(ツ)_/¯
testResults: Array<
| {
result: "FAIL"
message: string
styles: { icon: "close"; class: "cl-error-response" }
}
| {
result: "PASS"
styles: { icon: "check"; class: "success-response" }
}
| { startBlock: string; styles: { icon: ""; class: "" } }
| { endBlock: true; styles: { icon: ""; class: "" } }
>
errors: [] // ¯\_(ツ)_/¯
} = runTestScriptWithVariables(effectiveRequest.testScript, {
response: res,
}) as any
setRESTTestResults(translateToNewTestResults(testReport))
subscription.unsubscribe()
})
return stream
}
function isTestPass(x: any): x is {
result: "PASS"
styles: { icon: "check"; class: "success-response" }
} {
return x.result !== undefined && x.result === "PASS"
}
function isTestFail(x: any): x is {
result: "FAIL"
message: string
styles: { icon: "close"; class: "cl-error-response" }
} {
return x.result !== undefined && x.result === "FAIL"
}
function isStartBlock(
x: any
): x is { startBlock: string; styles: { icon: ""; class: "" } } {
return x.startBlock !== undefined
}
function isEndBlock(
x: any
): x is { endBlock: true; styles: { icon: ""; class: "" } } {
return x.endBlock !== undefined
}
function translateToNewTestResults(testReport: {
report: "" // ¯\_(ツ)_/¯
testResults: Array<
| {
result: "FAIL"
message: string
styles: { icon: "close"; class: "cl-error-response" }
}
| { result: "PASS"; styles: { icon: "check"; class: "success-response" } }
| { startBlock: string; styles: { icon: ""; class: "" } }
| { endBlock: true; styles: { icon: ""; class: "" } }
>
errors: [] // ¯\_(ツ)_/¯
}): HoppTestResult {
// Build a stack of test data which we eventually build up based on the results
const testsStack: HoppTestData[] = [
{
description: "root",
tests: [],
expectResults: [],
},
]
testReport.testResults.forEach((result) => {
// This is a test block start, push an empty test to the stack
if (isStartBlock(result)) {
testsStack.push({
description: result.startBlock,
tests: [],
expectResults: [],
})
} else if (isEndBlock(result)) {
// End of the block, pop the stack and add it as a child to the current stack top
const testData = testsStack.pop()!
testsStack[testsStack.length - 1].tests.push(testData)
} else if (isTestPass(result)) {
// A normal PASS expectation
testsStack[testsStack.length - 1].expectResults.push({
status: "pass",
})
} else if (isTestFail(result)) {
// A normal FAIL expectation
testsStack[testsStack.length - 1].expectResults.push({
status: "fail",
message: result.message,
})
}
})
// We should end up with only the root stack entry
if (testsStack.length !== 1) throw new Error("Invalid test result structure")
return {
expectResults: testsStack[0].expectResults,
tests: testsStack[0].tests,
}
}

View File

@@ -36,7 +36,7 @@ export default function getEnvironmentVariablesFromScript(script: string) {
// run pre-request script within this function so that it has access to the pw object.
// eslint-disable-next-line no-new-func
new Function("pw", script)(pw)
} catch (_e) {}
} catch (_e) { }
return _variables
}

View File

@@ -0,0 +1,14 @@
export type HoppTestExpectResult =
| { status: "pass" }
| { status: "fail" | "error"; message: string }
export type HoppTestData = {
description: string
expectResults: HoppTestExpectResult[]
tests: HoppTestData[]
}
export type HoppTestResult = {
tests: HoppTestData[]
expectResults: HoppTestExpectResult[]
}

View File

@@ -9,6 +9,7 @@ import {
} from "~/helpers/types/HoppRESTRequest"
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
import { useStream } from "~/helpers/utils/composables"
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
function getParamsInURL(url: string): { key: string; value: string }[] {
const result: { key: string; value: string }[] = []
@@ -114,6 +115,7 @@ function updateURLParam(
type RESTSession = {
request: HoppRESTRequest
response: HoppRESTResponse | null
testResults: HoppTestResult | null
}
const defaultRESTSession: RESTSession = {
@@ -127,6 +129,7 @@ const defaultRESTSession: RESTSession = {
testScript: "// pw.expect('variable').toBe('value');",
},
response: null,
testResults: null,
}
const dispatchers = defineDispatchers({
@@ -320,6 +323,14 @@ const dispatchers = defineDispatchers({
response: null,
}
},
setTestResults(
_curr: RESTSession,
{ newResults }: { newResults: HoppTestResult | null }
) {
return {
testResults: newResults,
}
},
})
const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers)
@@ -459,6 +470,15 @@ export function clearRESTResponse() {
})
}
export function setRESTTestResults(newResults: HoppTestResult | null) {
restSessionStore.dispatch({
dispatcher: "setTestResults",
payload: {
newResults,
},
})
}
export const restRequest$ = restSessionStore.subject$.pipe(
pluck("request"),
distinctUntilChanged()
@@ -514,6 +534,11 @@ export const completedRESTResponse$ = restResponse$.pipe(
)
)
export const restTestResults$ = restSessionStore.subject$.pipe(
pluck("testResults"),
distinctUntilChanged()
)
/**
* A Vue 3 composable function that gives access to a ref
* which is updated to the preRequestScript value in the store.