RequestRunner.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { Observable } from "rxjs"
  2. import { filter } from "rxjs/operators"
  3. import { chain, right, TaskEither } from "fp-ts/lib/TaskEither"
  4. import { flow, pipe } from "fp-ts/function"
  5. import * as O from "fp-ts/Option"
  6. import * as A from "fp-ts/Array"
  7. import { Environment } from "@hoppscotch/data"
  8. import {
  9. SandboxTestResult,
  10. runTestScript,
  11. TestDescriptor,
  12. } from "@hoppscotch/js-sandbox"
  13. import { isRight } from "fp-ts/Either"
  14. import cloneDeep from "lodash/cloneDeep"
  15. import {
  16. getCombinedEnvVariables,
  17. getFinalEnvsFromPreRequest,
  18. } from "./preRequest"
  19. import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
  20. import { HoppRESTResponse } from "./types/HoppRESTResponse"
  21. import { createRESTNetworkRequestStream } from "./network"
  22. import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
  23. import { isJSONContentType } from "./utils/contenttypes"
  24. import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
  25. import {
  26. environmentsStore,
  27. getCurrentEnvironment,
  28. getEnviroment,
  29. getGlobalVariables,
  30. setGlobalEnvVariables,
  31. updateEnvironment,
  32. } from "~/newstore/environments"
  33. const getTestableBody = (
  34. res: HoppRESTResponse & { type: "success" | "fail" }
  35. ) => {
  36. const contentTypeHeader = res.headers.find(
  37. (h) => h.key.toLowerCase() === "content-type"
  38. )
  39. const rawBody = new TextDecoder("utf-8")
  40. .decode(res.body)
  41. .replaceAll("\x00", "")
  42. const x = pipe(
  43. // This pipeline just decides whether JSON parses or not
  44. contentTypeHeader && isJSONContentType(contentTypeHeader.value)
  45. ? O.of(rawBody)
  46. : O.none,
  47. // Try parsing, if failed, go to the fail option
  48. O.chain((body) => O.tryCatch(() => JSON.parse(body))),
  49. // If JSON, return that (get), else return just the body string (else)
  50. O.getOrElse<any | string>(() => rawBody)
  51. )
  52. return x
  53. }
  54. const combineEnvVariables = (env: {
  55. global: Environment["variables"]
  56. selected: Environment["variables"]
  57. }) => [...env.selected, ...env.global]
  58. export const runRESTRequest$ = (): TaskEither<
  59. string | Error,
  60. Observable<HoppRESTResponse>
  61. > =>
  62. pipe(
  63. getFinalEnvsFromPreRequest(
  64. getRESTRequest().preRequestScript,
  65. getCombinedEnvVariables()
  66. ),
  67. chain((envs) => {
  68. const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
  69. name: "Env",
  70. variables: combineEnvVariables(envs),
  71. })
  72. const stream = createRESTNetworkRequestStream(effectiveRequest)
  73. // Run Test Script when request ran successfully
  74. const subscription = stream
  75. .pipe(filter((res) => res.type === "success" || res.type === "fail"))
  76. .subscribe(async (res) => {
  77. if (res.type === "success" || res.type === "fail") {
  78. const runResult = await runTestScript(res.req.testScript, envs, {
  79. status: res.statusCode,
  80. body: getTestableBody(res),
  81. headers: res.headers,
  82. })()
  83. if (isRight(runResult)) {
  84. setRESTTestResults(translateToSandboxTestResults(runResult.right))
  85. setGlobalEnvVariables(runResult.right.envs.global)
  86. if (environmentsStore.value.currentEnvironmentIndex !== -1) {
  87. const env = getEnviroment(
  88. environmentsStore.value.currentEnvironmentIndex
  89. )
  90. updateEnvironment(
  91. environmentsStore.value.currentEnvironmentIndex,
  92. {
  93. name: env.name,
  94. variables: runResult.right.envs.selected,
  95. }
  96. )
  97. }
  98. } else {
  99. setRESTTestResults({
  100. description: "",
  101. expectResults: [],
  102. tests: [],
  103. envDiff: {
  104. global: {
  105. additions: [],
  106. deletions: [],
  107. updations: [],
  108. },
  109. selected: {
  110. additions: [],
  111. deletions: [],
  112. updations: [],
  113. },
  114. },
  115. scriptError: true,
  116. })
  117. }
  118. subscription.unsubscribe()
  119. }
  120. })
  121. return right(stream)
  122. })
  123. )
  124. const getAddedEnvVariables = (
  125. current: Environment["variables"],
  126. updated: Environment["variables"]
  127. ) => updated.filter((x) => current.findIndex((y) => y.key === x.key) === -1)
  128. const getRemovedEnvVariables = (
  129. current: Environment["variables"],
  130. updated: Environment["variables"]
  131. ) => current.filter((x) => updated.findIndex((y) => y.key === x.key) === -1)
  132. const getUpdatedEnvVariables = (
  133. current: Environment["variables"],
  134. updated: Environment["variables"]
  135. ) =>
  136. pipe(
  137. updated,
  138. A.filterMap(
  139. flow(
  140. O.of,
  141. O.bindTo("env"),
  142. O.bind("index", ({ env }) =>
  143. pipe(
  144. current.findIndex((x) => x.key === env.key),
  145. O.fromPredicate((x) => x !== -1)
  146. )
  147. ),
  148. O.chain(
  149. O.fromPredicate(
  150. ({ env, index }) => env.value !== current[index].value
  151. )
  152. ),
  153. O.map(({ env, index }) => ({
  154. ...env,
  155. previousValue: current[index].value,
  156. }))
  157. )
  158. )
  159. )
  160. function translateToSandboxTestResults(
  161. testDesc: SandboxTestResult
  162. ): HoppTestResult {
  163. const translateChildTests = (child: TestDescriptor): HoppTestData => {
  164. return {
  165. description: child.descriptor,
  166. expectResults: child.expectResults,
  167. tests: child.children.map(translateChildTests),
  168. }
  169. }
  170. const globals = cloneDeep(getGlobalVariables())
  171. const env = cloneDeep(getCurrentEnvironment())
  172. return {
  173. description: "",
  174. expectResults: testDesc.tests.expectResults,
  175. tests: testDesc.tests.children.map(translateChildTests),
  176. scriptError: false,
  177. envDiff: {
  178. global: {
  179. additions: getAddedEnvVariables(globals, testDesc.envs.global),
  180. deletions: getRemovedEnvVariables(globals, testDesc.envs.global),
  181. updations: getUpdatedEnvVariables(globals, testDesc.envs.global),
  182. },
  183. selected: {
  184. additions: getAddedEnvVariables(env.variables, testDesc.envs.selected),
  185. deletions: getRemovedEnvVariables(
  186. env.variables,
  187. testDesc.envs.selected
  188. ),
  189. updations: getUpdatedEnvVariables(
  190. env.variables,
  191. testDesc.envs.selected
  192. ),
  193. },
  194. },
  195. }
  196. }