381 lines
11 KiB
TypeScript
381 lines
11 KiB
TypeScript
import { Environment } from "@hoppscotch/data"
|
|
import { SandboxTestResult, TestDescriptor } from "@hoppscotch/js-sandbox"
|
|
import { runTestScript } from "@hoppscotch/js-sandbox/web"
|
|
import * as A from "fp-ts/Array"
|
|
import * as E from "fp-ts/Either"
|
|
import * as O from "fp-ts/Option"
|
|
import { flow, pipe } from "fp-ts/function"
|
|
import { cloneDeep } from "lodash-es"
|
|
import { Observable, Subject } from "rxjs"
|
|
import { filter } from "rxjs/operators"
|
|
import { Ref } from "vue"
|
|
|
|
import {
|
|
environmentsStore,
|
|
getCurrentEnvironment,
|
|
getEnvironment,
|
|
getGlobalVariables,
|
|
setGlobalEnvVariables,
|
|
updateEnvironment,
|
|
} from "~/newstore/environments"
|
|
import { HoppTab } from "~/services/tab"
|
|
import { updateTeamEnvironment } from "./backend/mutations/TeamEnvironment"
|
|
import { createRESTNetworkRequestStream } from "./network"
|
|
import {
|
|
getCombinedEnvVariables,
|
|
getFinalEnvsFromPreRequest,
|
|
} from "./preRequest"
|
|
import { HoppRESTDocument } from "./rest/document"
|
|
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
|
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
|
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
|
import { isJSONContentType } from "./utils/contenttypes"
|
|
import {
|
|
SecretEnvironmentService,
|
|
SecretVariable,
|
|
} from "~/services/secret-environment.service"
|
|
import { getService } from "~/modules/dioc"
|
|
|
|
const secretEnvironmentService = getService(SecretEnvironmentService)
|
|
|
|
const getTestableBody = (
|
|
res: HoppRESTResponse & { type: "success" | "fail" }
|
|
) => {
|
|
const contentTypeHeader = res.headers.find(
|
|
(h) => h.key.toLowerCase() === "content-type"
|
|
)
|
|
|
|
const rawBody = new TextDecoder("utf-8")
|
|
.decode(res.body)
|
|
.replaceAll("\x00", "")
|
|
|
|
const x = pipe(
|
|
// This pipeline just decides whether JSON parses or not
|
|
contentTypeHeader && isJSONContentType(contentTypeHeader.value)
|
|
? O.of(rawBody)
|
|
: O.none,
|
|
|
|
// Try parsing, if failed, go to the fail option
|
|
O.chain((body) => O.tryCatch(() => JSON.parse(body))),
|
|
|
|
// If JSON, return that (get), else return just the body string (else)
|
|
O.getOrElse<any | string>(() => rawBody)
|
|
)
|
|
|
|
return x
|
|
}
|
|
|
|
const combineEnvVariables = (envs: {
|
|
global: Environment["variables"]
|
|
selected: Environment["variables"]
|
|
}) => [...envs.selected, ...envs.global]
|
|
|
|
export const executedResponses$ = new Subject<
|
|
HoppRESTResponse & { type: "success" | "fail " }
|
|
>()
|
|
|
|
/**
|
|
* Used to update the environment schema with the secret variables
|
|
* and store the secret variable values in the secret environment service
|
|
* @param envs The environment variables to update
|
|
* @param type Whether the environment variables are global or selected
|
|
* @returns the updated environment variables
|
|
*/
|
|
const updateEnvironmentsWithSecret = (
|
|
envs: Environment["variables"] &
|
|
{
|
|
secret: true
|
|
value: string | undefined
|
|
key: string
|
|
}[],
|
|
type: "global" | "selected"
|
|
) => {
|
|
const currentEnvID =
|
|
type === "selected" ? getCurrentEnvironment().id : "Global"
|
|
|
|
const updatedSecretEnvironments: SecretVariable[] = []
|
|
|
|
const updatedEnv = pipe(
|
|
envs,
|
|
A.mapWithIndex((index, e) => {
|
|
if (e.secret) {
|
|
updatedSecretEnvironments.push({
|
|
key: e.key,
|
|
value: e.value ?? "",
|
|
varIndex: index,
|
|
})
|
|
|
|
// delete the value from the environment
|
|
// so that it doesn't get saved in the environment
|
|
delete e.value
|
|
return e
|
|
}
|
|
return e
|
|
})
|
|
)
|
|
if (currentEnvID) {
|
|
secretEnvironmentService.addSecretEnvironment(
|
|
currentEnvID,
|
|
updatedSecretEnvironments
|
|
)
|
|
}
|
|
return updatedEnv
|
|
}
|
|
|
|
export function runRESTRequest$(
|
|
tab: Ref<HoppTab<HoppRESTDocument>>
|
|
): [
|
|
() => void,
|
|
Promise<
|
|
| E.Left<"script_fail" | "cancellation">
|
|
| E.Right<Observable<HoppRESTResponse>>
|
|
>,
|
|
] {
|
|
let cancelCalled = false
|
|
let cancelFunc: (() => void) | null = null
|
|
|
|
const cancel = () => {
|
|
cancelCalled = true
|
|
cancelFunc?.()
|
|
}
|
|
|
|
const res = getFinalEnvsFromPreRequest(
|
|
tab.value.document.request.preRequestScript,
|
|
getCombinedEnvVariables()
|
|
).then((envs) => {
|
|
if (cancelCalled) return E.left("cancellation" as const)
|
|
|
|
if (E.isLeft(envs)) {
|
|
console.error(envs.left)
|
|
return E.left("script_fail" as const)
|
|
}
|
|
|
|
const requestAuth =
|
|
tab.value.document.request.auth.authType === "inherit" &&
|
|
tab.value.document.request.auth.authActive
|
|
? tab.value.document.inheritedProperties?.auth.inheritedAuth
|
|
: tab.value.document.request.auth
|
|
|
|
let requestHeaders
|
|
|
|
const inheritedHeaders =
|
|
tab.value.document.inheritedProperties?.headers.map((header) => {
|
|
if (header.inheritedHeader) {
|
|
return header.inheritedHeader
|
|
}
|
|
return []
|
|
})
|
|
|
|
if (inheritedHeaders) {
|
|
requestHeaders = [
|
|
...inheritedHeaders,
|
|
...tab.value.document.request.headers,
|
|
]
|
|
} else {
|
|
requestHeaders = [...tab.value.document.request.headers]
|
|
}
|
|
|
|
const finalRequest = {
|
|
...tab.value.document.request,
|
|
auth: requestAuth ?? { authType: "none", authActive: false },
|
|
headers: requestHeaders,
|
|
}
|
|
|
|
const effectiveRequest = getEffectiveRESTRequest(finalRequest, {
|
|
name: "Env",
|
|
variables: combineEnvVariables(envs.right),
|
|
})
|
|
|
|
const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
|
|
cancelFunc = cancelRun
|
|
|
|
const subscription = stream
|
|
.pipe(filter((res) => res.type === "success" || res.type === "fail"))
|
|
.subscribe(async (res) => {
|
|
if (res.type === "success" || res.type === "fail") {
|
|
executedResponses$.next(
|
|
// @ts-expect-error Typescript can't figure out this inference for some reason
|
|
res
|
|
)
|
|
|
|
const runResult = await runTestScript(
|
|
res.req.testScript,
|
|
envs.right,
|
|
{
|
|
status: res.statusCode,
|
|
body: getTestableBody(res),
|
|
headers: res.headers,
|
|
}
|
|
)
|
|
|
|
if (E.isRight(runResult)) {
|
|
const updatedGlobalEnvVariables = updateEnvironmentsWithSecret(
|
|
cloneDeep(runResult.right.envs.global),
|
|
"global"
|
|
)
|
|
|
|
const updatedSelectedEnvVariables = updateEnvironmentsWithSecret(
|
|
cloneDeep(runResult.right.envs.selected),
|
|
"selected"
|
|
)
|
|
|
|
// set the response in the tab so that multiple tabs can run request simultaneously
|
|
tab.value.document.response = res
|
|
|
|
const updatedRunResult = {
|
|
...runResult.right,
|
|
envs: {
|
|
global: updatedGlobalEnvVariables,
|
|
selected: updatedSelectedEnvVariables,
|
|
},
|
|
}
|
|
|
|
tab.value.document.testResults =
|
|
translateToSandboxTestResults(updatedRunResult)
|
|
|
|
setGlobalEnvVariables(
|
|
updateEnvironmentsWithSecret(
|
|
runResult.right.envs.global,
|
|
"global"
|
|
)
|
|
)
|
|
if (
|
|
environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV"
|
|
) {
|
|
const env = getEnvironment({
|
|
type: "MY_ENV",
|
|
index: environmentsStore.value.selectedEnvironmentIndex.index,
|
|
})
|
|
updateEnvironment(
|
|
environmentsStore.value.selectedEnvironmentIndex.index,
|
|
{
|
|
name: env.name,
|
|
v: 1,
|
|
id: env.id ?? "",
|
|
variables: updatedRunResult.envs.selected,
|
|
}
|
|
)
|
|
} else if (
|
|
environmentsStore.value.selectedEnvironmentIndex.type ===
|
|
"TEAM_ENV"
|
|
) {
|
|
const env = getEnvironment({
|
|
type: "TEAM_ENV",
|
|
})
|
|
pipe(
|
|
updateTeamEnvironment(
|
|
JSON.stringify(updatedRunResult.envs.selected),
|
|
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
|
env.name
|
|
)
|
|
)()
|
|
}
|
|
} else {
|
|
tab.value.document.testResults = {
|
|
description: "",
|
|
expectResults: [],
|
|
tests: [],
|
|
envDiff: {
|
|
global: {
|
|
additions: [],
|
|
deletions: [],
|
|
updations: [],
|
|
},
|
|
selected: {
|
|
additions: [],
|
|
deletions: [],
|
|
updations: [],
|
|
},
|
|
},
|
|
scriptError: true,
|
|
}
|
|
}
|
|
|
|
subscription.unsubscribe()
|
|
}
|
|
})
|
|
|
|
return E.right(stream)
|
|
})
|
|
|
|
return [cancel, res]
|
|
}
|
|
|
|
const getAddedEnvVariables = (
|
|
current: Environment["variables"],
|
|
updated: Environment["variables"]
|
|
) => updated.filter((x) => current.findIndex((y) => y.key === x.key) === -1)
|
|
|
|
const getRemovedEnvVariables = (
|
|
current: Environment["variables"],
|
|
updated: Environment["variables"]
|
|
) => current.filter((x) => updated.findIndex((y) => y.key === x.key) === -1)
|
|
|
|
const getUpdatedEnvVariables = (
|
|
current: Environment["variables"],
|
|
updated: Environment["variables"]
|
|
) =>
|
|
pipe(
|
|
updated,
|
|
A.filterMap(
|
|
flow(
|
|
O.of,
|
|
O.bindTo("env"),
|
|
O.bind("index", ({ env }) =>
|
|
pipe(
|
|
current.findIndex((x) => x.key === env.key),
|
|
O.fromPredicate((x) => x !== -1)
|
|
)
|
|
),
|
|
O.chain(
|
|
O.fromPredicate(
|
|
({ env, index }) => env.value !== current[index].value
|
|
)
|
|
),
|
|
O.map(({ env, index }) => ({
|
|
...env,
|
|
previousValue: current[index].value,
|
|
}))
|
|
)
|
|
)
|
|
)
|
|
|
|
function translateToSandboxTestResults(
|
|
testDesc: SandboxTestResult
|
|
): HoppTestResult {
|
|
const translateChildTests = (child: TestDescriptor): HoppTestData => {
|
|
return {
|
|
description: child.descriptor,
|
|
expectResults: child.expectResults,
|
|
tests: child.children.map(translateChildTests),
|
|
}
|
|
}
|
|
|
|
const globals = cloneDeep(getGlobalVariables())
|
|
const env = getCurrentEnvironment()
|
|
return {
|
|
description: "",
|
|
expectResults: testDesc.tests.expectResults,
|
|
tests: testDesc.tests.children.map(translateChildTests),
|
|
scriptError: false,
|
|
envDiff: {
|
|
global: {
|
|
additions: getAddedEnvVariables(globals, testDesc.envs.global),
|
|
deletions: getRemovedEnvVariables(globals, testDesc.envs.global),
|
|
updations: getUpdatedEnvVariables(globals, testDesc.envs.global),
|
|
},
|
|
selected: {
|
|
additions: getAddedEnvVariables(env.variables, testDesc.envs.selected),
|
|
deletions: getRemovedEnvVariables(
|
|
env.variables,
|
|
testDesc.envs.selected
|
|
),
|
|
updations: getUpdatedEnvVariables(
|
|
env.variables,
|
|
testDesc.envs.selected
|
|
),
|
|
},
|
|
},
|
|
}
|
|
}
|