feat: move testing code to js-sandbox

This commit is contained in:
Andrew Bastin
2021-09-25 01:09:48 +05:30
parent 166f9e817b
commit 9454d8c100
4 changed files with 98 additions and 499 deletions

View File

@@ -1,155 +1,80 @@
import { Observable } from "rxjs"
import { filter } from "rxjs/operators"
import getEnvironmentVariablesFromScript from "./preRequest"
import { chain, right, TaskEither } from "fp-ts/lib/TaskEither"
import { pipe } from "fp-ts/lib/function"
import { runTestScript, TestDescriptor } from "@hoppscotch/js-sandbox"
import { isRight } from "fp-ts/lib/Either"
import { getFinalEnvsFromPreRequest } from "./preRequest"
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
import { HoppRESTResponse } from "./types/HoppRESTResponse"
import { createRESTNetworkRequestStream } from "./network"
import runTestScriptWithVariables, {
transformResponseForTesting,
} from "./postwomanTesting"
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
import { isJSONContentType } from "./utils/contenttypes"
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 getTestableBody = (res: HoppRESTResponse & { type: "success" }) => {
const contentTypeHeader = res.headers.find(
(h) => h.value.toLowerCase() === "content-type"
)
const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
name: "Env",
variables: Object.keys(envs).map((key) => {
return {
key,
value: envs[key],
}
}),
})
const rawBody = new TextDecoder("utf-8").decode(res.body)
const stream = createRESTNetworkRequestStream(effectiveRequest)
if (!contentTypeHeader || !isJSONContentType(contentTypeHeader.value))
return rawBody
// 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"
message: string
styles: { icon: "check"; class: "success-response" }
}
| { startBlock: string; styles: { icon: ""; class: "" } }
| { endBlock: true; styles: { icon: ""; class: "" } }
>
errors: [] // ¯\_(ツ)_/¯
} = runTestScriptWithVariables(effectiveRequest.testScript, {
response: transformResponseForTesting(res),
}) as any
return JSON.parse(rawBody)
}
setRESTTestResults(translateToNewTestResults(testReport))
subscription.unsubscribe()
export const runRESTRequest$: TaskEither<
string,
Observable<HoppRESTResponse>
> = pipe(
getFinalEnvsFromPreRequest(),
chain((envs) => {
console.log(envs)
const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
name: "Env",
variables: envs,
})
return stream
}
const stream = createRESTNetworkRequestStream(effectiveRequest)
function isTestPass(x: any): x is {
result: "PASS"
styles: { icon: "check"; class: "success-response" }
} {
return x.result !== undefined && x.result === "PASS"
}
// Run Test Script when request ran successfully
const subscription = stream
.pipe(filter((res) => res.type === "success"))
.subscribe(async (res) => {
if (res.type === "success") {
const runResult = await runTestScript(res.req.testScript, {
status: res.statusCode,
body: getTestableBody(res),
headers: res.headers,
})()
function isTestFail(x: any): x is {
result: "FAIL"
message: string
styles: { icon: "close"; class: "cl-error-response" }
} {
return x.result !== undefined && x.result === "FAIL"
}
// TODO: Handle script executation fails (isLeft)
if (isRight(runResult)) {
setRESTTestResults(translateToSandboxTestResults(runResult.right))
}
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"
message: string
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: [],
subscription.unsubscribe()
}
})
} 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",
message: result.message,
})
} else if (isTestFail(result)) {
// A normal FAIL expectation
testsStack[testsStack.length - 1].expectResults.push({
status: "fail",
message: result.message,
})
}
return right(stream)
})
)
// We should end up with only the root stack entry
if (testsStack.length !== 1) throw new Error("Invalid test result structure")
function translateToSandboxTestResults(
testDesc: TestDescriptor
): HoppTestResult {
const translateChildTests = (child: TestDescriptor): HoppTestData => {
return {
description: child.descriptor,
expectResults: child.expectResults,
tests: child.children.map(translateChildTests),
}
}
return {
expectResults: testsStack[0].expectResults,
tests: testsStack[0].tests,
expectResults: testDesc.expectResults,
tests: testDesc.children.map(translateChildTests),
}
}