feat(scripting-revamp): chai powered assertions and postman compatibility layer (#5417)
Co-authored-by: nivedin <nivedinp@gmail.com>
This commit is contained in:
parent
ecf7d2507a
commit
9cd6c7d6cf
90 changed files with 30793 additions and 1416 deletions
File diff suppressed because one or more lines are too long
|
|
@ -68,23 +68,36 @@ export const preRequestScriptRunner = (
|
|||
const { selected, global } = updatedEnvs;
|
||||
|
||||
return {
|
||||
updatedEnvs: <Environment>{
|
||||
// Keep the original updatedEnvs with separate global and selected arrays
|
||||
preRequestUpdatedEnvs: updatedEnvs,
|
||||
// Create Environment format for getEffectiveRESTRequest
|
||||
envForEffectiveRequest: <Environment>{
|
||||
name: "Env",
|
||||
variables: [...(selected ?? []), ...(global ?? [])],
|
||||
},
|
||||
updatedRequest: updatedRequest ?? {},
|
||||
};
|
||||
}),
|
||||
TE.chainW(({ updatedEnvs, updatedRequest }) => {
|
||||
TE.chainW(({ preRequestUpdatedEnvs, envForEffectiveRequest, updatedRequest }) => {
|
||||
const finalRequest = { ...request, ...updatedRequest };
|
||||
|
||||
return TE.tryCatch(
|
||||
() =>
|
||||
getEffectiveRESTRequest(
|
||||
async () => {
|
||||
const result = await getEffectiveRESTRequest(
|
||||
finalRequest,
|
||||
updatedEnvs,
|
||||
envForEffectiveRequest,
|
||||
collectionVariables
|
||||
),
|
||||
);
|
||||
// Replace the updatedEnvs from getEffectiveRESTRequest with the one from pre-request script
|
||||
// This preserves the global/selected separation
|
||||
if (E.isRight(result)) {
|
||||
return E.right({
|
||||
...result.right,
|
||||
updatedEnvs: preRequestUpdatedEnvs,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
(reason) => error({ code: "PRE_REQUEST_SCRIPT_ERROR", data: reason })
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -686,6 +686,8 @@
|
|||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_postman_import_summary": "Collections, Requests and response examples will be imported.",
|
||||
"import_scripts": "Import scripts",
|
||||
"import_scripts_description": "Supports Postman Collection v2.0/v2.1.",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Enter Gist URL",
|
||||
"from_har": "Import from HAR",
|
||||
|
|
@ -712,6 +714,9 @@
|
|||
"import_summary_pre_request_scripts_title": "Pre-request scripts",
|
||||
"import_summary_post_request_scripts_title": "Post request scripts",
|
||||
"import_summary_not_supported_by_hoppscotch_import": "We do not support importing {featureLabel} from this source right now.",
|
||||
"import_summary_script_found": "script found but not imported",
|
||||
"import_summary_scripts_found": "scripts found but not imported",
|
||||
"import_summary_enable_experimental_sandbox": "To import Postman scripts, enable 'Experimental scripting sandbox' in settings. Note: This feature is experimental.",
|
||||
"cors_error_modal": {
|
||||
"title": "CORS Error Detected",
|
||||
"description": "The import failed due to CORS (Cross-Origin Resource Sharing) restrictions imposed by the server.",
|
||||
|
|
@ -1314,6 +1319,7 @@
|
|||
"download_failed": "Download failed",
|
||||
"download_started": "Download started",
|
||||
"enabled": "Enabled",
|
||||
"experimental": "Experimental",
|
||||
"file_imported": "File imported",
|
||||
"finished_in": "Finished in {duration} ms",
|
||||
"hide": "Hide",
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
|
|||
import { TeamWorkspace } from "~/services/workspace.service"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
|
||||
const isPostmanImporterInProgress = ref(false)
|
||||
const isInsomniaImporterInProgress = ref(false)
|
||||
const isOpenAPIImporterInProgress = ref(false)
|
||||
const isRESTImporterInProgress = ref(false)
|
||||
|
|
@ -171,6 +170,7 @@ const emit = defineEmits<{
|
|||
const isHoppMyCollectionExporterInProgress = ref(false)
|
||||
const isHoppTeamCollectionExporterInProgress = ref(false)
|
||||
const isHoppGistCollectionExporterInProgress = ref(false)
|
||||
const isPostmanImporterInProgress = ref(false)
|
||||
|
||||
const isTeamWorkspace = computed(() => {
|
||||
return props.collectionsType.type === "team-collections"
|
||||
|
|
@ -179,19 +179,83 @@ const isTeamWorkspace = computed(() => {
|
|||
const currentImportSummary: Ref<{
|
||||
showImportSummary: boolean
|
||||
importedCollections: HoppCollection[] | null
|
||||
scriptsImported?: boolean
|
||||
originalScriptCounts?: { preRequest: number; test: number }
|
||||
}> = ref({
|
||||
showImportSummary: false,
|
||||
importedCollections: null,
|
||||
scriptsImported: false,
|
||||
originalScriptCounts: undefined,
|
||||
})
|
||||
|
||||
const setCurrentImportSummary = (collections: HoppCollection[]) => {
|
||||
const setCurrentImportSummary = (
|
||||
collections: HoppCollection[],
|
||||
scriptsImported?: boolean,
|
||||
originalScriptCounts?: { preRequest: number; test: number }
|
||||
) => {
|
||||
currentImportSummary.value.importedCollections = collections
|
||||
currentImportSummary.value.showImportSummary = true
|
||||
currentImportSummary.value.scriptsImported = scriptsImported
|
||||
currentImportSummary.value.originalScriptCounts = originalScriptCounts
|
||||
}
|
||||
|
||||
const unsetCurrentImportSummary = () => {
|
||||
currentImportSummary.value.importedCollections = null
|
||||
currentImportSummary.value.showImportSummary = false
|
||||
currentImportSummary.value.scriptsImported = false
|
||||
currentImportSummary.value.originalScriptCounts = undefined
|
||||
}
|
||||
|
||||
// Count scripts in raw Postman collection JSON (before import strips them)
|
||||
const countPostmanScripts = (
|
||||
content: string[]
|
||||
): { preRequest: number; test: number } => {
|
||||
let preRequestCount = 0
|
||||
let testCount = 0
|
||||
|
||||
const countInItem = (item: any) => {
|
||||
// Only count if this is a request (has request object), not a folder
|
||||
const isRequest = item?.request !== undefined
|
||||
|
||||
if (isRequest && item?.event) {
|
||||
const prerequest = item.event.find((e: any) => e.listen === "prerequest")
|
||||
const test = item.event.find((e: any) => e.listen === "test")
|
||||
|
||||
if (
|
||||
prerequest?.script?.exec &&
|
||||
Array.isArray(prerequest.script.exec) &&
|
||||
prerequest.script.exec.some((line: string) => line?.trim())
|
||||
) {
|
||||
preRequestCount++
|
||||
}
|
||||
|
||||
if (
|
||||
test?.script?.exec &&
|
||||
Array.isArray(test.script.exec) &&
|
||||
test.script.exec.some((line: string) => line?.trim())
|
||||
) {
|
||||
testCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively count in nested items (folders)
|
||||
if (item?.item && Array.isArray(item.item)) {
|
||||
item.item.forEach(countInItem)
|
||||
}
|
||||
}
|
||||
|
||||
content.forEach((fileContent) => {
|
||||
try {
|
||||
const collection = JSON.parse(fileContent)
|
||||
if (collection?.item && Array.isArray(collection.item)) {
|
||||
collection.item.forEach(countInItem)
|
||||
}
|
||||
} catch (e) {
|
||||
// Invalid JSON, skip
|
||||
}
|
||||
})
|
||||
|
||||
return { preRequest: preRequestCount, test: testCount }
|
||||
}
|
||||
|
||||
const HoppRESTImporter: ImporterOrExporter = {
|
||||
|
|
@ -379,15 +443,20 @@ const HoppPostmanImporter: ImporterOrExporter = {
|
|||
caption: "import.from_file",
|
||||
acceptedFileTypes: ".json",
|
||||
description: "import.from_postman_import_summary",
|
||||
onImportFromFile: async (content) => {
|
||||
showPostmanScriptOption: true,
|
||||
onImportFromFile: async (content: string[], importScripts?: boolean) => {
|
||||
isPostmanImporterInProgress.value = true
|
||||
|
||||
const res = await hoppPostmanImporter(content)()
|
||||
// Count scripts from raw Postman JSON before importing
|
||||
const originalCounts =
|
||||
importScripts === undefined ? countPostmanScripts(content) : undefined
|
||||
|
||||
const res = await hoppPostmanImporter(content, importScripts ?? false)()
|
||||
|
||||
if (E.isRight(res)) {
|
||||
await handleImportToStore(res.right)
|
||||
|
||||
setCurrentImportSummary(res.right)
|
||||
setCurrentImportSummary(res.right, importScripts, originalCounts)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
platform: "rest",
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@ props.importerModules.forEach((importer) => {
|
|||
props: () => ({
|
||||
collections: importSummary.value.importedCollections,
|
||||
importFormat: importer.metadata.format,
|
||||
scriptsImported: importSummary.value.scriptsImported,
|
||||
originalScriptCounts: importSummary.value.originalScriptCounts,
|
||||
"on-close": () => {
|
||||
emit("hide-modal")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -52,13 +52,41 @@
|
|||
}}
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<!-- Postman-specific: Script import checkbox (only use case so far) -->
|
||||
<div
|
||||
v-if="showPostmanScriptOption && experimentalScriptingEnabled"
|
||||
class="flex items-start space-x-3 px-1"
|
||||
>
|
||||
<HoppSmartCheckbox
|
||||
:on="importScripts"
|
||||
@change="importScripts = !importScripts"
|
||||
/>
|
||||
<label
|
||||
for="importScriptsCheckbox"
|
||||
class="cursor-pointer select-none text-secondary flex flex-col space-y-0.5"
|
||||
>
|
||||
<span class="font-semibold flex space-x-1">
|
||||
<span>
|
||||
{{ t("import.import_scripts") }}
|
||||
</span>
|
||||
<span class="text-tiny text-secondaryLight">
|
||||
({{ t("state.experimental") }})
|
||||
</span>
|
||||
</span>
|
||||
<span class="text-tiny text-secondaryLight">
|
||||
{{ t("import.import_scripts_description") }}</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<HoppButtonPrimary
|
||||
:disabled="disableImportCTA"
|
||||
:label="t('import.title')"
|
||||
:loading="loading"
|
||||
class="w-full"
|
||||
@click="emit('importFromFile', fileContent)"
|
||||
@click="handleImport"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -69,6 +97,7 @@ import { useI18n } from "@composables/i18n"
|
|||
import { useToast } from "@composables/toast"
|
||||
import { computed, ref } from "vue"
|
||||
import { platform } from "~/platform"
|
||||
import { useSetting } from "~/composables/settings"
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -76,16 +105,24 @@ const props = withDefaults(
|
|||
acceptedFileTypes: string
|
||||
loading?: boolean
|
||||
description?: string
|
||||
showPostmanScriptOption?: boolean
|
||||
}>(),
|
||||
{
|
||||
loading: false,
|
||||
description: undefined,
|
||||
showPostmanScriptOption: false,
|
||||
}
|
||||
)
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
// Postman-specific: Script import state (only use case so far)
|
||||
const importScripts = ref(false)
|
||||
const experimentalScriptingEnabled = useSetting(
|
||||
"EXPERIMENTAL_SCRIPTING_SANDBOX"
|
||||
)
|
||||
|
||||
const ALLOWED_FILE_SIZE_LIMIT = platform.limits?.collectionImportSizeLimit ?? 10 // Default to 10 MB
|
||||
|
||||
const importFilesCount = ref(0)
|
||||
|
|
@ -97,7 +134,7 @@ const fileContent = ref<string[]>([])
|
|||
const inputChooseFileToImportFrom = ref<HTMLInputElement | any>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "importFromFile", content: string[]): void
|
||||
(e: "importFromFile", content: string[], ...additionalArgs: any[]): void
|
||||
}>()
|
||||
|
||||
// Disable the import CTA if no file is selected, the file size limit is exceeded, or during an import action indicated by the `isLoading` prop
|
||||
|
|
@ -106,6 +143,16 @@ const disableImportCTA = computed(
|
|||
!hasFile.value || showFileSizeLimitExceededWarning.value || props.loading
|
||||
)
|
||||
|
||||
const handleImport = () => {
|
||||
// If Postman script option is enabled AND experimental sandbox is enabled, pass the importScripts value
|
||||
// Otherwise, don't pass it (undefined) to indicate the feature wasn't available
|
||||
if (props.showPostmanScriptOption && experimentalScriptingEnabled.value) {
|
||||
emit("importFromFile", fileContent.value, importScripts.value)
|
||||
} else {
|
||||
emit("importFromFile", fileContent.value)
|
||||
}
|
||||
}
|
||||
|
||||
const onFileChange = async () => {
|
||||
// Reset the state on entering the handler to avoid any stale state
|
||||
if (showFileSizeLimitExceededWarning.value) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,120 @@
|
|||
<template>
|
||||
<div class="flex flex-col p-1">
|
||||
<div class="space-y-4 p-1">
|
||||
<div v-for="feature in visibleFeatures" :key="feature.id">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary"
|
||||
:class="{
|
||||
'text-green-500':
|
||||
featureSupportForImportFormat[feature.id] === 'SUPPORTED' ||
|
||||
featureSupportForImportFormat[feature.id] === 'SKIPPED',
|
||||
'text-amber-500':
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT',
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle
|
||||
v-if="
|
||||
featureSupportForImportFormat[feature.id] === 'SUPPORTED' ||
|
||||
featureSupportForImportFormat[feature.id] === 'SKIPPED'
|
||||
"
|
||||
class="svg-icons"
|
||||
/>
|
||||
|
||||
<IconInfo
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
class="svg-icons"
|
||||
/>
|
||||
</span>
|
||||
<span>{{ t(feature.label) }}</span>
|
||||
</p>
|
||||
|
||||
<p class="ml-10 text-secondaryLight">
|
||||
<template
|
||||
v-if="featureSupportForImportFormat[feature.id] === 'SUPPORTED'"
|
||||
>
|
||||
{{ feature.count }}
|
||||
{{
|
||||
feature.count != 1
|
||||
? t(feature.label)
|
||||
: t(feature.label).slice(0, -1)
|
||||
}}
|
||||
Imported
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-else-if="featureSupportForImportFormat[feature.id] === 'SKIPPED'"
|
||||
>
|
||||
0 {{ t(feature.label) }} Imported
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
>
|
||||
<!-- Special message for Postman scripts when using legacy sandbox -->
|
||||
<template
|
||||
v-if="
|
||||
importFormat === 'postman' &&
|
||||
(feature.id === 'preRequestScripts' ||
|
||||
feature.id === 'testScripts') &&
|
||||
scriptsImported === undefined
|
||||
"
|
||||
>
|
||||
0 {{ t(feature.label) }} Imported
|
||||
</template>
|
||||
<!-- Generic message for other unsupported features -->
|
||||
<template v-else>
|
||||
{{
|
||||
t("import.import_summary_not_supported_by_hoppscotch_import", {
|
||||
featureLabel: t(feature.label),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informational banner for script imports when experimental sandbox is disabled -->
|
||||
<div
|
||||
v-if="showScriptImportInfo"
|
||||
class="mt-6 flex items-start space-x-3 rounded border border-dividerLight shadow-sm bg-primaryLight px-2 py-4"
|
||||
>
|
||||
<IconInfo class="flex-shrink-0 text-accent svg-icons" />
|
||||
<div class="flex-1 space-y-2">
|
||||
<p class="font-semibold text-secondary">
|
||||
{{ totalScriptsCount }}
|
||||
{{
|
||||
totalScriptsCount === 1
|
||||
? t("import.import_summary_script_found")
|
||||
: t("import.import_summary_scripts_found")
|
||||
}}
|
||||
</p>
|
||||
<p class="text-secondaryLight">
|
||||
{{ t("import.import_summary_enable_experimental_sandbox") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-9">
|
||||
<HoppButtonSecondary
|
||||
class="w-full"
|
||||
:label="t('action.close')"
|
||||
outline
|
||||
filled
|
||||
@click="onClose"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { computed, Ref, ref, watch } from "vue"
|
||||
|
|
@ -18,6 +135,7 @@ type FeatureStatus =
|
|||
| "SUPPORTED"
|
||||
| "NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT"
|
||||
| "NOT_SUPPORTED_BY_SOURCE"
|
||||
| "SKIPPED"
|
||||
|
||||
type FeatureWithCount = {
|
||||
count: number
|
||||
|
|
@ -28,6 +146,8 @@ type FeatureWithCount = {
|
|||
const props = defineProps<{
|
||||
importFormat: SupportedImportFormat
|
||||
collections: HoppCollection[]
|
||||
scriptsImported?: boolean
|
||||
originalScriptCounts?: { preRequest: number; test: number }
|
||||
onClose: () => void
|
||||
}>()
|
||||
|
||||
|
|
@ -158,7 +278,30 @@ watch(
|
|||
)
|
||||
|
||||
const featureSupportForImportFormat = computed(() => {
|
||||
return importSourceAndSupportedFeatures[props.importFormat]
|
||||
const baseSupport = importSourceAndSupportedFeatures[props.importFormat]
|
||||
|
||||
// Handle Postman script import status
|
||||
if (props.importFormat === "postman") {
|
||||
if (props.scriptsImported === true) {
|
||||
// User checked the box and imported scripts
|
||||
return {
|
||||
...baseSupport,
|
||||
preRequestScripts: "SUPPORTED" as FeatureStatus,
|
||||
testScripts: "SUPPORTED" as FeatureStatus,
|
||||
}
|
||||
} else if (props.scriptsImported === false) {
|
||||
// User explicitly didn't import scripts (checkbox unchecked)
|
||||
return {
|
||||
...baseSupport,
|
||||
preRequestScripts: "SKIPPED" as FeatureStatus,
|
||||
testScripts: "SKIPPED" as FeatureStatus,
|
||||
}
|
||||
}
|
||||
// props.scriptsImported === undefined means legacy sandbox or old import
|
||||
// Keep default NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT
|
||||
}
|
||||
|
||||
return baseSupport
|
||||
})
|
||||
|
||||
const visibleFeatures = computed(() => {
|
||||
|
|
@ -169,74 +312,23 @@ const visibleFeatures = computed(() => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
const showScriptImportInfo = computed(() => {
|
||||
return (
|
||||
props.importFormat === "postman" &&
|
||||
props.scriptsImported === undefined &&
|
||||
totalScriptsCount.value > 0
|
||||
)
|
||||
})
|
||||
|
||||
const totalScriptsCount = computed(() => {
|
||||
if (props.importFormat !== "postman" || props.scriptsImported !== undefined)
|
||||
return 0
|
||||
|
||||
// Use original counts from raw Postman JSON
|
||||
const preRequestScripts = props.originalScriptCounts?.preRequest || 0
|
||||
const testScripts = props.originalScriptCounts?.test || 0
|
||||
|
||||
return preRequestScripts + testScripts
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div v-for="feature in visibleFeatures" :key="feature.id">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary"
|
||||
:class="{
|
||||
'text-green-500':
|
||||
featureSupportForImportFormat[feature.id] === 'SUPPORTED',
|
||||
'text-amber-500':
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT',
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle
|
||||
v-if="featureSupportForImportFormat[feature.id] === 'SUPPORTED'"
|
||||
class="svg-icons"
|
||||
/>
|
||||
|
||||
<IconInfo
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
class="svg-icons"
|
||||
/>
|
||||
</span>
|
||||
<span>{{ t(feature.label) }}</span>
|
||||
</p>
|
||||
|
||||
<p class="ml-10 text-secondaryLight">
|
||||
<template
|
||||
v-if="featureSupportForImportFormat[feature.id] === 'SUPPORTED'"
|
||||
>
|
||||
{{ feature.count }}
|
||||
{{
|
||||
feature.count != 1
|
||||
? t(feature.label)
|
||||
: t(feature.label).slice(0, -1)
|
||||
}}
|
||||
Imported
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-else-if="
|
||||
featureSupportForImportFormat[feature.id] ===
|
||||
'NOT_SUPPORTED_BY_HOPPSCOTCH_IMPORT'
|
||||
"
|
||||
>
|
||||
{{
|
||||
t("import.import_summary_not_supported_by_hoppscotch_import", {
|
||||
featureLabel: t(feature.label),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
<HoppButtonSecondary
|
||||
class="w-full"
|
||||
:label="t('action.close')"
|
||||
outline
|
||||
filled
|
||||
@click="onClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,18 @@ import FileImportVue from "~/components/importExport/ImportExportSteps/FileImpor
|
|||
import { defineStep } from "~/composables/step-components"
|
||||
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { Ref } from "vue"
|
||||
import type { Ref } from "vue"
|
||||
|
||||
export function FileSource(metadata: {
|
||||
acceptedFileTypes: string
|
||||
caption: string
|
||||
onImportFromFile: (content: string[]) => any | Promise<any>
|
||||
onImportFromFile: (
|
||||
content: string[],
|
||||
importScripts?: boolean
|
||||
) => any | Promise<any>
|
||||
isLoading?: Ref<boolean>
|
||||
description?: string
|
||||
showPostmanScriptOption?: boolean
|
||||
}) {
|
||||
const stepID = uuidv4()
|
||||
|
||||
|
|
@ -19,5 +23,6 @@ export function FileSource(metadata: {
|
|||
onImportFromFile: metadata.onImportFromFile,
|
||||
loading: metadata.isLoading?.value,
|
||||
description: metadata.description,
|
||||
showPostmanScriptOption: metadata.showPostmanScriptOption,
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,30 @@ const safeParseJSON = (jsonStr: string) => O.tryCatch(() => JSON.parse(jsonStr))
|
|||
|
||||
const isPMItem = (x: unknown): x is Item => Item.isItem(x)
|
||||
|
||||
/**
|
||||
* Checks if the Postman collection schema version supports scripts (v2.0+)
|
||||
* @param schema - The schema URL from collection.info.schema
|
||||
* @returns true if v2.0 or v2.1, false otherwise
|
||||
*/
|
||||
const isSchemaVersionSupported = (schema?: string): boolean => {
|
||||
if (!schema) return false
|
||||
// Support both schema.getpostman.com and schema.postman.com
|
||||
return schema.includes("/v2.0.") || schema.includes("/v2.1.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the collection schema from raw JSON data
|
||||
* Note: PMCollection SDK doesn't expose .info.schema, so we parse raw JSON
|
||||
*/
|
||||
const getCollectionSchema = (jsonStr: string): string | null => {
|
||||
try {
|
||||
const data = JSON.parse(jsonStr)
|
||||
return data?.info?.schema ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const replacePMVarTemplating = flow(
|
||||
S.replace(/{{\s*/g, "<<"),
|
||||
S.replace(/\s*}}/g, ">>")
|
||||
|
|
@ -482,7 +506,56 @@ const getHoppReqURL = (url: Item["request"]["url"] | null): string => {
|
|||
)
|
||||
}
|
||||
|
||||
const getHoppRequest = (item: Item): HoppRESTRequest => {
|
||||
/**
|
||||
* Extracts script content from a Postman event
|
||||
* Handles both string format and exec array format
|
||||
*/
|
||||
const extractScriptFromEvent = (event: any): string => {
|
||||
if (!event?.script) return ""
|
||||
|
||||
if (typeof event.script === "string") {
|
||||
return event.script
|
||||
}
|
||||
|
||||
if (event.script.exec && Array.isArray(event.script.exec)) {
|
||||
return event.script.exec.join("\n")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
const getHoppScripts = (
|
||||
item: Item,
|
||||
importScripts: boolean
|
||||
): { preRequestScript: string; testScript: string } => {
|
||||
if (!importScripts) {
|
||||
return { preRequestScript: "", testScript: "" }
|
||||
}
|
||||
|
||||
let preRequestScript = ""
|
||||
let testScript = ""
|
||||
|
||||
// Postman stores scripts in the events array
|
||||
if (item.events) {
|
||||
const events = item.events.all()
|
||||
events.forEach((event: any) => {
|
||||
if (event.listen === "prerequest") {
|
||||
preRequestScript = extractScriptFromEvent(event)
|
||||
} else if (event.listen === "test") {
|
||||
testScript = extractScriptFromEvent(event)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { preRequestScript, testScript }
|
||||
}
|
||||
|
||||
const getHoppRequest = (
|
||||
item: Item,
|
||||
importScripts: boolean
|
||||
): HoppRESTRequest => {
|
||||
const { preRequestScript, testScript } = getHoppScripts(item, importScripts)
|
||||
|
||||
return makeRESTRequest({
|
||||
name: item.name,
|
||||
endpoint: getHoppReqURL(item.request.url),
|
||||
|
|
@ -496,38 +569,68 @@ const getHoppRequest = (item: Item): HoppRESTRequest => {
|
|||
}),
|
||||
requestVariables: getHoppReqVariables(item.request.url.variables),
|
||||
responses: getHoppResponses(item.responses),
|
||||
|
||||
// TODO: Decide about this
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
preRequestScript,
|
||||
testScript,
|
||||
})
|
||||
}
|
||||
|
||||
const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection =>
|
||||
const getHoppFolder = (
|
||||
ig: ItemGroup<Item>,
|
||||
importScripts: boolean
|
||||
): HoppCollection =>
|
||||
makeCollection({
|
||||
name: ig.name,
|
||||
folders: pipe(
|
||||
ig.items.all(),
|
||||
A.filter(isPMItemGroup),
|
||||
A.map(getHoppFolder)
|
||||
A.map((folder) => getHoppFolder(folder, importScripts))
|
||||
),
|
||||
requests: pipe(
|
||||
ig.items.all(),
|
||||
A.filter(isPMItem),
|
||||
A.map((item) => getHoppRequest(item, importScripts))
|
||||
),
|
||||
requests: pipe(ig.items.all(), A.filter(isPMItem), A.map(getHoppRequest)),
|
||||
auth: getHoppReqAuth(ig.auth),
|
||||
headers: [],
|
||||
variables: getHoppCollVariables(ig),
|
||||
})
|
||||
|
||||
export const getHoppCollections = (collections: PMCollection[]) => {
|
||||
return collections.map(getHoppFolder)
|
||||
export const getHoppCollections = (
|
||||
collections: PMCollection[],
|
||||
importScripts: boolean
|
||||
) => {
|
||||
return collections.map((collection) =>
|
||||
getHoppFolder(collection, importScripts)
|
||||
)
|
||||
}
|
||||
|
||||
export const hoppPostmanImporter = (fileContents: string[]) =>
|
||||
export const hoppPostmanImporter = (
|
||||
fileContents: string[],
|
||||
importScripts = false
|
||||
) =>
|
||||
pipe(
|
||||
// Try reading
|
||||
fileContents,
|
||||
A.traverse(O.Applicative)(readPMCollection),
|
||||
|
||||
O.map(flow(getHoppCollections)),
|
||||
O.map((collections) => {
|
||||
// Validate schema version if importing scripts
|
||||
if (importScripts && fileContents.length > 0) {
|
||||
const schema = getCollectionSchema(fileContents[0])
|
||||
const isSupported = isSchemaVersionSupported(schema ?? undefined)
|
||||
|
||||
if (!isSupported) {
|
||||
console.warn(
|
||||
`[Postman Import] Script import requested but collection schema "${schema ?? "unknown"}" does not support scripts. ` +
|
||||
`Only Postman Collection Format v2.0 and v2.1 are supported. Scripts will be skipped.`
|
||||
)
|
||||
// Skip script import for unsupported versions
|
||||
return getHoppCollections(collections, false)
|
||||
}
|
||||
}
|
||||
|
||||
return getHoppCollections(collections, importScripts)
|
||||
}),
|
||||
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -230,13 +230,227 @@ type HoppRESTAuth =
|
|||
| HoppRESTAuthAkamaiEdgeGrid
|
||||
| HoppRESTAuthJWT
|
||||
|
||||
interface Expectation extends ExpectationMethods {
|
||||
not: BaseExpectation
|
||||
// Length property that can be used both as property and callable with comparison methods
|
||||
// Matches actual runtime implementation in post-request.js lines 371-414
|
||||
interface ChaiLengthAssertion {
|
||||
// Primary comparison methods (actually implemented in runtime)
|
||||
above(n: number): ChaiExpectation
|
||||
below(n: number): ChaiExpectation
|
||||
within(min: number, max: number): ChaiExpectation
|
||||
least(n: number): ChaiExpectation
|
||||
most(n: number): ChaiExpectation
|
||||
gte(n: number): ChaiExpectation
|
||||
lte(n: number): ChaiExpectation
|
||||
|
||||
// Aliases implemented in runtime (for compatibility)
|
||||
greaterThan(n: number): ChaiExpectation // Alias for above()
|
||||
lessThan(n: number): ChaiExpectation // Alias for below()
|
||||
|
||||
// Postman-style .at.least() and .at.most() support
|
||||
at: Readonly<{
|
||||
least(n: number): ChaiExpectation
|
||||
most(n: number): ChaiExpectation
|
||||
}>
|
||||
}
|
||||
|
||||
interface BaseExpectation extends ExpectationMethods {}
|
||||
// Chai-powered assertion interface
|
||||
interface ChaiExpectation {
|
||||
// Negation
|
||||
not: ChaiExpectation
|
||||
|
||||
interface ExpectationMethods {
|
||||
// Language chains (improve readability without affecting assertions)
|
||||
to: ChaiExpectation
|
||||
be: ChaiExpectation
|
||||
been: ChaiExpectation
|
||||
is: ChaiExpectation
|
||||
that: ChaiExpectation
|
||||
which: ChaiExpectation
|
||||
and: ChaiExpectation
|
||||
has: ChaiExpectation
|
||||
have: ChaiExpectation
|
||||
with: ChaiExpectation
|
||||
at: ChaiExpectation
|
||||
of: ChaiExpectation
|
||||
same: ChaiExpectation
|
||||
but: ChaiExpectation
|
||||
does: ChaiExpectation
|
||||
still: ChaiExpectation
|
||||
also: ChaiExpectation
|
||||
|
||||
// Modifiers (can be used as both properties and methods)
|
||||
deep: ChaiExpectation
|
||||
nested: ChaiExpectation
|
||||
own: ChaiExpectation
|
||||
ordered: ChaiExpectation
|
||||
any: ChaiExpectation
|
||||
all: ChaiExpectation
|
||||
|
||||
// Include/contain can be used as both properties AND methods
|
||||
include: ChaiExpectation & ((value: any) => ChaiExpectation)
|
||||
contain: ChaiExpectation & ((value: any) => ChaiExpectation)
|
||||
includes: ChaiExpectation & ((value: any) => ChaiExpectation)
|
||||
contains: ChaiExpectation & ((value: any) => ChaiExpectation)
|
||||
|
||||
// Type assertions - can be used as both property and method
|
||||
a: ChaiExpectation & ((type: string) => ChaiExpectation)
|
||||
an: ChaiExpectation & ((type: string) => ChaiExpectation)
|
||||
|
||||
// Equality assertions
|
||||
equal(value: any): ChaiExpectation
|
||||
equals(value: any): ChaiExpectation
|
||||
eq(value: any): ChaiExpectation
|
||||
eql(value: any): ChaiExpectation
|
||||
|
||||
// Truthiness assertions
|
||||
true: ChaiExpectation
|
||||
false: ChaiExpectation
|
||||
ok: ChaiExpectation
|
||||
null: ChaiExpectation
|
||||
undefined: ChaiExpectation
|
||||
NaN: ChaiExpectation
|
||||
exist: ChaiExpectation
|
||||
empty: ChaiExpectation
|
||||
arguments: ChaiExpectation
|
||||
|
||||
// Numerical comparison assertions
|
||||
above(n: number): ChaiExpectation
|
||||
gt(n: number): ChaiExpectation
|
||||
greaterThan(n: number): ChaiExpectation
|
||||
below(n: number): ChaiExpectation
|
||||
lt(n: number): ChaiExpectation
|
||||
lessThan(n: number): ChaiExpectation
|
||||
least(n: number): ChaiExpectation
|
||||
gte(n: number): ChaiExpectation
|
||||
greaterThanOrEqual(n: number): ChaiExpectation
|
||||
most(n: number): ChaiExpectation
|
||||
lte(n: number): ChaiExpectation
|
||||
lessThanOrEqual(n: number): ChaiExpectation
|
||||
within(start: number, finish: number): ChaiExpectation
|
||||
closeTo(expected: number, delta: number): ChaiExpectation
|
||||
approximately(expected: number, delta: number): ChaiExpectation
|
||||
|
||||
// Property assertions
|
||||
property(name: string, value?: any): ChaiExpectation
|
||||
ownProperty(name: string, value?: any): ChaiExpectation
|
||||
haveOwnProperty(name: string, value?: any): ChaiExpectation
|
||||
ownPropertyDescriptor(
|
||||
name: string,
|
||||
descriptor?: PropertyDescriptor
|
||||
): ChaiExpectation
|
||||
|
||||
// Length assertions - SPECIAL: Can be used as property or called with comparison methods
|
||||
// Allow .length to be called as function or used as property with comparison methods
|
||||
length: ChaiLengthAssertion & number & ((n: number) => ChaiExpectation)
|
||||
lengthOf: ((n: number) => ChaiExpectation) & ChaiLengthAssertion
|
||||
|
||||
// String/Array inclusion assertions
|
||||
string(str: string): ChaiExpectation
|
||||
match(regex: RegExp): ChaiExpectation
|
||||
matches(regex: RegExp): ChaiExpectation
|
||||
members(set: any[]): ChaiExpectation
|
||||
oneOf(list: any[]): ChaiExpectation
|
||||
|
||||
// Key assertions
|
||||
keys(...keys: string[] | [string[]]): ChaiExpectation
|
||||
key(key: string | string[]): ChaiExpectation
|
||||
|
||||
// Function/Error assertions
|
||||
throw(
|
||||
errorLike?: any,
|
||||
errMsgMatcher?: string | RegExp,
|
||||
message?: string
|
||||
): ChaiExpectation
|
||||
throws(
|
||||
errorLike?: any,
|
||||
errMsgMatcher?: string | RegExp,
|
||||
message?: string
|
||||
): ChaiExpectation
|
||||
Throw(
|
||||
errorLike?: any,
|
||||
errMsgMatcher?: string | RegExp,
|
||||
message?: string
|
||||
): ChaiExpectation
|
||||
respondTo(method: string): ChaiExpectation
|
||||
respondsTo(method: string): ChaiExpectation
|
||||
itself: ChaiExpectation
|
||||
satisfy(matcher: (value: any) => boolean): ChaiExpectation
|
||||
satisfies(matcher: (value: any) => boolean): ChaiExpectation
|
||||
|
||||
// Object state assertions
|
||||
sealed: ChaiExpectation
|
||||
frozen: ChaiExpectation
|
||||
extensible: ChaiExpectation
|
||||
finite: ChaiExpectation
|
||||
|
||||
// instanceof assertion
|
||||
instanceof(constructor: any): ChaiExpectation
|
||||
instanceOf(constructor: any): ChaiExpectation
|
||||
|
||||
// Side-effect assertions
|
||||
// Side effect assertions - support both getter function and object+property patterns
|
||||
change(
|
||||
getter: () => any
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
change(
|
||||
obj: any,
|
||||
prop: string
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
increase(
|
||||
getter: () => any
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
increase(
|
||||
obj: any,
|
||||
prop: string
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
decrease(
|
||||
getter: () => any
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
decrease(
|
||||
obj: any,
|
||||
prop: string
|
||||
): ChaiExpectation & { by(delta: number): ChaiExpectation }
|
||||
|
||||
// Postman custom Chai assertions (available via pm.expect())
|
||||
/**
|
||||
* Assert that value matches JSON Schema
|
||||
* @param schema - JSON Schema object
|
||||
*/
|
||||
jsonSchema(schema: {
|
||||
type?: string
|
||||
required?: string[]
|
||||
properties?: Record<string, any>
|
||||
items?: any
|
||||
enum?: any[]
|
||||
minimum?: number
|
||||
maximum?: number
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
}): ChaiExpectation
|
||||
|
||||
/**
|
||||
* Assert that string value contains specific charset/encoding
|
||||
* @param expectedCharset - Expected charset (e.g., 'utf-8', 'iso-8859-1')
|
||||
*/
|
||||
charset(expectedCharset: string): ChaiExpectation
|
||||
|
||||
/**
|
||||
* Assert that cookie exists and optionally has specific value
|
||||
* @param cookieName - Name of the cookie
|
||||
* @param cookieValue - Optional expected value
|
||||
*/
|
||||
cookie(cookieName: string, cookieValue?: string): ChaiExpectation
|
||||
|
||||
/**
|
||||
* Assert that JSON path exists and optionally has specific value
|
||||
* @param path - JSONPath expression (e.g., '$.users[0].name')
|
||||
* @param expectedValue - Optional expected value at path
|
||||
*/
|
||||
jsonPath(path: string, expectedValue?: any): ChaiExpectation
|
||||
|
||||
// Legacy methods (kept for backward compatibility)
|
||||
toBe(value: any): void
|
||||
toBeLevel2xx(): void
|
||||
toBeLevel3xx(): void
|
||||
|
|
@ -247,9 +461,32 @@ interface ExpectationMethods {
|
|||
toInclude(value: any): void
|
||||
}
|
||||
|
||||
// Legacy expectation interface for pw namespace (backward compatibility only)
|
||||
interface LegacyExpectation extends LegacyExpectationMethods {
|
||||
not: LegacyBaseExpectation
|
||||
}
|
||||
|
||||
interface LegacyBaseExpectation extends LegacyExpectationMethods {}
|
||||
|
||||
interface LegacyExpectationMethods {
|
||||
toBe(value: any): void
|
||||
toBeLevel2xx(): void
|
||||
toBeLevel3xx(): void
|
||||
toBeLevel4xx(): void
|
||||
toBeLevel5xx(): void
|
||||
toBeType(type: string): void
|
||||
toHaveLength(length: number): void
|
||||
toInclude(value: any): void
|
||||
}
|
||||
|
||||
// Backward compatibility types for hopp and pm namespaces
|
||||
interface Expectation extends ChaiExpectation {}
|
||||
interface BaseExpectation extends ChaiExpectation {}
|
||||
interface ExpectationMethods extends ChaiExpectation {}
|
||||
|
||||
declare namespace pw {
|
||||
function test(name: string, func: () => void): void
|
||||
function expect(value: any): Expectation
|
||||
function expect(value: any): LegacyExpectation
|
||||
const response: Readonly<{
|
||||
status: number
|
||||
body: any
|
||||
|
|
@ -269,15 +506,27 @@ declare namespace hopp {
|
|||
get(key: string): string | null
|
||||
getRaw(key: string): string | null
|
||||
getInitialRaw(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
delete(key: string): void
|
||||
reset(key: string): void
|
||||
setInitial(key: string, value: string): void
|
||||
active: Readonly<{
|
||||
get(key: string): string | null
|
||||
getRaw(key: string): string | null
|
||||
getInitialRaw(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
delete(key: string): void
|
||||
reset(key: string): void
|
||||
setInitial(key: string, value: string): void
|
||||
}>
|
||||
global: Readonly<{
|
||||
get(key: string): string | null
|
||||
getRaw(key: string): string | null
|
||||
getInitialRaw(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
delete(key: string): void
|
||||
reset(key: string): void
|
||||
setInitial(key: string, value: string): void
|
||||
}>
|
||||
}>
|
||||
|
||||
|
|
@ -300,9 +549,35 @@ declare namespace hopp {
|
|||
readonly responseTime: number
|
||||
body: Readonly<{
|
||||
asText(): string
|
||||
asJSON(): Record<string, any>
|
||||
asJSON(): any
|
||||
bytes(): Uint8Array
|
||||
}>
|
||||
/**
|
||||
* Get response body as text string
|
||||
* @returns Response body as string
|
||||
*/
|
||||
text(): string
|
||||
/**
|
||||
* Get response body as parsed JSON
|
||||
* @returns Parsed JSON object
|
||||
*/
|
||||
json(): any
|
||||
/**
|
||||
* Get HTTP reason phrase (status text)
|
||||
* @returns HTTP reason phrase (e.g., "OK", "Not Found")
|
||||
*/
|
||||
reason(): string
|
||||
/**
|
||||
* Convert response to data URI format
|
||||
* @returns Data URI string with base64 encoded content
|
||||
*/
|
||||
dataURI(): string
|
||||
/**
|
||||
* Parse JSONP response
|
||||
* @param callbackName - Optional callback function name (default: "callback")
|
||||
* @returns Parsed JSON object from JSONP wrapper
|
||||
*/
|
||||
jsonp(callbackName?: string): any
|
||||
}>
|
||||
|
||||
const cookies: Readonly<{
|
||||
|
|
@ -315,7 +590,17 @@ declare namespace hopp {
|
|||
}>
|
||||
|
||||
function test(name: string, testFunction: () => void): void
|
||||
function expect(value: any): Expectation
|
||||
|
||||
interface HoppExpectFunction {
|
||||
(value: any): ChaiExpectation
|
||||
/**
|
||||
* Fail the test with a custom message
|
||||
* @param message - Optional message to display on failure
|
||||
*/
|
||||
fail(message?: string): never
|
||||
}
|
||||
|
||||
const expect: HoppExpectFunction
|
||||
|
||||
const info: Readonly<{
|
||||
readonly eventName: "post-request"
|
||||
|
|
@ -328,37 +613,258 @@ declare namespace hopp {
|
|||
|
||||
declare namespace pm {
|
||||
const environment: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
readonly name: string
|
||||
get(key: string): any
|
||||
set(key: string, value: any): void
|
||||
unset(key: string): void
|
||||
has(key: string): boolean
|
||||
clear(): never
|
||||
toObject(): never
|
||||
}>
|
||||
|
||||
const globals: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
unset(key: string): void
|
||||
has(key: string): boolean
|
||||
clear(): never
|
||||
toObject(): never
|
||||
}>
|
||||
|
||||
const variables: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
has(key: string): boolean
|
||||
clear(): void
|
||||
toObject(): Record<string, string>
|
||||
replaceIn(template: string): string
|
||||
}>
|
||||
|
||||
const globals: Readonly<{
|
||||
get(key: string): any
|
||||
/**
|
||||
* Set a global variable
|
||||
* @param key - Variable key
|
||||
* @param value - Variable value (undefined is preserved, other types are coerced to strings)
|
||||
*/
|
||||
set(key: string, value: any): void
|
||||
unset(key: string): void
|
||||
has(key: string): boolean
|
||||
clear(): void
|
||||
toObject(): Record<string, string>
|
||||
replaceIn(template: string): string
|
||||
}>
|
||||
|
||||
const variables: Readonly<{
|
||||
get(key: string): any
|
||||
/**
|
||||
* Set a variable in the active environment scope
|
||||
* @param key - Variable key
|
||||
* @param value - Variable value (undefined is preserved, other types are coerced to strings)
|
||||
*/
|
||||
set(key: string, value: any): void
|
||||
has(key: string): boolean
|
||||
replaceIn(template: string): string
|
||||
toObject(): Record<string, string>
|
||||
}>
|
||||
|
||||
const request: Readonly<{
|
||||
readonly url: { toString(): string }
|
||||
readonly url: Readonly<{
|
||||
toString(): string
|
||||
readonly protocol: string
|
||||
readonly host: string[]
|
||||
readonly port: string
|
||||
readonly path: string[]
|
||||
readonly hash: string
|
||||
|
||||
// URL Helper Methods (Postman-compatible)
|
||||
/**
|
||||
* Get the hostname as a string (e.g., "api.example.com")
|
||||
* @returns The hostname portion of the URL
|
||||
*/
|
||||
getHost(): string
|
||||
/**
|
||||
* Get the path with leading slash (e.g., "/v1/users")
|
||||
* @param unresolved - If true, returns unresolved path with variables (currently ignored)
|
||||
* @returns The path portion of the URL
|
||||
*/
|
||||
getPath(unresolved?: boolean): string
|
||||
/**
|
||||
* Get the path with query string (e.g., "/v1/users?page=1")
|
||||
* @returns Path and query string combined
|
||||
*/
|
||||
getPathWithQuery(): string
|
||||
/**
|
||||
* Get the query string without leading ? (e.g., "page=1&limit=20")
|
||||
* @param options - Optional configuration (currently ignored)
|
||||
* @returns Query string without the leading question mark
|
||||
*/
|
||||
getQueryString(options?: Record<string, unknown>): string
|
||||
/**
|
||||
* Get the hostname with port (e.g., "api.example.com:8080")
|
||||
* @param forcePort - If true, includes standard ports (80/443)
|
||||
* @returns Hostname with port if non-standard or forced
|
||||
*/
|
||||
getRemote(forcePort?: boolean): string
|
||||
|
||||
readonly query: Readonly<{
|
||||
/**
|
||||
* Get the value of a query parameter by key
|
||||
* @param key - Parameter key to retrieve
|
||||
* @returns Parameter value or null if not found
|
||||
*/
|
||||
get(key: string): string | null
|
||||
/**
|
||||
* Check if a query parameter exists
|
||||
* @param key - Parameter key to check
|
||||
* @returns true if parameter exists, false otherwise
|
||||
*/
|
||||
has(key: string): boolean
|
||||
/**
|
||||
* Get all query parameters as a key-value object
|
||||
* @returns Object with all query parameters
|
||||
*/
|
||||
all(): Record<string, string>
|
||||
/**
|
||||
* Convert query parameters to object (alias for all())
|
||||
* @returns Object with all query parameters
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
/**
|
||||
* Get the number of query parameters
|
||||
* @returns Count of parameters
|
||||
*/
|
||||
count(): number
|
||||
/**
|
||||
* Get a parameter by index
|
||||
* @param index - Zero-based index
|
||||
* @returns Parameter object or null if out of bounds
|
||||
*/
|
||||
idx(index: number): { key: string; value: string } | null
|
||||
|
||||
/**
|
||||
* Iterate over all query parameters
|
||||
* @param callback - Function to call for each parameter
|
||||
*/
|
||||
each(callback: (param: { key: string; value: string }) => void): void
|
||||
/**
|
||||
* Map query parameters to a new array
|
||||
* @param callback - Transform function
|
||||
* @returns Array of transformed values
|
||||
*/
|
||||
map<T>(callback: (param: { key: string; value: string }) => T): T[]
|
||||
/**
|
||||
* Filter query parameters
|
||||
* @param callback - Predicate function
|
||||
* @returns Array of parameters matching the predicate
|
||||
*/
|
||||
filter(
|
||||
callback: (param: { key: string; value: string }) => boolean
|
||||
): Array<{ key: string; value: string }>
|
||||
/**
|
||||
* Find a query parameter by string key or function predicate
|
||||
* @param rule - String key or predicate function
|
||||
* @param context - Optional context to bind the predicate function
|
||||
* @returns Matching parameter or null if not found
|
||||
*/
|
||||
find(
|
||||
rule: string | ((param: { key: string; value: string }) => boolean),
|
||||
context?: any
|
||||
): { key: string; value: string } | null
|
||||
/**
|
||||
* Get the index of a query parameter
|
||||
* @param item - String key or parameter object to find
|
||||
* @returns Index of parameter, or -1 if not found
|
||||
*/
|
||||
indexOf(item: string | { key: string; value: string }): number
|
||||
}>
|
||||
}>
|
||||
|
||||
/**
|
||||
* Client certificate used for mutual TLS authentication
|
||||
* In Postman, certificates are configured at the app/collection level, not programmatically in scripts
|
||||
* Returns undefined in Hoppscotch as certificate configuration is handled at the application level
|
||||
* @see https://learning.postman.com/docs/sending-requests/certificates/
|
||||
*/
|
||||
readonly certificate:
|
||||
| {
|
||||
readonly name: string
|
||||
readonly matches: string[]
|
||||
readonly key: { readonly src: string }
|
||||
readonly cert: { readonly src: string }
|
||||
readonly passphrase?: string
|
||||
}
|
||||
| undefined
|
||||
|
||||
/**
|
||||
* Proxy configuration for the request
|
||||
* In Postman, proxy is configured at the app level, not programmatically in scripts
|
||||
* Returns undefined in Hoppscotch as proxy configuration is handled at the application level
|
||||
* @see https://learning.postman.com/docs/sending-requests/capturing-request-data/proxy/
|
||||
*/
|
||||
readonly proxy:
|
||||
| {
|
||||
readonly host: string
|
||||
readonly port: number
|
||||
readonly tunnel: boolean
|
||||
readonly disabled: boolean
|
||||
}
|
||||
| undefined
|
||||
|
||||
readonly method: string
|
||||
readonly headers: Readonly<{
|
||||
/**
|
||||
* Get the value of a header by name (case-insensitive)
|
||||
* @param name - Header name to retrieve
|
||||
* @returns Header value or null if not found
|
||||
*/
|
||||
get(name: string): string | null
|
||||
/**
|
||||
* Check if a header exists (case-insensitive)
|
||||
* @param name - Header name to check
|
||||
* @returns true if header exists, false otherwise
|
||||
*/
|
||||
has(name: string): boolean
|
||||
/**
|
||||
* Get all headers as a key-value object
|
||||
* @returns Object with all headers (keys in lowercase)
|
||||
*/
|
||||
all(): Record<string, string>
|
||||
/**
|
||||
* Convert headers to object (alias for all())
|
||||
* @returns Object with all headers
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
/**
|
||||
* Get the number of headers
|
||||
* @returns Count of headers
|
||||
*/
|
||||
count(): number
|
||||
/**
|
||||
* Get a header by index
|
||||
* @param index - Zero-based index
|
||||
* @returns Header object or null if out of bounds
|
||||
*/
|
||||
idx(index: number): { key: string; value: string } | null
|
||||
|
||||
/**
|
||||
* Iterate over all headers
|
||||
* @param callback - Function to call for each header
|
||||
*/
|
||||
each(callback: (header: { key: string; value: string }) => void): void
|
||||
/**
|
||||
* Map headers to a new array
|
||||
* @param callback - Transform function
|
||||
* @returns Array of transformed values
|
||||
*/
|
||||
map<T>(callback: (header: { key: string; value: string }) => T): T[]
|
||||
/**
|
||||
* Filter headers
|
||||
* @param callback - Predicate function
|
||||
* @returns Array of headers matching the predicate
|
||||
*/
|
||||
filter(
|
||||
callback: (header: { key: string; value: string }) => boolean
|
||||
): Array<{ key: string; value: string }>
|
||||
/**
|
||||
* Find a header by string name or function predicate (case-insensitive)
|
||||
* @param rule - String name or predicate function
|
||||
* @param context - Optional context to bind the predicate function
|
||||
* @returns Matching header or null if not found
|
||||
*/
|
||||
find(
|
||||
rule: string | ((header: { key: string; value: string }) => boolean),
|
||||
context?: any
|
||||
): { key: string; value: string } | null
|
||||
/**
|
||||
* Get the index of a header (case-insensitive)
|
||||
* @param item - String name or header object to find
|
||||
* @returns Index of header, or -1 if not found
|
||||
*/
|
||||
indexOf(item: string | { key: string; value: string }): number
|
||||
}>
|
||||
readonly body: HoppRESTReqBody
|
||||
readonly auth: HoppRESTAuth
|
||||
|
|
@ -368,29 +874,104 @@ declare namespace pm {
|
|||
readonly code: number
|
||||
readonly status: string
|
||||
readonly responseTime: number
|
||||
readonly responseSize: number
|
||||
text(): string
|
||||
json(): Record<string, any>
|
||||
json(): any
|
||||
stream: Uint8Array
|
||||
/**
|
||||
* Get HTTP reason phrase (status text)
|
||||
* @returns HTTP reason phrase (e.g., "OK", "Not Found")
|
||||
*/
|
||||
reason(): string
|
||||
/**
|
||||
* Convert response to data URI format
|
||||
* @returns Data URI string with base64 encoded content
|
||||
*/
|
||||
dataURI(): string
|
||||
/**
|
||||
* Parse JSONP response
|
||||
* @param callbackName - Optional callback function name (default: "callback")
|
||||
* @returns Parsed JSON object from JSONP wrapper
|
||||
*/
|
||||
jsonp(callbackName?: string): any
|
||||
headers: Readonly<{
|
||||
get(name: string): string | null
|
||||
has(name: string): boolean
|
||||
all(): HoppRESTResponseHeader[]
|
||||
}>
|
||||
cookies: Readonly<{
|
||||
get(name: string): any
|
||||
has(name: string): any
|
||||
toObject(): any
|
||||
get(name: string): string | null
|
||||
has(name: string): boolean
|
||||
toObject(): Record<string, string>
|
||||
}>
|
||||
to: Readonly<{
|
||||
have: Readonly<{
|
||||
status(expectedCode: number): void
|
||||
header(headerName: string, headerValue?: string): void
|
||||
body(expectedBody: string): void
|
||||
jsonBody(): void
|
||||
jsonBody(key: string): void
|
||||
jsonBody(key: string, expectedValue: any): void
|
||||
jsonBody(schema: object): void
|
||||
responseTime: Readonly<{
|
||||
below(ms: number): void
|
||||
above(ms: number): void
|
||||
}>
|
||||
jsonSchema(schema: {
|
||||
type?: string
|
||||
required?: string[]
|
||||
properties?: Record<string, any>
|
||||
items?: any
|
||||
enum?: any[]
|
||||
minimum?: number
|
||||
maximum?: number
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
}): void
|
||||
charset(expectedCharset: string): void
|
||||
cookie(cookieName: string, cookieValue?: string): void
|
||||
jsonPath(path: string, expectedValue?: any): void
|
||||
}>
|
||||
be: Readonly<{
|
||||
ok(): void
|
||||
success(): void
|
||||
accepted(): void
|
||||
badRequest(): void
|
||||
unauthorized(): void
|
||||
forbidden(): void
|
||||
notFound(): void
|
||||
rateLimited(): void
|
||||
serverError(): void
|
||||
clientError(): void
|
||||
json(): void
|
||||
html(): void
|
||||
xml(): void
|
||||
text(): void
|
||||
}>
|
||||
}>
|
||||
}>
|
||||
|
||||
const cookies: Readonly<{
|
||||
get(name: string): any
|
||||
set(name: string, value: string, options?: any): any
|
||||
jar(): any
|
||||
jar(): never
|
||||
}>
|
||||
|
||||
function test(name: string, testFunction: () => void): void
|
||||
function expect(value: any): Expectation
|
||||
|
||||
interface ExpectFunction {
|
||||
(value: any): ChaiExpectation
|
||||
/**
|
||||
* Fail the test with a custom message
|
||||
* @param message - Optional message to display on failure
|
||||
*/
|
||||
fail(message?: string): never
|
||||
}
|
||||
|
||||
const expect: ExpectFunction
|
||||
|
||||
const info: Readonly<{
|
||||
readonly eventName: "post-request"
|
||||
|
|
@ -400,28 +981,111 @@ declare namespace pm {
|
|||
readonly iterationCount: never
|
||||
}>
|
||||
|
||||
const sendRequest: () => never
|
||||
/**
|
||||
* Send an HTTP request (unsupported)
|
||||
* @throws Error - sendRequest is not supported in Hoppscotch
|
||||
*/
|
||||
function sendRequest(
|
||||
request: string | { url: string; method?: string; [key: string]: any },
|
||||
callback?: (err: any, response: any) => void
|
||||
): never
|
||||
|
||||
/**
|
||||
* Visualizer API (unsupported)
|
||||
* The Postman Visualizer allows you to present response data as HTML templates with styling.
|
||||
* This feature is not supported in Hoppscotch as it requires a browser-based visualization UI.
|
||||
* @see https://learning.postman.com/docs/sending-requests/response-data/visualizer/
|
||||
*/
|
||||
const visualizer: Readonly<{
|
||||
/**
|
||||
* Set a Handlebars template to visualize response data (unsupported)
|
||||
* @param layout - HTML template string with Handlebars syntax
|
||||
* @param data - Data object to pass to the template
|
||||
* @param options - Optional configuration object
|
||||
* @throws Error - Visualizer is not supported in Hoppscotch
|
||||
*/
|
||||
set(
|
||||
layout: string,
|
||||
data?: Record<string, any>,
|
||||
options?: Record<string, any>
|
||||
): never
|
||||
|
||||
/**
|
||||
* Clear the current visualization (unsupported)
|
||||
* @throws Error - Visualizer is not supported in Hoppscotch
|
||||
*/
|
||||
clear(): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Collection variables (unsupported - Workspace feature)
|
||||
* Collection variables are not supported in Hoppscotch as they are a Postman Workspace feature
|
||||
*/
|
||||
const collectionVariables: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
has(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
has(key: string): never
|
||||
clear(): never
|
||||
toObject(): never
|
||||
replaceIn(template: string): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Postman Vault (unsupported)
|
||||
* Vault is not supported in Hoppscotch as it is a Postman-specific feature
|
||||
*/
|
||||
const vault: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Iteration data (unsupported - Collection Runner feature)
|
||||
* Iteration data is not supported in Hoppscotch as it requires Collection Runner
|
||||
*/
|
||||
const iterationData: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
has(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
has(key: string): never
|
||||
toObject(): never
|
||||
toJSON(): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Execution control
|
||||
*/
|
||||
const execution: Readonly<{
|
||||
setNextRequest(): never
|
||||
/**
|
||||
* Execution location identifier
|
||||
* Always returns ["Hoppscotch"] with current = "Hoppscotch"
|
||||
*/
|
||||
readonly location: readonly string[] & {
|
||||
readonly current: string
|
||||
}
|
||||
/**
|
||||
* Set next request to execute (unsupported - Collection Runner feature)
|
||||
* @param requestNameOrId - Name or ID of the next request
|
||||
*/
|
||||
setNextRequest(requestNameOrId: string | null): never
|
||||
/**
|
||||
* Skip current request execution (unsupported - Collection Runner feature)
|
||||
*/
|
||||
skipRequest(): never
|
||||
/**
|
||||
* Run a request (unsupported - Collection Runner feature)
|
||||
* @param requestNameOrId - Name or ID of the request to run
|
||||
*/
|
||||
runRequest(requestNameOrId: string): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Import packages from Package Library (unsupported)
|
||||
* @param packageName - Name of the package to import (e.g., '@team-domain/package-name' or 'npm:package-name@version')
|
||||
* @returns The imported package module
|
||||
* @throws Error - Package imports are not supported in Hoppscotch
|
||||
*/
|
||||
function require(packageName: string): never
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ declare namespace hopp {
|
|||
* - Complete replacement: When all fields are provided, replaces entire body
|
||||
*
|
||||
* @param body - Partial or complete HoppRESTReqBody object
|
||||
*
|
||||
*`
|
||||
* @example
|
||||
* // Partial update - just change content type
|
||||
* hopp.request.setBody({ contentType: "application/xml" })
|
||||
|
|
@ -348,41 +348,465 @@ declare namespace hopp {
|
|||
|
||||
declare namespace pm {
|
||||
const environment: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
/**
|
||||
* Get an environment variable value
|
||||
* @param key - Variable key
|
||||
* @returns Variable value or undefined if not found
|
||||
*/
|
||||
get(key: string): any
|
||||
/**
|
||||
* Set an environment variable
|
||||
* @param key - Variable key
|
||||
* @param value - Variable value
|
||||
*/
|
||||
set(key: string, value: any): void
|
||||
/**
|
||||
* Remove an environment variable
|
||||
* @param key - Variable key to remove
|
||||
*/
|
||||
unset(key: string): void
|
||||
/**
|
||||
* Check if an environment variable exists
|
||||
* @param key - Variable key to check
|
||||
* @returns true if variable exists, false otherwise
|
||||
*/
|
||||
has(key: string): boolean
|
||||
clear(): never
|
||||
toObject(): never
|
||||
/**
|
||||
* Clear all environment variables in the active environment
|
||||
*/
|
||||
clear(): void
|
||||
/**
|
||||
* Get all environment variables as an object
|
||||
* @returns Object with all environment variables as key-value pairs
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
}>
|
||||
|
||||
const globals: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
/**
|
||||
* Get a global variable value
|
||||
* @param key - Variable key
|
||||
* @returns Variable value or undefined if not found
|
||||
*/
|
||||
get(key: string): any
|
||||
/**
|
||||
* Set a global variable
|
||||
* @param key - Variable key
|
||||
* @param value - Variable value (undefined is preserved, other types are coerced to strings)
|
||||
*/
|
||||
set(key: string, value: any): void
|
||||
/**
|
||||
* Remove a global variable
|
||||
* @param key - Variable key to remove
|
||||
*/
|
||||
unset(key: string): void
|
||||
/**
|
||||
* Check if a global variable exists
|
||||
* @param key - Variable key to check
|
||||
* @returns true if variable exists, false otherwise
|
||||
*/
|
||||
has(key: string): boolean
|
||||
clear(): never
|
||||
toObject(): never
|
||||
/**
|
||||
* Clear all global variables
|
||||
*/
|
||||
clear(): void
|
||||
/**
|
||||
* Get all global variables as an object
|
||||
* @returns Object with all global variables as key-value pairs
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
}>
|
||||
|
||||
const variables: Readonly<{
|
||||
get(key: string): string | null
|
||||
set(key: string, value: string): void
|
||||
/**
|
||||
* Get a variable value from either environment or global scope
|
||||
* Environment variables take precedence over global variables
|
||||
* @param key - Variable key
|
||||
* @returns Variable value or undefined if not found
|
||||
*/
|
||||
get(key: string): any
|
||||
/**
|
||||
* Set a variable in the active environment scope
|
||||
* @param key - Variable key
|
||||
* @param value - Variable value (undefined is preserved, other types are coerced to strings)
|
||||
*/
|
||||
set(key: string, value: any): void
|
||||
/**
|
||||
* Check if a variable exists in either environment or global scope
|
||||
* @param key - Variable key to check
|
||||
* @returns true if variable exists, false otherwise
|
||||
*/
|
||||
has(key: string): boolean
|
||||
/**
|
||||
* Replace variables in a template string
|
||||
* @param template - Template string with {{variable}} placeholders
|
||||
* @returns String with variables replaced with their values
|
||||
*/
|
||||
replaceIn(template: string): string
|
||||
}>
|
||||
|
||||
const request: Readonly<{
|
||||
readonly url: { toString(): string }
|
||||
readonly method: string
|
||||
readonly headers: Readonly<{
|
||||
/**
|
||||
* Request object with full Postman compatibility
|
||||
* All properties are mutable in pre-request scripts to match Postman behavior
|
||||
*/
|
||||
let request: {
|
||||
// ID and name (read-only)
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
|
||||
/**
|
||||
* Client certificate used for mutual TLS authentication
|
||||
* In Postman, certificates are configured at the app/collection level, not programmatically in scripts
|
||||
* Returns undefined in Hoppscotch as certificate configuration is handled at the application level
|
||||
* @see https://learning.postman.com/docs/sending-requests/certificates/
|
||||
*/
|
||||
readonly certificate:
|
||||
| {
|
||||
readonly name: string
|
||||
readonly matches: string[]
|
||||
readonly key: { readonly src: string }
|
||||
readonly cert: { readonly src: string }
|
||||
readonly passphrase?: string
|
||||
}
|
||||
| undefined
|
||||
|
||||
/**
|
||||
* Proxy configuration for the request
|
||||
* In Postman, proxy is configured at the app level, not programmatically in scripts
|
||||
* Returns undefined in Hoppscotch as proxy configuration is handled at the application level
|
||||
* @see https://learning.postman.com/docs/sending-requests/capturing-request-data/proxy/
|
||||
*/
|
||||
readonly proxy:
|
||||
| {
|
||||
readonly host: string
|
||||
readonly port: number
|
||||
readonly tunnel: boolean
|
||||
readonly disabled: boolean
|
||||
}
|
||||
| undefined
|
||||
|
||||
// URL - Fully mutable with Postman URL object structure
|
||||
// Intersection type allows both string assignment AND object access
|
||||
url: string & {
|
||||
toString(): string
|
||||
protocol: string
|
||||
host: string[]
|
||||
port: string
|
||||
path: string[]
|
||||
hash: string
|
||||
|
||||
// URL Helper Methods (Postman-compatible)
|
||||
/**
|
||||
* Get the hostname as a string (e.g., "api.example.com")
|
||||
* @returns The hostname portion of the URL
|
||||
*/
|
||||
getHost(): string
|
||||
/**
|
||||
* Get the path with leading slash (e.g., "/v1/users")
|
||||
* @param unresolved - If true, returns unresolved path with variables (currently ignored)
|
||||
* @returns The path portion of the URL
|
||||
*/
|
||||
getPath(unresolved?: boolean): string
|
||||
/**
|
||||
* Get the path with query string (e.g., "/v1/users?page=1")
|
||||
* @returns Path and query string combined
|
||||
*/
|
||||
getPathWithQuery(): string
|
||||
/**
|
||||
* Get the query string without leading ? (e.g., "page=1&limit=20")
|
||||
* @param options - Optional configuration (currently ignored)
|
||||
* @returns Query string without the leading question mark
|
||||
*/
|
||||
getQueryString(options?: Record<string, unknown>): string
|
||||
/**
|
||||
* Get the hostname with port (e.g., "api.example.com:8080")
|
||||
* @param forcePort - If true, includes standard ports (80/443)
|
||||
* @returns Hostname with port if non-standard or forced
|
||||
*/
|
||||
getRemote(forcePort?: boolean): string
|
||||
/**
|
||||
* Update the entire URL from a string or object with toString()
|
||||
* @param url - New URL string or object with toString() method
|
||||
*/
|
||||
update(url: string | { toString(): string }): void
|
||||
/**
|
||||
* Add multiple query parameters to the URL
|
||||
* @param params - Array of parameter objects with key/value pairs
|
||||
*/
|
||||
addQueryParams(params: Array<{ key: string; value?: string }>): void
|
||||
/**
|
||||
* Remove query parameters by name
|
||||
* @param params - Single parameter name or array of names to remove
|
||||
*/
|
||||
removeQueryParams(params: string | string[]): void
|
||||
|
||||
query: {
|
||||
// Read methods
|
||||
/**
|
||||
* Get the value of a query parameter by key
|
||||
* @param key - Parameter key to retrieve
|
||||
* @returns Parameter value or null if not found
|
||||
*/
|
||||
get(key: string): string | null
|
||||
/**
|
||||
* Check if a query parameter exists
|
||||
* @param key - Parameter key to check
|
||||
* @returns true if parameter exists, false otherwise
|
||||
*/
|
||||
has(key: string): boolean
|
||||
/**
|
||||
* Get all query parameters as a key-value object
|
||||
* @returns Object with all query parameters
|
||||
*/
|
||||
all(): Record<string, string>
|
||||
/**
|
||||
* Convert query parameters to object (alias for all())
|
||||
* @returns Object with all query parameters
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
/**
|
||||
* Get the number of query parameters
|
||||
* @returns Count of parameters
|
||||
*/
|
||||
count(): number
|
||||
/**
|
||||
* Get a parameter by index
|
||||
* @param index - Zero-based index
|
||||
* @returns Parameter object or null if out of bounds
|
||||
*/
|
||||
idx(index: number): { key: string; value: string } | null
|
||||
|
||||
// Iteration methods
|
||||
/**
|
||||
* Iterate over all query parameters
|
||||
* @param callback - Function to call for each parameter
|
||||
*/
|
||||
each(callback: (param: { key: string; value: string }) => void): void
|
||||
/**
|
||||
* Map query parameters to a new array
|
||||
* @param callback - Transform function
|
||||
* @returns Array of transformed values
|
||||
*/
|
||||
map<T>(callback: (param: { key: string; value: string }) => T): T[]
|
||||
/**
|
||||
* Filter query parameters
|
||||
* @param callback - Predicate function
|
||||
* @returns Array of parameters matching the predicate
|
||||
*/
|
||||
filter(
|
||||
callback: (param: { key: string; value: string }) => boolean
|
||||
): Array<{ key: string; value: string }>
|
||||
/**
|
||||
* Find a query parameter by string key or function predicate
|
||||
* @param rule - String key or predicate function
|
||||
* @param context - Optional context to bind the predicate function
|
||||
* @returns Matching parameter or null if not found
|
||||
*/
|
||||
find(
|
||||
rule: string | ((param: { key: string; value: string }) => boolean),
|
||||
context?: any
|
||||
): { key: string; value: string } | null
|
||||
/**
|
||||
* Get the index of a query parameter
|
||||
* @param item - String key or parameter object to find
|
||||
* @returns Index of parameter, or -1 if not found
|
||||
*/
|
||||
indexOf(item: string | { key: string; value: string }): number
|
||||
|
||||
// Mutation methods
|
||||
/**
|
||||
* Add a new query parameter
|
||||
* @param param - Parameter to add
|
||||
*/
|
||||
add(param: { key: string; value: string }): void
|
||||
/**
|
||||
* Remove a query parameter by key
|
||||
* @param key - Parameter key to remove
|
||||
*/
|
||||
remove(key: string): void
|
||||
/**
|
||||
* Update an existing parameter or add a new one
|
||||
* @param param - Parameter to upsert
|
||||
*/
|
||||
upsert(param: { key: string; value: string }): void
|
||||
/**
|
||||
* Remove all query parameters
|
||||
*/
|
||||
clear(): void
|
||||
/**
|
||||
* Insert a query parameter before another parameter
|
||||
* @param item - Parameter to insert
|
||||
* @param before - String key or parameter object to insert before
|
||||
*/
|
||||
insert(
|
||||
item: { key: string; value: string },
|
||||
before: string | { key: string; value: string }
|
||||
): void
|
||||
/**
|
||||
* Move a parameter to the end or append a new one
|
||||
* @param item - Parameter to append
|
||||
*/
|
||||
append(item: { key: string; value: string }): void
|
||||
/**
|
||||
* Merge parameters from an array or object
|
||||
* @param source - Array of parameters or key-value object
|
||||
* @param prune - If true, remove parameters not in source
|
||||
*/
|
||||
assimilate(
|
||||
source:
|
||||
| Array<{ key: string; value: string }>
|
||||
| Record<string, string>,
|
||||
prune?: boolean
|
||||
): void
|
||||
}
|
||||
}
|
||||
|
||||
// Method - Mutable
|
||||
method: string
|
||||
|
||||
// Headers - With Postman mutation methods
|
||||
headers: {
|
||||
// Read methods
|
||||
/**
|
||||
* Get the value of a header by name (case-insensitive)
|
||||
* @param name - Header name to retrieve
|
||||
* @returns Header value or null if not found
|
||||
*/
|
||||
get(name: string): string | null
|
||||
/**
|
||||
* Check if a header exists (case-insensitive)
|
||||
* @param name - Header name to check
|
||||
* @returns true if header exists, false otherwise
|
||||
*/
|
||||
has(name: string): boolean
|
||||
all(): HoppRESTHeader[]
|
||||
}>
|
||||
readonly body: any
|
||||
readonly auth: any
|
||||
}>
|
||||
/**
|
||||
* Get all headers as a key-value object
|
||||
* @returns Object with all headers (keys in lowercase)
|
||||
*/
|
||||
all(): Record<string, string>
|
||||
/**
|
||||
* Convert headers to object (alias for all())
|
||||
* @returns Object with all headers (keys in lowercase)
|
||||
*/
|
||||
toObject(): Record<string, string>
|
||||
/**
|
||||
* Get the number of headers
|
||||
* @returns Count of headers
|
||||
*/
|
||||
count(): number
|
||||
/**
|
||||
* Get a header by index
|
||||
* @param index - Zero-based index
|
||||
* @returns Header object or null if out of bounds
|
||||
*/
|
||||
idx(index: number): { key: string; value: string } | null
|
||||
|
||||
// Iteration methods
|
||||
/**
|
||||
* Iterate over all headers
|
||||
* @param callback - Function to call for each header
|
||||
*/
|
||||
each(callback: (header: { key: string; value: string }) => void): void
|
||||
/**
|
||||
* Map headers to a new array
|
||||
* @param callback - Transform function
|
||||
* @returns Array of transformed values
|
||||
*/
|
||||
map<T>(callback: (header: { key: string; value: string }) => T): T[]
|
||||
/**
|
||||
* Filter headers
|
||||
* @param callback - Predicate function
|
||||
* @returns Array of headers matching the predicate
|
||||
*/
|
||||
filter(
|
||||
callback: (header: { key: string; value: string }) => boolean
|
||||
): Array<{ key: string; value: string }>
|
||||
/**
|
||||
* Find a header by string key or function predicate (case-insensitive)
|
||||
* @param rule - String key or predicate function
|
||||
* @param context - Optional context to bind the predicate function
|
||||
* @returns Matching header or null if not found
|
||||
*/
|
||||
find(
|
||||
rule: string | ((header: { key: string; value: string }) => boolean),
|
||||
context?: any
|
||||
): { key: string; value: string } | null
|
||||
/**
|
||||
* Get the index of a header (case-insensitive)
|
||||
* @param item - String key or header object to find
|
||||
* @returns Index of header, or -1 if not found
|
||||
*/
|
||||
indexOf(item: string | { key: string; value: string }): number
|
||||
|
||||
// Mutation methods
|
||||
/**
|
||||
* Add a new header
|
||||
* @param header - Header to add
|
||||
*/
|
||||
add(header: { key: string; value: string }): void
|
||||
/**
|
||||
* Remove a header by name (case-insensitive)
|
||||
* @param headerName - Header name to remove
|
||||
*/
|
||||
remove(headerName: string): void
|
||||
/**
|
||||
* Update an existing header or add a new one
|
||||
* @param header - Header to upsert
|
||||
*/
|
||||
upsert(header: { key: string; value: string }): void
|
||||
/**
|
||||
* Remove all headers
|
||||
*/
|
||||
clear(): void
|
||||
/**
|
||||
* Insert a header before another header
|
||||
* @param item - Header to insert
|
||||
* @param before - String key or header object to insert before
|
||||
*/
|
||||
insert(
|
||||
item: { key: string; value: string },
|
||||
before: string | { key: string; value: string }
|
||||
): void
|
||||
/**
|
||||
* Move a header to the end or append a new one
|
||||
* @param item - Header to append
|
||||
*/
|
||||
append(item: { key: string; value: string }): void
|
||||
/**
|
||||
* Merge headers from an array or object (case-insensitive)
|
||||
* @param source - Array of headers or key-value object
|
||||
* @param prune - If true, remove headers not in source
|
||||
*/
|
||||
assimilate(
|
||||
source: Array<{ key: string; value: string }> | Record<string, string>,
|
||||
prune?: boolean
|
||||
): void
|
||||
}
|
||||
|
||||
// Body - With Postman update() method
|
||||
// Uses HoppRESTReqBody for type safety with Postman's update() extension
|
||||
body: HoppRESTReqBody & {
|
||||
update(
|
||||
body:
|
||||
| string
|
||||
| {
|
||||
mode?: "raw" | "urlencoded" | "formdata" | "file"
|
||||
raw?: string
|
||||
urlencoded?: Array<{ key: string; value: string }>
|
||||
formdata?: Array<{ key: string; value: string | File }>
|
||||
file?: File
|
||||
options?: {
|
||||
raw?: {
|
||||
language?: "json" | "text" | "html" | "xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
): void
|
||||
}
|
||||
|
||||
// Auth - Mutable with proper type safety
|
||||
auth: HoppRESTAuth
|
||||
}
|
||||
|
||||
const info: Readonly<{
|
||||
readonly eventName: "pre-request"
|
||||
|
|
@ -393,27 +817,88 @@ declare namespace pm {
|
|||
}>
|
||||
|
||||
const sendRequest: () => never
|
||||
|
||||
/**
|
||||
* Collection variables (unsupported - Workspace feature)
|
||||
* Collection variables are not supported in Hoppscotch as they are a Postman Workspace feature
|
||||
*/
|
||||
const collectionVariables: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
has(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
has(key: string): never
|
||||
clear(): never
|
||||
toObject(): never
|
||||
replaceIn(template: string): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Postman Vault (unsupported)
|
||||
* Vault is not supported in Hoppscotch as it is a Postman-specific feature
|
||||
*/
|
||||
const vault: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Iteration data (unsupported - Collection Runner feature)
|
||||
* Iteration data is not supported in Hoppscotch as it requires Collection Runner
|
||||
*/
|
||||
const iterationData: Readonly<{
|
||||
get(): never
|
||||
set(): never
|
||||
unset(): never
|
||||
has(): never
|
||||
get(key: string): never
|
||||
set(key: string, value: string): never
|
||||
unset(key: string): never
|
||||
has(key: string): never
|
||||
toObject(): never
|
||||
toJSON(): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Visualizer API (unsupported)
|
||||
* The Postman Visualizer allows you to present response data as HTML templates with styling.
|
||||
* This feature is not supported in Hoppscotch as it requires a browser-based visualization UI.
|
||||
* @see https://learning.postman.com/docs/sending-requests/response-data/visualizer/
|
||||
*/
|
||||
const visualizer: Readonly<{
|
||||
/**
|
||||
* Set a Handlebars template to visualize response data (unsupported)
|
||||
* @param layout - HTML template string with Handlebars syntax
|
||||
* @param data - Data object to pass to the template
|
||||
* @param options - Optional configuration object
|
||||
* @throws Error - Visualizer is not supported in Hoppscotch
|
||||
*/
|
||||
set(
|
||||
layout: string,
|
||||
data?: Record<string, any>,
|
||||
options?: Record<string, any>
|
||||
): never
|
||||
|
||||
/**
|
||||
* Clear the current visualization (unsupported)
|
||||
* @throws Error - Visualizer is not supported in Hoppscotch
|
||||
*/
|
||||
clear(): never
|
||||
}>
|
||||
|
||||
/**
|
||||
* Execution control
|
||||
*/
|
||||
const execution: Readonly<{
|
||||
setNextRequest(): never
|
||||
/**
|
||||
* Set next request to execute (unsupported - Collection Runner feature)
|
||||
* @param requestNameOrId - Name or ID of the next request
|
||||
*/
|
||||
setNextRequest(requestNameOrId: string | null): never
|
||||
/**
|
||||
* Skip current request execution (unsupported - Collection Runner feature)
|
||||
*/
|
||||
skipRequest(): never
|
||||
/**
|
||||
* Run a request (unsupported - Collection Runner feature)
|
||||
* @param requestNameOrId - Name or ID of the request to run
|
||||
*/
|
||||
runRequest(requestNameOrId: string): never
|
||||
}>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
"dependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"chai": "6.2.0",
|
||||
"faraday-cage": "0.1.0",
|
||||
"fp-ts": "2.16.11",
|
||||
"lodash": "4.17.21",
|
||||
|
|
@ -61,6 +62,7 @@
|
|||
"devDependencies": {
|
||||
"@digitak/esrun": "3.2.26",
|
||||
"@relmify/jest-fp-ts": "2.1.1",
|
||||
"@types/chai": "5.2.2",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.9.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* Async/Await Support Tests
|
||||
*
|
||||
* Tests that pm.test() and hopp.test() properly support async functions with await,
|
||||
* which is critical for Postman script imports that use asynchronous patterns.
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)("%s.test() - Async/Await Support", (namespace) => {
|
||||
test("should support async function with await", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("async with await", async function() {
|
||||
const promise = new Promise((resolve) => {
|
||||
resolve(42)
|
||||
})
|
||||
const result = await promise
|
||||
${namespace}.expect(result).to.equal(42)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support async arrow function", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("async arrow", async () => {
|
||||
const result = await Promise.resolve("success")
|
||||
${namespace}.expect(result).to.equal("success")
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support Promise.all with await", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("Promise.all", async function() {
|
||||
const results = await Promise.all([
|
||||
Promise.resolve(1),
|
||||
Promise.resolve(2),
|
||||
Promise.resolve(3)
|
||||
])
|
||||
${namespace}.expect(results).to.eql([1, 2, 3])
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support async error handling", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("async error", async function() {
|
||||
try {
|
||||
await Promise.reject(new Error("test error"))
|
||||
${namespace}.expect.fail("Should not reach here")
|
||||
} catch (error) {
|
||||
${namespace}.expect(error.message).to.equal("test error")
|
||||
}
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support multiple sequential awaits", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("sequential awaits", async function() {
|
||||
const a = await Promise.resolve(10)
|
||||
const b = await Promise.resolve(20)
|
||||
const c = await Promise.resolve(30)
|
||||
${namespace}.expect(a + b + c).to.equal(60)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support async IIFE pattern", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("async IIFE", async function() {
|
||||
const result = await (async () => {
|
||||
const data = await Promise.resolve({ value: 100 })
|
||||
return data.value * 2
|
||||
})()
|
||||
${namespace}.expect(result).to.equal(200)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
/**
|
||||
* @see https://github.com/hoppscotch/hoppscotch/discussions/5221
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)("%s.expect() - Core Chai Assertions", (namespace) => {
|
||||
describe("Equality Assertions", () => {
|
||||
test("should support `.equal()` for strict equality", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("equality assertions", () => {
|
||||
${namespace}.expect(42).to.equal(42)
|
||||
${namespace}.expect('test').to.equal('test')
|
||||
${namespace}.expect(true).to.equal(true)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "equality assertions",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 42 to equal 42" },
|
||||
{ status: "pass", message: "Expected 'test' to equal 'test'" },
|
||||
{ status: "pass", message: "Expected true to equal true" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.eql()` for deep equality", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("deep equality", () => {
|
||||
${namespace}.expect({a: 1}).to.eql({a: 1})
|
||||
${namespace}.expect([1, 2, 3]).to.eql([1, 2, 3])
|
||||
${namespace}.expect({nested: {value: 42}}).to.eql({nested: {value: 42}})
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "deep equality",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected {a: 1} to eql {a: 1}" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to eql [1, 2, 3]",
|
||||
},
|
||||
{ status: "pass", message: expect.stringContaining("to eql") },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Type Assertions", () => {
|
||||
test("should assert primitive types with `.a()` and `.an()`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("type assertions", () => {
|
||||
${namespace}.expect('foo').to.be.a('string')
|
||||
${namespace}.expect({a: 1}).to.be.an('object')
|
||||
${namespace}.expect([1, 2, 3]).to.be.an('array')
|
||||
${namespace}.expect(42).to.be.a('number')
|
||||
${namespace}.expect(true).to.be.a('boolean')
|
||||
${namespace}.expect(null).to.be.a('null')
|
||||
${namespace}.expect(undefined).to.be.an('undefined')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "type assertions",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'foo' to be a string" },
|
||||
{ status: "pass", message: "Expected {a: 1} to be an object" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to be an array",
|
||||
},
|
||||
{ status: "pass", message: "Expected 42 to be a number" },
|
||||
{ status: "pass", message: "Expected true to be a boolean" },
|
||||
{ status: "pass", message: "Expected null to be a null" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected undefined to be an undefined",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Truthiness Assertions", () => {
|
||||
test("should support `.true`, `.false`, `.ok` assertions", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("truthiness", () => {
|
||||
${namespace}.expect(true).to.be.true
|
||||
${namespace}.expect(false).to.be.false
|
||||
${namespace}.expect(1).to.be.ok
|
||||
${namespace}.expect('hello').to.be.ok
|
||||
${namespace}.expect(0).to.not.be.ok
|
||||
${namespace}.expect('').to.not.be.ok
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "truthiness",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Numerical Comparisons", () => {
|
||||
test("should support `.above()` and `.below()` comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("numerical comparisons", () => {
|
||||
${namespace}.expect(10).to.be.above(5)
|
||||
${namespace}.expect(5).to.be.below(10)
|
||||
${namespace}.expect(5).to.not.be.above(10)
|
||||
${namespace}.expect(10).to.not.be.below(5)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "numerical comparisons",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.within()` for range comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("within range", () => {
|
||||
${namespace}.expect(5).to.be.within(1, 10)
|
||||
${namespace}.expect(1).to.be.within(1, 10)
|
||||
${namespace}.expect(10).to.be.within(1, 10)
|
||||
${namespace}.expect(0).to.not.be.within(1, 10)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "within range",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.closeTo()` for floating point comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("close to", () => {
|
||||
${namespace}.expect(1.5).to.be.closeTo(1.0, 0.6)
|
||||
${namespace}.expect(10.5).to.be.closeTo(10.0, 0.5)
|
||||
${namespace}.expect(5.1).to.not.be.closeTo(5.0, 0.05)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "close to",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Property Assertions", () => {
|
||||
test("should support `.property()` for property existence and value", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("property assertions", () => {
|
||||
${namespace}.expect({a: 1}).to.have.property('a')
|
||||
${namespace}.expect({a: 1}).to.have.property('a', 1)
|
||||
${namespace}.expect({x: {y: 2}}).to.have.property('x')
|
||||
${namespace}.expect({}).to.not.have.property('missing')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "property assertions",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.ownProperty()` for own properties", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("own property", () => {
|
||||
${namespace}.expect({a: 1}).to.have.ownProperty('a')
|
||||
${namespace}.expect({a: 1}).to.have.ownProperty('a', 1)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "own property",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Collection Assertions", () => {
|
||||
test("should support `.include()` for arrays and strings", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("include assertions", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.include(2)
|
||||
${namespace}.expect('hoppscotch').to.include('hopp')
|
||||
${namespace}.expect({a: 1, b: 2}).to.include({a: 1})
|
||||
${namespace}.expect([1, 2, 3]).to.not.include(5)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "include assertions",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Negation with `.not`", () => {
|
||||
test("should support negation for all assertion types", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("negation works", () => {
|
||||
${namespace}.expect(42).to.not.equal(43)
|
||||
${namespace}.expect('foo').to.not.be.a('number')
|
||||
${namespace}.expect(false).to.not.be.true
|
||||
${namespace}.expect(5).to.not.be.above(10)
|
||||
${namespace}.expect({a: 1}).to.not.have.property('b')
|
||||
${namespace}.expect([1, 2]).to.not.include(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "negation works",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Deep Equality Modifiers", () => {
|
||||
test("should support `.deep.equal()` and `.deep.property()`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("deep modifiers", () => {
|
||||
${namespace}.expect({a: {b: 1}}).to.deep.equal({a: {b: 1}})
|
||||
${namespace}.expect({a: {b: 1}}).to.have.deep.property('a', {b: 1})
|
||||
${namespace}.expect([{x: 1}]).to.deep.include({x: 1})
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "deep modifiers",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Language Chains", () => {
|
||||
test("should support basic language chain properties (`.to`, `.be`, `.that`)", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("basic language chains", () => {
|
||||
${namespace}.expect(2).to.equal(2)
|
||||
${namespace}.expect(2).to.be.equal(2)
|
||||
${namespace}.expect(2).to.be.a('number').that.equals(2)
|
||||
${namespace}.expect([1,2,3]).to.be.an('array').that.has.lengthOf(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "basic language chains",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should chain type assertion with include using `.and`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("and chain - type and include", () => {
|
||||
${namespace}.expect('hello world').to.be.a('string').and.include('world')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "and chain - type and include",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should chain type assertion with lengthOf using `.and`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("and chain - type and length", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.be.an('array').and.have.lengthOf(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "and chain - type and length",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support complex chaining with multiple `.and` and `.that`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("complex and chaining", () => {
|
||||
${namespace}.expect({ name: 'John', age: 30 })
|
||||
.to.be.an('object')
|
||||
.and.have.property('name')
|
||||
.that.is.a('string')
|
||||
.and.equals('John')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "complex and chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should work with `.and.not` for negation", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("and with negation", () => {
|
||||
${namespace}.expect({ a: 1, b: 2 })
|
||||
.to.be.an('object')
|
||||
.and.not.be.empty
|
||||
.and.not.have.property('c')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "and with negation",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should chain numeric comparisons with `.and`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("and with numbers", () => {
|
||||
${namespace}.expect(200)
|
||||
.to.be.a('number')
|
||||
.and.be.within(200, 299)
|
||||
.and.equal(200)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "and with numbers",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - Basic Function Assertions",
|
||||
(namespace) => {
|
||||
test("should support `.throw()` for error throwing", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("throw assertions", () => {
|
||||
${namespace}.expect(() => { throw new Error('oops') }).to.throw()
|
||||
${namespace}.expect(() => { throw new Error('oops') }).to.throw(Error)
|
||||
${namespace}.expect(() => {}).to.not.throw()
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "throw assertions",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.respondTo()` for method existence", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("respondTo assertions", () => {
|
||||
const obj = { method: () => {} }
|
||||
${namespace}.expect(obj).to.respondTo('method')
|
||||
${namespace}.expect([]).to.respondTo('push')
|
||||
${namespace}.expect('').to.respondTo('charAt')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "respondTo assertions",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.satisfy()` for custom predicates", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("satisfy assertions", () => {
|
||||
${namespace}.expect(2).to.satisfy((n) => n > 0)
|
||||
${namespace}.expect(10).to.satisfy((n) => n % 2 === 0)
|
||||
${namespace}.expect('hello').to.satisfy((s) => s.length > 3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "satisfy assertions",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - deep.include() - Object Property Inclusion",
|
||||
(namespace) => {
|
||||
test("should pass when object deeply includes partial match", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - object property inclusion", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3 }).to.deep.include({ a: 1, b: 2 });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - object property inclusion",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should pass with negation when object does not include properties", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - negated", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2 }).to.not.deep.include({ a: 1, c: 3 });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - negated",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with nested objects", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - nested objects", (namespace) => {
|
||||
const obj = { a: { b: { c: 1 } }, d: 2 };
|
||||
${namespace}.expect(obj).to.deep.include({ a: { b: { c: 1 } } });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - nested objects",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - deep.include() - Array Object Inclusion",
|
||||
(namespace) => {
|
||||
test("should pass when array deeply includes object", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - array object inclusion", (namespace) => {
|
||||
${namespace}.expect([{ id: 1 }, { id: 2 }]).to.deep.include({ id: 1 });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - array object inclusion",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should pass with negation when array does not include object", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - array negated", (namespace) => {
|
||||
${namespace}.expect([{ id: 1 }, { id: 2 }]).to.not.deep.include({ id: 3 });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - array negated",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with exact nested objects in arrays", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("deep.include() - complex array objects", (namespace) => {
|
||||
const arr = [
|
||||
{ name: 'John', age: 30, address: { city: 'NYC' } },
|
||||
{ name: 'Jane', age: 25, address: { city: 'LA' } }
|
||||
];
|
||||
// deep.include on arrays requires exact object match
|
||||
${namespace}.expect(arr).to.deep.include({ name: 'John', age: 30, address: { city: 'NYC' } });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.include() - complex array objects",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - include.deep() - Alternative Syntax",
|
||||
(namespace) => {
|
||||
test("should work with include.deep syntax", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.deep() - alternative syntax", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3 }).to.include.deep({ a: 1, b: 2 });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.deep() - alternative syntax",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - include.keys() - Partial Key Matching",
|
||||
(namespace) => {
|
||||
test("should pass when object has at least the specified keys", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.keys() - partial key matching", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3 }).to.include.keys('a', 'b');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.keys() - partial key matching",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should pass even when object has extra keys", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.keys() - with extra keys", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3, d: 4, e: 5 }).to.include.keys('a', 'b');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.keys() - with extra keys",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should pass with negation when object does not have key", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.keys() - negated", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2 }).to.not.include.keys('c');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.keys() - negated",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with array of keys", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.keys() - array syntax", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3 }).to.include.keys(['a', 'b']);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.keys() - array syntax",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with single key", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("include.keys() - single key", (namespace) => {
|
||||
${namespace}.expect({ a: 1, b: 2, c: 3 }).to.include.keys('a');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.keys() - single key",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)("%s.expect() - Combination Patterns", (namespace) => {
|
||||
test("should work with chained assertions", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("chained deep.include", (namespace) => {
|
||||
const response = { status: 'success', data: { user: { id: 1, name: 'John' } } };
|
||||
${namespace}.expect(response).to.be.an('object')
|
||||
.and.deep.include({ status: 'success' });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "chained deep.include",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with response validation patterns", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("response validation with deep.include", (namespace) => {
|
||||
const jsonData = { status: 200, message: 'OK', data: { results: [] } };
|
||||
${namespace}.expect(jsonData).to.deep.include({ status: 200, message: 'OK' });
|
||||
${namespace}.expect(jsonData).to.include.keys('status', 'message', 'data');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "response validation with deep.include",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,503 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - instanceof assertions - Built-in types",
|
||||
(namespace) => {
|
||||
test("should support instanceof Object for plain objects", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof Object", () => {
|
||||
const obj = { key: "value" };
|
||||
${namespace}.expect(obj).to.be.an.instanceof(Object);
|
||||
${namespace}.expect(obj).to.be.instanceOf(Object);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof Object",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof Array", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof Array", () => {
|
||||
const arr = [1, 2, 3];
|
||||
${namespace}.expect(arr).to.be.an.instanceof(Array);
|
||||
${namespace}.expect(arr).to.be.instanceOf(Array);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof Array",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof Date", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof Date", () => {
|
||||
const date = new Date();
|
||||
${namespace}.expect(date).to.be.an.instanceof(Date);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof Date",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof RegExp", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof RegExp", () => {
|
||||
const regex = /test/;
|
||||
${namespace}.expect(regex).to.be.an.instanceof(RegExp);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof RegExp",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof Error", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof Error", () => {
|
||||
const err = new Error("test error");
|
||||
${namespace}.expect(err).to.be.an.instanceof(Error);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof Error",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support arrays as instanceof Object", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("array instanceof Object", () => {
|
||||
const arr = [1, 2, 3];
|
||||
${namespace}.expect(arr).to.be.an.instanceof(Object);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "array instanceof Object",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - instanceof assertions - Custom classes",
|
||||
(namespace) => {
|
||||
test("should support instanceof with custom class definitions", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("custom class instanceof", () => {
|
||||
class Person {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
const john = new Person("John");
|
||||
${namespace}.expect(john).to.be.an.instanceof(Person);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom class instanceof",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof with ES6 class syntax", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("ES6 class instanceof", () => {
|
||||
class Animal {
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
class Dog extends Animal {
|
||||
constructor(name) {
|
||||
super("dog");
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
const rover = new Dog("Rover");
|
||||
${namespace}.expect(rover).to.be.an.instanceof(Dog);
|
||||
${namespace}.expect(rover).to.be.an.instanceof(Animal);
|
||||
${namespace}.expect(rover).to.be.an.instanceof(Object);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "ES6 class instanceof",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support instanceof with constructor functions", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("constructor function instanceof", () => {
|
||||
function Car(make, model) {
|
||||
this.make = make;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
const tesla = new Car("Tesla", "Model 3");
|
||||
${namespace}.expect(tesla).to.be.an.instanceof(Car);
|
||||
${namespace}.expect(tesla).to.be.an.instanceof(Object);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "constructor function instanceof",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with custom classes in response data context", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("custom class with response data", () => {
|
||||
class ResponseData {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = pm.response.json();
|
||||
const wrapped = new ResponseData(responseData);
|
||||
|
||||
${namespace}.expect(wrapped).to.be.an.instanceof(ResponseData);
|
||||
${namespace}.expect(wrapped).to.be.an.instanceof(Object);
|
||||
});
|
||||
`
|
||||
|
||||
const mockResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 100,
|
||||
headers: [],
|
||||
body: { test: "data" },
|
||||
}
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom class with response data",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - instanceof assertions - Custom classes with other assertions",
|
||||
(namespace) => {
|
||||
test("should work with custom classes in property assertions", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("custom class with properties", () => {
|
||||
class User {
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
const user = new User("Alice", 30);
|
||||
|
||||
${namespace}.expect(user).to.be.an.instanceof(User);
|
||||
${namespace}.expect(user).to.have.property("name", "Alice");
|
||||
${namespace}.expect(user).to.have.property("age", 30);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom class with properties",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with custom classes in array assertions", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("array of custom class instances", () => {
|
||||
class Item {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
const items = [new Item(1), new Item(2), new Item(3)];
|
||||
|
||||
${namespace}.expect(items).to.be.an.instanceof(Array);
|
||||
${namespace}.expect(items).to.have.lengthOf(3);
|
||||
${namespace}.expect(items[0]).to.be.an.instanceof(Item);
|
||||
${namespace}.expect(items[1]).to.be.an.instanceof(Item);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "array of custom class instances",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with custom classes and deep equality", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("custom class deep equality", () => {
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
const p1 = new Point(10, 20);
|
||||
const p2 = new Point(10, 20);
|
||||
|
||||
${namespace}.expect(p1).to.be.an.instanceof(Point);
|
||||
${namespace}.expect(p2).to.be.an.instanceof(Point);
|
||||
${namespace}.expect(p1).to.deep.equal(p2);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom class deep equality",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - instanceof assertions - Negation and failure cases",
|
||||
(namespace) => {
|
||||
test("should support negation with .not.instanceof", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("negated instanceof", () => {
|
||||
const str = "hello";
|
||||
const num = 42;
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
${namespace}.expect(str).to.not.be.an.instanceof(Number);
|
||||
${namespace}.expect(num).to.not.be.an.instanceof(String);
|
||||
${namespace}.expect(arr).to.not.be.an.instanceof(String);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "negated instanceof",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when instanceof check fails", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("instanceof failure", () => {
|
||||
const str = "hello";
|
||||
${namespace}.expect(str).to.be.an.instanceof(Array);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "instanceof failure",
|
||||
expectResults: [expect.objectContaining({ status: "fail" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
/**
|
||||
* @see https://github.com/hoppscotch/hoppscotch/issues/5489
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)("%s.expect() - Keys Assertions", (namespace) => {
|
||||
describe("keys() method", () => {
|
||||
test("should accept array syntax: keys(['a', 'b'])", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Keys with array syntax', function () {
|
||||
${namespace}.expect({a: 1, b: 2}).to.have.keys(['a','b']);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Keys with array syntax",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: 1, b: 2} to have keys 'a', 'b'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should accept spread syntax: keys('a', 'b')", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Keys with spread syntax', function () {
|
||||
${namespace}.expect({a: 1, b: 2}).to.have.keys('a','b');
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Keys with spread syntax",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: 1, b: 2} to have keys 'a', 'b'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negation with array syntax", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Negated keys with array', function () {
|
||||
${namespace}.expect({a: 1}).to.not.have.keys(['b','c']);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Negated keys with array",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: 1} to not have keys 'b', 'c'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negation with spread syntax", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Negated keys with spread', function () {
|
||||
${namespace}.expect({x:5}).to.not.have.keys('y','z');
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Negated keys with spread",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {x: 5} to not have keys 'y', 'z'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("key() singular method", () => {
|
||||
test("should accept single string argument", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Single key check', function () {
|
||||
${namespace}.expect({name: 'test'}).to.have.key('name');
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Single key check",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {name: 'test'} to have keys 'name'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negation: not.have.key('z')", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Negated key assertion', function () {
|
||||
${namespace}.expect({x:5}).to.not.have.key('z');
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Negated key assertion",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {x: 5} to not have keys 'z'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe.each(NAMESPACES)("%s.expect() - Members Assertions", (namespace) => {
|
||||
describe("members() method", () => {
|
||||
test("should match members in any order", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Members matching', function () {
|
||||
${namespace}.expect([1,2,3]).to.have.members([3,2,1]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Members matching",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to have members [3, 2, 1]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negation", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Negated members', function () {
|
||||
${namespace}.expect([1,2,3]).to.not.have.members([4,5,6]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Negated members",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to not have members [4, 5, 6]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when members don't match", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Members mismatch', function () {
|
||||
${namespace}.expect([1,2]).to.have.members([1,2,3]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Members mismatch",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("to have members"),
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("include.members() method", () => {
|
||||
test("should match subset of members", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Include members', function () {
|
||||
${namespace}.expect([1,2,3]).to.include.members([2]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Include members",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to include members [2]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should match multiple subset members", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Multiple include members', function () {
|
||||
${namespace}.expect([1,2,3,4,5]).to.include.members([2,4]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Multiple include members",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3, 4, 5] to include members [2, 4]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negation: not.include.members([5])", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Negated include.members', function () {
|
||||
${namespace}.expect([1,2,3]).to.not.include.members([5]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Negated include.members",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to not include members [5]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when subset members not present", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Missing subset members', function () {
|
||||
${namespace}.expect([1,2,3]).to.include.members([4,5]);
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Missing subset members",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("to include members"),
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - Combined Keys and Members",
|
||||
(namespace) => {
|
||||
test("should handle all assertions from the reported issue", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test('Contains and includes', function () {
|
||||
var arr = [1,2,3];
|
||||
${namespace}.expect(arr).to.include(2);
|
||||
${namespace}.expect('hoppscotch').to.include('hopp');
|
||||
${namespace}.expect({a: 1, b: 2}).to.have.keys(['a','b']);
|
||||
${namespace}.expect({x:5}).to.not.have.key('z');
|
||||
});
|
||||
|
||||
${namespace}.test('Members matching', function () {
|
||||
${namespace}.expect([1,2,3]).to.have.members([3,2,1]);
|
||||
${namespace}.expect([1,2,3]).to.include.members([2]);
|
||||
${namespace}.expect([1,2,3]).to.not.include.members([5]);
|
||||
});
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "Contains and includes",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to include 2",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'hoppscotch' to include 'hopp'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: 1, b: 2} to have keys 'a', 'b'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {x: 5} to not have keys 'z'",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
descriptor: "Members matching",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to have members [3, 2, 1]",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to include members [2]",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to not include members [5]",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,604 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { TestResponse } from "~/types"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)("%s.expect() - Length Assertions", (namespace) => {
|
||||
const mockResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 100,
|
||||
headers: [{ key: "content-type", value: "application/json" }],
|
||||
body: {
|
||||
items: ["apple", "banana", "cherry"],
|
||||
emptyArray: [],
|
||||
singleItem: ["solo"],
|
||||
data: {
|
||||
nested: {
|
||||
values: [1, 2, 3, 4, 5],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe(".length getter - Basic comparison methods", () => {
|
||||
test("should support .length.above() for arrays", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("length.above()", () => {
|
||||
${namespace}.expect([1, 2, 3, 4]).to.have.length.above(3);
|
||||
${namespace}.expect([1, 2]).to.not.have.length.above(5);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "length.above()",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .length.above() for strings", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("string length.above()", () => {
|
||||
${namespace}.expect('hello world').to.have.length.above(5);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "string length.above()",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .length.below() for arrays", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("length.below()", () => {
|
||||
${namespace}.expect([1, 2]).to.have.length.below(5);
|
||||
${namespace}.expect([1, 2, 3, 4]).to.not.have.length.below(3);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "length.below()",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .length.within() for range checks", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("length.within()", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.within(2, 5);
|
||||
${namespace}.expect('test').to.have.length.within(1, 10);
|
||||
${namespace}.expect([1]).to.not.have.length.within(5, 10);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "length.within()",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".length.at.least() and .length.at.most() - Postman chain syntax", () => {
|
||||
test("should pass when array length meets minimum (.at.least)", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Array length at least", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.length.at.least(1)
|
||||
${namespace}.expect(items).to.have.length.at.least(3)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Array length at least",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when array length below minimum (.at.least)", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Array too short", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.length.at.least(10)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Array too short",
|
||||
expectResults: [expect.objectContaining({ status: "fail" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should pass when array length within maximum (.at.most)", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Array length at most", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.length.at.most(10)
|
||||
${namespace}.expect(items).to.have.length.at.most(3)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Array length at most",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when array length exceeds maximum (.at.most)", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Array too long", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.length.at.most(2)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Array too long",
|
||||
expectResults: [expect.objectContaining({ status: "fail" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".length.least() and .length.most() - Direct methods without .at", () => {
|
||||
test("should support .length.least() without .at chain", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Direct least", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.least(1)
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.least(3)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(script, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Direct least",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support mixed syntax with and without .at", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Mixed syntax", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.length.least(1)
|
||||
${namespace}.expect(items).to.have.length.at.least(1)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Mixed syntax",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".length.gte() and .length.lte() - Aliases", () => {
|
||||
test("should support .length.gte() as alias for .least()", async () => {
|
||||
const script = `
|
||||
${namespace}.test("GTE alias", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.gte(3)
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.gte(1)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(script, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "GTE alias",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .length.lte() as alias for .most()", async () => {
|
||||
const script = `
|
||||
${namespace}.test("LTE alias", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.lte(3)
|
||||
${namespace}.expect([1, 2, 3]).to.have.length.lte(10)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(script, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "LTE alias",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".length(n) - Callable method for exact length", () => {
|
||||
test("should support .length(n) as method for exact length", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("length as method", () => {
|
||||
${namespace}.expect([1, 2, 3]).to.have.length(3);
|
||||
${namespace}.expect('abc').to.have.length(3);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "length as method",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".lengthOf(n) - Method for exact length", () => {
|
||||
test("should support .lengthOf(n) for exact length", async () => {
|
||||
const testScript = `
|
||||
${namespace}.test("lengthOf()", () => {
|
||||
${namespace}.expect('hello').to.have.lengthOf(5);
|
||||
${namespace}.expect([1, 2, 3, 4, 5]).to.have.lengthOf(5);
|
||||
${namespace}.expect('').to.have.lengthOf(0);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "lengthOf()",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe(".lengthOf.at.least() and .lengthOf.at.most() - Alternative syntax", () => {
|
||||
const lengthOfMockResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 100,
|
||||
headers: [],
|
||||
body: {
|
||||
items: ["a", "b", "c", "d"],
|
||||
},
|
||||
}
|
||||
|
||||
test("should support .lengthOf.at.least()", async () => {
|
||||
const script = `
|
||||
${namespace}.test("lengthOf.at.least", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.lengthOf.at.least(1)
|
||||
${namespace}.expect(items).to.have.lengthOf.at.least(4)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
lengthOfMockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "lengthOf.at.least",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .lengthOf.at.most()", async () => {
|
||||
const script = `
|
||||
${namespace}.test("lengthOf.at.most", () => {
|
||||
const items = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.items
|
||||
${namespace}.expect(items).to.have.lengthOf.at.most(10)
|
||||
${namespace}.expect(items).to.have.lengthOf.at.most(4)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
lengthOfMockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "lengthOf.at.most",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Edge cases and special scenarios", () => {
|
||||
test("should work with empty arrays", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Empty array", () => {
|
||||
const empty = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.emptyArray
|
||||
${namespace}.expect(empty).to.have.length.at.least(0)
|
||||
${namespace}.expect(empty).to.have.length.at.most(0)
|
||||
${namespace}.expect(empty).to.have.length(0)
|
||||
${namespace}.expect(empty).to.have.lengthOf(0)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Empty array",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with strings", async () => {
|
||||
const script = `
|
||||
${namespace}.test("String length", () => {
|
||||
const str = "hello world"
|
||||
${namespace}.expect(str).to.have.length.at.least(5)
|
||||
${namespace}.expect(str).to.have.length.at.most(20)
|
||||
${namespace}.expect(str).to.have.length(11)
|
||||
${namespace}.expect(str).to.have.lengthOf(11)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(script, { global: [], selected: [] })()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "String length",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should work with nested arrays", async () => {
|
||||
const script = `
|
||||
${namespace}.test("Nested array length", () => {
|
||||
const nested = ${namespace === "pm" ? "pm.response.json()" : "hopp.response.body.asJSON()"}.data.nested.values
|
||||
${namespace}.expect(nested).to.have.length.at.least(5)
|
||||
${namespace}.expect(nested).to.have.lengthOf(5)
|
||||
})
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
script,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Nested array length",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,426 @@
|
|||
/**
|
||||
* @see https://github.com/hoppscotch/hoppscotch/discussions/5221
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const NAMESPACES = ["pm", "hopp"] as const
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - Side Effect Assertions (Standard Pattern)",
|
||||
(namespace) => {
|
||||
describe("`.change()` with object and property", () => {
|
||||
test("should detect property changes", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change assertions work", () => {
|
||||
const obj = { val: 0 }
|
||||
${namespace}.expect(() => { obj.val = 5 }).to.change(obj, 'val')
|
||||
${namespace}.expect(() => { obj.val = 5 }).to.not.change(obj, 'val')
|
||||
${namespace}.expect(() => {}).to.not.change(obj, 'val')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change assertions work",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should detect changes by specific delta using `.by()`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change by delta works", () => {
|
||||
const obj = { val: 0 }
|
||||
${namespace}.expect(() => { obj.val += 5 }).to.change(obj, 'val').by(5)
|
||||
${namespace}.expect(() => { obj.val -= 3 }).to.change(obj, 'val').by(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change by delta works",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support negative delta", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change with negative delta", () => {
|
||||
const obj = { value: 50 }
|
||||
const decreaseValue = () => { obj.value = 30 }
|
||||
${namespace}.expect(decreaseValue).to.change(obj, "value").by(-20)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change with negative delta",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`.increase()` with object and property", () => {
|
||||
test("should detect property increases", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("increase assertions work", () => {
|
||||
const obj = { count: 0 }
|
||||
${namespace}.expect(() => { obj.count++ }).to.increase(obj, 'count')
|
||||
${namespace}.expect(() => { obj.count += 5 }).to.increase(obj, 'count')
|
||||
${namespace}.expect(() => { obj.count-- }).to.not.increase(obj, 'count')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "increase assertions work",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should detect increases by specific amount using `.by()`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("increase by amount works", () => {
|
||||
const obj = { count: 0 }
|
||||
${namespace}.expect(() => { obj.count += 3 }).to.increase(obj, 'count').by(3)
|
||||
${namespace}.expect(() => { obj.count += 7 }).to.increase(obj, 'count').by(7)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "increase by amount works",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`.decrease()` with object and property", () => {
|
||||
test("should detect property decreases", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("decrease assertions work", () => {
|
||||
const obj = { count: 10 }
|
||||
${namespace}.expect(() => { obj.count-- }).to.decrease(obj, 'count')
|
||||
${namespace}.expect(() => { obj.count -= 3 }).to.decrease(obj, 'count')
|
||||
${namespace}.expect(() => { obj.count++ }).to.not.decrease(obj, 'count')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "decrease assertions work",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should detect decreases by specific amount using `.by()`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("decrease by amount works", () => {
|
||||
const obj = { count: 10 }
|
||||
${namespace}.expect(() => { obj.count -= 2 }).to.decrease(obj, 'count').by(2)
|
||||
${namespace}.expect(() => { obj.count -= 4 }).to.decrease(obj, 'count').by(4)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "decrease by amount works",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
describe.each(NAMESPACES)(
|
||||
"%s.expect() - Side Effect Assertions (Getter Function Pattern)",
|
||||
(namespace) => {
|
||||
describe("`.change()` with getter function", () => {
|
||||
test("should detect when getter value changes", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change with getter function", () => {
|
||||
let value = 0
|
||||
const changeFn = () => { value = 1 }
|
||||
${namespace}.expect(changeFn).to.change(() => value)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should pass with negation when value does not change", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change negated when no change", () => {
|
||||
let value = 0
|
||||
const noChangeFn = () => { value = 0 }
|
||||
${namespace}.expect(noChangeFn).to.not.change(() => value)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change negated when no change",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.by()` chaining with getter", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("change by with getter function", () => {
|
||||
let value = 5
|
||||
const addFive = () => { value += 5 }
|
||||
${namespace}.expect(addFive).to.change(() => value).by(5)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "change by with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`.increase()` with getter function", () => {
|
||||
test("should detect when getter value increases", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("increase with getter function", () => {
|
||||
let counter = 0
|
||||
const incrementFn = () => { counter++ }
|
||||
${namespace}.expect(incrementFn).to.increase(() => counter)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "increase with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should pass with negation when value does not increase", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("increase negated when no increase", () => {
|
||||
let counter = 5
|
||||
const noIncreaseFn = () => { counter-- }
|
||||
${namespace}.expect(noIncreaseFn).to.not.increase(() => counter)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "increase negated when no increase",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.by()` chaining with getter", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("increase by with getter function", () => {
|
||||
let value = 5
|
||||
const addFive = () => { value += 5 }
|
||||
${namespace}.expect(addFive).to.increase(() => value).by(5)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "increase by with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`.decrease()` with getter function", () => {
|
||||
test("should detect when getter value decreases", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("decrease with getter function", () => {
|
||||
let counter = 10
|
||||
const decrementFn = () => { counter-- }
|
||||
${namespace}.expect(decrementFn).to.decrease(() => counter)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "decrease with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should pass with negation when value does not decrease", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("decrease negated when no decrease", () => {
|
||||
let counter = 5
|
||||
const noDecreaseFn = () => { counter++ }
|
||||
${namespace}.expect(noDecreaseFn).to.not.decrease(() => counter)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "decrease negated when no decrease",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.by()` chaining with getter", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
${namespace}.test("decrease by with getter function", () => {
|
||||
let value = 10
|
||||
const subtractThree = () => { value -= 3 }
|
||||
${namespace}.expect(subtractThree).to.decrease(() => value).by(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "decrease by with getter function",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,616 @@
|
|||
/**
|
||||
* @see https://github.com/hoppscotch/hoppscotch/discussions/5221
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("`hopp.expect` - Core Chai Assertions", () => {
|
||||
describe("Language Chains", () => {
|
||||
test("should support all language chain properties (`to`, `be`, `that`, `and`, `has`, etc.)", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("language chains work", () => {
|
||||
hopp.expect(2).to.equal(2)
|
||||
hopp.expect(2).to.be.equal(2)
|
||||
hopp.expect(2).to.be.a('number').that.equals(2)
|
||||
hopp.expect([1,2,3]).to.be.an('array').that.has.lengthOf(3)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "language chains work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 2 to equal 2" },
|
||||
{ status: "pass", message: "Expected 2 to be equal 2" },
|
||||
{ status: "pass", message: "Expected 2 to be a number" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 2 to be a number that equals 2",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to be an array",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
"Expected [1, 2, 3] to be an array that has lengthOf 3",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support multiple modifier combinations with language chains", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("complex chains work", () => {
|
||||
hopp.expect([1,2,3]).to.be.an('array')
|
||||
hopp.expect([1,2,3]).to.have.lengthOf(3)
|
||||
hopp.expect([1,2,3]).to.include(2)
|
||||
hopp.expect({a: 1, b: 2}).to.be.an('object')
|
||||
hopp.expect({a: 1, b: 2}).to.have.property('a')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "complex chains work",
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: expect.stringMatching(/array/) },
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/lengthOf 3/),
|
||||
},
|
||||
{ status: "pass", message: expect.stringMatching(/include 2/) },
|
||||
{ status: "pass", message: expect.stringMatching(/object/) },
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/property 'a'/),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Type Assertions", () => {
|
||||
test("should assert primitive types correctly (`.a()`, `.an()`)", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("type assertions work", () => {
|
||||
hopp.expect('foo').to.be.a('string')
|
||||
hopp.expect({a: 1}).to.be.an('object')
|
||||
hopp.expect([1, 2, 3]).to.be.an('array')
|
||||
hopp.expect(null).to.be.a('null')
|
||||
hopp.expect(undefined).to.be.an('undefined')
|
||||
hopp.expect(42).to.be.a('number')
|
||||
hopp.expect(true).to.be.a('boolean')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "type assertions work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'foo' to be a string" },
|
||||
{ status: "pass", message: "Expected {a: 1} to be an object" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to be an array",
|
||||
},
|
||||
{ status: "pass", message: "Expected null to be a null" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected undefined to be an undefined",
|
||||
},
|
||||
{ status: "pass", message: "Expected 42 to be a number" },
|
||||
{ status: "pass", message: "Expected true to be a boolean" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should assert Symbol and BigInt types", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("modern type assertions work", () => {
|
||||
hopp.expect(Symbol('test')).to.be.a('symbol')
|
||||
hopp.expect(Symbol.for('shared')).to.be.a('symbol')
|
||||
hopp.expect(BigInt(123)).to.be.a('bigint')
|
||||
hopp.expect(BigInt('999999999999999999')).to.be.a('bigint')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "modern type assertions work",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(
|
||||
/Expected Symbol\(test\) to be a symbol/
|
||||
),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(
|
||||
/Expected Symbol\(shared\) to be a symbol/
|
||||
),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 123n to be a bigint",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 999999999999999999n to be a bigint",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Equality Assertions", () => {
|
||||
test("should support `.equal()`, `.equals()`, `.eq()` for strict equality", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("equality works", () => {
|
||||
hopp.expect(42).to.equal(42)
|
||||
hopp.expect('test').to.equals('test')
|
||||
hopp.expect(true).to.eq(true)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "equality works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 42 to equal 42" },
|
||||
{ status: "pass", message: "Expected 'test' to equals 'test'" },
|
||||
{ status: "pass", message: "Expected true to eq true" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.eql()` for deep equality", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("deep equality works", () => {
|
||||
hopp.expect({a: 1}).to.eql({a: 1})
|
||||
hopp.expect([1, 2, 3]).to.eql([1, 2, 3])
|
||||
hopp.expect({a: {b: 2}}).to.eql({a: {b: 2}})
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "deep equality works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: 1} to eql {a: 1}",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected [1, 2, 3] to eql [1, 2, 3]",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected {a: {b: 2}} to eql {a: {b: 2}}",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Truthiness Assertions", () => {
|
||||
test("should support `.true`, `.false`, `.ok` assertions", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("truthiness assertions work", () => {
|
||||
hopp.expect(true).to.be.true
|
||||
hopp.expect(false).to.be.false
|
||||
hopp.expect(1).to.be.ok
|
||||
hopp.expect('hello').to.be.ok
|
||||
hopp.expect({}).to.be.ok
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "truthiness assertions work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected true to be true" },
|
||||
{ status: "pass", message: "Expected false to be false" },
|
||||
{ status: "pass", message: "Expected 1 to be ok" },
|
||||
{ status: "pass", message: "Expected 'hello' to be ok" },
|
||||
{ status: "pass", message: "Expected {} to be ok" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Nullish Assertions", () => {
|
||||
test("should support `.null`, `.undefined`, `.NaN` assertions", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("nullish assertions work", () => {
|
||||
hopp.expect(null).to.be.null
|
||||
hopp.expect(undefined).to.be.undefined
|
||||
hopp.expect(NaN).to.be.NaN
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "nullish assertions work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected null to be null" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected undefined to be undefined",
|
||||
},
|
||||
{ status: "pass", message: "Expected NaN to be NaN" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.exist` assertion", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("exist assertion works", () => {
|
||||
hopp.expect(0).to.exist
|
||||
hopp.expect('').to.exist
|
||||
hopp.expect(false).to.exist
|
||||
hopp.expect({}).to.exist
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "exist assertion works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 0 to exist" },
|
||||
{ status: "pass", message: "Expected '' to exist" },
|
||||
{ status: "pass", message: "Expected false to exist" },
|
||||
{ status: "pass", message: "Expected {} to exist" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Numerical Comparisons", () => {
|
||||
test("should support `.above()` and `.below()` comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("numerical comparisons work", () => {
|
||||
hopp.expect(10).to.be.above(5)
|
||||
hopp.expect(5).to.be.below(10)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "numerical comparisons work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 10 to be above 5" },
|
||||
{ status: "pass", message: "Expected 5 to be below 10" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support comparison aliases (`.gt()`, `.gte()`, `.lt()`, `.lte()`)", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("comparison aliases work", () => {
|
||||
hopp.expect(10).to.be.gt(5)
|
||||
hopp.expect(10).to.be.gte(10)
|
||||
hopp.expect(5).to.be.lt(10)
|
||||
hopp.expect(5).to.be.lte(5)
|
||||
hopp.expect(10).to.be.greaterThan(5)
|
||||
hopp.expect(10).to.be.greaterThanOrEqual(10)
|
||||
hopp.expect(5).to.be.lessThan(10)
|
||||
hopp.expect(5).to.be.lessThanOrEqual(5)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "comparison aliases work",
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: expect.stringMatching(/above|gt/) },
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/above|gte|at least/),
|
||||
},
|
||||
{ status: "pass", message: expect.stringMatching(/below|lt/) },
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/below|lte|at most/),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.within()` for range comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("within range works", () => {
|
||||
hopp.expect(5).to.be.within(1, 10)
|
||||
hopp.expect(7).to.be.within(7, 7)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "within range works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 5 to be within 1, 10" },
|
||||
{ status: "pass", message: "Expected 7 to be within 7, 7" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.closeTo()` and `.approximately()` for floating point comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("close to works", () => {
|
||||
hopp.expect(1.5).to.be.closeTo(1.0, 0.6)
|
||||
hopp.expect(1.5).to.be.approximately(1.0, 0.6)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "close to works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 1.5 to be closeTo 1, 0.6",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 1.5 to be approximately 1, 0.6",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Special Value Assertions", () => {
|
||||
test("should support `.empty` assertion for various types", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("empty assertion works", () => {
|
||||
hopp.expect('').to.be.empty
|
||||
hopp.expect([]).to.be.empty
|
||||
hopp.expect({}).to.be.empty
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "empty assertion works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected '' to be empty" },
|
||||
{ status: "pass", message: "Expected [] to be empty" },
|
||||
{ status: "pass", message: "Expected {} to be empty" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.finite` assertion for numbers", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("finite assertion works", () => {
|
||||
hopp.expect(42).to.be.finite
|
||||
hopp.expect(0).to.be.finite
|
||||
hopp.expect(-100.5).to.be.finite
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "finite assertion works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 42 to be finite" },
|
||||
{ status: "pass", message: "Expected 0 to be finite" },
|
||||
{ status: "pass", message: "Expected -100.5 to be finite" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should detect Infinity and reject `.finite`", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("infinity is not finite", () => {
|
||||
hopp.expect(Infinity).to.not.be.finite
|
||||
hopp.expect(-Infinity).to.not.be.finite
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "infinity is not finite",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected Infinity to not be finite",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected -Infinity to not be finite",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Negation with `.not`", () => {
|
||||
test("should support negation for all assertion types", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("negation works", () => {
|
||||
hopp.expect(1).to.not.equal(2)
|
||||
hopp.expect('foo').to.not.be.a('number')
|
||||
hopp.expect(false).to.not.be.true
|
||||
hopp.expect('foo').to.not.be.empty
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
{
|
||||
descriptor: "root",
|
||||
expectResults: [],
|
||||
children: [
|
||||
{
|
||||
descriptor: "negation works",
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 1 to not equal 2" },
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'foo' to not be a number",
|
||||
},
|
||||
{ status: "pass", message: "Expected false to not be true" },
|
||||
{ status: "pass", message: "Expected 'foo' to not be empty" },
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Boundary Value Testing", () => {
|
||||
test("should handle boundary values correctly in comparisons", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("boundary values work", () => {
|
||||
hopp.expect(Number.MAX_SAFE_INTEGER).to.be.a('number')
|
||||
hopp.expect(Number.MIN_SAFE_INTEGER).to.be.a('number')
|
||||
hopp.expect(Number.EPSILON).to.be.above(0)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "boundary values work",
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: expect.stringMatching(/number/) },
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Failure Cases", () => {
|
||||
test("should produce meaningful error messages on assertion failures", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("failures have good messages", () => {
|
||||
hopp.expect(1).to.equal(2)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "failures have good messages",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("Expected 1 to equal 2"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,333 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.expect - String and Regex Methods", () => {
|
||||
describe("String Inclusion (.string())", () => {
|
||||
test("should support .string() for substring inclusion", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("string inclusion works", () => {
|
||||
hopp.expect('hello world').to.have.string('world')
|
||||
hopp.expect('foobar').to.have.string('foo')
|
||||
hopp.expect('foobar').to.have.string('bar')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "string inclusion works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to have string"),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to have string"),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to have string"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support .string() negation", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("string negation works", () => {
|
||||
hopp.expect('hello').to.not.have.string('goodbye')
|
||||
hopp.expect('foo').to.not.have.string('bar')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "string negation works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to not have string"),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to not have string"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail on missing substring", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("string assertion fails correctly", () => {
|
||||
hopp.expect('hello').to.have.string('goodbye')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "string assertion fails correctly",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("to have string"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should work with empty strings", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("empty string edge case", () => {
|
||||
hopp.expect('hello').to.have.string('')
|
||||
hopp.expect('').to.have.string('')
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "empty string edge case",
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Regex Matching (.match())", () => {
|
||||
test("should support .match() with regex patterns", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("regex matching works", () => {
|
||||
hopp.expect('hello123').to.match(/\\d+/)
|
||||
hopp.expect('test@example.com').to.match(/^[^@]+@[^@]+\\.[^@]+$/)
|
||||
hopp.expect('ABC').to.match(/[A-Z]+/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "regex matching works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to match/),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to match/),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to match/),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support .match() negation", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("regex negation works", () => {
|
||||
hopp.expect('hello').to.not.match(/\\d+/)
|
||||
hopp.expect('abc').to.not.match(/[A-Z]+/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "regex negation works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to not match/),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to not match/),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support .matches() alias", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("matches alias works", () => {
|
||||
hopp.expect('abc123').to.matches(/[a-z]+\\d+/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "matches alias works",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringMatching(/to match/),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail on non-matching regex", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("regex assertion fails correctly", () => {
|
||||
hopp.expect('hello').to.match(/\\d+/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "regex assertion fails correctly",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringMatching(/to match/),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle regex with flags", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("regex flags work", () => {
|
||||
hopp.expect('HELLO').to.match(/hello/i)
|
||||
hopp.expect('hello\\nworld').to.match(/hello.world/s)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "regex flags work",
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle complex regex patterns", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("complex regex patterns", () => {
|
||||
hopp.expect('user@example.com').to.match(/^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$/i)
|
||||
hopp.expect('192.168.1.1').to.match(/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/)
|
||||
hopp.expect('+1-555-123-4567').to.match(/^\\+?\\d{1,3}-?\\d{3}-\\d{3}-\\d{4}$/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "complex regex patterns",
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Combined String and Regex Tests", () => {
|
||||
test("should work with both string and regex in same test", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("combined assertions", () => {
|
||||
const email = 'test@example.com'
|
||||
hopp.expect(email).to.have.string('@')
|
||||
hopp.expect(email).to.match(/^[^@]+@[^@]+$/)
|
||||
hopp.expect(email).to.have.string('example')
|
||||
hopp.expect(email).to.match(/\\.com$/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "combined assertions",
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should chain with other assertions", () => {
|
||||
return expect(
|
||||
runTest(`
|
||||
hopp.test("chained assertions", () => {
|
||||
hopp.expect('hello world').to.be.a('string').and.have.string('world')
|
||||
hopp.expect('test123').to.be.a('string').and.match(/\\d+/)
|
||||
})
|
||||
`)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "chained assertions",
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,41 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTestAndGetEnvs, runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.delete", () => {
|
||||
test("removes variable from selected environment", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, {
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
|
|
@ -50,7 +19,7 @@ describe("hopp.env.delete", () => {
|
|||
|
||||
test("removes variable from global environment", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, {
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [
|
||||
{
|
||||
key: "baseUrl",
|
||||
|
|
@ -65,7 +34,7 @@ describe("hopp.env.delete", () => {
|
|||
|
||||
test("removes only from selected if present in both", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, {
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [
|
||||
{
|
||||
key: "baseUrl",
|
||||
|
|
@ -99,7 +68,7 @@ describe("hopp.env.delete", () => {
|
|||
|
||||
test("removes only first matching entry if duplicates exist in selected", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, {
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [
|
||||
{
|
||||
key: "baseUrl",
|
||||
|
|
@ -146,7 +115,7 @@ describe("hopp.env.delete", () => {
|
|||
|
||||
test("removes only first matching entry if duplicates exist in global", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, {
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [
|
||||
{
|
||||
key: "baseUrl",
|
||||
|
|
@ -179,19 +148,22 @@ describe("hopp.env.delete", () => {
|
|||
|
||||
test("no change if attempting to delete non-existent key", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete("baseUrl")`, { global: [], selected: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.delete("baseUrl")`, {
|
||||
global: [],
|
||||
selected: [],
|
||||
})()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({ global: [], selected: [] })
|
||||
))
|
||||
|
||||
test("key must be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.delete(5)`, { global: [], selected: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.delete(5)`, { global: [], selected: [] })()
|
||||
).resolves.toBeLeft())
|
||||
|
||||
test("reflected in script execution", () =>
|
||||
expect(
|
||||
funcTest(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.delete("baseUrl")
|
||||
hopp.expect(hopp.env.get("baseUrl")).toBe(null)
|
||||
|
|
@ -220,7 +192,7 @@ describe("hopp.env.delete", () => {
|
|||
describe("hopp.env.active.delete", () => {
|
||||
test("removes variable from selected environment", () =>
|
||||
expect(
|
||||
func(`hopp.env.active.delete("foo")`, {
|
||||
runTestAndGetEnvs(`hopp.env.active.delete("foo")`, {
|
||||
selected: [
|
||||
{
|
||||
key: "foo",
|
||||
|
|
@ -254,7 +226,7 @@ describe("hopp.env.active.delete", () => {
|
|||
|
||||
test("no effect if not present in selected", () =>
|
||||
expect(
|
||||
func(`hopp.env.active.delete("nope")`, {
|
||||
runTestAndGetEnvs(`hopp.env.active.delete("nope")`, {
|
||||
selected: [],
|
||||
global: [
|
||||
{
|
||||
|
|
@ -281,14 +253,17 @@ describe("hopp.env.active.delete", () => {
|
|||
|
||||
test("key must be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.active.delete({})`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.active.delete({})`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft())
|
||||
})
|
||||
|
||||
describe("hopp.env.global.delete", () => {
|
||||
test("removes variable from global environment", () =>
|
||||
expect(
|
||||
func(`hopp.env.global.delete("foo")`, {
|
||||
runTestAndGetEnvs(`hopp.env.global.delete("foo")`, {
|
||||
selected: [
|
||||
{
|
||||
key: "foo",
|
||||
|
|
@ -322,7 +297,7 @@ describe("hopp.env.global.delete", () => {
|
|||
|
||||
test("no effect if not present in global", () =>
|
||||
expect(
|
||||
func(`hopp.env.global.delete("missing")`, {
|
||||
runTestAndGetEnvs(`hopp.env.global.delete("missing")`, {
|
||||
selected: [
|
||||
{
|
||||
key: "missing",
|
||||
|
|
@ -349,6 +324,9 @@ describe("hopp.env.global.delete", () => {
|
|||
|
||||
test("key must be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.global.delete([])`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.global.delete([])`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,31 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.get", () => {
|
||||
test("returns the correct value for an existing selected environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get("a")
|
||||
hopp.expect(data).toBe("b")
|
||||
|
|
@ -51,7 +30,7 @@ describe("hopp.env.get", () => {
|
|||
|
||||
test("returns the correct value for an existing global environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get("a")
|
||||
hopp.expect(data).toBe("b")
|
||||
|
|
@ -77,7 +56,7 @@ describe("hopp.env.get", () => {
|
|||
|
||||
test("returns null for a key that is not present in both selected or global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get("a")
|
||||
hopp.expect(data).toBe(null)
|
||||
|
|
@ -98,7 +77,7 @@ describe("hopp.env.get", () => {
|
|||
|
||||
test("returns the value defined in selected environment if also present in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get("a")
|
||||
hopp.expect(data).toBe("selected val")
|
||||
|
|
@ -136,7 +115,7 @@ describe("hopp.env.get", () => {
|
|||
|
||||
test("resolves environment values recursively by default", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get("a")
|
||||
hopp.expect(data).toBe("hello")
|
||||
|
|
@ -170,7 +149,7 @@ describe("hopp.env.get", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.get(5)
|
||||
`,
|
||||
|
|
@ -186,7 +165,7 @@ describe("hopp.env.get", () => {
|
|||
describe("hopp.env.active.get", () => {
|
||||
test("returns the value from selected environment if present", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.active.get("a")
|
||||
hopp.expect(data).toBe("selectedVal")
|
||||
|
|
@ -224,7 +203,7 @@ describe("hopp.env.active.get", () => {
|
|||
|
||||
test("returns null if key does not exist in selected", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.active.get("absent")
|
||||
hopp.expect(data).toBe(null)
|
||||
|
|
@ -252,7 +231,7 @@ describe("hopp.env.active.get", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.get({})
|
||||
`,
|
||||
|
|
@ -265,7 +244,7 @@ describe("hopp.env.active.get", () => {
|
|||
describe("hopp.env.global.get", () => {
|
||||
test("returns the value from global environment if present", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.global.get("foo")
|
||||
hopp.expect(data).toBe("globalVal")
|
||||
|
|
@ -300,7 +279,7 @@ describe("hopp.env.global.get", () => {
|
|||
|
||||
test("returns null if key does not exist in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.global.get("not_here")
|
||||
hopp.expect(data).toBe(null)
|
||||
|
|
@ -328,7 +307,7 @@ describe("hopp.env.global.get", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.get([])
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.getInitialRaw", () => {
|
||||
test("returns initial value for existing selected env variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("foo")
|
||||
hopp.expect(val).toBe("bar")
|
||||
|
|
@ -54,7 +32,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("returns initial value from global if not in selected", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("foo")
|
||||
hopp.expect(val).toBe("bar")
|
||||
|
|
@ -82,7 +60,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("selected shadows global when both present", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("foo")
|
||||
hopp.expect(val).toBe("selVal")
|
||||
|
|
@ -117,7 +95,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("returns null for missing key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("notFound")
|
||||
hopp.expect(val).toBe(null)
|
||||
|
|
@ -135,7 +113,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("returns empty string if initial value was empty", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("empty")
|
||||
hopp.expect(val).toBe("")
|
||||
|
|
@ -156,7 +134,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("returns literal template syntax, no resolution", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.getInitialRaw("templ")
|
||||
hopp.expect(val).toBe("<<FOO>>")
|
||||
|
|
@ -190,7 +168,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.getInitialRaw(5)
|
||||
`,
|
||||
|
|
@ -203,7 +181,7 @@ describe("hopp.env.getInitialRaw", () => {
|
|||
describe("hopp.env.active.getInitialRaw", () => {
|
||||
test("returns initial value if present in selected env", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.active.getInitialRaw("alpha")
|
||||
hopp.expect(val).toBe("a_value")
|
||||
|
|
@ -238,7 +216,7 @@ describe("hopp.env.active.getInitialRaw", () => {
|
|||
|
||||
test("returns null if not present in selected env", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.active.getInitialRaw("missing")
|
||||
hopp.expect(val).toBe(null)
|
||||
|
|
@ -266,7 +244,7 @@ describe("hopp.env.active.getInitialRaw", () => {
|
|||
|
||||
test("returns '' if initial value was empty string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.active.getInitialRaw("blank")
|
||||
hopp.expect(val).toBe("")
|
||||
|
|
@ -287,7 +265,7 @@ describe("hopp.env.active.getInitialRaw", () => {
|
|||
|
||||
test("returns literal template if present", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.active.getInitialRaw("tmpl")
|
||||
hopp.expect(val).toBe("<<BAR>>")
|
||||
|
|
@ -321,7 +299,7 @@ describe("hopp.env.active.getInitialRaw", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.getInitialRaw({})
|
||||
`,
|
||||
|
|
@ -334,7 +312,7 @@ describe("hopp.env.active.getInitialRaw", () => {
|
|||
describe("hopp.env.global.getInitialRaw", () => {
|
||||
test("returns initial value if present in global env", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.global.getInitialRaw("gamma")
|
||||
hopp.expect(val).toBe("g_val")
|
||||
|
|
@ -369,7 +347,7 @@ describe("hopp.env.global.getInitialRaw", () => {
|
|||
|
||||
test("returns null if not present in global env", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.global.getInitialRaw("none")
|
||||
hopp.expect(val).toBe(null)
|
||||
|
|
@ -397,7 +375,7 @@ describe("hopp.env.global.getInitialRaw", () => {
|
|||
|
||||
test("returns '' if initial value was empty string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.global.getInitialRaw("empty")
|
||||
hopp.expect(val).toBe("")
|
||||
|
|
@ -418,7 +396,7 @@ describe("hopp.env.global.getInitialRaw", () => {
|
|||
|
||||
test("returns literal template value if present", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const val = hopp.env.global.getInitialRaw("tmpl")
|
||||
hopp.expect(val).toBe("<<ZED>>")
|
||||
|
|
@ -452,7 +430,7 @@ describe("hopp.env.global.getInitialRaw", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.getInitialRaw([])
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.getRaw", () => {
|
||||
test("returns the correct value for an existing selected environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe("b")
|
||||
|
|
@ -47,7 +25,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("returns the correct value for an existing global environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe("b")
|
||||
|
|
@ -68,7 +46,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("returns null for a key that is not present in both selected and global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe(null)
|
||||
|
|
@ -89,7 +67,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("returns the value defined in selected if also present in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe("selected val")
|
||||
|
|
@ -127,7 +105,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("does not resolve values recursively", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe("<<hello>>")
|
||||
|
|
@ -161,7 +139,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("returns the value as is even if there is a potential recursion", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw("a")
|
||||
hopp.expect(data).toBe("<<hello>>")
|
||||
|
|
@ -195,7 +173,7 @@ describe("hopp.env.getRaw", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = hopp.env.getRaw(5)
|
||||
`,
|
||||
|
|
@ -208,7 +186,7 @@ describe("hopp.env.getRaw", () => {
|
|||
describe("hopp.env.active.getRaw", () => {
|
||||
test("returns only from selected", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.expect(hopp.env.active.getRaw("a")).toBe("a-selected")
|
||||
hopp.expect(hopp.env.active.getRaw("b")).toBe(null)
|
||||
|
|
@ -247,7 +225,7 @@ describe("hopp.env.active.getRaw", () => {
|
|||
|
||||
test("returns null if key absent in selected", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.expect(hopp.env.active.getRaw("missing")).toBe(null)
|
||||
`,
|
||||
|
|
@ -274,7 +252,7 @@ describe("hopp.env.active.getRaw", () => {
|
|||
|
||||
test("errors if key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.getRaw({})
|
||||
`,
|
||||
|
|
@ -287,7 +265,7 @@ describe("hopp.env.active.getRaw", () => {
|
|||
describe("hopp.env.global.getRaw", () => {
|
||||
test("returns only from global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.expect(hopp.env.global.getRaw("b")).toBe("b-global")
|
||||
hopp.expect(hopp.env.global.getRaw("a")).toBe(null)
|
||||
|
|
@ -323,7 +301,7 @@ describe("hopp.env.global.getRaw", () => {
|
|||
|
||||
test("returns null if key absent in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.expect(hopp.env.global.getRaw("missing")).toBe(null)
|
||||
`,
|
||||
|
|
@ -350,7 +328,7 @@ describe("hopp.env.global.getRaw", () => {
|
|||
|
||||
test("errors if key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.getRaw([])
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,42 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTestAndGetEnvs, runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.reset", () => {
|
||||
test("resets selected variable to its initial value", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.set("foo", "changed")
|
||||
hopp.env.reset("foo")
|
||||
|
|
@ -68,7 +36,7 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("resets global variable to its initial value if not in selected", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.set("bar", "override")
|
||||
hopp.env.reset("bar")
|
||||
|
|
@ -100,7 +68,7 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("if variable exists in both, only selected variable is reset", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.set("a", "S")
|
||||
hopp.env.global.set("a", "G")
|
||||
|
|
@ -148,7 +116,7 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("resets only the first occurrence if duplicates exist in selected", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.set("dup", "X")
|
||||
hopp.env.reset("dup")
|
||||
|
|
@ -172,7 +140,7 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("resets only the first occurrence if duplicates exist in global", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.global.set("gdup", "Y")
|
||||
hopp.env.reset("gdup")
|
||||
|
|
@ -216,19 +184,22 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("no change if attempting to reset a non-existent key", () =>
|
||||
expect(
|
||||
func(`hopp.env.reset("none")`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.reset("none")`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({ selected: [], global: [] })
|
||||
))
|
||||
|
||||
test("keys should be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.reset(123)`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.reset(123)`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft())
|
||||
|
||||
test("reset reflected in subsequent get in the same script (selected)", () =>
|
||||
expect(
|
||||
funcTest(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.set("foo", "override")
|
||||
hopp.env.reset("foo")
|
||||
|
|
@ -256,7 +227,7 @@ describe("hopp.env.reset", () => {
|
|||
|
||||
test("reset works for secret variables", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.set("secret", "newVal")
|
||||
hopp.env.reset("secret")
|
||||
|
|
@ -290,7 +261,7 @@ describe("hopp.env.reset", () => {
|
|||
describe("hopp.env.active.reset", () => {
|
||||
test("resets variable only in selected environment", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.active.set("xxx", "MUT")
|
||||
hopp.env.active.reset("xxx")
|
||||
|
|
@ -317,7 +288,7 @@ describe("hopp.env.active.reset", () => {
|
|||
|
||||
test("no effect if key not in selected", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.active.reset("nonexistent")
|
||||
`,
|
||||
|
|
@ -349,14 +320,17 @@ describe("hopp.env.active.reset", () => {
|
|||
|
||||
test("key must be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.active.reset(123)`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.active.reset(123)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft())
|
||||
})
|
||||
|
||||
describe("hopp.env.global.reset", () => {
|
||||
test("resets variable only in global environment", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.global.set("yyy", "GGG")
|
||||
hopp.env.global.reset("yyy")
|
||||
|
|
@ -388,7 +362,7 @@ describe("hopp.env.global.reset", () => {
|
|||
|
||||
test("no effect if key not in global", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.global.reset("nonexistent")
|
||||
`,
|
||||
|
|
@ -422,7 +396,7 @@ describe("hopp.env.global.reset", () => {
|
|||
describe("hopp.env.reset - regression cases", () => {
|
||||
test("create via setInitial then set, and reset restores to initial (selected)", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
// Variable does not exist initially
|
||||
hopp.env.setInitial("AUTH_TOKEN", "seeded-v1")
|
||||
|
|
@ -450,7 +424,7 @@ describe("hopp.env.global.reset", () => {
|
|||
|
||||
test("scope flip: remove from global, create in active, reset only affects active and not deleted global", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
// Start by ensuring global is cleared
|
||||
hopp.env.global.delete("API_KEY")
|
||||
|
|
@ -490,7 +464,7 @@ describe("hopp.env.global.reset", () => {
|
|||
|
||||
test("delete then reset within same script should be a no-op (selected)", () =>
|
||||
expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
hopp.env.active.delete("SESSION_ID")
|
||||
// Reset after unset should not reintroduce or change anything
|
||||
|
|
@ -517,6 +491,9 @@ describe("hopp.env.global.reset", () => {
|
|||
})
|
||||
test("key must be a string", () =>
|
||||
expect(
|
||||
func(`hopp.env.global.reset([])`, { selected: [], global: [] })()
|
||||
runTestAndGetEnvs(`hopp.env.global.reset([])`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("hopp.env.setInitial", () => {
|
||||
test("sets initial value in selected env when key doesn't exist", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("newKey", "newValue")
|
||||
const val = hopp.env.getInitialRaw("newKey")
|
||||
|
|
@ -48,7 +26,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("updates initial value in selected env when key exists", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("existing", "updated")
|
||||
const val = hopp.env.getInitialRaw("existing")
|
||||
|
|
@ -77,7 +55,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("updates selected env when key exists in both selected and global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("shared", "selectedUpdate")
|
||||
const val = hopp.env.getInitialRaw("shared")
|
||||
|
|
@ -116,7 +94,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("sets initial value in global env when only exists in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("globalOnly", "globalUpdate")
|
||||
const val = hopp.env.getInitialRaw("globalOnly")
|
||||
|
|
@ -148,7 +126,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("allows setting empty string as initial value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("empty", "")
|
||||
const val = hopp.env.getInitialRaw("empty")
|
||||
|
|
@ -168,7 +146,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("allows setting template syntax as initial value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("template", "<<FOO>>")
|
||||
const val = hopp.env.getInitialRaw("template")
|
||||
|
|
@ -190,7 +168,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial(123, "value")
|
||||
`,
|
||||
|
|
@ -201,7 +179,7 @@ describe("hopp.env.setInitial", () => {
|
|||
|
||||
test("errors for non-string value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.setInitial("key", 456)
|
||||
`,
|
||||
|
|
@ -214,7 +192,7 @@ describe("hopp.env.setInitial", () => {
|
|||
describe("hopp.env.active.setInitial", () => {
|
||||
test("sets initial value in selected env only", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial("activeKey", "activeValue")
|
||||
const activeVal = hopp.env.active.getInitialRaw("activeKey")
|
||||
|
|
@ -242,7 +220,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
|
||||
test("updates existing selected env variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial("existing", "updated")
|
||||
const val = hopp.env.active.getInitialRaw("existing")
|
||||
|
|
@ -271,7 +249,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
|
||||
test("does not affect global env even if key exists there", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial("shared", "activeUpdate")
|
||||
const activeVal = hopp.env.active.getInitialRaw("shared")
|
||||
|
|
@ -316,7 +294,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
|
||||
test("allows setting empty string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial("blank", "")
|
||||
const val = hopp.env.active.getInitialRaw("blank")
|
||||
|
|
@ -336,7 +314,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial(null, "value")
|
||||
`,
|
||||
|
|
@ -347,7 +325,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
|
||||
test("errors for non-string value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.active.setInitial("key", {})
|
||||
`,
|
||||
|
|
@ -360,7 +338,7 @@ describe("hopp.env.active.setInitial", () => {
|
|||
describe("hopp.env.global.setInitial", () => {
|
||||
test("sets initial value in global env only", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("globalKey", "globalValue")
|
||||
const globalVal = hopp.env.global.getInitialRaw("globalKey")
|
||||
|
|
@ -388,7 +366,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("updates existing global env variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("existing", "updated")
|
||||
const val = hopp.env.global.getInitialRaw("existing")
|
||||
|
|
@ -417,7 +395,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("does not affect selected env even if key exists there", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("shared", "globalUpdate")
|
||||
const globalVal = hopp.env.global.getInitialRaw("shared")
|
||||
|
|
@ -462,7 +440,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("allows setting empty string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("empty", "")
|
||||
const val = hopp.env.global.getInitialRaw("empty")
|
||||
|
|
@ -482,7 +460,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("allows setting template syntax", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("template", "<<BAR>>")
|
||||
const val = hopp.env.global.getInitialRaw("template")
|
||||
|
|
@ -504,7 +482,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("errors for non-string key", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial([], "value")
|
||||
`,
|
||||
|
|
@ -515,7 +493,7 @@ describe("hopp.env.global.setInitial", () => {
|
|||
|
||||
test("errors for non-string value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
hopp.env.global.setInitial("key", true)
|
||||
`,
|
||||
|
|
|
|||
120
packages/hoppscotch-js-sandbox/src/__tests__/hopp-namespace/env/undefined-rejection.spec.ts
vendored
Normal file
120
packages/hoppscotch-js-sandbox/src/__tests__/hopp-namespace/env/undefined-rejection.spec.ts
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
// Hopp namespace enforces strict string typing (unlike PM namespace)
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "test",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const execEnv = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
describe("hopp.env.set undefined rejection", () => {
|
||||
test("hopp.env.set rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", undefined)`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.active.set rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.active.set("key", undefined)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.global.set rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.global.set("key", undefined)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.setInitial rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.setInitial("key", undefined)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.active.setInitial rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.active.setInitial("key", undefined)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.global.setInitial rejects undefined value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.global.setInitial("key", undefined)`, {
|
||||
selected: [],
|
||||
global: [],
|
||||
})()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.set rejects null value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", null)`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.set rejects boolean value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", true)`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.set rejects object value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", {})`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.set rejects array value", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", [])`, { selected: [], global: [] })()
|
||||
).resolves.toBeLeft()
|
||||
})
|
||||
|
||||
test("hopp.env.set accepts string value (baseline)", () => {
|
||||
return expect(
|
||||
execEnv(`hopp.env.set("key", "value")`, { selected: [], global: [] })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
selected: [
|
||||
{
|
||||
key: "key",
|
||||
currentValue: "value",
|
||||
initialValue: "value",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -9,7 +9,7 @@ import { runPreRequestScript, runTestScript } from "~/web"
|
|||
import { TestResponse } from "~/types"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "15",
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://example.com/api",
|
||||
method: "GET",
|
||||
|
|
@ -143,7 +143,7 @@ describe("hopp.request", () => {
|
|||
)
|
||||
})
|
||||
|
||||
test("hopp.request.setMethod should update and uppercase the method", () => {
|
||||
test("hopp.request.setMethod should update the method (case preserved)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(`hopp.request.setMethod("post")`, {
|
||||
envs: { global: [], selected: [] },
|
||||
|
|
@ -152,7 +152,7 @@ describe("hopp.request", () => {
|
|||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
method: "POST",
|
||||
method: "post",
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
|
@ -722,4 +722,295 @@ describe("hopp.request", () => {
|
|||
).resolves.toEqualLeft(expect.stringContaining("read-only"))
|
||||
})
|
||||
})
|
||||
|
||||
describe("setter methods immediately reflect in console.log", () => {
|
||||
test("setUrl should reflect immediately in hopp.request.url", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.url)
|
||||
hopp.request.setUrl("https://updated.com/api")
|
||||
console.log("After:", hopp.request.url)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", "https://example.com/api"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", "https://updated.com/api"],
|
||||
}),
|
||||
],
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://updated.com/api",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setMethod should reflect immediately in hopp.request.method", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.method)
|
||||
hopp.request.setMethod("POST")
|
||||
console.log("After:", hopp.request.method)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", "GET"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", "POST"],
|
||||
}),
|
||||
],
|
||||
updatedRequest: expect.objectContaining({
|
||||
method: "POST",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setHeader should reflect immediately in hopp.request.headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const before = hopp.request.headers.find(h => h.key === "X-Test")
|
||||
console.log("Before value:", before.value)
|
||||
hopp.request.setHeader("X-Test", "modified")
|
||||
const after = hopp.request.headers.find(h => h.key === "X-Test")
|
||||
console.log("After value:", after.value)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before value:", "val1"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After value:", "modified"],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setHeaders should reflect immediately in hopp.request.headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before length:", hopp.request.headers.length)
|
||||
hopp.request.setHeaders([
|
||||
{ key: "X-New-1", value: "val1", active: true, description: "" },
|
||||
{ key: "X-New-2", value: "val2", active: true, description: "" }
|
||||
])
|
||||
console.log("After length:", hopp.request.headers.length)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before length:", 1],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After length:", 2],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("removeHeader should reflect immediately in hopp.request.headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.headers.map(h => h.key))
|
||||
hopp.request.removeHeader("X-Test")
|
||||
console.log("After:", hopp.request.headers.map(h => h.key))
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", ["X-Test"]],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", []],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setParam should reflect immediately in hopp.request.params", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const before = hopp.request.params.find(p => p.key === "q")
|
||||
console.log("Before value:", before.value)
|
||||
hopp.request.setParam("q", "updated")
|
||||
const after = hopp.request.params.find(p => p.key === "q")
|
||||
console.log("After value:", after.value)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before value:", "search"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After value:", "updated"],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setParams should reflect immediately in hopp.request.params", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before length:", hopp.request.params.length)
|
||||
hopp.request.setParams([
|
||||
{ key: "page", value: "1", active: true, description: "" },
|
||||
{ key: "limit", value: "10", active: true, description: "" }
|
||||
])
|
||||
console.log("After length:", hopp.request.params.length)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before length:", 1],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After length:", 2],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("removeParam should reflect immediately in hopp.request.params", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.params.map(p => p.key))
|
||||
hopp.request.removeParam("q")
|
||||
console.log("After:", hopp.request.params.map(p => p.key))
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", ["q"]],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", []],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setBody should reflect immediately in hopp.request.body", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.body.contentType)
|
||||
hopp.request.setBody({
|
||||
contentType: "application/json",
|
||||
body: '{"test": true}'
|
||||
})
|
||||
console.log("After:", hopp.request.body.contentType)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", null],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", "application/json"],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("setAuth should reflect immediately in hopp.request.auth", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before:", hopp.request.auth.authType)
|
||||
hopp.request.setAuth({ authType: "bearer", token: "test-token" })
|
||||
console.log("After:", hopp.request.auth.authType)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: baseRequest,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: [
|
||||
expect.objectContaining({
|
||||
args: ["Before:", "none"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After:", "bearer"],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -462,4 +462,148 @@ describe("hopp.response", () => {
|
|||
).resolves.toEqualLeft(expect.stringContaining("read-only"))
|
||||
})
|
||||
})
|
||||
|
||||
describe("hopp.response utility methods", () => {
|
||||
test("hopp.response.text() should return response as text", async () => {
|
||||
await expect(
|
||||
runTestScript(
|
||||
`hopp.expect(hopp.response.text()).toBe("Plaintext response")`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: sampleTextResponse,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
"Expected 'Plaintext response' to be 'Plaintext response'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.response.json() should parse JSON response", async () => {
|
||||
await expect(
|
||||
runTestScript(`hopp.expect(hopp.response.json().ok).toBe(true)`, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: sampleJSONResponse,
|
||||
})
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.response.reason() should return HTTP reason phrase", async () => {
|
||||
await expect(
|
||||
runTestScript(`hopp.expect(hopp.response.reason()).toBe("OK")`, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: sampleTextResponse,
|
||||
})
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'OK' to be 'OK'" },
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.response.dataURI() should convert response to data URI", async () => {
|
||||
await expect(
|
||||
runTestScript(
|
||||
`
|
||||
const dataURI = hopp.response.dataURI()
|
||||
hopp.expect(dataURI).toBeType("string")
|
||||
hopp.expect(dataURI.startsWith("data:")).toBe(true)
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: sampleJSONResponse,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.response.jsonp() should parse JSONP response", async () => {
|
||||
const jsonpResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: 'callback({"data": "test"})',
|
||||
headers: [{ key: "Content-Type", value: "application/javascript" }],
|
||||
statusText: "OK",
|
||||
responseTime: 100,
|
||||
}
|
||||
|
||||
await expect(
|
||||
runTestScript(
|
||||
`hopp.expect(hopp.response.jsonp("callback").data).toBe("test")`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: jsonpResponse,
|
||||
}
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'test' to be 'test'" },
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.response.jsonp() should handle plain JSON without callback", async () => {
|
||||
const plainJSONResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: '{"plain": "json"}',
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
statusText: "OK",
|
||||
responseTime: 100,
|
||||
}
|
||||
|
||||
await expect(
|
||||
runTestScript(`hopp.expect(hopp.response.jsonp().plain).toBe("json")`, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: plainJSONResponse,
|
||||
})
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'json' to be 'json'" },
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,806 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("`pm.response.to.have.jsonSchema` - JSON Schema Validation", () => {
|
||||
test("should validate simple type schema", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John", age: 30 }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response matches schema", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
required: ["name", "age"],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
age: { type: "number" }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Response matches schema",
|
||||
// Note: jsonSchema assertion currently doesn't populate expectResults
|
||||
// TODO: Enhance implementation to track individual schema validation results
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate nested object schema", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
user: {
|
||||
id: 123,
|
||||
profile: {
|
||||
name: "John",
|
||||
email: "john@example.com",
|
||||
},
|
||||
},
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Nested schema validation", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
required: ["user"],
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
required: ["id", "profile"],
|
||||
properties: {
|
||||
id: { type: "number" },
|
||||
profile: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
email: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Nested schema validation",
|
||||
// Note: jsonSchema assertion currently doesn't populate expectResults
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate array schema with items", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify([
|
||||
{ id: 1, name: "Item 1" },
|
||||
{ id: 2, name: "Item 2" },
|
||||
]),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Array schema validation", function() {
|
||||
const schema = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
required: ["id", "name"],
|
||||
properties: {
|
||||
id: { type: "number" },
|
||||
name: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Array schema validation",
|
||||
// Note: jsonSchema assertion currently doesn't populate expectResults
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate enum constraints", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ status: "active", role: "admin" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Enum validation", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { enum: ["active", "inactive", "pending"] },
|
||||
role: { enum: ["admin", "user", "guest"] }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Enum validation",
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate number constraints (min/max)", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ age: 25, score: 85 }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Number constraints", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
age: { type: "number", minimum: 0, maximum: 120 },
|
||||
score: { type: "number", minimum: 0, maximum: 100 }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Number constraints",
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate string constraints (length, pattern)", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
username: "john123",
|
||||
email: "john@example.com",
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("String constraints", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
username: { type: "string", minLength: 3, maxLength: 20 },
|
||||
email: { type: "string", pattern: "^[^@]+@[^@]+\\\\.[^@]+$" }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "String constraints",
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should validate array length constraints", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
items: [1, 2, 3],
|
||||
tags: ["tag1", "tag2"],
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Array length constraints", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
items: { type: "array", minItems: 1, maxItems: 10 },
|
||||
tags: { type: "array", minItems: 1 }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Array length constraints",
|
||||
expectResults: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when required property is missing", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Missing required property", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
required: ["name", "age"],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
age: { type: "number" }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualLeft(
|
||||
expect.stringContaining("Required property 'age' is missing")
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when type doesn't match", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ age: "thirty" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Wrong type", function() {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
age: { type: "number" }
|
||||
}
|
||||
}
|
||||
pm.response.to.have.jsonSchema(schema)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualLeft(
|
||||
expect.stringContaining("Expected type number, got string")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.charset` - Charset Assertions", () => {
|
||||
test("should assert UTF-8 charset", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Hello World",
|
||||
headers: [{ key: "Content-Type", value: "text/html; charset=utf-8" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response has UTF-8 charset", function() {
|
||||
pm.response.to.have.charset("utf-8")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Response has UTF-8 charset",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'utf-8' to equal 'utf-8'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should assert ISO-8859-1 charset", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Hello World",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "text/plain; charset=ISO-8859-1" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response has ISO-8859-1 charset", function() {
|
||||
pm.response.to.have.charset("iso-8859-1")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Response has ISO-8859-1 charset",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'iso-8859-1' to equal 'iso-8859-1'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle charset case-insensitively", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json; charset=UTF-8" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Charset is case-insensitive", function() {
|
||||
pm.response.to.have.charset("utf-8")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Charset is case-insensitive",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'utf-8' to equal 'utf-8'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when charset doesn't match", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Hello",
|
||||
headers: [{ key: "Content-Type", value: "text/html; charset=utf-8" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Wrong charset fails", function() {
|
||||
pm.response.to.have.charset("iso-8859-1")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Wrong charset fails",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'utf-8' to equal 'iso-8859-1'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.jsonPath` - JSONPath Queries", () => {
|
||||
test("should query simple property", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John", age: 30 }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Query simple property", function() {
|
||||
pm.response.to.have.jsonPath("$.name", "John")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Query simple property",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'John' to deep equal 'John'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should query nested property", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
user: {
|
||||
profile: {
|
||||
name: "John Doe",
|
||||
age: 30,
|
||||
},
|
||||
},
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Query nested property", function() {
|
||||
pm.response.to.have.jsonPath("$.user.profile.name", "John Doe")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Query nested property",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'John Doe' to deep equal 'John Doe'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should query array element by index", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{ id: 1, name: "Item 1" },
|
||||
{ id: 2, name: "Item 2" },
|
||||
],
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Query array element", function() {
|
||||
pm.response.to.have.jsonPath("$.items[0].name", "Item 1")
|
||||
pm.response.to.have.jsonPath("$.items[1].id", 2)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Query array element",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should query without expected value (existence check)", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ user: { id: 123, name: "John" } }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Check property exists", function() {
|
||||
pm.response.to.have.jsonPath("$.user.id")
|
||||
pm.response.to.have.jsonPath("$.user.name")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Check property exists",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle root path", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John", age: 30 }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Query root", function() {
|
||||
const root = pm.response.json()
|
||||
pm.response.to.have.jsonPath("$", root)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Query root",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("deep equal"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when path doesn't exist", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Non-existent path fails", function() {
|
||||
pm.response.to.have.jsonPath("$.nonexistent")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualLeft(
|
||||
expect.stringContaining("Property 'nonexistent' not found")
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when array index is out of bounds", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ items: [1, 2, 3] }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Out of bounds index fails", function() {
|
||||
pm.response.to.have.jsonPath("$.items[10]")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualLeft(
|
||||
expect.stringContaining("Array index '10' out of bounds")
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when value doesn't match", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ name: "John" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Wrong value fails", function() {
|
||||
pm.response.to.have.jsonPath("$.name", "Jane")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Wrong value fails",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'John' to deep equal 'Jane'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm.expect - Advanced Chai Features", () => {
|
||||
describe(".nested property assertions", () => {
|
||||
test("should access nested properties using dot notation", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Nested property access", function() {
|
||||
const obj = { a: { b: { c: "value" } } }
|
||||
pm.expect(obj).to.have.nested.property("a.b.c", "value")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Nested property access",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should access nested properties without value check", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Nested property existence", function() {
|
||||
const obj = { x: { y: { z: 123 } } }
|
||||
pm.expect(obj).to.have.nested.property("x.y.z")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Nested property existence",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle nested array indices", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Nested array access", function() {
|
||||
const obj = { items: [{ name: "first" }, { name: "second" }] }
|
||||
pm.expect(obj).to.have.nested.property("items[1].name", "second")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Nested array access",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should work with .not negation", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Negated nested property", function() {
|
||||
const obj = { a: { b: "value" } }
|
||||
pm.expect(obj).to.not.have.nested.property("a.c")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Negated nested property",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
// Side-effect assertions with .by() chaining are comprehensively tested in
|
||||
// change-increase-decrease-getter.spec.ts which includes both getter and object+property patterns,
|
||||
// positive/negative deltas, and all assertion combinations
|
||||
})
|
||||
|
|
@ -0,0 +1,689 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { TestResponse } from "~/types"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const mockResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 0,
|
||||
body: "OK",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
describe("Chai Edge Cases - .include.members() / .contain.members() Pattern", () => {
|
||||
test("should support .include.members() for subset matching", async () => {
|
||||
const testScript = `
|
||||
pm.test("include.members subset", () => {
|
||||
pm.expect([1, 2, 3, 4]).to.include.members([1, 2]);
|
||||
pm.expect([1, 2, 3, 4]).to.contain.members([3, 4]);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "include.members subset",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .have.members() for exact matching", async () => {
|
||||
const testScript = `
|
||||
pm.test("exact members", () => {
|
||||
pm.expect([1, 2, 3]).to.have.members([1, 2, 3]);
|
||||
pm.expect([1, 2, 3]).to.have.members([3, 2, 1]); // Order doesn't matter
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "exact members",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp namespace should support include.members", async () => {
|
||||
const testScript = `
|
||||
hopp.test("hopp include.members", () => {
|
||||
hopp.expect([1, 2, 3, 4]).to.include.members([2, 3]);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "hopp include.members",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .any.keys() / .all.keys() Patterns", () => {
|
||||
test("should support .any.keys() - at least one key matches", async () => {
|
||||
const testScript = `
|
||||
pm.test("any.keys pattern", () => {
|
||||
const obj = { a: 1, b: 2, c: 3 };
|
||||
pm.expect(obj).to.have.any.keys('a', 'b'); // Has both
|
||||
pm.expect(obj).to.have.any.keys('a', 'z'); // Has at least one (a)
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "any.keys pattern",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .all.keys() - must have exactly these keys", async () => {
|
||||
const testScript = `
|
||||
pm.test("all.keys pattern", () => {
|
||||
const obj = { a: 1, b: 2 };
|
||||
pm.expect(obj).to.have.all.keys('a', 'b'); // Exact match
|
||||
pm.expect(obj).to.have.keys('a', 'b'); // Default is .all
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "all.keys pattern",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when .any.keys() has no matching keys", async () => {
|
||||
const testScript = `
|
||||
pm.test("any.keys failure", () => {
|
||||
const obj = { a: 1, b: 2 };
|
||||
pm.expect(obj).to.have.any.keys('x', 'y', 'z'); // None match
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "any.keys failure",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "fail" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .ordered.members() Pattern", () => {
|
||||
test("should support .ordered.members() - order matters", async () => {
|
||||
const testScript = `
|
||||
pm.test("ordered.members", () => {
|
||||
pm.expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "ordered.members",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail .ordered.members() when order is wrong", async () => {
|
||||
const testScript = `
|
||||
pm.test("ordered.members wrong order", () => {
|
||||
pm.expect([1, 2, 3]).to.have.ordered.members([3, 2, 1]);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "ordered.members wrong order",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "fail" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .own.include() Pattern", () => {
|
||||
test("should support .own.include() - own properties only", async () => {
|
||||
const testScript = `
|
||||
pm.test("own.include", () => {
|
||||
const obj = Object.create({ inherited: 'value' });
|
||||
obj.own = 'ownValue';
|
||||
|
||||
pm.expect(obj).to.have.own.include({ own: 'ownValue' });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "own.include",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail .own.include() for inherited properties", async () => {
|
||||
const testScript = `
|
||||
pm.test("own.include excludes inherited", () => {
|
||||
const obj = Object.create({ inherited: 'value' });
|
||||
obj.own = 'ownValue';
|
||||
|
||||
// This should fail because 'inherited' is not an own property
|
||||
pm.expect(obj).to.have.own.include({ inherited: 'value' });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "own.include excludes inherited",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "fail" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .nested.include() Pattern", () => {
|
||||
test("should support .nested.include() - dot notation for nested properties", async () => {
|
||||
const testScript = `
|
||||
pm.test("nested.include", () => {
|
||||
const obj = {
|
||||
user: {
|
||||
name: 'John',
|
||||
address: {
|
||||
city: 'NYC'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pm.expect(obj).to.nested.include({ 'user.name': 'John' });
|
||||
pm.expect(obj).to.nested.include({ 'user.address.city': 'NYC' });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "nested.include",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .nested.include() with bracket notation", async () => {
|
||||
const testScript = `
|
||||
pm.test("nested.include bracket notation", () => {
|
||||
const obj = {
|
||||
'user.name': 'Alice', // Literal key with dot
|
||||
user: { name: 'Bob' }
|
||||
};
|
||||
|
||||
// Bracket notation for literal key with dot
|
||||
pm.expect(obj).to.nested.include({ '["user.name"]': 'Alice' });
|
||||
// Dot notation for nested property
|
||||
pm.expect(obj).to.nested.include({ 'user.name': 'Bob' });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "nested.include bracket notation",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - Modifier Combinations", () => {
|
||||
test("should support .deep.own.include() - stacked modifiers", async () => {
|
||||
const testScript = `
|
||||
pm.test("deep.own.include", () => {
|
||||
const obj = Object.create({ inherited: { a: 1 } });
|
||||
obj.own = { b: 2 };
|
||||
|
||||
pm.expect(obj).to.have.deep.own.include({ own: { b: 2 } });
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.own.include",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .deep.equal() - deep equality check", async () => {
|
||||
const testScript = `
|
||||
pm.test("deep.equal", () => {
|
||||
const obj1 = { a: { b: { c: 1 } } };
|
||||
const obj2 = { a: { b: { c: 1 } } };
|
||||
|
||||
pm.expect(obj1).to.deep.equal(obj2);
|
||||
pm.expect(obj1).to.eql(obj2); // eql is alias for deep.equal
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.equal",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .deep.nested.include() - multiple modifiers", async () => {
|
||||
const testScript = `
|
||||
pm.test("deep.nested.include", () => {
|
||||
const obj = {
|
||||
user: {
|
||||
profile: {
|
||||
settings: { theme: 'dark', notifications: true }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pm.expect(obj).to.deep.nested.include({
|
||||
'user.profile.settings': { theme: 'dark', notifications: true }
|
||||
});
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "deep.nested.include",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .ownPropertyDescriptor() with Chaining", () => {
|
||||
test("should support chaining after .ownPropertyDescriptor()", async () => {
|
||||
const testScript = `
|
||||
pm.test("ownPropertyDescriptor chaining", () => {
|
||||
const obj = {};
|
||||
Object.defineProperty(obj, 'foo', {
|
||||
value: 42,
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
pm.expect(obj).to.have.ownPropertyDescriptor('foo')
|
||||
.that.has.property('enumerable', true);
|
||||
|
||||
pm.expect(obj).to.have.ownPropertyDescriptor('foo')
|
||||
.that.has.property('writable', false);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "ownPropertyDescriptor chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - .respondTo() with .itself", () => {
|
||||
test("should support .itself.respondTo() for function methods", async () => {
|
||||
const testScript = `
|
||||
pm.test("itself.respondTo", () => {
|
||||
function MyFunc() {}
|
||||
MyFunc.staticMethod = function() {};
|
||||
|
||||
// Check that the function itself has the method
|
||||
pm.expect(MyFunc).itself.to.respondTo('staticMethod');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "itself.respondTo",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Chai Edge Cases - Real-World Patterns", () => {
|
||||
test("should handle complex nested assertions from API responses", async () => {
|
||||
const jsonResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 0,
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
users: [
|
||||
{ id: 1, name: "Alice", roles: ["admin", "user"] },
|
||||
{ id: 2, name: "Bob", roles: ["user"] },
|
||||
],
|
||||
meta: {
|
||||
total: 2,
|
||||
page: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("complex API response validation", () => {
|
||||
const response = pm.response.json();
|
||||
|
||||
// Deep nested property checks
|
||||
pm.expect(response).to.nested.include({ 'data.meta.total': 2 });
|
||||
pm.expect(response).to.nested.include({ 'data.meta.page': 1 });
|
||||
|
||||
// Array member checks
|
||||
const userIds = response.data.users.map(u => u.id);
|
||||
pm.expect(userIds).to.include.members([1, 2]);
|
||||
|
||||
// Deep property on array element
|
||||
pm.expect(response.data.users[0]).to.deep.include({
|
||||
roles: ["admin", "user"]
|
||||
});
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
jsonResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "complex API response validation",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
message: expect.stringContaining("nested include"),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
message: expect.stringContaining("nested include"),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
message: expect.stringContaining("include members"),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
message: expect.stringContaining("deep include"),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest, runTestAndGetEnvs } from "~/utils/test-helpers"
|
||||
import { runPreRequestScript } from "~/node"
|
||||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
|
||||
const DEFAULT_REQUEST = getDefaultRESTRequest()
|
||||
|
||||
// Undefined marker pattern ensures values survive serialization
|
||||
|
||||
describe("Cross-namespace undefined preservation", () => {
|
||||
test("hopp.env.get can read undefined set by pm.environment.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("undef_var", undefined)
|
||||
const value = hopp.env.get("undef_var")
|
||||
pm.expect(value).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pw.env.get can read undefined set by pm.environment.set in pre-request", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set("env_undef_pre", undefined)
|
||||
// Verify pw.env.get can read the undefined value
|
||||
const value = pw.env.get("env_undef_pre")
|
||||
// Store the result to verify it was read correctly
|
||||
pw.env.set("read_result", value === undefined ? "success" : "failed")
|
||||
`,
|
||||
{
|
||||
envs: {
|
||||
global: [],
|
||||
selected: [],
|
||||
},
|
||||
request: DEFAULT_REQUEST,
|
||||
cookies: null,
|
||||
experimentalScriptingSandbox: true,
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "env_undef_pre",
|
||||
currentValue: "undefined", // Converted from UNDEFINED_MARKER
|
||||
initialValue: "undefined",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "read_result",
|
||||
currentValue: "success", // Confirms pw.env.get returned undefined
|
||||
initialValue: "success",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("pm.variables.get can read undefined from environment", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("env_undef", undefined)
|
||||
const value = pm.variables.get("env_undef")
|
||||
pm.expect(value).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("hopp.env.active.get can read undefined set by pm.variables.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.variables.set("var_undef", undefined)
|
||||
const value = hopp.env.active.get("var_undef")
|
||||
pm.expect(value).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("hopp.env.global.get can read undefined set by pm.globals.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("global_test", undefined)
|
||||
const value = hopp.env.global.get("global_test")
|
||||
pm.expect(value).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("undefined value appears correctly in environment array", () => {
|
||||
return expect(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pm.environment.set("stored_undef", undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
selected: [
|
||||
{
|
||||
key: "stored_undef",
|
||||
// The value should be stored as the marker internally but exposed as "undefined" string for UI
|
||||
currentValue: "undefined",
|
||||
initialValue: "undefined",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("undefined is preserved across multiple namespace reads", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("multi_read", undefined)
|
||||
|
||||
// Read from PM namespace
|
||||
const pmValue = pm.environment.get("multi_read")
|
||||
pm.expect(pmValue).toBe(undefined)
|
||||
|
||||
// Read from hopp namespace
|
||||
const hoppValue = hopp.env.get("multi_read")
|
||||
pm.expect(hoppValue).toBe(undefined)
|
||||
|
||||
// Read from pm.variables (which resolves from environment)
|
||||
const varValue = pm.variables.get("multi_read")
|
||||
pm.expect(varValue).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("overwriting undefined with string works across namespaces", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
// Set undefined via PM
|
||||
pm.environment.set("changeable", undefined)
|
||||
pm.expect(hopp.env.get("changeable")).toBe(undefined)
|
||||
|
||||
// Overwrite with string via PM
|
||||
pm.environment.set("changeable", "new_value")
|
||||
pm.expect(hopp.env.get("changeable")).toBe("new_value")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'new_value' to be 'new_value'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("undefined precedence in pm.variables.get (environment over global)", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
// Set undefined in both global and environment
|
||||
pm.globals.set("precedence_test", undefined)
|
||||
pm.environment.set("precedence_test", undefined)
|
||||
|
||||
// pm.variables should return environment's undefined
|
||||
const value = pm.variables.get("precedence_test")
|
||||
pm.expect(value).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -1,35 +1,14 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm.environment additional coverage", () => {
|
||||
test("pm.environment.set creates and retrieves environment variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("test_set", "set_value")
|
||||
const retrieved = pm.environment.get("test_set")
|
||||
pw.expect(retrieved).toBe("set_value")
|
||||
pm.expect(retrieved).toBe("set_value")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
|
|
@ -50,12 +29,12 @@ describe("pm.environment additional coverage", () => {
|
|||
|
||||
test("pm.environment.has correctly identifies existing and non-existing variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const hasExisting = pm.environment.has("existing_var")
|
||||
const hasNonExisting = pm.environment.has("non_existing_var")
|
||||
pw.expect(hasExisting.toString()).toBe("true")
|
||||
pw.expect(hasNonExisting.toString()).toBe("false")
|
||||
pm.expect(hasExisting.toString()).toBe("true")
|
||||
pm.expect(hasNonExisting.toString()).toBe("false")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
|
|
@ -84,16 +63,180 @@ describe("pm.environment additional coverage", () => {
|
|||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.environment.toObject returns all environment variables set via pm.environment.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("key1", "value1")
|
||||
pm.environment.set("key2", "value2")
|
||||
pm.environment.set("key3", "value3")
|
||||
|
||||
const envObj = pm.environment.toObject()
|
||||
|
||||
pm.expect(envObj.key1).toBe("value1")
|
||||
pm.expect(envObj.key2).toBe("value2")
|
||||
pm.expect(envObj.key3).toBe("value3")
|
||||
pm.expect(Object.keys(envObj).length.toString()).toBe("3")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value1' to be 'value1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value2' to be 'value2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value3' to be 'value3'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '3' to be '3'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.environment.toObject returns empty object when no variables are set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const envObj = pm.environment.toObject()
|
||||
pm.expect(Object.keys(envObj).length.toString()).toBe("0")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '0' to be '0'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.environment.clear removes all environment variables set via pm.environment.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("key1", "value1")
|
||||
pm.environment.set("key2", "value2")
|
||||
|
||||
// Verify variables are set
|
||||
pm.expect(pm.environment.get("key1")).toBe("value1")
|
||||
pm.expect(pm.environment.get("key2")).toBe("value2")
|
||||
|
||||
// Clear all
|
||||
pm.environment.clear()
|
||||
|
||||
// Verify variables are cleared
|
||||
pm.expect(pm.environment.get("key1")).toBe(undefined)
|
||||
pm.expect(pm.environment.get("key2")).toBe(undefined)
|
||||
|
||||
// Verify toObject returns empty
|
||||
const envObj = pm.environment.toObject()
|
||||
pm.expect(Object.keys(envObj).length.toString()).toBe("0")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value1' to be 'value1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value2' to be 'value2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '0' to be '0'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.environment.unset removes key from tracking", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("key1", "value1")
|
||||
pm.environment.set("key2", "value2")
|
||||
|
||||
// Unset one key
|
||||
pm.environment.unset("key1")
|
||||
|
||||
// Verify key1 is removed but key2 remains
|
||||
const envObj = pm.environment.toObject()
|
||||
pm.expect(envObj.key1).toBe(undefined)
|
||||
pm.expect(envObj.key2).toBe("value2")
|
||||
pm.expect(Object.keys(envObj).length.toString()).toBe("1")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'value2' to be 'value2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '1' to be '1'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.globals additional coverage", () => {
|
||||
test("pm.globals.set creates and retrieves global variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("test_global", "global_value")
|
||||
const retrieved = pm.globals.get("test_global")
|
||||
pw.expect(retrieved).toBe("global_value")
|
||||
pm.expect(retrieved).toBe("global_value")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
|
|
@ -111,16 +254,259 @@ describe("pm.globals additional coverage", () => {
|
|||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.toObject returns all global variables set via pm.globals.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("globalKey1", "globalValue1")
|
||||
pm.globals.set("globalKey2", "globalValue2")
|
||||
pm.globals.set("globalKey3", "globalValue3")
|
||||
|
||||
const globalObj = pm.globals.toObject()
|
||||
|
||||
pm.expect(globalObj.globalKey1).toBe("globalValue1")
|
||||
pm.expect(globalObj.globalKey2).toBe("globalValue2")
|
||||
pm.expect(globalObj.globalKey3).toBe("globalValue3")
|
||||
pm.expect(Object.keys(globalObj).length.toString()).toBe("3")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue1' to be 'globalValue1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue2' to be 'globalValue2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue3' to be 'globalValue3'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '3' to be '3'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.toObject returns empty object when no globals are set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const globalObj = pm.globals.toObject()
|
||||
pm.expect(Object.keys(globalObj).length.toString()).toBe("0")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '0' to be '0'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.clear removes all global variables set via pm.globals.set", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("globalKey1", "globalValue1")
|
||||
pm.globals.set("globalKey2", "globalValue2")
|
||||
|
||||
// Verify variables are set
|
||||
pm.expect(pm.globals.get("globalKey1")).toBe("globalValue1")
|
||||
pm.expect(pm.globals.get("globalKey2")).toBe("globalValue2")
|
||||
|
||||
// Clear all
|
||||
pm.globals.clear()
|
||||
|
||||
// Verify variables are cleared
|
||||
pm.expect(pm.globals.get("globalKey1")).toBe(undefined)
|
||||
pm.expect(pm.globals.get("globalKey2")).toBe(undefined)
|
||||
|
||||
// Verify toObject returns empty
|
||||
const globalObj = pm.globals.toObject()
|
||||
pm.expect(Object.keys(globalObj).length.toString()).toBe("0")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue1' to be 'globalValue1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue2' to be 'globalValue2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '0' to be '0'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.clear also removes initial global variables from environment", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
// Verify initial globals exist
|
||||
pm.expect(pm.globals.get("initial_global1")).toBe("initial_value1")
|
||||
pm.expect(pm.globals.get("initial_global2")).toBe("initial_value2")
|
||||
|
||||
// Add tracked globals
|
||||
pm.globals.set("tracked_global", "tracked_value")
|
||||
pm.expect(pm.globals.get("tracked_global")).toBe("tracked_value")
|
||||
|
||||
// Verify toObject includes both initial and tracked
|
||||
const before = pm.globals.toObject()
|
||||
pm.expect(before.initial_global1).toBe("initial_value1")
|
||||
pm.expect(before.tracked_global).toBe("tracked_value")
|
||||
|
||||
// Clear all (both initial and tracked)
|
||||
pm.globals.clear()
|
||||
|
||||
// Verify ALL globals are cleared
|
||||
pm.expect(pm.globals.get("initial_global1")).toBe(undefined)
|
||||
pm.expect(pm.globals.get("initial_global2")).toBe(undefined)
|
||||
pm.expect(pm.globals.get("tracked_global")).toBe(undefined)
|
||||
|
||||
// Verify toObject returns empty
|
||||
const after = pm.globals.toObject()
|
||||
pm.expect(Object.keys(after).length.toString()).toBe("0")
|
||||
`,
|
||||
{
|
||||
global: [
|
||||
{
|
||||
key: "initial_global1",
|
||||
currentValue: "initial_value1",
|
||||
initialValue: "initial_value1",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "initial_global2",
|
||||
currentValue: "initial_value2",
|
||||
initialValue: "initial_value2",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'initial_value1' to be 'initial_value1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'initial_value2' to be 'initial_value2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'tracked_value' to be 'tracked_value'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'initial_value1' to be 'initial_value1'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'tracked_value' to be 'tracked_value'",
|
||||
},
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected '0' to be '0'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.unset removes key from tracking", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("globalKey1", "globalValue1")
|
||||
pm.globals.set("globalKey2", "globalValue2")
|
||||
|
||||
// Unset one key
|
||||
pm.globals.unset("globalKey1")
|
||||
|
||||
// Verify key1 is removed but key2 remains
|
||||
const globalObj = pm.globals.toObject()
|
||||
pm.expect(globalObj.globalKey1).toBe(undefined)
|
||||
pm.expect(globalObj.globalKey2).toBe("globalValue2")
|
||||
pm.expect(Object.keys(globalObj).length.toString()).toBe("1")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'globalValue2' to be 'globalValue2'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '1' to be '1'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.variables additional coverage", () => {
|
||||
test("pm.variables.set creates and retrieves variable in active environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.variables.set("test_var", "test_value")
|
||||
const retrieved = pm.variables.get("test_var")
|
||||
pw.expect(retrieved).toBe("test_value")
|
||||
pm.expect(retrieved).toBe("test_value")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
|
|
@ -141,12 +527,12 @@ describe("pm.variables additional coverage", () => {
|
|||
|
||||
test("pm.variables.has correctly identifies existing and non-existing variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const hasExisting = pm.variables.has("existing_var")
|
||||
const hasNonExisting = pm.variables.has("non_existing_var")
|
||||
pw.expect(hasExisting.toString()).toBe("true")
|
||||
pw.expect(hasNonExisting.toString()).toBe("false")
|
||||
pm.expect(hasExisting.toString()).toBe("true")
|
||||
pm.expect(hasNonExisting.toString()).toBe("false")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
|
|
@ -178,10 +564,10 @@ describe("pm.variables additional coverage", () => {
|
|||
|
||||
test("pm.variables.get returns the correct value from any scope", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pm.variables.get("scopedVar")
|
||||
pw.expect(data).toBe("scopedValue")
|
||||
pm.expect(data).toBe("scopedValue")
|
||||
`,
|
||||
{
|
||||
global: [
|
||||
|
|
@ -209,11 +595,11 @@ describe("pm.variables additional coverage", () => {
|
|||
|
||||
test("pm.variables.replaceIn handles multiple variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const template = "User {{name}} has {{count}} items in {{location}}"
|
||||
const result = pm.variables.replaceIn(template)
|
||||
pw.expect(result).toBe("User Alice has 10 items in Cart")
|
||||
pm.expect(result).toBe("User Alice has 10 items in Cart")
|
||||
`,
|
||||
{
|
||||
global: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,310 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { TestResponse } from "~/types"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const mockResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 0,
|
||||
body: "OK",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
describe("pm.expect.fail() - Explicit test failures", () => {
|
||||
test("pm.expect.fail() with no arguments fails the test", async () => {
|
||||
const testScript = `
|
||||
pm.test("explicit failure", () => {
|
||||
pm.expect.fail();
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "explicit failure",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: expect.stringContaining("expect.fail()"),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("pm.expect.fail() with custom message", async () => {
|
||||
const testScript = `
|
||||
pm.test("custom failure message", () => {
|
||||
pm.expect.fail("This test intentionally fails");
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom failure message",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: "This test intentionally fails",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("pm.expect.fail() with actual and expected values", async () => {
|
||||
const testScript = `
|
||||
pm.test("failure with values", () => {
|
||||
pm.expect.fail(5, 10);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "failure with values",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: expect.stringMatching(/expected.*5.*equal.*10/i),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("pm.expect.fail() practical use case - conditional validation", async () => {
|
||||
const jsonResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 0,
|
||||
body: JSON.stringify({ id: 1, name: "Test" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("validate response", () => {
|
||||
const data = pm.response.json();
|
||||
|
||||
if (!data.email) {
|
||||
pm.expect.fail("Missing required email field");
|
||||
}
|
||||
|
||||
pm.expect(data).to.be.an("object");
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
jsonResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "validate response",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: "Missing required email field",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("hopp.expect.fail() - Explicit test failures", () => {
|
||||
test("hopp.expect.fail() with no arguments fails the test", async () => {
|
||||
const testScript = `
|
||||
hopp.test("explicit failure", () => {
|
||||
hopp.expect.fail();
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "explicit failure",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: expect.stringContaining("expect.fail()"),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.expect.fail() with custom message", async () => {
|
||||
const testScript = `
|
||||
hopp.test("custom failure message", () => {
|
||||
hopp.expect.fail("This test intentionally fails");
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "custom failure message",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: "This test intentionally fails",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.expect.fail() with actual and expected values", async () => {
|
||||
const testScript = `
|
||||
hopp.test("failure with values", () => {
|
||||
hopp.expect.fail("hello", "world");
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "failure with values",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: expect.stringMatching(
|
||||
/expected.*hello.*equal.*world/i
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("expect.fail() - Cross-namespace compatibility", () => {
|
||||
test("both pm and hopp namespaces support fail() with same behavior", async () => {
|
||||
const testScript = `
|
||||
pm.test("pm namespace fail", () => {
|
||||
pm.expect.fail("PM failure");
|
||||
});
|
||||
|
||||
hopp.test("hopp namespace fail", () => {
|
||||
hopp.expect.fail("Hopp failure");
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(
|
||||
testScript,
|
||||
{ global: [], selected: [] },
|
||||
mockResponse
|
||||
)()
|
||||
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "pm namespace fail",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: "PM failure",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
descriptor: "hopp namespace fail",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "fail",
|
||||
message: "Hopp failure",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -2,28 +2,10 @@ import { getDefaultRESTRequest } from "@hoppscotch/data"
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
import { runTest, defaultRequest, fakeResponse } from "~/utils/test-helpers"
|
||||
import { runPreRequestScript } from "~/web"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "test response",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: { ...defaultRequest, name: "default-request", id: "test-id" },
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
describe("pm.info context", () => {
|
||||
test("pm.info.eventName returns 'pre-request' in pre-request context", () => {
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
|
|
@ -50,12 +32,12 @@ describe("pm.info context", () => {
|
|||
)
|
||||
})
|
||||
|
||||
test("pm.info.eventName returns 'post-request' in post-request context", () => {
|
||||
test("pm.info.eventName returns 'test' in test context", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Event name is correct", () => {
|
||||
pm.expect(pm.info.eventName).toBe("post-request")
|
||||
pm.expect(pm.info.eventName).toBe("test")
|
||||
})
|
||||
`,
|
||||
{
|
||||
|
|
@ -71,7 +53,7 @@ describe("pm.info context", () => {
|
|||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'post-request' to be 'post-request'",
|
||||
message: "Expected 'test' to be 'test'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
@ -81,8 +63,14 @@ describe("pm.info context", () => {
|
|||
})
|
||||
|
||||
test("pm.info provides requestName and requestId", () => {
|
||||
const customRequest = {
|
||||
...defaultRequest,
|
||||
name: "default-request",
|
||||
id: "test-id",
|
||||
}
|
||||
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Request info is available", () => {
|
||||
pm.expect(pm.info.requestName).toBe("default-request")
|
||||
|
|
@ -92,7 +80,9 @@ describe("pm.info context", () => {
|
|||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
},
|
||||
fakeResponse,
|
||||
customRequest
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
|
|
@ -111,4 +101,36 @@ describe("pm.info context", () => {
|
|||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.info.requestId falls back to requestName when id is undefined", () => {
|
||||
return expect(
|
||||
pipe(
|
||||
runTestScript(
|
||||
`
|
||||
pm.test("Request ID fallback works", () => {
|
||||
pm.expect(pm.info.requestId).to.exist
|
||||
pm.expect(pm.info.requestId).toBe("fallback-request-name")
|
||||
})
|
||||
`,
|
||||
{
|
||||
envs: { global: [], selected: [] },
|
||||
request: { ...defaultRequest, name: "fallback-request-name" },
|
||||
response: fakeResponse,
|
||||
}
|
||||
),
|
||||
TE.map((x) => x.tests)
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Request ID fallback works",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
// Map/Set serialize as {} across sandbox boundary, so we extract .size before serialization
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("Map.size property assertions", () => {
|
||||
test("should support .property('size') for Map", async () => {
|
||||
const testScript = `
|
||||
pm.test("Map - size property", () => {
|
||||
const myMap = new Map([['key1', 'value1'], ['key2', 'value2']]);
|
||||
pm.expect(myMap).to.have.property('size', 2);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Map - size property",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .property('size') with chaining for Map", async () => {
|
||||
const testScript = `
|
||||
pm.test("Map - size with chaining", () => {
|
||||
const myMap = new Map([['a', 1], ['b', 2], ['c', 3]]);
|
||||
pm.expect(myMap).to.have.property('size').that.equals(3);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Map - size with chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support negation for Map size", async () => {
|
||||
const testScript = `
|
||||
pm.test("Map - size negation", () => {
|
||||
const myMap = new Map([['key1', 'value1']]);
|
||||
pm.expect(myMap).to.not.have.property('size', 5);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Map - size negation",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Set.size property assertions", () => {
|
||||
test("should support .property('size') for Set", async () => {
|
||||
const testScript = `
|
||||
pm.test("Set - size property", () => {
|
||||
const mySet = new Set([1, 2, 3]);
|
||||
pm.expect(mySet).to.have.property('size', 3);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Set - size property",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .property('size') with chaining for Set", async () => {
|
||||
const testScript = `
|
||||
pm.test("Set - size with chaining", () => {
|
||||
const mySet = new Set(['a', 'b', 'c', 'd']);
|
||||
pm.expect(mySet).to.have.property('size').that.is.above(2);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Set - size with chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support negation for Set size", async () => {
|
||||
const testScript = `
|
||||
pm.test("Set - size negation", () => {
|
||||
const mySet = new Set([1, 2]);
|
||||
pm.expect(mySet).to.not.have.property('size', 10);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Set - size negation",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should handle empty Set", async () => {
|
||||
const testScript = `
|
||||
pm.test("Set - empty size", () => {
|
||||
const mySet = new Set();
|
||||
pm.expect(mySet).to.have.property('size', 0);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Set - empty size",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should handle empty Map", async () => {
|
||||
const testScript = `
|
||||
pm.test("Map - empty size", () => {
|
||||
const myMap = new Map();
|
||||
pm.expect(myMap).to.have.property('size', 0);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "Map - empty size",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import { runPreRequestScript } from "~/node"
|
||||
|
||||
const DEFAULT_REQUEST = getDefaultRESTRequest()
|
||||
|
||||
// Pre-request scripts use markers to preserve null/undefined across serialization
|
||||
|
||||
describe("PM namespace type preservation in pre-request context", () => {
|
||||
const emptyEnvs = {
|
||||
envs: {
|
||||
global: [],
|
||||
selected: [],
|
||||
},
|
||||
request: DEFAULT_REQUEST,
|
||||
}
|
||||
|
||||
describe("pm.environment.set() type preservation", () => {
|
||||
test("preserves arrays (not String() coercion to '1,2,3')", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('testArray', [1, 2, 3])
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "testArray",
|
||||
currentValue: "[1,2,3]", // JSON stringified for UI display
|
||||
initialValue: "[1,2,3]",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves objects (not String() coercion to '[object Object]')", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('testObj', { foo: 'bar', num: 42 })
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "testObj",
|
||||
currentValue: '{"foo":"bar","num":42}', // JSON stringified for UI display
|
||||
secret: false,
|
||||
initialValue: '{"foo":"bar","num":42}',
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves null with NULL_MARKER", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('nullValue', null)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "nullValue",
|
||||
currentValue: "null", // Converted from NULL_MARKER by getUpdatedEnvs
|
||||
initialValue: "null",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves undefined with UNDEFINED_MARKER", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('undefinedValue', undefined)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "undefinedValue",
|
||||
currentValue: "undefined", // Converted from UNDEFINED_MARKER by getUpdatedEnvs
|
||||
initialValue: "undefined",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves nested structures", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('nested', {
|
||||
users: [
|
||||
{ id: 1, name: "Alice" },
|
||||
{ id: 2, name: "Bob" }
|
||||
],
|
||||
meta: { count: 2, filters: ["active", "verified"] }
|
||||
})
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "nested",
|
||||
currentValue:
|
||||
'{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}],"meta":{"count":2,"filters":["active","verified"]}}',
|
||||
initialValue:
|
||||
'{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}],"meta":{"count":2,"filters":["active","verified"]}}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves primitives correctly", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('str', 'hello')
|
||||
pm.environment.set('num', 42)
|
||||
pm.environment.set('bool', true)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "str",
|
||||
currentValue: "hello",
|
||||
initialValue: "hello",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "num",
|
||||
currentValue: "42", // Converted to string for UI compatibility
|
||||
initialValue: "42",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "bool",
|
||||
currentValue: "true", // Converted to string for UI compatibility
|
||||
initialValue: "true",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.globals.set() type preservation", () => {
|
||||
test("preserves arrays in globals", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.globals.set('globalArray', [10, 20, 30])
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [
|
||||
{
|
||||
key: "globalArray",
|
||||
currentValue: "[10,20,30]",
|
||||
initialValue: "[10,20,30]",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves objects in globals", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.globals.set('globalObj', { env: 'prod', port: 8080 })
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [
|
||||
{
|
||||
key: "globalObj",
|
||||
currentValue: '{"env":"prod","port":8080}',
|
||||
initialValue: '{"env":"prod","port":8080}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves null in globals", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.globals.set('globalNull', null)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [
|
||||
{
|
||||
key: "globalNull",
|
||||
currentValue: "null", // Converted from NULL_MARKER
|
||||
initialValue: "null",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves undefined in globals", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.globals.set('globalUndefined', undefined)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [
|
||||
{
|
||||
key: "globalUndefined",
|
||||
currentValue: "undefined", // Converted from UNDEFINED_MARKER
|
||||
initialValue: "undefined",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.variables.set() type preservation", () => {
|
||||
test("preserves arrays (uses active scope)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.variables.set('varArray', [5, 10, 15])
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "varArray",
|
||||
currentValue: "[5,10,15]",
|
||||
initialValue: "[5,10,15]",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves objects", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.variables.set('varObj', { status: 'active', count: 100 })
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "varObj",
|
||||
currentValue: '{"status":"active","count":100}',
|
||||
initialValue: '{"status":"active","count":100}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves null", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.variables.set('varNull', null)
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "varNull",
|
||||
currentValue: "null",
|
||||
initialValue: "null",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Regression tests for String() coercion bug", () => {
|
||||
test("CRITICAL: does NOT convert [1,2,3] to '1,2,3' string", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('arr', [1, 2, 3])
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "arr",
|
||||
currentValue: "[1,2,3]", // JSON stringified for UI display
|
||||
initialValue: "[1,2,3]",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("CRITICAL: does NOT convert object to '[object Object]'", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('obj', { foo: 'bar' })
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "obj",
|
||||
currentValue: '{"foo":"bar"}', // Object (JSON string for UI), not "[object Object]" string
|
||||
initialValue: '{"foo":"bar"}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Complex scenarios", () => {
|
||||
test("mixed array of primitives, null, undefined, objects", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('mixed', [
|
||||
'string',
|
||||
42,
|
||||
true,
|
||||
null,
|
||||
undefined,
|
||||
[1, 2],
|
||||
{ key: 'value' }
|
||||
])
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "mixed",
|
||||
currentValue:
|
||||
'["string",42,true,null,null,[1,2],{"key":"value"}]',
|
||||
initialValue:
|
||||
'["string",42,true,null,null,[1,2],{"key":"value"}]', // JSON stringified for UI display
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
|
||||
test("multiple PM namespace calls in same pre-request", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.environment.set('arr1', [1, 2])
|
||||
pm.globals.set('arr2', [3, 4])
|
||||
pm.variables.set('arr3', [5, 6])
|
||||
pm.environment.set('obj1', { a: 1 })
|
||||
pm.globals.set('obj2', { b: 2 })
|
||||
`,
|
||||
emptyEnvs
|
||||
)()
|
||||
).resolves.toEqualRight({
|
||||
updatedEnvs: {
|
||||
selected: [
|
||||
{
|
||||
key: "arr1",
|
||||
currentValue: "[1,2]",
|
||||
initialValue: "[1,2]",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "arr3",
|
||||
currentValue: "[5,6]",
|
||||
initialValue: "[5,6]",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "obj1",
|
||||
currentValue: '{"a":1}',
|
||||
initialValue: '{"a":1}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
global: [
|
||||
{
|
||||
key: "arr2",
|
||||
currentValue: "[3,4]",
|
||||
initialValue: "[3,4]",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "obj2",
|
||||
currentValue: '{"b":2}',
|
||||
initialValue: '{"b":2}',
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
updatedRequest: DEFAULT_REQUEST,
|
||||
updatedCookies: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
// .property('key') returns a NEW expectation wrapping the property value
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("property() with .that chaining", () => {
|
||||
test("should chain property value with .that.equals()", async () => {
|
||||
const testScript = `
|
||||
pm.test("property chaining with that", () => {
|
||||
pm.expect({ a: 1, b: 2 }).to.have.property('a').that.equals(1);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "property chaining with that",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should chain nested property with .that.is.an()", async () => {
|
||||
const testScript = `
|
||||
pm.test("nested property chaining", () => {
|
||||
pm.expect({ nested: { value: 42 } })
|
||||
.to.have.property('nested')
|
||||
.that.is.an('object');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "nested property chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support .which as alias for .that", async () => {
|
||||
const testScript = `
|
||||
pm.test("property chaining with which", () => {
|
||||
pm.expect({ x: 1 }).to.have.property('x').which.equals(1);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "property chaining with which",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should support complex chaining", async () => {
|
||||
const testScript = `
|
||||
pm.test("complex property chaining", () => {
|
||||
pm.expect({ name: 'John', age: 30 })
|
||||
.to.be.an('object')
|
||||
.and.have.property('name')
|
||||
.that.is.a('string')
|
||||
.and.equals('John');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "complex property chaining",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when chained value doesn't match", async () => {
|
||||
const testScript = `
|
||||
pm.test("property chaining fails correctly", () => {
|
||||
pm.expect({ a: 1, b: 2 }).to.have.property('a').that.equals(2);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "property chaining fails correctly",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "fail" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("property() with value parameter", () => {
|
||||
test("should assert property value directly", async () => {
|
||||
const testScript = `
|
||||
pm.test("property with value", () => {
|
||||
pm.expect({ a: 1, b: 2 }).to.have.property('a', 1);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "property with value",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail when property value doesn't match", async () => {
|
||||
const testScript = `
|
||||
pm.test("property value mismatch", () => {
|
||||
pm.expect({ a: 1, b: 2 }).to.not.have.property('a', 2);
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "property value mismatch",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("own.property() assertions", () => {
|
||||
test("should check own properties vs inherited", async () => {
|
||||
const testScript = `
|
||||
pm.test("own property check", () => {
|
||||
const obj = Object.create({ inherited: true });
|
||||
obj.own = true;
|
||||
pm.expect(obj).to.have.own.property('own');
|
||||
pm.expect(obj).to.not.have.own.property('inherited');
|
||||
pm.expect(obj).to.have.property('inherited');
|
||||
});
|
||||
`
|
||||
|
||||
const result = await runTest(testScript)()
|
||||
expect(result).toEqualRight(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
descriptor: "own property check",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,31 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm.request coverage", () => {
|
||||
test("pm.request object provides access to request data", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(pm.request.url.toString()).toBe("https://echo.hoppscotch.io")
|
||||
pw.expect(pm.request.method).toBe("GET")
|
||||
|
|
@ -59,7 +38,7 @@ describe("pm.request coverage", () => {
|
|||
|
||||
test("pm.request.url provides correct URL value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(pm.request.url.toString()).toBe("https://echo.hoppscotch.io")
|
||||
pw.expect(pm.request.url.toString().length).toBe(26)
|
||||
|
|
@ -93,7 +72,7 @@ describe("pm.request coverage", () => {
|
|||
|
||||
test("pm.request.headers functionality", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(pm.request.headers.get("Content-Type")).toBe(null)
|
||||
pw.expect(pm.request.headers.has("Content-Type")).toBe(false)
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript } from "~/web"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://api.example.com/users",
|
||||
method: "POST",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json", active: true },
|
||||
{ key: "Authorization", value: "Bearer token123", active: true },
|
||||
{ key: "X-Custom-Header", value: "custom-value", active: true },
|
||||
],
|
||||
params: [],
|
||||
body: { contentType: null, body: null },
|
||||
auth: { authType: "none", authActive: false },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}
|
||||
|
||||
const envs = { global: [], selected: [] }
|
||||
|
||||
describe("pm.request.headers.find()", () => {
|
||||
test("finds header by predicate function", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const result = pm.request.headers.find((header) => header.key === 'Authorization')
|
||||
|
||||
console.log("Found header:", result)
|
||||
console.log("Header key:", result.key)
|
||||
console.log("Header value:", result.value)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Found header:",
|
||||
expect.objectContaining({
|
||||
key: "Authorization",
|
||||
value: "Bearer token123",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Header key:", "Authorization"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Header value:", "Bearer token123"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("finds header by key string (case-insensitive)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const result = pm.request.headers.find('content-type')
|
||||
|
||||
console.log("Found by string:", result)
|
||||
console.log("Value:", result.value)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Found by string:",
|
||||
expect.objectContaining({
|
||||
key: "Content-Type",
|
||||
value: "application/json",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Value:", "application/json"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns null when header not found", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const result = pm.request.headers.find('Nonexistent-Header')
|
||||
|
||||
console.log("Result:", result)
|
||||
console.log("Is null:", result === null)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Result:", null] }),
|
||||
expect.objectContaining({ args: ["Is null:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.indexOf()", () => {
|
||||
test("returns index of header by key (case-insensitive)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const idx1 = pm.request.headers.indexOf('content-type')
|
||||
const idx2 = pm.request.headers.indexOf('AUTHORIZATION')
|
||||
const idx3 = pm.request.headers.indexOf('X-Custom-Header')
|
||||
|
||||
console.log("Index of content-type:", idx1)
|
||||
console.log("Index of AUTHORIZATION:", idx2)
|
||||
console.log("Index of X-Custom-Header:", idx3)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Index of content-type:", 0] }),
|
||||
expect.objectContaining({ args: ["Index of AUTHORIZATION:", 1] }),
|
||||
expect.objectContaining({ args: ["Index of X-Custom-Header:", 2] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns index of header by object (case-insensitive)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const idx = pm.request.headers.indexOf({ key: 'authorization' })
|
||||
|
||||
console.log("Index:", idx)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Index:", 1] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns -1 when header not found", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const idx = pm.request.headers.indexOf('Nonexistent')
|
||||
|
||||
console.log("Index:", idx)
|
||||
console.log("Is -1:", idx === -1)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Index:", -1] }),
|
||||
expect.objectContaining({ args: ["Is -1:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.insert()", () => {
|
||||
test("inserts header before specified key", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.insert({ key: 'X-API-Key', value: 'secret123' }, 'Authorization')
|
||||
|
||||
const allHeaders = pm.request.headers.map((h) => h.key)
|
||||
console.log("All headers:", allHeaders)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
["Content-Type", "X-API-Key", "Authorization", "X-Custom-Header"],
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("appends header if 'before' key not found", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.insert({ key: 'X-New-Header', value: 'new' }, 'NonExistent')
|
||||
|
||||
const allHeaders = pm.request.headers.map((h) => h.key)
|
||||
console.log("All headers:", allHeaders)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
[
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Custom-Header",
|
||||
"X-New-Header",
|
||||
],
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("appends header when no 'before' specified", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.insert({ key: 'X-Added-Header', value: 'added' })
|
||||
|
||||
const allHeaders = pm.request.headers.map((h) => h.key)
|
||||
console.log("All headers:", allHeaders)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
[
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Custom-Header",
|
||||
"X-Added-Header",
|
||||
],
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("throws error when item has no key", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
try {
|
||||
pm.request.headers.insert({ value: 'test' })
|
||||
console.log("Should not reach here")
|
||||
} catch (error) {
|
||||
console.log("Error caught:", error.message)
|
||||
}
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Error caught:", "Header must have a 'key' property"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.append()", () => {
|
||||
test("moves existing header to end", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.append({ key: 'Content-Type', value: 'application/xml' })
|
||||
|
||||
const allHeaders = pm.request.headers.map((h) => h.key)
|
||||
const contentType = pm.request.headers.get('Content-Type')
|
||||
|
||||
console.log("All headers:", allHeaders)
|
||||
console.log("Content-Type value:", contentType)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
["Authorization", "X-Custom-Header", "Content-Type"],
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Content-Type value:", "application/xml"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("adds new header at end", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.append({ key: 'X-New-Header', value: 'new-value' })
|
||||
|
||||
const allHeaders = pm.request.headers.map((h) => h.key)
|
||||
console.log("All headers:", allHeaders)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
[
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Custom-Header",
|
||||
"X-New-Header",
|
||||
],
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("throws error when item has no key", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
try {
|
||||
pm.request.headers.append({ value: 'test' })
|
||||
console.log("Should not reach here")
|
||||
} catch (error) {
|
||||
console.log("Error caught:", error.message)
|
||||
}
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Error caught:", "Header must have a 'key' property"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.assimilate()", () => {
|
||||
test("updates existing headers and adds new ones from array", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.assimilate([
|
||||
{ key: 'Content-Type', value: 'text/plain' },
|
||||
{ key: 'X-API-Key', value: 'key123' }
|
||||
])
|
||||
|
||||
const allHeaders = pm.request.headers.all()
|
||||
console.log("Content-Type:", allHeaders['Content-Type'])
|
||||
console.log("Authorization:", allHeaders['Authorization'])
|
||||
console.log("X-API-Key:", allHeaders['X-API-Key'])
|
||||
console.log("Count:", pm.request.headers.count())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Content-Type:", "text/plain"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Authorization:", "Bearer token123"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["X-API-Key:", "key123"] }),
|
||||
expect.objectContaining({ args: ["Count:", 4] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates existing headers and adds new ones from object", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.assimilate({
|
||||
'Content-Type': 'application/xml',
|
||||
'X-New-Header': 'new-value'
|
||||
})
|
||||
|
||||
const allHeaders = pm.request.headers.all()
|
||||
console.log("All headers:", allHeaders)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
expect.objectContaining({
|
||||
"Content-Type": "application/xml",
|
||||
Authorization: "Bearer token123",
|
||||
"X-Custom-Header": "custom-value",
|
||||
"X-New-Header": "new-value",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("prunes headers not in source when prune=true", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.assimilate(
|
||||
{
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': 'key123'
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const allHeaders = pm.request.headers.all()
|
||||
const count = pm.request.headers.count()
|
||||
|
||||
console.log("All headers:", allHeaders)
|
||||
console.log("Count:", count)
|
||||
console.log("Has Authorization:", pm.request.headers.has('Authorization'))
|
||||
console.log("Has X-Custom-Header:", pm.request.headers.has('X-Custom-Header'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"All headers:",
|
||||
expect.objectContaining({
|
||||
"Content-Type": "application/json",
|
||||
"X-API-Key": "key123",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Count:", 2] }),
|
||||
expect.objectContaining({ args: ["Has Authorization:", false] }),
|
||||
expect.objectContaining({
|
||||
args: ["Has X-Custom-Header:", false],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("throws error for invalid source", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
try {
|
||||
pm.request.headers.assimilate("invalid")
|
||||
console.log("Should not reach here")
|
||||
} catch (error) {
|
||||
console.log("Error caught:", error.message)
|
||||
}
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Error caught:", "Source must be an array or object"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,950 @@
|
|||
import { HoppRESTRequest, getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript } from "~/web"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://api.example.com/users",
|
||||
method: "GET",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "application/json",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
key: "Authorization",
|
||||
value: "Bearer token123",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
key: "X-API-Version",
|
||||
value: "v1",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
params: [],
|
||||
body: { contentType: null, body: null },
|
||||
auth: { authType: "none", authActive: false },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}
|
||||
|
||||
const envs = { global: [], selected: [] }
|
||||
|
||||
describe("pm.request.headers.each()", () => {
|
||||
test("iterates over all headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const keys = []
|
||||
const values = []
|
||||
|
||||
pm.request.headers.each((header) => {
|
||||
keys.push(header.key)
|
||||
values.push(header.value)
|
||||
})
|
||||
|
||||
console.log("Keys:", keys)
|
||||
console.log("Values:", values)
|
||||
console.log("Count:", keys.length)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Keys:", ["Content-Type", "Authorization", "X-API-Version"]],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Values:", ["application/json", "Bearer token123", "v1"]],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Count:", 3] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("callback receives complete header object", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.each((header) => {
|
||||
console.log("Key:", header.key)
|
||||
console.log("Value:", header.value)
|
||||
console.log("Has active:", 'active' in header)
|
||||
return // Check only first header
|
||||
})
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Key:", "Content-Type"] }),
|
||||
expect.objectContaining({ args: ["Value:", "application/json"] }),
|
||||
expect.objectContaining({ args: ["Has active:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates after headers are modified", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
let count1 = 0
|
||||
pm.request.headers.each(() => { count1++ })
|
||||
console.log("Initial count:", count1)
|
||||
|
||||
pm.request.headers.add({ key: 'X-Custom', value: 'custom-value' })
|
||||
|
||||
let count2 = 0
|
||||
pm.request.headers.each(() => { count2++ })
|
||||
console.log("Count after add:", count2)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial count:", 3] }),
|
||||
expect.objectContaining({ args: ["Count after add:", 4] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.map()", () => {
|
||||
test("transforms headers and returns new array", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const mapped = pm.request.headers.map((header) => {
|
||||
return header.key.toLowerCase() + ': ' + header.value
|
||||
})
|
||||
|
||||
console.log("Mapped:", mapped)
|
||||
console.log("Array length:", mapped.length)
|
||||
console.log("First item:", mapped[0])
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Mapped:",
|
||||
[
|
||||
"content-type: application/json",
|
||||
"authorization: Bearer token123",
|
||||
"x-api-version: v1",
|
||||
],
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Array length:", 3] }),
|
||||
expect.objectContaining({
|
||||
args: ["First item:", "content-type: application/json"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("does not modify original headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const originalCount = pm.request.headers.count()
|
||||
|
||||
const mapped = pm.request.headers.map((header) => {
|
||||
return { modified: header.key }
|
||||
})
|
||||
|
||||
const afterCount = pm.request.headers.count()
|
||||
|
||||
console.log("Original count:", originalCount)
|
||||
console.log("Mapped length:", mapped.length)
|
||||
console.log("After count:", afterCount)
|
||||
console.log("Headers unchanged:", originalCount === afterCount)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Original count:", 3] }),
|
||||
expect.objectContaining({ args: ["Mapped length:", 3] }),
|
||||
expect.objectContaining({ args: ["After count:", 3] }),
|
||||
expect.objectContaining({ args: ["Headers unchanged:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns array with custom objects", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const customHeaders = pm.request.headers.map((header) => ({
|
||||
name: header.key,
|
||||
val: header.value,
|
||||
length: header.value.length
|
||||
}))
|
||||
|
||||
console.log("First custom:", customHeaders[0])
|
||||
console.log("Has name:", 'name' in customHeaders[0])
|
||||
console.log("Has val:", 'val' in customHeaders[0])
|
||||
console.log("Has length:", 'length' in customHeaders[0])
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"First custom:",
|
||||
{ name: "Content-Type", val: "application/json", length: 16 },
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Has name:", true] }),
|
||||
expect.objectContaining({ args: ["Has val:", true] }),
|
||||
expect.objectContaining({ args: ["Has length:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.filter()", () => {
|
||||
test("filters headers based on condition", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const filtered = pm.request.headers.filter((header) => {
|
||||
return header.key.startsWith('X-')
|
||||
})
|
||||
|
||||
console.log("Filtered:", filtered)
|
||||
console.log("Count:", filtered.length)
|
||||
console.log("Keys:", filtered.map(h => h.key))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Count:", 1] }),
|
||||
expect.objectContaining({ args: ["Keys:", ["X-API-Version"]] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("filters by value content", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const filtered = pm.request.headers.filter((header) => {
|
||||
return header.value.includes('Bearer')
|
||||
})
|
||||
|
||||
console.log("Filtered count:", filtered.length)
|
||||
console.log("Header key:", filtered[0].key)
|
||||
console.log("Header value:", filtered[0].value)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Filtered count:", 1] }),
|
||||
expect.objectContaining({ args: ["Header key:", "Authorization"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Header value:", "Bearer token123"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns empty array when no matches", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const filtered = pm.request.headers.filter((header) => {
|
||||
return header.key === 'NonExistent'
|
||||
})
|
||||
|
||||
console.log("Filtered:", filtered)
|
||||
console.log("Is array:", Array.isArray(filtered))
|
||||
console.log("Length:", filtered.length)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Filtered:", []] }),
|
||||
expect.objectContaining({ args: ["Is array:", true] }),
|
||||
expect.objectContaining({ args: ["Length:", 0] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns all headers when condition always true", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const filtered = pm.request.headers.filter((header) => {
|
||||
return true
|
||||
})
|
||||
|
||||
console.log("Count:", filtered.length)
|
||||
console.log("Equals total:", filtered.length === pm.request.headers.count())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Count:", 3] }),
|
||||
expect.objectContaining({ args: ["Equals total:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.count()", () => {
|
||||
test("returns correct count of headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const count = pm.request.headers.count()
|
||||
|
||||
console.log("Count:", count)
|
||||
console.log("Type:", typeof count)
|
||||
|
||||
let manualCount = 0
|
||||
pm.request.headers.each(() => { manualCount++ })
|
||||
console.log("Manual count:", manualCount)
|
||||
console.log("Counts match:", count === manualCount)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Count:", 3] }),
|
||||
expect.objectContaining({ args: ["Type:", "number"] }),
|
||||
expect.objectContaining({ args: ["Manual count:", 3] }),
|
||||
expect.objectContaining({ args: ["Counts match:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates after headers are added or removed", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial count:", pm.request.headers.count())
|
||||
|
||||
pm.request.headers.add({ key: 'X-New-Header', value: 'value' })
|
||||
console.log("After add:", pm.request.headers.count())
|
||||
|
||||
pm.request.headers.remove('Content-Type')
|
||||
console.log("After remove:", pm.request.headers.count())
|
||||
|
||||
pm.request.headers.clear()
|
||||
console.log("After clear:", pm.request.headers.count())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial count:", 3] }),
|
||||
expect.objectContaining({ args: ["After add:", 4] }),
|
||||
expect.objectContaining({ args: ["After remove:", 3] }),
|
||||
expect.objectContaining({ args: ["After clear:", 0] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns 0 for empty headers", () => {
|
||||
const requestWithoutHeaders: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const count = pm.request.headers.count()
|
||||
|
||||
console.log("Count:", count)
|
||||
console.log("Is zero:", count === 0)
|
||||
`,
|
||||
{ envs, request: requestWithoutHeaders }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Count:", 0] }),
|
||||
expect.objectContaining({ args: ["Is zero:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.idx()", () => {
|
||||
test("returns header at specific index", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const first = pm.request.headers.idx(0)
|
||||
const second = pm.request.headers.idx(1)
|
||||
const third = pm.request.headers.idx(2)
|
||||
|
||||
console.log("First:", first)
|
||||
console.log("Second:", second)
|
||||
console.log("Third:", third)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"First:",
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "application/json",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Second:",
|
||||
{
|
||||
key: "Authorization",
|
||||
value: "Bearer token123",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Third:",
|
||||
{
|
||||
key: "X-API-Version",
|
||||
value: "v1",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns null for out-of-bounds index", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const header = pm.request.headers.idx(999)
|
||||
|
||||
console.log("Out of bounds:", header)
|
||||
console.log("Is null:", header === null)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Out of bounds:", null] }),
|
||||
expect.objectContaining({ args: ["Is null:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns null for negative index", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const header = pm.request.headers.idx(-1)
|
||||
|
||||
console.log("Negative index:", header)
|
||||
console.log("Is null:", header === null)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Negative index:", null] }),
|
||||
expect.objectContaining({ args: ["Is null:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("can access header properties via idx", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const header = pm.request.headers.idx(0)
|
||||
|
||||
console.log("Key:", header.key)
|
||||
console.log("Value:", header.value)
|
||||
console.log("Active:", header.active)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Key:", "Content-Type"] }),
|
||||
expect.objectContaining({ args: ["Value:", "application/json"] }),
|
||||
expect.objectContaining({ args: ["Active:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.clear()", () => {
|
||||
test("removes all headers", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Count before:", pm.request.headers.count())
|
||||
console.log("Headers before:", pm.request.headers.toObject())
|
||||
|
||||
pm.request.headers.clear()
|
||||
|
||||
console.log("Count after:", pm.request.headers.count())
|
||||
console.log("Headers after:", pm.request.headers.toObject())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
headers: [],
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Count before:", 3] }),
|
||||
expect.objectContaining({ args: ["Count after:", 0] }),
|
||||
expect.objectContaining({ args: ["Headers after:", {}] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("allows adding headers after clearing", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.clear()
|
||||
console.log("After clear:", pm.request.headers.count())
|
||||
|
||||
pm.request.headers.add({ key: 'X-New', value: 'value' })
|
||||
console.log("After add:", pm.request.headers.count())
|
||||
console.log("Headers:", pm.request.headers.toObject())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["After clear:", 0] }),
|
||||
expect.objectContaining({ args: ["After add:", 1] }),
|
||||
expect.objectContaining({ args: ["Headers:", { "X-New": "value" }] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("clear followed by get returns null", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Before clear - Content-Type:", pm.request.headers.get('Content-Type'))
|
||||
|
||||
pm.request.headers.clear()
|
||||
|
||||
console.log("After clear - Content-Type:", pm.request.headers.get('Content-Type'))
|
||||
console.log("After clear - has Content-Type:", pm.request.headers.has('Content-Type'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Before clear - Content-Type:", "application/json"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After clear - Content-Type:", null],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["After clear - has Content-Type:", false],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.toObject()", () => {
|
||||
test("returns headers as key-value object", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const obj = pm.request.headers.toObject()
|
||||
|
||||
console.log("Headers object:", obj)
|
||||
console.log("Type:", typeof obj)
|
||||
console.log("Content-Type:", obj['Content-Type'])
|
||||
console.log("Authorization:", obj['Authorization'])
|
||||
console.log("X-API-Version:", obj['X-API-Version'])
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Headers object:",
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer token123",
|
||||
"X-API-Version": "v1",
|
||||
},
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Type:", "object"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Content-Type:", "application/json"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Authorization:", "Bearer token123"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["X-API-Version:", "v1"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("matches all() method", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const toObjectResult = pm.request.headers.toObject()
|
||||
const allResult = pm.request.headers.all()
|
||||
|
||||
console.log("toObject:", toObjectResult)
|
||||
console.log("all:", allResult)
|
||||
console.log("Are equal:", JSON.stringify(toObjectResult) === JSON.stringify(allResult))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Are equal:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns empty object for empty headers", () => {
|
||||
const requestWithoutHeaders: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const obj = pm.request.headers.toObject()
|
||||
|
||||
console.log("Headers object:", obj)
|
||||
console.log("Keys count:", Object.keys(obj).length)
|
||||
console.log("Is empty:", Object.keys(obj).length === 0)
|
||||
`,
|
||||
{ envs, request: requestWithoutHeaders }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Headers object:", {}] }),
|
||||
expect.objectContaining({ args: ["Keys count:", 0] }),
|
||||
expect.objectContaining({ args: ["Is empty:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates dynamically after mutations", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const before = pm.request.headers.toObject()
|
||||
console.log("Before:", before)
|
||||
|
||||
pm.request.headers.add({ key: 'X-Custom', value: 'test' })
|
||||
|
||||
const after = pm.request.headers.toObject()
|
||||
console.log("After:", after)
|
||||
console.log("Has X-Custom:", 'X-Custom' in after)
|
||||
console.log("X-Custom value:", after['X-Custom'])
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Has X-Custom:", true] }),
|
||||
expect.objectContaining({ args: ["X-Custom value:", "test"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("combined headers PropertyList operations", () => {
|
||||
test("chaining multiple PropertyList methods", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const filteredAndMapped = pm.request.headers
|
||||
.filter(h => h.key.startsWith('X-') || h.key === 'Content-Type')
|
||||
.map(h => ({ name: h.key, length: h.value.length }))
|
||||
|
||||
console.log("Result:", filteredAndMapped)
|
||||
console.log("Count:", filteredAndMapped.length)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Result:",
|
||||
[
|
||||
{ name: "Content-Type", length: 16 },
|
||||
{ name: "X-API-Version", length: 2 },
|
||||
],
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Count:", 2] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("using each to build custom structure", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const headerMap = new Map()
|
||||
|
||||
pm.request.headers.each((header) => {
|
||||
headerMap.set(header.key.toLowerCase(), header.value.toUpperCase())
|
||||
})
|
||||
|
||||
console.log("Map size:", headerMap.size)
|
||||
console.log("content-type:", headerMap.get('content-type'))
|
||||
console.log("authorization:", headerMap.get('authorization'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Map size:", 3] }),
|
||||
expect.objectContaining({
|
||||
args: ["content-type:", "APPLICATION/JSON"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["authorization:", "BEARER TOKEN123"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.headers.remove() - case insensitive", () => {
|
||||
const envs: TestResult["envs"] = {
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
...getDefaultRESTRequest(),
|
||||
name: "Test",
|
||||
method: "GET",
|
||||
endpoint: "https://example.com/api",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json", active: true },
|
||||
{ key: "Authorization", value: "Bearer token123", active: true },
|
||||
{ key: "X-Custom-Header", value: "custom-value", active: true },
|
||||
],
|
||||
}
|
||||
|
||||
test("removes header with exact case match", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const hasContentType = pm.request.headers.has("Content-Type")
|
||||
pm.request.headers.remove("Content-Type")
|
||||
const afterRemove = pm.request.headers.has("Content-Type")
|
||||
|
||||
console.log("Has before:", hasContentType)
|
||||
console.log("Has after:", afterRemove)
|
||||
console.log("Count after:", pm.request.headers.count())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
headers: expect.not.arrayContaining([
|
||||
expect.objectContaining({ key: "Content-Type" }),
|
||||
]),
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Has before:", true] }),
|
||||
expect.objectContaining({ args: ["Has after:", false] }),
|
||||
expect.objectContaining({ args: ["Count after:", 2] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("removes header with different case (lowercase)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const hasAuth = pm.request.headers.has("Authorization")
|
||||
// Remove with lowercase - should be case-insensitive
|
||||
pm.request.headers.remove("authorization")
|
||||
const afterRemove = pm.request.headers.has("Authorization")
|
||||
|
||||
console.log("Has before:", hasAuth)
|
||||
console.log("Has after:", afterRemove)
|
||||
console.log("Count after:", pm.request.headers.count())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
headers: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: expect.stringMatching(/^authorization$/i),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Has before:", true] }),
|
||||
expect.objectContaining({ args: ["Has after:", false] }),
|
||||
expect.objectContaining({ args: ["Count after:", 2] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("removes header with different case (UPPERCASE)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const hasCustom = pm.request.headers.has("X-Custom-Header")
|
||||
// Remove with uppercase - should be case-insensitive
|
||||
pm.request.headers.remove("X-CUSTOM-HEADER")
|
||||
const afterRemove = pm.request.headers.has("X-Custom-Header")
|
||||
|
||||
console.log("Has before:", hasCustom)
|
||||
console.log("Has after:", afterRemove)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
headers: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: expect.stringMatching(/^x-custom-header$/i),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Has before:", true] }),
|
||||
expect.objectContaining({ args: ["Has after:", false] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("multiple remove operations with mixed case", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.headers.remove("Content-Type")
|
||||
pm.request.headers.remove("authorization") // lowercase
|
||||
|
||||
const hasContentType = pm.request.headers.has("Content-Type")
|
||||
const hasAuth = pm.request.headers.has("Authorization")
|
||||
const hasCustom = pm.request.headers.has("X-Custom-Header")
|
||||
|
||||
console.log("Final count:", pm.request.headers.count())
|
||||
console.log("Has Content-Type:", hasContentType)
|
||||
console.log("Has Authorization:", hasAuth)
|
||||
console.log("Has Custom:", hasCustom)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
headers: [
|
||||
{ key: "X-Custom-Header", value: "custom-value", active: true },
|
||||
],
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Final count:", 1] }),
|
||||
expect.objectContaining({ args: ["Has Content-Type:", false] }),
|
||||
expect.objectContaining({ args: ["Has Authorization:", false] }),
|
||||
expect.objectContaining({ args: ["Has Custom:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,633 @@
|
|||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript } from "~/web"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint:
|
||||
"https://api.example.com:8080/v1/users/profile?filter=active&sort=name",
|
||||
method: "GET",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "application/json",
|
||||
active: true,
|
||||
description: "",
|
||||
},
|
||||
],
|
||||
params: [
|
||||
{ key: "filter", value: "active", active: true, description: "" },
|
||||
{ key: "sort", value: "name", active: true, description: "" },
|
||||
],
|
||||
body: { contentType: null, body: null },
|
||||
auth: { authType: "none", authActive: false },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}
|
||||
|
||||
const envs = { global: [], selected: [] }
|
||||
|
||||
describe("pm.request.url.getHost()", () => {
|
||||
test("returns hostname as a string", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const host = pm.request.url.getHost()
|
||||
console.log("Host:", host)
|
||||
console.log("Host type:", typeof host)
|
||||
console.log("Is string:", typeof host === 'string')
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Host:", "api.example.com"] }),
|
||||
expect.objectContaining({ args: ["Host type:", "string"] }),
|
||||
expect.objectContaining({ args: ["Is string:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates after host mutation", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial host:", pm.request.url.getHost())
|
||||
|
||||
pm.request.url.host = ['newapi', 'test', 'com']
|
||||
|
||||
console.log("Updated host:", pm.request.url.getHost())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Initial host:", "api.example.com"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Updated host:", "newapi.test.com"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.getPath()", () => {
|
||||
test("returns path as string with leading slash", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const path = pm.request.url.getPath()
|
||||
console.log("Path:", path)
|
||||
console.log("Starts with slash:", path.startsWith('/'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Path:", "/v1/users/profile"] }),
|
||||
expect.objectContaining({ args: ["Starts with slash:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns '/' for empty path", () => {
|
||||
const requestWithoutPath: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const path = pm.request.url.getPath()
|
||||
console.log("Path:", path)
|
||||
console.log("Is root:", path === '/')
|
||||
`,
|
||||
{ envs, request: requestWithoutPath }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Path:", "/"] }),
|
||||
expect.objectContaining({ args: ["Is root:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates after path mutation", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial path:", pm.request.url.getPath())
|
||||
|
||||
pm.request.url.path = ['api', 'v2', 'posts']
|
||||
|
||||
console.log("Updated path:", pm.request.url.getPath())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Initial path:", "/v1/users/profile"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Updated path:", "/api/v2/posts"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.getPathWithQuery()", () => {
|
||||
test("returns path with query string", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const pathWithQuery = pm.request.url.getPathWithQuery()
|
||||
console.log("Path with query:", pathWithQuery)
|
||||
console.log("Includes path:", pathWithQuery.includes('/v1/users/profile'))
|
||||
console.log("Includes query:", pathWithQuery.includes('filter=active'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Path with query:",
|
||||
"/v1/users/profile?filter=active&sort=name",
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Includes path:", true] }),
|
||||
expect.objectContaining({ args: ["Includes query:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns only path when no query parameters", () => {
|
||||
const requestWithoutQuery: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
params: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const pathWithQuery = pm.request.url.getPathWithQuery()
|
||||
console.log("Path with query:", pathWithQuery)
|
||||
console.log("Has question mark:", pathWithQuery.includes('?'))
|
||||
`,
|
||||
{ envs, request: requestWithoutQuery }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Path with query:", "/users"] }),
|
||||
expect.objectContaining({ args: ["Has question mark:", false] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("updates after query mutation", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial:", pm.request.url.getPathWithQuery())
|
||||
|
||||
pm.request.url.query.add({ key: 'page', value: '5' })
|
||||
|
||||
console.log("Updated:", pm.request.url.getPathWithQuery())
|
||||
console.log("Includes new param:", pm.request.url.getPathWithQuery().includes('page=5'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Initial:", "/v1/users/profile?filter=active&sort=name"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Updated:",
|
||||
"/v1/users/profile?filter=active&sort=name&page=5",
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Includes new param:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.getQueryString()", () => {
|
||||
test("returns query string without leading question mark", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const queryString = pm.request.url.getQueryString()
|
||||
console.log("Query string:", queryString)
|
||||
console.log("Starts with question mark:", queryString.startsWith('?'))
|
||||
console.log("Contains filter:", queryString.includes('filter=active'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Query string:", "filter=active&sort=name"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Starts with question mark:", false],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Contains filter:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("returns empty string when no query parameters", () => {
|
||||
const requestWithoutQuery: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
params: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const queryString = pm.request.url.getQueryString()
|
||||
console.log("Query string:", queryString)
|
||||
console.log("Is empty:", queryString === '')
|
||||
console.log("Length:", queryString.length)
|
||||
`,
|
||||
{ envs, request: requestWithoutQuery }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Query string:", ""] }),
|
||||
expect.objectContaining({ args: ["Is empty:", true] }),
|
||||
expect.objectContaining({ args: ["Length:", 0] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.getRemote()", () => {
|
||||
test("includes port when not standard (443/80)", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const remote = pm.request.url.getRemote()
|
||||
console.log("Remote:", remote)
|
||||
console.log("Includes port:", remote.includes(':8080'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Remote:", "api.example.com:8080"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Includes port:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("excludes standard HTTPS port (443) by default", () => {
|
||||
const requestWithStandardPort: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const remote = pm.request.url.getRemote()
|
||||
console.log("Remote:", remote)
|
||||
console.log("Has port in string:", remote.includes(':'))
|
||||
`,
|
||||
{ envs, request: requestWithStandardPort }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Remote:", "api.example.com"] }),
|
||||
expect.objectContaining({ args: ["Has port in string:", false] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("forces port display when forcePort is true", () => {
|
||||
const requestWithStandardPort: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const remote = pm.request.url.getRemote(true)
|
||||
console.log("Remote with forced port:", remote)
|
||||
console.log("Has port in string:", remote.includes(':443'))
|
||||
`,
|
||||
{ envs, request: requestWithStandardPort }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Remote with forced port:", "api.example.com:443"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Has port in string:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.update()", () => {
|
||||
test("updates entire URL from string", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
|
||||
pm.request.url.update('http://newapi.test.com:3000/v2/posts?id=123')
|
||||
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
console.log("Protocol:", pm.request.url.protocol)
|
||||
console.log("Host:", pm.request.url.getHost())
|
||||
console.log("Port:", pm.request.url.port)
|
||||
console.log("Path:", pm.request.url.getPath())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "http://newapi.test.com:3000/v2/posts?id=123",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Protocol:", "http"] }),
|
||||
expect.objectContaining({ args: ["Host:", "newapi.test.com"] }),
|
||||
expect.objectContaining({ args: ["Port:", "3000"] }),
|
||||
expect.objectContaining({ args: ["Path:", "/v2/posts"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("accepts object with toString() method", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const urlObj = {
|
||||
toString: () => 'https://custom.api.com/endpoint'
|
||||
}
|
||||
|
||||
pm.request.url.update(urlObj)
|
||||
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://custom.api.com/endpoint",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Updated URL:", "https://custom.api.com/endpoint"],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("throws error for invalid input", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
try {
|
||||
pm.request.url.update(12345)
|
||||
console.log("Should not reach here")
|
||||
} catch (error) {
|
||||
console.log("Error caught:", error.message)
|
||||
}
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: expect.arrayContaining([
|
||||
"Error caught:",
|
||||
expect.stringContaining("URL update requires"),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.addQueryParams()", () => {
|
||||
test("adds multiple query parameters", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial params:", pm.request.url.query.all())
|
||||
|
||||
pm.request.url.addQueryParams([
|
||||
{ key: 'page', value: '1' },
|
||||
{ key: 'limit', value: '20' }
|
||||
])
|
||||
|
||||
console.log("Updated params:", pm.request.url.query.all())
|
||||
console.log("URL:", pm.request.url.toString())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: expect.stringContaining("page=1&limit=20"),
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Updated params:",
|
||||
{ filter: "active", sort: "name", page: "1", limit: "20" },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("handles empty value parameters", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.url.addQueryParams([
|
||||
{ key: 'flag' },
|
||||
{ key: 'emptyValue', value: '' }
|
||||
])
|
||||
|
||||
console.log("Params:", pm.request.url.query.all())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: [
|
||||
"Params:",
|
||||
{ filter: "active", sort: "name", flag: "", emptyValue: "" },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("throws error for non-array input", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
try {
|
||||
pm.request.url.addQueryParams({ key: 'test', value: '123' })
|
||||
console.log("Should not reach here")
|
||||
} catch (error) {
|
||||
console.log("Error caught:", error.message)
|
||||
}
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: expect.arrayContaining([
|
||||
"Error caught:",
|
||||
expect.stringContaining("requires an array"),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm.request.url.removeQueryParams()", () => {
|
||||
test("removes single query parameter by name", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial params:", pm.request.url.query.all())
|
||||
|
||||
pm.request.url.removeQueryParams('filter')
|
||||
|
||||
console.log("Updated params:", pm.request.url.query.all())
|
||||
console.log("URL:", pm.request.url.toString())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com:8080/v1/users/profile?sort=name",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Updated params:", { sort: "name" }],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("removes multiple query parameters by array", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial params:", pm.request.url.query.all())
|
||||
|
||||
pm.request.url.removeQueryParams(['filter', 'sort'])
|
||||
|
||||
console.log("Updated params:", pm.request.url.query.all())
|
||||
console.log("Params object is empty:", Object.keys(pm.request.url.query.all()).length === 0)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com:8080/v1/users/profile",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Updated params:", {}],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Params object is empty:", true],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("handles non-existent parameter names gracefully", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial params:", pm.request.url.query.all())
|
||||
|
||||
pm.request.url.removeQueryParams(['nonexistent', 'alsoNotThere'])
|
||||
|
||||
console.log("Updated params:", pm.request.url.query.all())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Initial params:", { filter: "active", sort: "name" }],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
args: ["Updated params:", { filter: "active", sort: "name" }],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript } from "~/web"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://api.example.com/users#section1",
|
||||
method: "GET",
|
||||
headers: [],
|
||||
params: [],
|
||||
body: { contentType: null, body: null },
|
||||
auth: { authType: "none", authActive: false },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}
|
||||
|
||||
const envs = { global: [], selected: [] }
|
||||
|
||||
describe("pm.request.url.hash property", () => {
|
||||
test("hash getter returns fragment without leading #", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const hash = pm.request.url.hash
|
||||
console.log("Hash:", hash)
|
||||
console.log("Hash type:", typeof hash)
|
||||
console.log("Starts with #:", hash.startsWith('#'))
|
||||
console.log("Full URL:", pm.request.url.toString())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Hash:", "section1"] }),
|
||||
expect.objectContaining({ args: ["Hash type:", "string"] }),
|
||||
expect.objectContaining({ args: ["Starts with #:", false] }),
|
||||
expect.objectContaining({
|
||||
args: expect.arrayContaining([
|
||||
"Full URL:",
|
||||
expect.stringContaining("#section1"),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash getter returns empty string when no fragment", () => {
|
||||
const requestWithoutHash: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
const hash = pm.request.url.hash
|
||||
console.log("Hash:", hash)
|
||||
console.log("Hash is empty:", hash === '')
|
||||
console.log("Hash length:", hash.length)
|
||||
`,
|
||||
{ envs, request: requestWithoutHash }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Hash:", ""] }),
|
||||
expect.objectContaining({ args: ["Hash is empty:", true] }),
|
||||
expect.objectContaining({ args: ["Hash length:", 0] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash setter adds fragment to URL", () => {
|
||||
const requestWithoutHash: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial hash:", pm.request.url.hash)
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
|
||||
pm.request.url.hash = 'overview'
|
||||
|
||||
console.log("Updated hash:", pm.request.url.hash)
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
console.log("URL includes #:", pm.request.url.toString().includes('#overview'))
|
||||
`,
|
||||
{ envs, request: requestWithoutHash }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com/users#overview",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial hash:", ""] }),
|
||||
expect.objectContaining({ args: ["Updated hash:", "overview"] }),
|
||||
expect.objectContaining({ args: ["URL includes #:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash setter updates existing fragment", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial hash:", pm.request.url.hash)
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
|
||||
pm.request.url.hash = 'newsection'
|
||||
|
||||
console.log("Updated hash:", pm.request.url.hash)
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com/users#newsection",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial hash:", "section1"] }),
|
||||
expect.objectContaining({ args: ["Updated hash:", "newsection"] }),
|
||||
expect.objectContaining({
|
||||
args: expect.arrayContaining([
|
||||
"Updated URL:",
|
||||
expect.stringContaining("#newsection"),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash setter accepts value with leading #", () => {
|
||||
const requestWithoutHash: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users",
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pm.request.url.hash = '#details'
|
||||
|
||||
console.log("Hash:", pm.request.url.hash)
|
||||
console.log("URL:", pm.request.url.toString())
|
||||
console.log("Hash value:", pm.request.url.hash === 'details')
|
||||
`,
|
||||
{ envs, request: requestWithoutHash }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com/users#details",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Hash:", "details"] }),
|
||||
expect.objectContaining({ args: ["Hash value:", true] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash setter removes fragment when set to empty string", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial hash:", pm.request.url.hash)
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
|
||||
pm.request.url.hash = ''
|
||||
|
||||
console.log("Updated hash:", pm.request.url.hash)
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
console.log("URL has #:", pm.request.url.toString().includes('#'))
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com/users",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial hash:", "section1"] }),
|
||||
expect.objectContaining({ args: ["Updated hash:", ""] }),
|
||||
expect.objectContaining({ args: ["URL has #:", false] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test("hash works with query parameters", () => {
|
||||
const requestWithQueryAndHash: HoppRESTRequest = {
|
||||
...baseRequest,
|
||||
endpoint: "https://api.example.com/users?filter=active#top",
|
||||
params: [
|
||||
{ key: "filter", value: "active", active: true, description: "" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
console.log("Initial hash:", pm.request.url.hash)
|
||||
console.log("Query params:", pm.request.url.query.all())
|
||||
|
||||
pm.request.url.hash = 'bottom'
|
||||
|
||||
console.log("Updated URL:", pm.request.url.toString())
|
||||
console.log("Updated hash:", pm.request.url.hash)
|
||||
console.log("Query params unchanged:", JSON.stringify(pm.request.url.query.all()))
|
||||
`,
|
||||
{ envs, request: requestWithQueryAndHash }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://api.example.com/users?filter=active#bottom",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({ args: ["Initial hash:", "top"] }),
|
||||
expect.objectContaining({ args: ["Updated hash:", "bottom"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Query params:", { filter: "active" }],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("combined host and hash mutations", () => {
|
||||
test("host and hash can be changed independently", () => {
|
||||
return expect(
|
||||
runPreRequestScript(
|
||||
`
|
||||
console.log("Initial URL:", pm.request.url.toString())
|
||||
console.log("Initial host:", pm.request.url.getHost())
|
||||
console.log("Initial hash:", pm.request.url.hash)
|
||||
|
||||
pm.request.url.host = ['newdomain', 'com']
|
||||
console.log("After host change:", pm.request.url.toString())
|
||||
|
||||
pm.request.url.hash = 'newhash'
|
||||
console.log("After hash change:", pm.request.url.toString())
|
||||
|
||||
console.log("Final host:", pm.request.url.getHost())
|
||||
console.log("Final hash:", pm.request.url.hash)
|
||||
`,
|
||||
{ envs, request: baseRequest }
|
||||
)
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
updatedRequest: expect.objectContaining({
|
||||
endpoint: "https://newdomain.com/users#newhash",
|
||||
}),
|
||||
consoleEntries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
args: ["Initial host:", "api.example.com"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Initial hash:", "section1"] }),
|
||||
expect.objectContaining({
|
||||
args: ["Final host:", "newdomain.com"],
|
||||
}),
|
||||
expect.objectContaining({ args: ["Final hash:", "newhash"] }),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello, World!" }),
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "Authorization", value: "Bearer token123" },
|
||||
],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
describe("pm.response", () => {
|
||||
test("pm.response.code provides access to status code", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const code = pm.response.code
|
||||
pw.expect(code.toString()).toBe("200")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '200' to be '200'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.status provides access to status text", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const status = pm.response.status
|
||||
pw.expect(status).toBe("OK")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'OK' to be 'OK'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.text() provides response body as text", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const text = pm.response.text()
|
||||
pw.expect(text).toBe('{"message":"Hello, World!"}')
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.json() provides parsed JSON response", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const json = pm.response.json()
|
||||
pw.expect(json.message).toBe("Hello, World!")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Hello, World!' to be 'Hello, World!'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.headers provides access to response headers", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const headers = pm.response.headers
|
||||
pw.expect(headers.get("Content-Type")).toBe("application/json")
|
||||
pw.expect(headers.get("Authorization")).toBe("Bearer token123")
|
||||
pw.expect(headers.get("nonexistent")).toBe(null)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'application/json' to be 'application/json'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Bearer token123' to be 'Bearer token123'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'null' to be 'null'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response object has correct structure and values", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
pw.expect(pm.response.code).toBe(200)
|
||||
pw.expect(pm.response.status).toBe("OK")
|
||||
pw.expect(pm.response.text()).toBe('{"message":"Hello, World!"}')
|
||||
pw.expect(pm.response.json().message).toBe("Hello, World!")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '200' to be '200'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'OK' to be 'OK'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Hello, World!' to be 'Hello, World!'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
const customResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello, World!" }),
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "Authorization", value: "Bearer token123" },
|
||||
],
|
||||
}
|
||||
|
||||
const func = (
|
||||
script: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse = customResponse
|
||||
) => runTest(script, envs, response)
|
||||
|
||||
describe("pm.response", () => {
|
||||
test("pm.response.code provides access to status code", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const code = pm.response.code
|
||||
pw.expect(code.toString()).toBe("200")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '200' to be '200'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.status provides access to status text", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const status = pm.response.status
|
||||
pw.expect(status).toBe("OK")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'OK' to be 'OK'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.text() provides response body as text", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const text = pm.response.text()
|
||||
pw.expect(text).toBe('{"message":"Hello, World!"}')
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.json() provides parsed JSON response", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const json = pm.response.json()
|
||||
pw.expect(json.message).toBe("Hello, World!")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Hello, World!' to be 'Hello, World!'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.headers provides access to response headers", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const headers = pm.response.headers
|
||||
pw.expect(headers.get("Content-Type")).toBe("application/json")
|
||||
pw.expect(headers.get("Authorization")).toBe("Bearer token123")
|
||||
// Postman returns undefined for non-existent headers, not null
|
||||
pw.expect(headers.get("nonexistent")).toBe(undefined)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'application/json' to be 'application/json'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Bearer token123' to be 'Bearer token123'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response object has correct structure and values", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
pw.expect(pm.response.code).toBe(200)
|
||||
pw.expect(pm.response.status).toBe("OK")
|
||||
pw.expect(pm.response.text()).toBe('{"message":"Hello, World!"}')
|
||||
pw.expect(pm.response.json().message).toBe("Hello, World!")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected '200' to be '200'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'OK' to be 'OK'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'Hello, World!' to be 'Hello, World!'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.stream provides response body as Uint8Array", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const stream = pm.response.stream
|
||||
// Verify it's a Uint8Array by checking it can be decoded
|
||||
const decoder = new TextDecoder()
|
||||
const decoded = decoder.decode(stream)
|
||||
pw.expect(decoded).toBe('{"message":"Hello, World!"}')
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.stream contains correct byte data", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const stream = pm.response.stream
|
||||
const decoder = new TextDecoder()
|
||||
const text = decoder.decode(stream)
|
||||
pw.expect(text).toBe('{"message":"Hello, World!"}')
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message:
|
||||
'Expected \'{"message":"Hello, World!"}\' to be \'{"message":"Hello, World!"}\'',
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.reason() returns HTTP reason phrase", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const reason = pm.response.reason()
|
||||
pw.expect(reason).toBe("OK")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'OK' to be 'OK'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.dataURI() converts response to data URI", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const dataURI = pm.response.dataURI()
|
||||
pw.expect(dataURI).toBeType("string")
|
||||
// Check it starts with data: and contains base64
|
||||
const startsWithData = dataURI.startsWith("data:")
|
||||
pw.expect(startsWithData).toBe(true)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("to be type 'string'"),
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'true' to be 'true'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.jsonp() parses JSONP response", () => {
|
||||
const jsonpResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: 'callback({"data": "test value"})',
|
||||
headers: [{ key: "Content-Type", value: "application/javascript" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const data = pm.response.jsonp("callback")
|
||||
pw.expect(data.data).toBe("test value")
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
jsonpResponse
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'test value' to be 'test value'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.response.jsonp() handles response without callback wrapper", () => {
|
||||
const jsonResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: '{"plain": "json"}',
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
const data = pm.response.jsonp()
|
||||
pw.expect(data.plain).toBe("json")
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
jsonResponse
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'json' to be 'json'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,897 @@
|
|||
/**
|
||||
* @file Tests for Postman BDD-style response assertions (pm.response.to.*)
|
||||
*
|
||||
* These tests verify the Postman compatibility layer's BDD-style assertion helpers
|
||||
* which are commonly used in Postman collections. They provide syntactic sugar over
|
||||
* the standard Chai assertions for common response validation patterns.
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("`pm.response.to.have.*` - Status Code Assertions", () => {
|
||||
test("should support `.status()` for exact status code matching", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Status code is 200", function() {
|
||||
pm.response.to.have.status(200)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Status code is 200",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 200 to equal 200",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail `.status()` when status code doesn't match", () => {
|
||||
const response: TestResponse = {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Status code is 200", function() {
|
||||
pm.response.to.have.status(200)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Status code is 200",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("Expected 404 to equal 200"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.be.*` - Status Code Convenience Methods", () => {
|
||||
test("should support `.ok()` for 2xx status codes", () => {
|
||||
const responses = [
|
||||
{ status: 200, statusText: "OK" },
|
||||
{ status: 201, statusText: "Created" },
|
||||
{ status: 204, statusText: "No Content" },
|
||||
]
|
||||
|
||||
return Promise.all(
|
||||
responses.map((r) =>
|
||||
expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is OK", function() {
|
||||
pm.response.to.be.ok()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
{ ...r, body: "{}", headers: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
test("should fail `.ok()` for non-2xx status codes", () => {
|
||||
const response: TestResponse = {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is OK", function() {
|
||||
pm.response.to.be.ok()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.any(String),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.accepted()` for 202 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 202,
|
||||
statusText: "Accepted",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Request accepted", function() {
|
||||
pm.response.to.be.accepted()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.badRequest()` for 400 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 400,
|
||||
statusText: "Bad Request",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Bad request error", function() {
|
||||
pm.response.to.be.badRequest()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.unauthorized()` for 401 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Unauthorized error", function() {
|
||||
pm.response.to.be.unauthorized()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.forbidden()` for 403 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 403,
|
||||
statusText: "Forbidden",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Forbidden error", function() {
|
||||
pm.response.to.be.forbidden()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.notFound()` for 404 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Not found error", function() {
|
||||
pm.response.to.be.notFound()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.rateLimited()` for 429 status code", () => {
|
||||
const response: TestResponse = {
|
||||
status: 429,
|
||||
statusText: "Too Many Requests",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Rate limited", function() {
|
||||
pm.response.to.be.rateLimited()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.serverError()` for 5xx status codes", () => {
|
||||
const responses = [
|
||||
{ status: 500, statusText: "Internal Server Error" },
|
||||
{ status: 502, statusText: "Bad Gateway" },
|
||||
{ status: 503, statusText: "Service Unavailable" },
|
||||
]
|
||||
|
||||
return Promise.all(
|
||||
responses.map((r) =>
|
||||
expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Server error", function() {
|
||||
pm.response.to.be.serverError()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
{ ...r, body: "{}", headers: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.header()` - Header Assertions", () => {
|
||||
test("should check header existence", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "X-Custom-Header", value: "custom-value" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Headers exist", function() {
|
||||
pm.response.to.have.header("Content-Type")
|
||||
pm.response.to.have.header("X-Custom-Header")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should check header value when specified", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Header has correct value", function() {
|
||||
pm.response.to.have.header("Content-Type", "application/json")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should be case-insensitive for header names", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Case insensitive header check", function() {
|
||||
pm.response.to.have.header("content-type")
|
||||
pm.response.to.have.header("CONTENT-TYPE")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.body()` - Body Assertions", () => {
|
||||
test("should match exact body content", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Hello, World!",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Body matches", function() {
|
||||
pm.response.to.have.body("Hello, World!")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.jsonBody()` - JSON Body Assertions", () => {
|
||||
test("should assert response is JSON object when called without arguments", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello", count: 42 }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is JSON", function() {
|
||||
pm.response.to.have.jsonBody()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should check for property existence when key provided", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello", count: 42 }),
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("JSON has properties", function() {
|
||||
pm.response.to.have.jsonBody("message")
|
||||
pm.response.to.have.jsonBody("count")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should check property value when key and value provided", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello", count: 42 }),
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("JSON property values match", function() {
|
||||
pm.response.to.have.jsonBody("message", "Hello")
|
||||
pm.response.to.have.jsonBody("count", 42)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support nested property checks", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ user: { name: "Alice", age: 30 } }),
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Nested properties", function() {
|
||||
const data = pm.response.json()
|
||||
pm.expect(data.user).to.have.property("name")
|
||||
pm.expect(data.user.name).to.equal("Alice")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.be.*` - Content Type Convenience Methods", () => {
|
||||
test("should support `.json()` for JSON content type", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json; charset=utf-8" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is JSON", function() {
|
||||
pm.response.to.be.json()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.html()` for HTML content type", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "<html></html>",
|
||||
headers: [{ key: "Content-Type", value: "text/html; charset=utf-8" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is HTML", function() {
|
||||
pm.response.to.be.html()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.xml()` for XML content types", () => {
|
||||
const responses = [
|
||||
{ headers: [{ key: "Content-Type", value: "application/xml" }] },
|
||||
{ headers: [{ key: "Content-Type", value: "text/xml" }] },
|
||||
]
|
||||
|
||||
return Promise.all(
|
||||
responses.map((r) =>
|
||||
expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is XML", function() {
|
||||
pm.response.to.be.xml()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
{ status: 200, statusText: "OK", body: "<xml></xml>", ...r }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
test("should support `.text()` for plain text content type", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Plain text response",
|
||||
headers: [{ key: "Content-Type", value: "text/plain" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response is text", function() {
|
||||
pm.response.to.be.text()
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.responseTime.*` - Response Time Assertions", () => {
|
||||
test("should support `.below()` for response time upper bound", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
responseTime: 250,
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response time is acceptable", function() {
|
||||
pm.response.to.have.responseTime.below(500)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.above()` for response time lower bound", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
responseTime: 250,
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response time is above threshold", function() {
|
||||
pm.response.to.have.responseTime.above(100)
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Real-world Postman script patterns", () => {
|
||||
test("should handle complex assertion combinations", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: { id: 123, name: "Test" },
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json" },
|
||||
{ key: "X-Request-ID", value: "abc-123" },
|
||||
],
|
||||
responseTime: 150,
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("API response validation", function() {
|
||||
// Status code checks
|
||||
pm.response.to.have.status(200)
|
||||
pm.response.to.be.ok()
|
||||
|
||||
// Header checks
|
||||
pm.response.to.have.header("Content-Type")
|
||||
pm.response.to.have.header("X-Request-ID", "abc-123")
|
||||
|
||||
// Content type
|
||||
pm.response.to.be.json()
|
||||
|
||||
// JSON body checks
|
||||
pm.response.to.have.jsonBody("success", true)
|
||||
pm.response.to.have.jsonBody("data")
|
||||
|
||||
// Response time
|
||||
pm.response.to.have.responseTime.below(500)
|
||||
|
||||
// Detailed JSON assertions
|
||||
const json = pm.response.json()
|
||||
pm.expect(json.data.id).to.equal(123)
|
||||
pm.expect(json.data.name).to.equal("Test")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "API response validation",
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: expect.any(String) },
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
/**
|
||||
* @file Tests for Postman cookie handling (pm.response.cookies.*, pm.response.to.have.cookie)
|
||||
*
|
||||
* These tests verify cookie parsing from Set-Cookie headers and cookie assertions.
|
||||
* Cookies in responses are extracted from Set-Cookie headers and made available
|
||||
* through the pm.response.cookies API.
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("`pm.response.cookies` - Cookie Access Methods", () => {
|
||||
test("should support `.get()` to retrieve a cookie value by name", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Set-Cookie", value: "session=abc123; Path=/; HttpOnly" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Can retrieve cookie value by name", function() {
|
||||
const cookieValue = pm.response.cookies.get("session")
|
||||
pm.expect(cookieValue).to.not.be.null
|
||||
pm.expect(cookieValue).to.equal("abc123")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Can retrieve cookie value by name",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should return null for non-existent cookies", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Set-Cookie", value: "session=abc123; Path=/" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Returns null for non-existent cookie", function() {
|
||||
const cookie = pm.response.cookies.get("nonexistent")
|
||||
pm.expect(cookie).to.be.null
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Returns null for non-existent cookie",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining("Expected null to be null"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.has()` to check cookie existence", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Set-Cookie", value: "auth_token=xyz789; Secure" },
|
||||
{ key: "Set-Cookie", value: "user_id=42; SameSite=Strict" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Can check cookie existence", function() {
|
||||
pm.expect(pm.response.cookies.has("auth_token")).to.be.true
|
||||
pm.expect(pm.response.cookies.has("user_id")).to.be.true
|
||||
pm.expect(pm.response.cookies.has("nonexistent")).to.be.false
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Can check cookie existence",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should support `.toObject()` to get all cookies", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{ key: "Set-Cookie", value: "cookie1=value1; Path=/" },
|
||||
{ key: "Set-Cookie", value: "cookie2=value2; Domain=example.com" },
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Can get all cookies as object", function() {
|
||||
const cookies = pm.response.cookies.toObject()
|
||||
pm.expect(cookies).to.be.an("object")
|
||||
pm.expect(cookies.cookie1).to.equal("value1")
|
||||
pm.expect(cookies.cookie2).to.equal("value2")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Can get all cookies as object",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should return just the cookie value (matching Postman behavior)", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{
|
||||
key: "Set-Cookie",
|
||||
value:
|
||||
"full_cookie=test_value; Domain=.example.com; Path=/api; Max-Age=3600; Secure; HttpOnly; SameSite=Lax",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Returns only cookie value, not attributes", function() {
|
||||
const cookieValue = pm.response.cookies.get("full_cookie")
|
||||
pm.expect(cookieValue).to.equal("test_value")
|
||||
pm.expect(cookieValue).to.be.a("string")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Returns only cookie value, not attributes",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle cookies with equals signs in value", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [
|
||||
{
|
||||
key: "Set-Cookie",
|
||||
value:
|
||||
"jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=; Path=/",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Handles equals signs in cookie value", function() {
|
||||
const cookieValue = pm.response.cookies.get("jwt")
|
||||
pm.expect(cookieValue).to.include("=")
|
||||
pm.expect(cookieValue).to.equal("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Handles equals signs in cookie value",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("`pm.response.to.have.cookie` - Cookie Assertions", () => {
|
||||
test("should assert cookie exists by name", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Set-Cookie", value: "session=abc123; Path=/" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Response has session cookie", function() {
|
||||
pm.response.to.have.cookie("session")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Response has session cookie",
|
||||
expectResults: expect.arrayContaining([
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should assert cookie value matches", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Set-Cookie", value: "user=john_doe; Path=/" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Cookie has correct value", function() {
|
||||
pm.response.to.have.cookie("user", "john_doe")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Cookie has correct value",
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'john_doe' to equal 'john_doe'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when cookie doesn't exist", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Missing cookie fails", function() {
|
||||
pm.response.to.have.cookie("nonexistent")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Missing cookie fails",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining("Expected false to be true"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should fail when cookie value doesn't match", () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "{}",
|
||||
headers: [{ key: "Set-Cookie", value: "token=wrong_value; Path=/" }],
|
||||
}
|
||||
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Wrong cookie value fails", function() {
|
||||
pm.response.to.have.cookie("token", "expected_value")
|
||||
})
|
||||
`,
|
||||
{ global: [], selected: [] },
|
||||
response
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
descriptor: "root",
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Wrong cookie value fails",
|
||||
expectResults: [
|
||||
{
|
||||
status: "fail",
|
||||
message: expect.stringContaining(
|
||||
"Expected 'wrong_value' to equal 'expected_value'"
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm.response.dataURI() comprehensive coverage", () => {
|
||||
test("should handle Content-Type without charset", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ test: "data" }),
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI format without charset", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.include('data:')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('application/json')
|
||||
pm.expect(dataUri).to.include('base64,')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI format without charset",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle Content-Type with charset parameter", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ test: "data" }),
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json; charset=utf-8" },
|
||||
],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI format with charset", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.include('data:')
|
||||
// Updated regex pattern that handles charset parameters
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('application/json')
|
||||
pm.expect(dataUri).to.include('charset=utf-8')
|
||||
pm.expect(dataUri).to.include('base64,')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI format with charset",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle text/html with charset", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "<html><body>Hello</body></html>",
|
||||
headers: [{ key: "Content-Type", value: "text/html; charset=utf-8" }],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI with text/html and charset", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('text/html')
|
||||
pm.expect(dataUri).to.include('charset=utf-8')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI with text/html and charset",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle text/plain with multiple parameters", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Plain text content",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "text/plain; charset=utf-8; format=flowed",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI with multiple parameters", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
// Regex should handle multiple semicolons
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('text/plain')
|
||||
pm.expect(dataUri).to.include('base64,')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI with multiple parameters",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle application/xml with charset", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: '<?xml version="1.0"?><root><data>test</data></root>',
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/xml; charset=utf-8" },
|
||||
],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI with XML and charset", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('application/xml')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI with XML and charset",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle missing Content-Type header", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: "Some content",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI without Content-Type header", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
// Should default to application/octet-stream
|
||||
pm.expect(dataUri).to.include('application/octet-stream')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI without Content-Type header",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should properly encode UTF-8 content", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
body: JSON.stringify({ message: "Hello 世界 🌍" }),
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/json; charset=utf-8" },
|
||||
],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI with UTF-8 characters", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri.length).to.be.above(50)
|
||||
// Should contain valid base64 characters after "base64,"
|
||||
const base64Part = dataUri.split('base64,')[1]
|
||||
pm.expect(base64Part).to.be.a('string')
|
||||
pm.expect(base64Part.length).to.be.above(0)
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI with UTF-8 characters",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("should handle empty response body", async () => {
|
||||
const response: TestResponse = {
|
||||
status: 204,
|
||||
statusText: "No Content",
|
||||
body: "",
|
||||
headers: [{ key: "Content-Type", value: "text/plain" }],
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
pm.test("dataURI with empty body", () => {
|
||||
const dataUri = pm.response.dataURI()
|
||||
pm.expect(dataUri).to.be.a('string')
|
||||
pm.expect(dataUri).to.match(/^data:.+;base64,/)
|
||||
pm.expect(dataUri).to.include('text/plain')
|
||||
})
|
||||
`
|
||||
|
||||
return expect(
|
||||
runTest(testScript, { global: [], selected: [] }, response)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "dataURI with empty body",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import { describe, expect, it } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types/test-runner"
|
||||
|
||||
describe("pm.response method existence checks", () => {
|
||||
const mockResponse: TestResponse = {
|
||||
status: 200,
|
||||
headers: [{ key: "Content-Type", value: "application/json" }],
|
||||
body: JSON.stringify({ message: "Hello" }),
|
||||
}
|
||||
|
||||
it("should recognize pm.response.reason as a function", async () => {
|
||||
const testScript = `
|
||||
pm.test("pm.response.reason is a function", () => {
|
||||
pm.expect(pm.response.reason).to.be.a('function')
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "pm.response.reason is a function",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should recognize pm.response.dataURI as a function", async () => {
|
||||
const testScript = `
|
||||
pm.test("pm.response.dataURI is a function", () => {
|
||||
pm.expect(pm.response.dataURI).to.be.a('function')
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "pm.response.dataURI is a function",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should recognize pm.response.jsonp as a function", async () => {
|
||||
const testScript = `
|
||||
pm.test("pm.response.jsonp is a function", () => {
|
||||
pm.expect(pm.response.jsonp).to.be.a('function')
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "pm.response.jsonp is a function",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should work with typeof checks", async () => {
|
||||
const testScript = `
|
||||
pm.test("typeof pm.response.reason equals function", () => {
|
||||
pm.expect(typeof pm.response.reason).to.equal('function')
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "typeof pm.response.reason equals function",
|
||||
expectResults: [
|
||||
expect.objectContaining({
|
||||
status: "pass",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should verify all three utility methods exist as functions", async () => {
|
||||
const testScript = `
|
||||
pm.test("all response utility methods exist", () => {
|
||||
pm.expect(pm.response.reason).to.be.a('function')
|
||||
pm.expect(pm.response.dataURI).to.be.a('function')
|
||||
pm.expect(pm.response.jsonp).to.be.a('function')
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "all response utility methods exist",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should verify methods work correctly when called", async () => {
|
||||
const testScript = `
|
||||
pm.test("response.reason() returns status text", () => {
|
||||
pm.expect(pm.response.reason()).to.equal('OK')
|
||||
})
|
||||
|
||||
pm.test("response.dataURI() returns data URI string", () => {
|
||||
const uri = pm.response.dataURI()
|
||||
pm.expect(uri).to.be.a('string')
|
||||
pm.expect(uri).to.include('data:')
|
||||
})
|
||||
|
||||
pm.test("response.jsonp() parses JSON", () => {
|
||||
const result = pm.response.jsonp()
|
||||
pm.expect(result).to.deep.equal({ message: "Hello" })
|
||||
})
|
||||
`
|
||||
|
||||
await expect(
|
||||
runTestScript(testScript, { response: mockResponse })()
|
||||
).resolves.toEqualRight(
|
||||
expect.objectContaining({
|
||||
tests: [
|
||||
expect.objectContaining({
|
||||
expectResults: [],
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "response.reason() returns status text",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
descriptor: "response.dataURI() returns data URI string",
|
||||
expectResults: [
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
expect.objectContaining({ status: "pass" }),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
descriptor: "response.jsonp() parses JSON",
|
||||
expectResults: [expect.objectContaining({ status: "pass" })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,360 @@
|
|||
import { describe, expect, test } from "vitest"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
// Postman compatibility: all types preserved during runtime, only undefined needs special handling
|
||||
|
||||
describe("PM namespace type preservation (Postman compatibility)", () => {
|
||||
describe("Array preservation", () => {
|
||||
test("arrays are preserved as arrays with .length property", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("array", [1, 2, 3])
|
||||
const value = pm.environment.get("array")
|
||||
pm.expect(Array.isArray(value)).toBe(true)
|
||||
pm.expect(value.length).toBe(3)
|
||||
pm.expect(value[0]).toBe(1)
|
||||
pm.expect(value[2]).toBe(3)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '3' to be '3'" },
|
||||
{ status: "pass", message: "Expected '1' to be '1'" },
|
||||
{ status: "pass", message: "Expected '3' to be '3'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("single-element arrays remain arrays", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("single", [42])
|
||||
const value = pm.environment.get("single")
|
||||
pm.expect(Array.isArray(value)).toBe(true)
|
||||
pm.expect(value.length).toBe(1)
|
||||
pm.expect(value[0]).toBe(42)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '1' to be '1'" },
|
||||
{ status: "pass", message: "Expected '42' to be '42'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("empty arrays are preserved", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("empty", [])
|
||||
const value = pm.environment.get("empty")
|
||||
pm.expect(Array.isArray(value)).toBe(true)
|
||||
pm.expect(value.length).toBe(0)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '0' to be '0'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("nested arrays are preserved", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("nested", [[1, 2], [3, 4]])
|
||||
const value = pm.environment.get("nested")
|
||||
pm.expect(Array.isArray(value)).toBe(true)
|
||||
pm.expect(value.length).toBe(2)
|
||||
pm.expect(Array.isArray(value[0])).toBe(true)
|
||||
pm.expect(value[0][1]).toBe(2)
|
||||
pm.expect(value[1][0]).toBe(3)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '2' to be '2'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '2' to be '2'" },
|
||||
{ status: "pass", message: "Expected '3' to be '3'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Object preservation", () => {
|
||||
test("objects are preserved with accessible properties", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("obj", { key: "value", num: 42 })
|
||||
const value = pm.environment.get("obj")
|
||||
pm.expect(typeof value).toBe("object")
|
||||
pm.expect(value.key).toBe("value")
|
||||
pm.expect(value.num).toBe(42)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'object' to be 'object'" },
|
||||
{ status: "pass", message: "Expected 'value' to be 'value'" },
|
||||
{ status: "pass", message: "Expected '42' to be '42'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("empty objects are preserved", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("empty_obj", {})
|
||||
const value = pm.environment.get("empty_obj")
|
||||
pm.expect(typeof value).toBe("object")
|
||||
pm.expect(Array.isArray(value)).toBe(false)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'object' to be 'object'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("nested objects are preserved", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const original = { key: "value", nested: { prop: 123, deep: { inner: "test" } } }
|
||||
pm.environment.set("nested_obj", original)
|
||||
const retrieved = pm.environment.get("nested_obj")
|
||||
|
||||
pm.expect(typeof retrieved).toBe("object")
|
||||
pm.expect(retrieved.key).toBe("value")
|
||||
pm.expect(retrieved.nested.prop).toBe(123)
|
||||
pm.expect(retrieved.nested.deep.inner).toBe("test")
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'object' to be 'object'" },
|
||||
{ status: "pass", message: "Expected 'value' to be 'value'" },
|
||||
{ status: "pass", message: "Expected '123' to be '123'" },
|
||||
{ status: "pass", message: "Expected 'test' to be 'test'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Null preservation", () => {
|
||||
test("null is preserved as actual null value", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("nullable", null)
|
||||
const value = pm.environment.get("nullable")
|
||||
pm.expect(value).toBe(null)
|
||||
pm.expect(value === null).toBe(true)
|
||||
pm.expect(typeof value).toBe("object")
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'null' to be 'null'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected 'object' to be 'object'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Undefined preservation (special case)", () => {
|
||||
test("undefined is preserved as actual undefined", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("undef", undefined)
|
||||
const value = pm.environment.get("undef")
|
||||
pm.expect(value).toBe(undefined)
|
||||
pm.expect(typeof value).toBe("undefined")
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'undefined' to be 'undefined'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("undefined is distinguishable from non-existent keys", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("explicit_undef", undefined)
|
||||
pm.expect(pm.environment.has("explicit_undef")).toBe(true)
|
||||
pm.expect(pm.environment.has("never_set")).toBe(false)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Primitive type preservation", () => {
|
||||
test("numbers are preserved as numbers", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("num", 123)
|
||||
const value = pm.environment.get("num")
|
||||
pm.expect(typeof value).toBe("number")
|
||||
pm.expect(value).toBe(123)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'number' to be 'number'" },
|
||||
{ status: "pass", message: "Expected '123' to be '123'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("booleans are preserved as booleans", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("bool", true)
|
||||
const value = pm.environment.get("bool")
|
||||
pm.expect(typeof value).toBe("boolean")
|
||||
pm.expect(value).toBe(true)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'boolean' to be 'boolean'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("strings remain strings", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("str", "hello")
|
||||
const value = pm.environment.get("str")
|
||||
pm.expect(typeof value).toBe("string")
|
||||
pm.expect(value).toBe("hello")
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'string' to be 'string'" },
|
||||
{ status: "pass", message: "Expected 'hello' to be 'hello'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Cross-scope type preservation", () => {
|
||||
test("pm.globals preserves arrays", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("global_array", [1, 2, 3])
|
||||
const value = pm.globals.get("global_array")
|
||||
pm.expect(Array.isArray(value)).toBe(true)
|
||||
pm.expect(value.length).toBe(3)
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected '3' to be '3'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.variables preserves objects", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
pm.variables.set("var_obj", { key: "value" })
|
||||
const value = pm.variables.get("var_obj")
|
||||
pm.expect(typeof value).toBe("object")
|
||||
pm.expect(value.key).toBe("value")
|
||||
`,
|
||||
{ global: [], selected: [] }
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'object' to be 'object'" },
|
||||
{ status: "pass", message: "Expected 'value' to be 'value'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,31 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "test response",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm namespace - unsupported features", () => {
|
||||
test("pm.info.iteration throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
const iteration = pm.info.iteration
|
||||
|
|
@ -57,7 +36,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.info.iterationCount throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
const iterationCount = pm.info.iterationCount
|
||||
|
|
@ -89,7 +68,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.collectionVariables.get() throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.collectionVariables.get("test")
|
||||
|
|
@ -121,7 +100,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.vault.get() throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.vault.get("test")
|
||||
|
|
@ -153,7 +132,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.iterationData.get() throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.iterationData.get("test")
|
||||
|
|
@ -185,7 +164,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.execution.setNextRequest() throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.execution.setNextRequest("next-request")
|
||||
|
|
@ -217,7 +196,7 @@ describe("pm namespace - unsupported features", () => {
|
|||
|
||||
test("pm.sendRequest() throws error", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.sendRequest("https://example.com", () => {})
|
||||
|
|
@ -246,4 +225,68 @@ describe("pm namespace - unsupported features", () => {
|
|||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.visualizer.set() throws error", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.visualizer.set("<h1>Test</h1>")
|
||||
pm.test("Should not reach here", () => {
|
||||
pm.expect(true).toBe(false)
|
||||
})
|
||||
} catch (error) {
|
||||
pm.test("Throws correct error", () => {
|
||||
pm.expect(error.message).toInclude("pm.visualizer.set() is not supported")
|
||||
})
|
||||
}
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Throws correct error",
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.visualizer.clear() throws error", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
try {
|
||||
pm.visualizer.clear()
|
||||
pm.test("Should not reach here", () => {
|
||||
pm.expect(true).toBe(false)
|
||||
})
|
||||
} catch (error) {
|
||||
pm.test("Throws correct error", () => {
|
||||
pm.expect(error.message).toInclude("pm.visualizer.clear() is not supported")
|
||||
})
|
||||
}
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
children: [
|
||||
expect.objectContaining({
|
||||
descriptor: "Throws correct error",
|
||||
expectResults: [{ status: "pass", message: expect.any(String) }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,31 +1,12 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { runTestScript, runPreRequestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runPreRequestScript } from "~/node"
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pm.environment", () => {
|
||||
test("pm.environment.get returns the correct value for an existing active environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pm.environment.get("a")
|
||||
pm.expect(data).toBe("b")
|
||||
|
|
@ -51,7 +32,7 @@ describe("pm.environment", () => {
|
|||
|
||||
test("pm.environment.set creates and retrieves environment variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("test_set", "set_value")
|
||||
const retrieved = pm.environment.get("test_set")
|
||||
|
|
@ -76,7 +57,7 @@ describe("pm.environment", () => {
|
|||
|
||||
test("pm.environment.set works correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.environment.set("newVar", "newValue")
|
||||
const data = pm.environment.get("newVar")
|
||||
|
|
@ -98,7 +79,7 @@ describe("pm.environment", () => {
|
|||
|
||||
test("pm.environment.has correctly identifies existing and non-existing variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const hasExisting = pm.environment.has("existing_var")
|
||||
const hasNonExisting = pm.environment.has("non_existing_var")
|
||||
|
|
@ -137,7 +118,7 @@ describe("pm.environment", () => {
|
|||
describe("pm.globals", () => {
|
||||
test("pm.globals.get returns the correct value for an existing global environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pm.globals.get("globalVar")
|
||||
pm.expect(data).toBe("globalValue")
|
||||
|
|
@ -168,7 +149,7 @@ describe("pm.globals", () => {
|
|||
|
||||
test("pm.globals.set creates and retrieves global variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.globals.set("test_global", "global_value")
|
||||
const retrieved = pm.globals.get("test_global")
|
||||
|
|
@ -195,7 +176,7 @@ describe("pm.globals", () => {
|
|||
describe("pm.variables", () => {
|
||||
test("pm.variables.get returns the correct value from any scope", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pm.variables.get("scopedVar")
|
||||
pm.expect(data).toBe("scopedValue")
|
||||
|
|
@ -226,7 +207,7 @@ describe("pm.variables", () => {
|
|||
|
||||
test("pm.variables.set creates and retrieves variable in active environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.variables.set("test_var", "test_value")
|
||||
const retrieved = pm.variables.get("test_var")
|
||||
|
|
@ -251,7 +232,7 @@ describe("pm.variables", () => {
|
|||
|
||||
test("pm.variables.has correctly identifies existing and non-existing variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const hasExisting = pm.variables.has("existing_var")
|
||||
const hasNonExisting = pm.variables.has("non_existing_var")
|
||||
|
|
@ -288,7 +269,7 @@ describe("pm.variables", () => {
|
|||
|
||||
test("pm.variables.replaceIn works correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const template = "Hello {{name}}, welcome to {{place}}!"
|
||||
const result = pm.variables.replaceIn(template)
|
||||
|
|
@ -327,7 +308,7 @@ describe("pm.variables", () => {
|
|||
|
||||
test("pm.variables.replaceIn handles multiple variables", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const template = "User {{name}} has {{count}} items in {{location}}"
|
||||
const result = pm.variables.replaceIn(template)
|
||||
|
|
@ -375,7 +356,7 @@ describe("pm.variables", () => {
|
|||
describe("pm.test", () => {
|
||||
test("pm.test works as expected", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pm.test("Simple test", function() {
|
||||
pm.expect(1 + 1).toBe(2)
|
||||
|
|
@ -401,6 +382,238 @@ describe("pm.test", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("pm environment get() null vs undefined behavior", () => {
|
||||
test("pm.environment.get() returns undefined (not null) for non-existent keys", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const value = pm.environment.get("non_existent_key")
|
||||
pw.expect(value).toBe(undefined)
|
||||
pw.expect(value === null).toBe(false)
|
||||
pw.expect(value === undefined).toBe(true)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.get() returns undefined (not null) for non-existent keys", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const value = pm.globals.get("non_existent_global")
|
||||
pw.expect(value).toBe(undefined)
|
||||
pw.expect(value === null).toBe(false)
|
||||
pw.expect(value === undefined).toBe(true)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.variables.get() returns undefined (not null) for non-existent keys", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const value = pm.variables.get("non_existent_var")
|
||||
pw.expect(value).toBe(undefined)
|
||||
pw.expect(value === null).toBe(false)
|
||||
pw.expect(value === undefined).toBe(true)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{ status: "pass", message: "Expected 'undefined' to be 'undefined'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm environment clear() and toObject() methods", () => {
|
||||
test("pm.environment.clear() removes all environment variables", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
// Set some variables
|
||||
pm.environment.set("var1", "value1")
|
||||
pm.environment.set("var2", "value2")
|
||||
pm.environment.set("var3", "value3")
|
||||
|
||||
// Verify they exist
|
||||
pw.expect(pm.environment.has("var1")).toBe(true)
|
||||
pw.expect(pm.environment.has("var2")).toBe(true)
|
||||
pw.expect(pm.environment.has("var3")).toBe(true)
|
||||
|
||||
// Clear all
|
||||
pm.environment.clear()
|
||||
|
||||
// Verify all are removed
|
||||
pw.expect(pm.environment.has("var1")).toBe(false)
|
||||
pw.expect(pm.environment.has("var2")).toBe(false)
|
||||
pw.expect(pm.environment.has("var3")).toBe(false)
|
||||
|
||||
// Verify toObject returns empty
|
||||
const allVars = pm.environment.toObject()
|
||||
pw.expect(Object.keys(allVars).length).toBe(0)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
{ status: "pass", message: "Expected '0' to be '0'" },
|
||||
]),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.environment.toObject() returns all environment variables as object", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const allVars = pm.environment.toObject()
|
||||
pw.expect(typeof allVars).toBe("object")
|
||||
pw.expect(allVars.existing_var).toBe("existing_value")
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "existing_var",
|
||||
currentValue: "existing_value",
|
||||
initialValue: "existing_value",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'object' to be 'object'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'existing_value' to be 'existing_value'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.clear() removes all global variables", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
// Set some globals
|
||||
pm.globals.set("global1", "value1")
|
||||
pm.globals.set("global2", "value2")
|
||||
|
||||
// Verify they exist
|
||||
pw.expect(pm.globals.has("global1")).toBe(true)
|
||||
pw.expect(pm.globals.has("global2")).toBe(true)
|
||||
|
||||
// Clear all
|
||||
pm.globals.clear()
|
||||
|
||||
// Verify all are removed
|
||||
pw.expect(pm.globals.has("global1")).toBe(false)
|
||||
pw.expect(pm.globals.has("global2")).toBe(false)
|
||||
|
||||
// Verify toObject returns empty
|
||||
const allGlobals = pm.globals.toObject()
|
||||
pw.expect(Object.keys(allGlobals).length).toBe(0)
|
||||
`,
|
||||
{
|
||||
global: [],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: expect.arrayContaining([
|
||||
{ status: "pass", message: "Expected 'true' to be 'true'" },
|
||||
{ status: "pass", message: "Expected 'false' to be 'false'" },
|
||||
{ status: "pass", message: "Expected '0' to be '0'" },
|
||||
]),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
test("pm.globals.toObject() returns all global variables as object", () => {
|
||||
return expect(
|
||||
runTest(
|
||||
`
|
||||
const allGlobals = pm.globals.toObject()
|
||||
pw.expect(typeof allGlobals).toBe("object")
|
||||
pw.expect(allGlobals.global_var).toBe("global_value")
|
||||
`,
|
||||
{
|
||||
global: [
|
||||
{
|
||||
key: "global_var",
|
||||
currentValue: "global_value",
|
||||
initialValue: "global_value",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [],
|
||||
}
|
||||
)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'object' to be 'object'",
|
||||
},
|
||||
{
|
||||
status: "pass",
|
||||
message: "Expected 'global_value' to be 'global_value'",
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("pm namespace - pre-request scripts", () => {
|
||||
const DEFAULT_REQUEST = getDefaultRESTRequest()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pw.env.get", () => {
|
||||
test("returns the correct value for an existing selected environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get("a")
|
||||
pw.expect(data).toBe("b")
|
||||
|
|
@ -57,7 +35,7 @@ describe("pw.env.get", () => {
|
|||
|
||||
test("returns the correct value for an existing global environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get("a")
|
||||
pw.expect(data).toBe("b")
|
||||
|
|
@ -88,7 +66,7 @@ describe("pw.env.get", () => {
|
|||
|
||||
test("returns undefined for a key that is not present in both selected or environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get("a")
|
||||
pw.expect(data).toBe(undefined)
|
||||
|
|
@ -112,7 +90,7 @@ describe("pw.env.get", () => {
|
|||
|
||||
test("returns the value defined in selected environment if it is also present in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get("a")
|
||||
pw.expect(data).toBe("selected val")
|
||||
|
|
@ -150,7 +128,7 @@ describe("pw.env.get", () => {
|
|||
|
||||
test("does not resolve environment values", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get("a")
|
||||
pw.expect(data).toBe("<<hello>>")
|
||||
|
|
@ -181,7 +159,7 @@ describe("pw.env.get", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.get(5)
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pw.env.getResolve", () => {
|
||||
test("returns the correct value for an existing selected environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe("b")
|
||||
|
|
@ -57,7 +35,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("returns the correct value for an existing global environment value", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe("b")
|
||||
|
|
@ -88,7 +66,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("returns undefined for a key that is not present in both selected or environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe(undefined)
|
||||
|
|
@ -112,7 +90,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("returns the value defined in selected environment if it is also present in global", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe("selected val")
|
||||
|
|
@ -150,7 +128,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("resolve environment values", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe("there")
|
||||
|
|
@ -187,7 +165,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("returns unresolved value on infinite loop in resolution", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve("a")
|
||||
pw.expect(data).toBe("<<hello>>")
|
||||
|
|
@ -224,7 +202,7 @@ describe("pw.env.getResolve", () => {
|
|||
|
||||
test("errors if the key is not a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.getResolve(5)
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pw.env.resolve", () => {
|
||||
test("value should be a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.env.resolve(5)
|
||||
`,
|
||||
|
|
@ -40,7 +18,7 @@ describe("pw.env.resolve", () => {
|
|||
|
||||
test("resolves global variables correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.resolve("<<hello>>")
|
||||
pw.expect(data).toBe("there")
|
||||
|
|
@ -71,7 +49,7 @@ describe("pw.env.resolve", () => {
|
|||
|
||||
test("resolves selected env variables correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.resolve("<<hello>>")
|
||||
pw.expect(data).toBe("there")
|
||||
|
|
@ -102,7 +80,7 @@ describe("pw.env.resolve", () => {
|
|||
|
||||
test("chooses selected env variable over global variables when both have same variable", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.resolve("<<hello>>")
|
||||
pw.expect(data).toBe("there")
|
||||
|
|
@ -140,7 +118,7 @@ describe("pw.env.resolve", () => {
|
|||
|
||||
test("if infinite loop in resolution, abandons resolutions altogether", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
const data = pw.env.resolve("<<hello>>")
|
||||
pw.expect(data).toBe("<<hello>>")
|
||||
|
|
|
|||
|
|
@ -1,42 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTestAndGetEnvs, runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pw.env.set", () => {
|
||||
test("updates the selected environment variable correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set("a", "c")
|
||||
`,
|
||||
|
|
@ -68,7 +36,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("updates the global environment variable correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set("a", "c")
|
||||
`,
|
||||
|
|
@ -100,7 +68,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("updates the selected environment if env present in both", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set("a", "c")
|
||||
`,
|
||||
|
|
@ -147,7 +115,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("non existent keys are created in the selected environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set("a", "c")
|
||||
`,
|
||||
|
|
@ -173,7 +141,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("keys should be a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set(5, "c")
|
||||
`,
|
||||
|
|
@ -187,7 +155,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("values should be a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set("a", 5)
|
||||
`,
|
||||
|
|
@ -201,7 +169,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("both keys and values should be strings", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.set(5, 5)
|
||||
`,
|
||||
|
|
@ -215,7 +183,7 @@ describe("pw.env.set", () => {
|
|||
|
||||
test("set environment values are reflected in the script execution", () => {
|
||||
return expect(
|
||||
funcTest(
|
||||
runTest(
|
||||
`
|
||||
pw.env.set("a", "b")
|
||||
pw.expect(pw.env.get("a")).toBe("b")
|
||||
|
|
|
|||
|
|
@ -1,42 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request: defaultRequest,
|
||||
response: fakeResponse,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTestAndGetEnvs, runTest } from "~/utils/test-helpers"
|
||||
|
||||
describe("pw.env.unset", () => {
|
||||
test("removes the variable set in selected environment correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -61,7 +29,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("removes the variable set in global environment correctly", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -86,7 +54,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("removes the variable from selected environment if the entry is present in both selected and global environments", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -126,7 +94,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("removes the initial occurrence of an entry if duplicate entries exist in the selected environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -179,7 +147,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("removes the initial occurrence of an entry if duplicate entries exist in the global environment", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -218,7 +186,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("no change if attempting to delete non-existent keys", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
`,
|
||||
|
|
@ -237,7 +205,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("keys should be a string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTestAndGetEnvs(
|
||||
`
|
||||
pw.env.unset(5)
|
||||
`,
|
||||
|
|
@ -251,7 +219,7 @@ describe("pw.env.unset", () => {
|
|||
|
||||
test("set environment values are reflected in the script execution", () => {
|
||||
return expect(
|
||||
funcTest(
|
||||
runTest(
|
||||
`
|
||||
pw.env.unset("baseUrl")
|
||||
pw.expect(pw.env.get("baseUrl")).toBe(undefined)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,11 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("toBe", () => {
|
||||
describe("general assertion (no negation)", () => {
|
||||
test("expect equals expected passes assertion", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBe(2)
|
||||
`,
|
||||
|
|
@ -44,7 +22,7 @@ describe("toBe", () => {
|
|||
|
||||
test("expect not equals expected fails assertion", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBe(4)
|
||||
`,
|
||||
|
|
@ -63,7 +41,7 @@ describe("toBe", () => {
|
|||
describe("general assertion (with negation)", () => {
|
||||
test("expect equals expected fails assertion", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).not.toBe(2)
|
||||
`,
|
||||
|
|
@ -83,7 +61,7 @@ describe("toBe", () => {
|
|||
|
||||
test("expect not equals expected passes assertion", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).not.toBe(4)
|
||||
`,
|
||||
|
|
@ -105,7 +83,7 @@ describe("toBe", () => {
|
|||
|
||||
test("strict checks types", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBe("2")
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,34 +1,12 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
||||
describe("toBeLevel2xx", () => {
|
||||
test("assertion passes for 200 series with no negation", async () => {
|
||||
for (let i = 200; i < 300; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -45,7 +23,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for non 200 series with no negation", async () => {
|
||||
for (let i = 300; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -61,7 +39,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value was not a number with no negation", async () => {
|
||||
await expect(
|
||||
func(`pw.expect("foo").toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -78,7 +56,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for 200 series with negation", async () => {
|
||||
for (let i = 200; i < 300; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -95,7 +73,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for non 200 series with negation", async () => {
|
||||
for (let i = 300; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -111,7 +89,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value was not a number with negation", async () => {
|
||||
await expect(
|
||||
func(`pw.expect("foo").not.toBeLevel2xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").not.toBeLevel2xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -130,7 +108,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for 300 series with no negation", async () => {
|
||||
for (let i = 300; i < 400; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -147,7 +125,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for non 300 series with no negation", async () => {
|
||||
for (let i = 400; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -163,7 +141,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value is not a number without negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -180,7 +158,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for 400 series with negation", async () => {
|
||||
for (let i = 300; i < 400; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -197,7 +175,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for non 200 series with negation", async () => {
|
||||
for (let i = 400; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -213,7 +191,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value is not a number with negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").not.toBeLevel3xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").not.toBeLevel3xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -232,7 +210,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for 400 series with no negation", async () => {
|
||||
for (let i = 400; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -249,7 +227,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for non 400 series with no negation", async () => {
|
||||
for (let i = 500; i < 600; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -265,7 +243,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expected value is not a number without negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -282,7 +260,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for 400 series with negation", async () => {
|
||||
for (let i = 400; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -299,7 +277,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for non 400 series with negation", async () => {
|
||||
for (let i = 500; i < 600; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -315,7 +293,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expected value is not a number with negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").not.toBeLevel4xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").not.toBeLevel4xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -334,7 +312,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for 500 series with no negation", async () => {
|
||||
for (let i = 500; i < 600; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -351,7 +329,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for non 500 series with no negation", async () => {
|
||||
for (let i = 200; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -367,7 +345,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value is not a number with no negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -384,7 +362,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion fails for 500 series with negation", async () => {
|
||||
for (let i = 500; i < 600; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -401,7 +379,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
test("assertion passes for non 500 series with negation", async () => {
|
||||
for (let i = 200; i < 500; i++) {
|
||||
await expect(
|
||||
func(`pw.expect(${i}).not.toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect(${i}).not.toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -417,7 +395,7 @@ describe("toBeLevelxxx", { timeout: 100000 }, () => {
|
|||
|
||||
test("give error if the expect value is not a number with negation", () => {
|
||||
return expect(
|
||||
func(`pw.expect("foo").not.toBeLevel5xx()`, fakeResponse)()
|
||||
runTest(`pw.expect("foo").not.toBeLevel5xx()`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("toBeType", () => {
|
||||
test("asserts true for valid type expectations with no negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBeType("number")
|
||||
pw.expect("2").toBeType("string")
|
||||
|
|
@ -77,7 +55,7 @@ describe("toBeType", () => {
|
|||
|
||||
test("asserts false for invalid type expectations with no negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBeType("string")
|
||||
pw.expect("2").toBeType("number")
|
||||
|
|
@ -108,7 +86,7 @@ describe("toBeType", () => {
|
|||
|
||||
test("asserts false for valid type expectations with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).not.toBeType("number")
|
||||
pw.expect("2").not.toBeType("string")
|
||||
|
|
@ -142,7 +120,7 @@ describe("toBeType", () => {
|
|||
|
||||
test("asserts true for invalid type expectations with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).not.toBeType("string")
|
||||
pw.expect("2").not.toBeType("number")
|
||||
|
|
@ -197,7 +175,7 @@ describe("toBeType", () => {
|
|||
|
||||
test("gives error for invalid type names without negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).toBeType("foo")
|
||||
pw.expect("2").toBeType("bar")
|
||||
|
|
@ -237,7 +215,7 @@ describe("toBeType", () => {
|
|||
|
||||
test("gives error for invalid type names with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(2).not.toBeType("foo")
|
||||
pw.expect("2").not.toBeType("bar")
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("toHaveLength", () => {
|
||||
test("asserts true for valid lengths with no negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).toHaveLength(4)
|
||||
pw.expect([]).toHaveLength(0)
|
||||
|
|
@ -45,7 +23,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("asserts false for invalid lengths with no negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([]).toHaveLength(4)
|
||||
pw.expect([1, 2, 3, 4]).toHaveLength(0)
|
||||
|
|
@ -64,7 +42,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("asserts false for valid lengths with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).not.toHaveLength(4)
|
||||
pw.expect([]).not.toHaveLength(0)
|
||||
|
|
@ -89,7 +67,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("asserts true for invalid lengths with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([]).not.toHaveLength(4)
|
||||
pw.expect([1, 2, 3, 4]).not.toHaveLength(0)
|
||||
|
|
@ -114,7 +92,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("gives error if not called on an array or a string with no negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(5).toHaveLength(0)
|
||||
pw.expect(true).toHaveLength(0)
|
||||
|
|
@ -141,7 +119,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("gives error if not called on an array or a string with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(5).not.toHaveLength(0)
|
||||
pw.expect(true).not.toHaveLength(0)
|
||||
|
|
@ -168,7 +146,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("gives an error if toHaveLength parameter is not a number without negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).toHaveLength("a")
|
||||
`,
|
||||
|
|
@ -188,7 +166,7 @@ describe("toHaveLength", () => {
|
|||
|
||||
test("gives an error if toHaveLength parameter is not a number with negation", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).not.toHaveLength("a")
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("toInclude", () => {
|
||||
test("asserts true for collections with matching values", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3]).toInclude(1)
|
||||
pw.expect("123").toInclude(1)
|
||||
|
|
@ -45,7 +23,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("asserts false for collections without matching values", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3]).toInclude(4)
|
||||
pw.expect("123").toInclude(4)
|
||||
|
|
@ -64,7 +42,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("asserts false for empty collections", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([]).not.toInclude(0)
|
||||
pw.expect("").not.toInclude(0)
|
||||
|
|
@ -89,7 +67,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("asserts false for [number array].includes(string)", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1]).not.toInclude("1")
|
||||
`,
|
||||
|
|
@ -112,7 +90,7 @@ describe("toInclude", () => {
|
|||
// (`"123".includes(123)` returns `True` in Node.js v14.19.1)
|
||||
// See https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.includes
|
||||
return expect(
|
||||
func(`pw.expect("123").toInclude(123)`, fakeResponse)()
|
||||
runTest(`pw.expect("123").toInclude(123)`, fakeResponse)()
|
||||
).resolves.toEqualRight([
|
||||
expect.objectContaining({
|
||||
expectResults: [
|
||||
|
|
@ -127,7 +105,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("gives error if not called on an array or string", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect(5).not.toInclude(0)
|
||||
pw.expect(true).not.toInclude(0)
|
||||
|
|
@ -152,7 +130,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("gives an error if toInclude parameter is null", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).not.toInclude(null)
|
||||
`,
|
||||
|
|
@ -172,7 +150,7 @@ describe("toInclude", () => {
|
|||
|
||||
test("gives an error if toInclude parameter is undefined", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.expect([1, 2, 3, 4]).not.toInclude(undefined)
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const defaultRequest = getDefaultRESTRequest()
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs: { global: [], selected: [] },
|
||||
request: defaultRequest,
|
||||
response: res,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
import { runTest, fakeResponse } from "~/utils/test-helpers"
|
||||
|
||||
describe("runTestScript", () => {
|
||||
test("returns a resolved promise for a valid test script with all green", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.test("Arithmetic operations", () => {
|
||||
const size = 500 + 500;
|
||||
|
|
@ -43,7 +21,7 @@ describe("runTestScript", () => {
|
|||
|
||||
test("resolves for tests with failed expectations", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.test("Arithmetic operations", () => {
|
||||
const size = 500 + 500;
|
||||
|
|
@ -61,7 +39,7 @@ describe("runTestScript", () => {
|
|||
// TODO: We need a more concrete behavior for this
|
||||
test("rejects for invalid syntax on tests", () => {
|
||||
return expect(
|
||||
func(
|
||||
runTest(
|
||||
`
|
||||
pw.test("Arithmetic operations", () => {
|
||||
const size = 500 + 500;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { describe, expect, test } from "vitest"
|
|||
import { getRequestSetterMethods } from "~/utils/pre-request"
|
||||
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "15",
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://example.com/api",
|
||||
method: "GET",
|
||||
|
|
@ -30,11 +30,11 @@ describe("getRequestSetterMethods", () => {
|
|||
expect(updatedRequest.endpoint).toBe("https://updated.com/api")
|
||||
})
|
||||
|
||||
test("`setMethod` should update and uppercase the method", () => {
|
||||
test("`setMethod` should update the method (case preserved)", () => {
|
||||
const { methods, updatedRequest } = getRequestSetterMethods(baseRequest)
|
||||
methods.setMethod("post")
|
||||
|
||||
expect(updatedRequest.method).toBe("POST")
|
||||
expect(updatedRequest.method).toBe("post")
|
||||
})
|
||||
|
||||
test("`setHeader` setter should update existing header case-insensitively", () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
getSharedCookieMethods,
|
||||
getSharedEnvMethods,
|
||||
getSharedRequestProps,
|
||||
preventCyclicObjects,
|
||||
} from "~/utils/shared"
|
||||
|
||||
import { Cookie, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { TestResult } from "~/types"
|
||||
|
||||
describe("preventCyclicObjects", () => {
|
||||
test("succeeds with a simple object", () => {
|
||||
|
|
@ -30,7 +32,7 @@ describe("preventCyclicObjects", () => {
|
|||
|
||||
describe("getSharedRequestProps", () => {
|
||||
const baseRequest: HoppRESTRequest = {
|
||||
v: "15",
|
||||
v: "16",
|
||||
name: "Test Request",
|
||||
endpoint: "https://example.com/api",
|
||||
method: "GET",
|
||||
|
|
@ -73,21 +75,6 @@ describe("getSharedRequestProps", () => {
|
|||
const request = getSharedRequestProps(baseRequest)
|
||||
expect(request.auth).toEqual(baseRequest.auth)
|
||||
})
|
||||
|
||||
test("`params` getter", () => {
|
||||
const request = getSharedRequestProps(baseRequest)
|
||||
expect(request.params).toEqual(baseRequest.params)
|
||||
})
|
||||
|
||||
test("`body` getter", () => {
|
||||
const request = getSharedRequestProps(baseRequest)
|
||||
expect(request.body).toEqual(baseRequest.body)
|
||||
})
|
||||
|
||||
test("`auth` getter", () => {
|
||||
const request = getSharedRequestProps(baseRequest)
|
||||
expect(request.auth).toEqual(baseRequest.auth)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getSharedCookieMethods", () => {
|
||||
|
|
@ -184,3 +171,340 @@ describe("getSharedCookieMethods", () => {
|
|||
expect(() => methods.clear(123 as any)).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("getSharedEnvMethods - Experimental Sandbox (isHoppNamespace=true)", () => {
|
||||
const baseEnvs: TestResult["envs"] = {
|
||||
global: [
|
||||
{
|
||||
key: "globalKey",
|
||||
currentValue: "globalVal",
|
||||
initialValue: "globalVal",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [
|
||||
{
|
||||
key: "selectedKey",
|
||||
currentValue: "selectedVal",
|
||||
initialValue: "selectedVal",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
test("returns pw and hopp namespace structure", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
expect(methods).toHaveProperty("pw")
|
||||
expect(methods).toHaveProperty("hopp")
|
||||
expect(methods.pw).toHaveProperty("get")
|
||||
expect(methods.hopp).toHaveProperty("set")
|
||||
})
|
||||
|
||||
test("pw.get retrieves from selected then global", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
expect(methods.pw.get("selectedKey")).toBe("selectedVal")
|
||||
expect(methods.pw.get("globalKey")).toBe("globalVal")
|
||||
expect(methods.pw.get("nonexistent")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("pw.set updates selected environment", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
methods.pw.set("newKey", "newVal")
|
||||
|
||||
expect(updatedEnvs.selected).toContainEqual({
|
||||
key: "newKey",
|
||||
currentValue: "newVal",
|
||||
initialValue: "newVal",
|
||||
secret: false,
|
||||
})
|
||||
})
|
||||
|
||||
test("pw.set validates string key and value", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
expect(() => methods.pw.set(123 as any, "value")).toThrow(
|
||||
"Expected key to be a string"
|
||||
)
|
||||
expect(() => methods.pw.set("key", 123 as any)).toThrow(
|
||||
"Expected value to be a string"
|
||||
)
|
||||
})
|
||||
|
||||
test("pw.resolve handles template strings", () => {
|
||||
const { methods } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "name",
|
||||
currentValue: "Alice",
|
||||
initialValue: "Alice",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "greeting",
|
||||
currentValue: "Hello <<name>>",
|
||||
initialValue: "Hello <<name>>",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const resolved = methods.pw.resolve("<<greeting>>")
|
||||
expect(resolved).toBe("Hello Alice")
|
||||
})
|
||||
|
||||
test("pw.getResolve combines get and resolve", () => {
|
||||
const { methods } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "baseUrl",
|
||||
currentValue: "https://api.example.com",
|
||||
initialValue: "https://api.example.com",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "endpoint",
|
||||
currentValue: "<<baseUrl>>/users",
|
||||
initialValue: "<<baseUrl>>/users",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const resolved = methods.pw.getResolve("endpoint")
|
||||
expect(resolved).toBe("https://api.example.com/users")
|
||||
})
|
||||
|
||||
test("hopp.set creates new variable in selected scope (default source='all')", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
methods.hopp.set("hoppKey", "hoppVal")
|
||||
|
||||
expect(updatedEnvs.selected).toContainEqual({
|
||||
key: "hoppKey",
|
||||
currentValue: "hoppVal",
|
||||
initialValue: "hoppVal",
|
||||
secret: false,
|
||||
})
|
||||
})
|
||||
|
||||
test("hopp.set validates string types", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
expect(() => methods.hopp.set(123 as any, "value")).toThrow(
|
||||
"Expected key to be a string"
|
||||
)
|
||||
expect(() => methods.hopp.set("key", 123 as any)).toThrow(
|
||||
"Expected value to be a string"
|
||||
)
|
||||
})
|
||||
|
||||
test("hopp.delete removes variable from selected scope", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
methods.hopp.delete("selectedKey")
|
||||
|
||||
expect(updatedEnvs.selected).not.toContainEqual(
|
||||
expect.objectContaining({ key: "selectedKey" })
|
||||
)
|
||||
expect(updatedEnvs.global.length).toBe(1)
|
||||
})
|
||||
|
||||
test("hopp.reset resets a variable to its initial value", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "testKey",
|
||||
currentValue: "modified",
|
||||
initialValue: "original",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
methods.hopp.reset("testKey")
|
||||
|
||||
const variable = updatedEnvs.selected.find((e) => e.key === "testKey")
|
||||
expect(variable?.currentValue).toBe("original")
|
||||
expect(variable?.initialValue).toBe("original")
|
||||
})
|
||||
|
||||
test("hopp.getInitialRaw returns initial value", () => {
|
||||
const { methods } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "testKey",
|
||||
currentValue: "currentVal",
|
||||
initialValue: "initialVal",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
expect(methods.hopp.getInitialRaw("testKey")).toBe("initialVal")
|
||||
expect(methods.hopp.getInitialRaw("nonexistent")).toBeNull()
|
||||
})
|
||||
|
||||
test("hopp.setInitial sets initial value", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(baseEnvs, true)
|
||||
|
||||
methods.hopp.setInitial("initKey", "initVal")
|
||||
|
||||
const created = updatedEnvs.selected.find((e) => e.key === "initKey")
|
||||
expect(created).toBeDefined()
|
||||
expect(created?.initialValue).toBe("initVal")
|
||||
expect(created?.currentValue).toBe("initVal")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getSharedEnvMethods - Legacy Sandbox (isHoppNamespace=false)", () => {
|
||||
const baseEnvs: TestResult["envs"] = {
|
||||
global: [
|
||||
{
|
||||
key: "globalKey",
|
||||
currentValue: "globalVal",
|
||||
initialValue: "globalVal",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
selected: [
|
||||
{
|
||||
key: "selectedKey",
|
||||
currentValue: "selectedVal",
|
||||
initialValue: "selectedVal",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
test("returns env object structure (not pw/hopp)", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
expect(methods).toHaveProperty("env")
|
||||
expect(methods).not.toHaveProperty("pw")
|
||||
expect(methods).not.toHaveProperty("hopp")
|
||||
})
|
||||
|
||||
test("env object has all expected methods", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
expect(typeof methods.env.get).toBe("function")
|
||||
expect(typeof methods.env.set).toBe("function")
|
||||
expect(typeof methods.env.resolve).toBe("function")
|
||||
expect(typeof methods.env.getResolve).toBe("function")
|
||||
})
|
||||
|
||||
test("env.get retrieves from selected then global", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
expect(methods.env.get("selectedKey")).toBe("selectedVal")
|
||||
expect(methods.env.get("globalKey")).toBe("globalVal")
|
||||
expect(methods.env.get("nonexistent")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("env.set updates environment correctly", () => {
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
methods.env.set("newKey", "newVal")
|
||||
|
||||
expect(updatedEnvs.selected).toContainEqual({
|
||||
key: "newKey",
|
||||
currentValue: "newVal",
|
||||
initialValue: "newVal",
|
||||
secret: false,
|
||||
})
|
||||
})
|
||||
|
||||
test("env.set validates string types (regression test for #5433)", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
// This is the bug that was fixed in #5433 - missing validation
|
||||
expect(() => methods.env.set(123 as any, "value")).toThrow(
|
||||
"Expected key to be a string"
|
||||
)
|
||||
expect(() => methods.env.set("key", 123 as any)).toThrow(
|
||||
"Expected value to be a string"
|
||||
)
|
||||
})
|
||||
|
||||
test("env.resolve handles template strings", () => {
|
||||
const { methods } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "user",
|
||||
currentValue: "Bob",
|
||||
initialValue: "Bob",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "message",
|
||||
currentValue: "Hello <<user>>",
|
||||
initialValue: "Hello <<user>>",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
const resolved = methods.env.resolve("<<message>>")
|
||||
expect(resolved).toBe("Hello Bob")
|
||||
})
|
||||
|
||||
test("env.getResolve returns resolved value", () => {
|
||||
const { methods } = getSharedEnvMethods(
|
||||
{
|
||||
global: [],
|
||||
selected: [
|
||||
{
|
||||
key: "domain",
|
||||
currentValue: "example.com",
|
||||
initialValue: "example.com",
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
key: "apiUrl",
|
||||
currentValue: "https://<<domain>>/api",
|
||||
initialValue: "https://<<domain>>/api",
|
||||
secret: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
const resolved = methods.env.getResolve("apiUrl")
|
||||
expect(resolved).toBe("https://example.com/api")
|
||||
})
|
||||
|
||||
test("env object structure prevents #5433 regression (pw.env not recognized)", () => {
|
||||
const { methods } = getSharedEnvMethods(baseEnvs, false)
|
||||
|
||||
// In legacy sandbox, this gets assigned to pw.env
|
||||
// The bug was that pw.env was undefined because the structure wasn't correct
|
||||
expect(methods.env).toBeDefined()
|
||||
expect(typeof methods.env).toBe("object")
|
||||
expect(methods.env.get).toBeDefined()
|
||||
expect(methods.env.set).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,9 @@ export const createPmNamespaceMethods = (
|
|||
return config.request.name
|
||||
}),
|
||||
pmInfoRequestId: defineSandboxFn(ctx, "pmInfoRequestId", () => {
|
||||
return config.request.id
|
||||
// Use request.id if available, fallback to request.name
|
||||
// Postman uses a unique ID, but for compatibility we use name if ID not set
|
||||
return config.request.id || config.request.name
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import { CageModuleCtx, defineSandboxFn } from "faraday-cage/modules"
|
||||
|
||||
import type { EnvMethods, RequestProps, PwNamespaceMethods } from "~/types"
|
||||
import type {
|
||||
EnvMethods,
|
||||
RequestProps,
|
||||
PwNamespaceMethods,
|
||||
SandboxValue,
|
||||
} from "~/types"
|
||||
|
||||
/**
|
||||
* Creates pw namespace methods for the sandbox environment
|
||||
|
|
@ -13,41 +18,49 @@ export const createPwNamespaceMethods = (
|
|||
): PwNamespaceMethods => {
|
||||
return {
|
||||
// `pw` namespace environment methods
|
||||
envGet: defineSandboxFn(ctx, "envGet", function (key: any, options: any) {
|
||||
return envMethods.pw.get(key, options)
|
||||
}),
|
||||
envGet: defineSandboxFn(
|
||||
ctx,
|
||||
"envGet",
|
||||
function (key: SandboxValue, options: SandboxValue) {
|
||||
return envMethods.pw.get(key, options)
|
||||
}
|
||||
),
|
||||
envGetResolve: defineSandboxFn(
|
||||
ctx,
|
||||
"envGetResolve",
|
||||
function (key: any, options: any) {
|
||||
function (key: SandboxValue, options: SandboxValue) {
|
||||
return envMethods.pw.getResolve(key, options)
|
||||
}
|
||||
),
|
||||
envSet: defineSandboxFn(
|
||||
ctx,
|
||||
"envSet",
|
||||
function (key: any, value: any, options: any) {
|
||||
function (key: SandboxValue, value: SandboxValue, options: SandboxValue) {
|
||||
return envMethods.pw.set(key, value, options)
|
||||
}
|
||||
),
|
||||
envUnset: defineSandboxFn(
|
||||
ctx,
|
||||
"envUnset",
|
||||
function (key: any, options: any) {
|
||||
function (key: SandboxValue, options: SandboxValue) {
|
||||
return envMethods.pw.unset(key, options)
|
||||
}
|
||||
),
|
||||
envResolve: defineSandboxFn(ctx, "envResolve", function (key: any) {
|
||||
return envMethods.pw.resolve(key)
|
||||
}),
|
||||
envResolve: defineSandboxFn(
|
||||
ctx,
|
||||
"envResolve",
|
||||
function (key: SandboxValue) {
|
||||
return envMethods.pw.resolve(key)
|
||||
}
|
||||
),
|
||||
|
||||
// Request variable operations
|
||||
getRequestVariable: defineSandboxFn(
|
||||
ctx,
|
||||
"getRequestVariable",
|
||||
function (key: any) {
|
||||
function (key: SandboxValue) {
|
||||
const reqVarEntry = requestProps.requestVariables.find(
|
||||
(reqVar: any) => reqVar.key === key
|
||||
(reqVar: SandboxValue) => reqVar.key === key
|
||||
)
|
||||
return reqVarEntry ? reqVarEntry.value : null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import {
|
|||
defineSandboxObject,
|
||||
} from "faraday-cage/modules"
|
||||
|
||||
import { getStatusReason } from "~/constants/http-status-codes"
|
||||
import { TestDescriptor, TestResponse, TestResult } from "~/types"
|
||||
import postRequestBootstrapCode from "../bootstrap-code/post-request?raw"
|
||||
import preRequestBootstrapCode from "../bootstrap-code/pre-request?raw"
|
||||
import { createBaseInputs } from "./utils/base-inputs"
|
||||
import { createChaiMethods } from "./utils/chai-helpers"
|
||||
import { createExpectationMethods } from "./utils/expectation-helpers"
|
||||
import { createRequestSetterMethods } from "./utils/request-setters"
|
||||
|
||||
|
|
@ -125,20 +127,21 @@ const createScriptingInputsObj = (
|
|||
type: ModuleType,
|
||||
config: ModuleConfig
|
||||
) => {
|
||||
// Create base inputs shared across all namespaces
|
||||
const baseInputs = createBaseInputs(ctx, {
|
||||
envs: config.envs,
|
||||
request: config.request,
|
||||
cookies: config.cookies,
|
||||
})
|
||||
|
||||
if (type === "pre") {
|
||||
const preConfig = config as PreRequestModuleConfig
|
||||
|
||||
// Create request setter methods for pre-request scripts
|
||||
// Create request setter methods FIRST for pre-request scripts
|
||||
const { methods: requestSetterMethods, getUpdatedRequest } =
|
||||
createRequestSetterMethods(ctx, preConfig.request)
|
||||
|
||||
// Create base inputs with access to updated request
|
||||
const baseInputs = createBaseInputs(ctx, {
|
||||
envs: config.envs,
|
||||
request: config.request,
|
||||
cookies: config.cookies,
|
||||
getUpdatedRequest, // Pass the updater function for pre-request
|
||||
})
|
||||
|
||||
// Register hook with helper function
|
||||
registerAfterScriptExecutionHook(ctx, "pre", preConfig, baseInputs, {
|
||||
getUpdatedRequest,
|
||||
|
|
@ -150,6 +153,13 @@ const createScriptingInputsObj = (
|
|||
}
|
||||
}
|
||||
|
||||
// Create base inputs shared across all namespaces (post-request path)
|
||||
const baseInputs = createBaseInputs(ctx, {
|
||||
envs: config.envs,
|
||||
request: config.request,
|
||||
cookies: config.cookies,
|
||||
})
|
||||
|
||||
if (type === "post") {
|
||||
const postConfig = config as PostRequestModuleConfig
|
||||
|
||||
|
|
@ -159,20 +169,24 @@ const createScriptingInputsObj = (
|
|||
postConfig.testRunStack
|
||||
)
|
||||
|
||||
// Create Chai methods
|
||||
const chaiMethods = createChaiMethods(ctx, postConfig.testRunStack)
|
||||
|
||||
// Register hook with helper function
|
||||
registerAfterScriptExecutionHook(ctx, "post", postConfig, baseInputs)
|
||||
|
||||
return {
|
||||
...baseInputs,
|
||||
...expectationMethods,
|
||||
...chaiMethods,
|
||||
|
||||
// Test management methods
|
||||
preTest: defineSandboxFn(
|
||||
ctx,
|
||||
"preTest",
|
||||
function preTest(descriptor: any) {
|
||||
function preTest(descriptor: unknown) {
|
||||
postConfig.testRunStack.push({
|
||||
descriptor,
|
||||
descriptor: descriptor as string,
|
||||
expectResults: [],
|
||||
children: [],
|
||||
})
|
||||
|
|
@ -187,6 +201,90 @@ const createScriptingInputsObj = (
|
|||
getResponse: defineSandboxFn(ctx, "getResponse", function getResponse() {
|
||||
return postConfig.response
|
||||
}),
|
||||
// Response utility methods as cage functions
|
||||
responseReason: defineSandboxFn(
|
||||
ctx,
|
||||
"responseReason",
|
||||
function responseReason() {
|
||||
return getStatusReason(postConfig.response.status)
|
||||
}
|
||||
),
|
||||
responseDataURI: defineSandboxFn(
|
||||
ctx,
|
||||
"responseDataURI",
|
||||
function responseDataURI() {
|
||||
try {
|
||||
const body = postConfig.response.body
|
||||
const contentType =
|
||||
postConfig.response.headers.find(
|
||||
(h) => h.key.toLowerCase() === "content-type"
|
||||
)?.value || "application/octet-stream"
|
||||
|
||||
// Convert body to base64 (browser and Node.js compatible)
|
||||
let base64Body: string
|
||||
const bodyString = typeof body === "string" ? body : String(body)
|
||||
|
||||
// Check if we're in a browser environment (btoa available)
|
||||
if (typeof btoa !== "undefined") {
|
||||
// Browser environment: use btoa
|
||||
// btoa requires binary string, so we need to handle UTF-8 properly
|
||||
const utf8Bytes = new TextEncoder().encode(bodyString)
|
||||
const binaryString = Array.from(utf8Bytes, (byte) =>
|
||||
String.fromCharCode(byte)
|
||||
).join("")
|
||||
base64Body = btoa(binaryString)
|
||||
} else if (typeof Buffer !== "undefined") {
|
||||
// Node.js environment: use Buffer
|
||||
base64Body = Buffer.from(bodyString).toString("base64")
|
||||
} else {
|
||||
throw new Error("No base64 encoding method available")
|
||||
}
|
||||
|
||||
return `data:${contentType};base64,${base64Body}`
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to convert response to data URI: ${error}`)
|
||||
}
|
||||
}
|
||||
),
|
||||
responseJsonp: defineSandboxFn(
|
||||
ctx,
|
||||
"responseJsonp",
|
||||
function responseJsonp(...args: unknown[]) {
|
||||
const callbackName = args[0]
|
||||
const body = postConfig.response.body
|
||||
const text = typeof body === "string" ? body : String(body)
|
||||
|
||||
if (callbackName && typeof callbackName === "string") {
|
||||
// Escape special regex characters in callback name
|
||||
const escapedName = callbackName.replace(
|
||||
/[.*+?^${}()|[\]\\]/g,
|
||||
"\\$&"
|
||||
)
|
||||
const regex = new RegExp(
|
||||
`^\\s*${escapedName}\\s*\\(([\\s\\S]*)\\)\\s*;?\\s*$`
|
||||
)
|
||||
const match = text.match(regex)
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect callback wrapper
|
||||
const autoDetect = text.match(
|
||||
/^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(([\s\S]*)\)\s*;?\s*$/
|
||||
)
|
||||
if (autoDetect && autoDetect[2]) {
|
||||
try {
|
||||
return JSON.parse(autoDetect[2])
|
||||
} catch {
|
||||
// If parsing fails, fall through to plain JSON
|
||||
}
|
||||
}
|
||||
|
||||
// No JSONP wrapper found, parse as plain JSON
|
||||
return JSON.parse(text)
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { Cookie, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { CageModuleCtx, defineSandboxFn } from "faraday-cage/modules"
|
||||
|
||||
import { TestResult, BaseInputs } from "~/types"
|
||||
import { TestResult, BaseInputs, SandboxValue } from "~/types"
|
||||
import {
|
||||
getSharedCookieMethods,
|
||||
getSharedEnvMethods,
|
||||
getSharedRequestProps,
|
||||
} from "~/utils/shared"
|
||||
import { UNDEFINED_MARKER, NULL_MARKER } from "~/constants/sandbox-markers"
|
||||
import { createHoppNamespaceMethods } from "../namespaces/hopp-namespace"
|
||||
import { createPmNamespaceMethods } from "../namespaces/pm-namespace"
|
||||
import { createPwNamespaceMethods } from "../namespaces/pw-namespace"
|
||||
|
|
@ -15,6 +16,7 @@ type BaseInputsConfig = {
|
|||
envs: TestResult["envs"]
|
||||
request: HoppRESTRequest
|
||||
cookies: Cookie[] | null
|
||||
getUpdatedRequest?: () => HoppRESTRequest
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -25,56 +27,146 @@ export const createBaseInputs = (
|
|||
config: BaseInputsConfig
|
||||
): BaseInputs => {
|
||||
// Get environment methods - Applicable to both hopp and pw namespaces
|
||||
const { methods: envMethods, updatedEnvs } = getSharedEnvMethods(
|
||||
config.envs,
|
||||
true
|
||||
)
|
||||
const {
|
||||
methods: envMethods,
|
||||
pmSetAny,
|
||||
updatedEnvs,
|
||||
} = getSharedEnvMethods(config.envs, true)
|
||||
|
||||
const { methods: cookieMethods, getUpdatedCookies } = getSharedCookieMethods(
|
||||
config.cookies
|
||||
)
|
||||
|
||||
// Get request properties - shared across pre and post request contexts
|
||||
const requestProps = getSharedRequestProps(config.request)
|
||||
// For pre-request, use the updater function to read from mutated request
|
||||
const requestProps = getSharedRequestProps(
|
||||
config.request,
|
||||
config.getUpdatedRequest
|
||||
)
|
||||
|
||||
// Cookie accessors
|
||||
const cookieProps = {
|
||||
cookieGet: defineSandboxFn(ctx, "cookieGet", (domain: any, name: any) => {
|
||||
return cookieMethods.get(domain, name) || null
|
||||
}),
|
||||
cookieSet: defineSandboxFn(ctx, "cookieSet", (domain: any, cookie: any) => {
|
||||
return cookieMethods.set(domain, cookie)
|
||||
}),
|
||||
cookieHas: defineSandboxFn(ctx, "cookieHas", (domain: any, name: any) => {
|
||||
return cookieMethods.has(domain, name)
|
||||
}),
|
||||
cookieGetAll: defineSandboxFn(ctx, "cookieGetAll", (domain: any) => {
|
||||
return cookieMethods.getAll(domain)
|
||||
}),
|
||||
cookieGet: defineSandboxFn(
|
||||
ctx,
|
||||
"cookieGet",
|
||||
(domain: SandboxValue, name: SandboxValue) => {
|
||||
return cookieMethods.get(domain, name) || null
|
||||
}
|
||||
),
|
||||
cookieSet: defineSandboxFn(
|
||||
ctx,
|
||||
"cookieSet",
|
||||
(domain: SandboxValue, cookie: SandboxValue) => {
|
||||
return cookieMethods.set(domain, cookie)
|
||||
}
|
||||
),
|
||||
cookieHas: defineSandboxFn(
|
||||
ctx,
|
||||
"cookieHas",
|
||||
(domain: SandboxValue, name: SandboxValue) => {
|
||||
return cookieMethods.has(domain, name)
|
||||
}
|
||||
),
|
||||
cookieGetAll: defineSandboxFn(
|
||||
ctx,
|
||||
"cookieGetAll",
|
||||
(domain: SandboxValue) => {
|
||||
return cookieMethods.getAll(domain)
|
||||
}
|
||||
),
|
||||
cookieDelete: defineSandboxFn(
|
||||
ctx,
|
||||
"cookieDelete",
|
||||
(domain: any, name: any) => {
|
||||
(domain: SandboxValue, name: SandboxValue) => {
|
||||
return cookieMethods.delete(domain, name)
|
||||
}
|
||||
),
|
||||
cookieClear: defineSandboxFn(ctx, "cookieClear", (domain: any) => {
|
||||
cookieClear: defineSandboxFn(ctx, "cookieClear", (domain: SandboxValue) => {
|
||||
return cookieMethods.clear(domain)
|
||||
}),
|
||||
}
|
||||
|
||||
// Environment accessors for toObject() support
|
||||
const envAccessors = {
|
||||
getAllSelectedEnvs: defineSandboxFn(ctx, "getAllSelectedEnvs", () => {
|
||||
return updatedEnvs.selected || []
|
||||
}),
|
||||
getAllGlobalEnvs: defineSandboxFn(ctx, "getAllGlobalEnvs", () => {
|
||||
return updatedEnvs.global || []
|
||||
}),
|
||||
}
|
||||
|
||||
// Combine all namespace methods
|
||||
const pwMethods = createPwNamespaceMethods(ctx, envMethods, requestProps)
|
||||
const hoppMethods = createHoppNamespaceMethods(ctx, envMethods, requestProps)
|
||||
const pmMethods = createPmNamespaceMethods(ctx, config)
|
||||
|
||||
// PM namespace-specific setter that accepts any type (for type preservation)
|
||||
const pmEnvSetAny = defineSandboxFn(
|
||||
ctx,
|
||||
"pmEnvSetAny",
|
||||
function (key: SandboxValue, value: SandboxValue, options: SandboxValue) {
|
||||
return pmSetAny(key, value, options)
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
...pwMethods,
|
||||
...hoppMethods,
|
||||
...pmMethods,
|
||||
...cookieProps,
|
||||
...envAccessors,
|
||||
// PM-specific env setter (preserves all types)
|
||||
pmEnvSetAny,
|
||||
// Expose the updated state accessors
|
||||
getUpdatedEnvs: () => updatedEnvs,
|
||||
getUpdatedEnvs: () => {
|
||||
// Convert markers back to strings for UI display
|
||||
// (using centralized markers from constants/sandbox-markers.ts)
|
||||
|
||||
// Handle case where envs is not provided
|
||||
if (!updatedEnvs) {
|
||||
return { global: [], selected: [] }
|
||||
}
|
||||
|
||||
const convertMarkersToStrings = (env: SandboxValue) => {
|
||||
const convertValue = (value: SandboxValue) => {
|
||||
// Convert markers to string representations
|
||||
if (value === UNDEFINED_MARKER) return "undefined"
|
||||
if (value === NULL_MARKER) return "null"
|
||||
|
||||
// Convert complex types (arrays, objects) to JSON strings for UI display
|
||||
// This prevents Vue UI from calling .match() on non-string values
|
||||
if (typeof value === "object" && value !== null) {
|
||||
try {
|
||||
return JSON.stringify(value)
|
||||
} catch (_) {
|
||||
// If JSON.stringify fails (circular refs, etc.), return string representation
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert all non-string primitives to strings for UI compatibility
|
||||
// Vue UI calls .match() on values, which only works on strings
|
||||
if (typeof value !== "string") {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// Return strings as-is
|
||||
return value
|
||||
}
|
||||
|
||||
return {
|
||||
...env,
|
||||
currentValue: convertValue(env.currentValue),
|
||||
initialValue: convertValue(env.initialValue),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
global: (updatedEnvs.global || []).map(convertMarkersToStrings),
|
||||
selected: (updatedEnvs.selected || []).map(convertMarkersToStrings),
|
||||
}
|
||||
},
|
||||
getUpdatedCookies,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
import { CageModuleCtx, defineSandboxFn } from "faraday-cage/modules"
|
||||
|
||||
import { TestDescriptor, ExpectationMethods } from "~/types"
|
||||
import { TestDescriptor, ExpectationMethods, SandboxValue } from "~/types"
|
||||
import { createExpectation } from "~/utils/shared"
|
||||
|
||||
/**
|
||||
|
|
@ -10,49 +10,53 @@ export const createExpectationMethods = (
|
|||
ctx: CageModuleCtx,
|
||||
testRunStack: TestDescriptor[]
|
||||
): ExpectationMethods => {
|
||||
const createExpect = (expectVal: any) =>
|
||||
const createExpect = (expectVal: SandboxValue) =>
|
||||
createExpectation(expectVal, false, testRunStack)
|
||||
|
||||
return {
|
||||
expectToBe: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBe",
|
||||
(expectVal: any, expectedVal: any) => {
|
||||
(expectVal: SandboxValue, expectedVal: SandboxValue) => {
|
||||
return createExpect(expectVal).toBe(expectedVal)
|
||||
}
|
||||
),
|
||||
expectToBeLevel2xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBeLevel2xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).toBeLevel2xx()
|
||||
}
|
||||
),
|
||||
expectToBeLevel3xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBeLevel3xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).toBeLevel3xx()
|
||||
}
|
||||
),
|
||||
expectToBeLevel4xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBeLevel4xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).toBeLevel4xx()
|
||||
}
|
||||
),
|
||||
expectToBeLevel5xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBeLevel5xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).toBeLevel5xx()
|
||||
}
|
||||
),
|
||||
expectToBeType: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToBeType",
|
||||
(expectVal: any, expectedType: any, isDate: any) => {
|
||||
(
|
||||
expectVal: SandboxValue,
|
||||
expectedType: SandboxValue,
|
||||
isDate: SandboxValue
|
||||
) => {
|
||||
const resolved =
|
||||
isDate && typeof expectVal === "string"
|
||||
? new Date(expectVal)
|
||||
|
|
@ -65,14 +69,14 @@ export const createExpectationMethods = (
|
|||
expectToHaveLength: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToHaveLength",
|
||||
(expectVal: any, expectedLength: any) => {
|
||||
(expectVal: SandboxValue, expectedLength: SandboxValue) => {
|
||||
return createExpect(expectVal).toHaveLength(expectedLength)
|
||||
}
|
||||
),
|
||||
expectToInclude: defineSandboxFn(
|
||||
ctx,
|
||||
"expectToInclude",
|
||||
(expectVal: any, needle: any) => {
|
||||
(expectVal: SandboxValue, needle: SandboxValue) => {
|
||||
return createExpect(expectVal).toInclude(needle)
|
||||
}
|
||||
),
|
||||
|
|
@ -81,42 +85,46 @@ export const createExpectationMethods = (
|
|||
expectNotToBe: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBe",
|
||||
(expectVal: any, expectedVal: any) => {
|
||||
(expectVal: SandboxValue, expectedVal: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toBe(expectedVal)
|
||||
}
|
||||
),
|
||||
expectNotToBeLevel2xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBeLevel2xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toBeLevel2xx()
|
||||
}
|
||||
),
|
||||
expectNotToBeLevel3xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBeLevel3xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toBeLevel3xx()
|
||||
}
|
||||
),
|
||||
expectNotToBeLevel4xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBeLevel4xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toBeLevel4xx()
|
||||
}
|
||||
),
|
||||
expectNotToBeLevel5xx: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBeLevel5xx",
|
||||
(expectVal: any) => {
|
||||
(expectVal: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toBeLevel5xx()
|
||||
}
|
||||
),
|
||||
expectNotToBeType: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToBeType",
|
||||
(expectVal: any, expectedType: any, isDate: any) => {
|
||||
(
|
||||
expectVal: SandboxValue,
|
||||
expectedType: SandboxValue,
|
||||
isDate: SandboxValue
|
||||
) => {
|
||||
const resolved =
|
||||
isDate && typeof expectVal === "string"
|
||||
? new Date(expectVal)
|
||||
|
|
@ -129,14 +137,14 @@ export const createExpectationMethods = (
|
|||
expectNotToHaveLength: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToHaveLength",
|
||||
(expectVal: any, expectedLength: any) => {
|
||||
(expectVal: SandboxValue, expectedLength: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toHaveLength(expectedLength)
|
||||
}
|
||||
),
|
||||
expectNotToInclude: defineSandboxFn(
|
||||
ctx,
|
||||
"expectNotToInclude",
|
||||
(expectVal: any, needle: any) => {
|
||||
(expectVal: SandboxValue, needle: SandboxValue) => {
|
||||
return createExpect(expectVal).not.toInclude(needle)
|
||||
}
|
||||
),
|
||||
|
|
|
|||
|
|
@ -23,68 +23,68 @@ export const createRequestSetterMethods = (
|
|||
|
||||
const setterMethods = {
|
||||
// Request setter methods
|
||||
setRequestUrl: defineSandboxFn(ctx, "setRequestUrl", (url: any) => {
|
||||
setRequestUrl: defineSandboxFn(ctx, "setRequestUrl", (url: unknown) => {
|
||||
requestMethods.setUrl(url as string)
|
||||
}),
|
||||
setRequestMethod: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestMethod",
|
||||
(method: any) => {
|
||||
(method: unknown) => {
|
||||
requestMethods.setMethod(method as string)
|
||||
}
|
||||
),
|
||||
setRequestHeader: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestHeader",
|
||||
(name: any, value: any) => {
|
||||
(name: unknown, value: unknown) => {
|
||||
requestMethods.setHeader(name as string, value as string)
|
||||
}
|
||||
),
|
||||
setRequestHeaders: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestHeaders",
|
||||
(headers: any) => {
|
||||
(headers: unknown) => {
|
||||
requestMethods.setHeaders(headers as HoppRESTHeaders)
|
||||
}
|
||||
),
|
||||
removeRequestHeader: defineSandboxFn(
|
||||
ctx,
|
||||
"removeRequestHeader",
|
||||
(key: any) => {
|
||||
(key: unknown) => {
|
||||
requestMethods.removeHeader(key as string)
|
||||
}
|
||||
),
|
||||
setRequestParam: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestParam",
|
||||
(name: any, value: any) => {
|
||||
(name: unknown, value: unknown) => {
|
||||
requestMethods.setParam(name as string, value as string)
|
||||
}
|
||||
),
|
||||
setRequestParams: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestParams",
|
||||
(params: any) => {
|
||||
(params: unknown) => {
|
||||
requestMethods.setParams(params as HoppRESTParams)
|
||||
}
|
||||
),
|
||||
removeRequestParam: defineSandboxFn(
|
||||
ctx,
|
||||
"removeRequestParam",
|
||||
(key: any) => {
|
||||
(key: unknown) => {
|
||||
requestMethods.removeParam(key as string)
|
||||
}
|
||||
),
|
||||
setRequestBody: defineSandboxFn(ctx, "setRequestBody", (body: any) => {
|
||||
setRequestBody: defineSandboxFn(ctx, "setRequestBody", (body: unknown) => {
|
||||
requestMethods.setBody(body as HoppRESTReqBody)
|
||||
}),
|
||||
setRequestAuth: defineSandboxFn(ctx, "setRequestAuth", (auth: any) => {
|
||||
setRequestAuth: defineSandboxFn(ctx, "setRequestAuth", (auth: unknown) => {
|
||||
requestMethods.setAuth(auth as HoppRESTAuth)
|
||||
}),
|
||||
setRequestVariable: defineSandboxFn(
|
||||
ctx,
|
||||
"setRequestVariable",
|
||||
(key: any, value: any) => {
|
||||
(key: unknown, value: unknown) => {
|
||||
requestMethods.setRequestVariable(key as string, value as string)
|
||||
}
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* HTTP Status Code Reason Phrases
|
||||
*
|
||||
* Standard HTTP status codes and their corresponding reason phrases
|
||||
* as defined in RFC 7231 and related specifications.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc7231#section-6
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
*/
|
||||
export const HTTP_STATUS_REASONS: Readonly<Record<number, string>> = {
|
||||
// 1xx Informational
|
||||
100: "Continue",
|
||||
101: "Switching Protocols",
|
||||
102: "Processing",
|
||||
103: "Early Hints",
|
||||
|
||||
// 2xx Success
|
||||
200: "OK",
|
||||
201: "Created",
|
||||
202: "Accepted",
|
||||
203: "Non-Authoritative Information",
|
||||
204: "No Content",
|
||||
205: "Reset Content",
|
||||
206: "Partial Content",
|
||||
207: "Multi-Status",
|
||||
208: "Already Reported",
|
||||
226: "IM Used",
|
||||
|
||||
// 3xx Redirection
|
||||
300: "Multiple Choices",
|
||||
301: "Moved Permanently",
|
||||
302: "Found",
|
||||
303: "See Other",
|
||||
304: "Not Modified",
|
||||
305: "Use Proxy",
|
||||
307: "Temporary Redirect",
|
||||
308: "Permanent Redirect",
|
||||
|
||||
// 4xx Client Error
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
402: "Payment Required",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Timeout",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Payload Too Large",
|
||||
414: "URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Range Not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
418: "I'm a teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Entity",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
425: "Too Early",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required",
|
||||
429: "Too Many Requests",
|
||||
431: "Request Header Fields Too Large",
|
||||
451: "Unavailable For Legal Reasons",
|
||||
|
||||
// 5xx Server Error
|
||||
500: "Internal Server Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
505: "HTTP Version Not Supported",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
510: "Not Extended",
|
||||
511: "Network Authentication Required",
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Get the reason phrase for an HTTP status code
|
||||
* @param statusCode - The HTTP status code
|
||||
* @returns The reason phrase, or "Unknown" if not found
|
||||
*/
|
||||
export const getStatusReason = (statusCode: number): string => {
|
||||
return HTTP_STATUS_REASONS[statusCode] || "Unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a status code is informational (1xx)
|
||||
*/
|
||||
export const isInformational = (statusCode: number): boolean => {
|
||||
return statusCode >= 100 && statusCode < 200
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a status code is successful (2xx)
|
||||
*/
|
||||
export const isSuccess = (statusCode: number): boolean => {
|
||||
return statusCode >= 200 && statusCode < 300
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a status code is a redirection (3xx)
|
||||
*/
|
||||
export const isRedirection = (statusCode: number): boolean => {
|
||||
return statusCode >= 300 && statusCode < 400
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a status code is a client error (4xx)
|
||||
*/
|
||||
export const isClientError = (statusCode: number): boolean => {
|
||||
return statusCode >= 400 && statusCode < 500
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a status code is a server error (5xx)
|
||||
*/
|
||||
export const isServerError = (statusCode: number): boolean => {
|
||||
return statusCode >= 500 && statusCode < 600
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Special marker constants for preserving undefined and null values
|
||||
* across the sandbox boundary during serialization.
|
||||
*
|
||||
* These markers are used because:
|
||||
* - JSON.stringify converts undefined to null or omits the property
|
||||
* - We need to distinguish between actual null and serialized undefined
|
||||
* - The sandbox boundary requires serialization, losing type information
|
||||
*/
|
||||
export const UNDEFINED_MARKER = "__HOPPSCOTCH_UNDEFINED__" as const
|
||||
export const NULL_MARKER = "__HOPPSCOTCH_NULL__" as const
|
||||
|
||||
export type SandboxMarker = typeof UNDEFINED_MARKER | typeof NULL_MARKER
|
||||
|
||||
/**
|
||||
* Converts marker strings back to their original values
|
||||
*/
|
||||
export const convertMarkerToValue = (value: unknown): unknown => {
|
||||
if (value === UNDEFINED_MARKER) return undefined
|
||||
if (value === NULL_MARKER) return null
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts null/undefined values to marker strings for serialization
|
||||
*/
|
||||
export const convertValueToMarker = (value: unknown): unknown => {
|
||||
if (value === undefined) return UNDEFINED_MARKER
|
||||
if (value === null) return NULL_MARKER
|
||||
return value
|
||||
}
|
||||
|
|
@ -6,6 +6,34 @@ import type { EnvAPIOptions } from "~/utils/shared"
|
|||
// Infer the return type of defineSandboxFn from faraday-cage
|
||||
type SandboxFunction = ReturnType<typeof defineSandboxFn>
|
||||
|
||||
/**
|
||||
* Type alias for values that cross the QuickJS sandbox boundary.
|
||||
*
|
||||
* Values passed between the host environment and the QuickJS sandbox lose their
|
||||
* TypeScript type information during serialization. This type alias serves as
|
||||
* a documented alternative to raw `any`, making it explicit that these values:
|
||||
*
|
||||
* - Come from or go to the sandbox (pre-request/post-request scripts)
|
||||
* - Have been serialized and may not preserve complex types
|
||||
* - Require runtime validation when type safety is needed
|
||||
*
|
||||
* Use this type for:
|
||||
* - Function parameters that accept user script values
|
||||
* - Return values sent back to the sandbox
|
||||
* - PM namespace compatibility (preserves non-string types like arrays, objects)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Function accepting values from user scripts
|
||||
* const envSetAny = (key: SandboxValue, value: SandboxValue) => {
|
||||
* // Runtime validation
|
||||
* if (typeof key !== "string") throw new Error("Expected string key")
|
||||
* // ... handle value
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type SandboxValue = any
|
||||
|
||||
/**
|
||||
* The response object structure exposed to the test script
|
||||
*/
|
||||
|
|
@ -93,14 +121,14 @@ export type SandboxPreRequestResult = {
|
|||
}
|
||||
|
||||
export interface Expectation {
|
||||
toBe(expectedVal: any): void
|
||||
toBe(expectedVal: SandboxValue): void
|
||||
toBeLevel2xx(): void
|
||||
toBeLevel3xx(): void
|
||||
toBeLevel4xx(): void
|
||||
toBeLevel5xx(): void
|
||||
toBeType(expectedType: any): void
|
||||
toHaveLength(expectedLength: any): void
|
||||
toInclude(needle: any): void
|
||||
toBeType(expectedType: SandboxValue): void
|
||||
toHaveLength(expectedLength: SandboxValue): void
|
||||
toInclude(needle: SandboxValue): void
|
||||
readonly not: Expectation
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +280,7 @@ export interface BaseInputs
|
|||
cookieGetAll: SandboxFunction
|
||||
cookieDelete: SandboxFunction
|
||||
cookieClear: SandboxFunction
|
||||
getUpdatedEnvs: () => any
|
||||
getUpdatedEnvs: () => SandboxValue
|
||||
getUpdatedCookies: () => Cookie[] | null
|
||||
[key: string]: any
|
||||
[key: string]: SandboxValue // Index signature for dynamic namespace properties
|
||||
}
|
||||
|
|
|
|||
354
packages/hoppscotch-js-sandbox/src/utils/chai-integration.ts
Normal file
354
packages/hoppscotch-js-sandbox/src/utils/chai-integration.ts
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
import * as chai from "chai"
|
||||
import { TestDescriptor, SandboxValue } from "../types"
|
||||
|
||||
/**
|
||||
* Creates a Chai expectation that records results to the test stack
|
||||
* This integrates actual Chai.js with Hoppscotch's test reporting system
|
||||
*
|
||||
* Returns a serializable proxy object that can cross the sandbox boundary
|
||||
*/
|
||||
export function createChaiExpectation(
|
||||
value: SandboxValue,
|
||||
testStack: TestDescriptor[]
|
||||
) {
|
||||
// Create the actual Chai assertion
|
||||
const assertion = chai.expect(value)
|
||||
|
||||
// Create a serializable proxy that can cross the sandbox boundary
|
||||
return createSerializableProxy(assertion, value, testStack, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a serializable proxy object that mimics Chai's API
|
||||
* This can cross the sandbox boundary unlike the actual Chai assertion object
|
||||
*/
|
||||
function createSerializableProxy(
|
||||
assertion: any, // Chai assertion object - dynamic API, must be any
|
||||
originalValue: SandboxValue,
|
||||
testStack: TestDescriptor[],
|
||||
flags: SandboxValue
|
||||
): any {
|
||||
// Returns dynamic proxy with Chai-like API
|
||||
const proxy: any = {} // Dynamic proxy object with Chai-like methods
|
||||
|
||||
// Helper to create assertion methods
|
||||
const createMethod = (methodName: string) => {
|
||||
return (...args: SandboxValue[]) => {
|
||||
try {
|
||||
// Call the actual Chai method
|
||||
const result = assertion[methodName](...args)
|
||||
|
||||
// Record success
|
||||
recordResult(
|
||||
testStack,
|
||||
true,
|
||||
buildMessage(assertion, methodName, args, originalValue, false)
|
||||
)
|
||||
|
||||
// If result is a Chai assertion, return a new serializable proxy
|
||||
if (result && typeof result === "object" && result.__flags) {
|
||||
return createSerializableProxy(
|
||||
result,
|
||||
result._obj,
|
||||
testStack,
|
||||
result.__flags
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: any) {
|
||||
// Record failure but DON'T throw - allow test to continue
|
||||
recordResult(testStack, false, extractErrorMessage(error))
|
||||
// Return a proxy to allow chaining even after failure
|
||||
return createSerializableProxy(
|
||||
assertion,
|
||||
originalValue,
|
||||
testStack,
|
||||
flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create assertion getter properties (these perform assertions when accessed)
|
||||
const createAssertionGetter = (propName: string) => {
|
||||
return () => {
|
||||
try {
|
||||
// Access the property which triggers the assertion
|
||||
void assertion[propName]
|
||||
|
||||
// Record success
|
||||
recordResult(
|
||||
testStack,
|
||||
true,
|
||||
buildMessage(assertion, propName, [], originalValue, false)
|
||||
)
|
||||
|
||||
// Return undefined (assertion getters don't return values)
|
||||
return undefined
|
||||
} catch (error: any) {
|
||||
// Record failure but DON'T throw - allow test to continue
|
||||
recordResult(testStack, false, extractErrorMessage(error))
|
||||
// Return undefined to allow test to continue
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create language chain getters (these just return new assertions)
|
||||
const createChainGetter = (propName: string) => {
|
||||
return () => {
|
||||
// Access the property on the Chai assertion
|
||||
const value = assertion[propName]
|
||||
// Return a new serializable proxy
|
||||
if (value && typeof value === "object" && value.__flags) {
|
||||
return createSerializableProxy(
|
||||
value,
|
||||
value._obj || originalValue,
|
||||
testStack,
|
||||
value.__flags
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// Add all Chai assertion methods (functions)
|
||||
const methods = [
|
||||
"equal",
|
||||
"equals",
|
||||
"eq",
|
||||
"eql",
|
||||
"include",
|
||||
"includes",
|
||||
"contain",
|
||||
"contains",
|
||||
"a",
|
||||
"an",
|
||||
"instanceof",
|
||||
"instanceOf",
|
||||
"property",
|
||||
"ownProperty",
|
||||
"ownPropertyDescriptor",
|
||||
"lengthOf",
|
||||
"length",
|
||||
"match",
|
||||
"matches",
|
||||
"string",
|
||||
"keys",
|
||||
"key",
|
||||
"throw",
|
||||
"throws",
|
||||
"Throw",
|
||||
"respondTo",
|
||||
"respondsTo",
|
||||
"satisfy",
|
||||
"satisfies",
|
||||
"closeTo",
|
||||
"approximately",
|
||||
"members",
|
||||
"oneOf",
|
||||
"change",
|
||||
"changes",
|
||||
"increase",
|
||||
"increases",
|
||||
"decrease",
|
||||
"decreases",
|
||||
"by",
|
||||
"above",
|
||||
"gt",
|
||||
"greaterThan",
|
||||
"least",
|
||||
"gte",
|
||||
"below",
|
||||
"lt",
|
||||
"lessThan",
|
||||
"most",
|
||||
"lte",
|
||||
"within",
|
||||
]
|
||||
|
||||
// Add all methods to the proxy
|
||||
methods.forEach((method) => {
|
||||
proxy[method] = createMethod(method)
|
||||
})
|
||||
|
||||
// Add assertion getters (these perform assertions when accessed)
|
||||
const assertionGetters = [
|
||||
"ok",
|
||||
"true",
|
||||
"false",
|
||||
"null",
|
||||
"undefined",
|
||||
"NaN",
|
||||
"exist",
|
||||
"empty",
|
||||
"arguments",
|
||||
"Arguments",
|
||||
"finite",
|
||||
"extensible",
|
||||
"sealed",
|
||||
"frozen",
|
||||
]
|
||||
|
||||
assertionGetters.forEach((getter) => {
|
||||
Object.defineProperty(proxy, getter, {
|
||||
get: createAssertionGetter(getter),
|
||||
enumerable: false, // Don't enumerate to avoid serialization issues
|
||||
configurable: true,
|
||||
})
|
||||
})
|
||||
|
||||
// Add language chains as getters (these just return new assertions)
|
||||
const chains = [
|
||||
"to",
|
||||
"be",
|
||||
"been",
|
||||
"is",
|
||||
"that",
|
||||
"which",
|
||||
"and",
|
||||
"has",
|
||||
"have",
|
||||
"with",
|
||||
"at",
|
||||
"of",
|
||||
"same",
|
||||
"but",
|
||||
"does",
|
||||
"not",
|
||||
"deep",
|
||||
"nested",
|
||||
"own",
|
||||
"ordered",
|
||||
"any",
|
||||
"all",
|
||||
"itself",
|
||||
]
|
||||
|
||||
chains.forEach((chain) => {
|
||||
Object.defineProperty(proxy, chain, {
|
||||
get: createChainGetter(chain),
|
||||
enumerable: false, // Don't enumerate to avoid serialization issues
|
||||
configurable: true,
|
||||
})
|
||||
})
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an assertion result to the test stack
|
||||
*/
|
||||
function recordResult(
|
||||
testStack: TestDescriptor[],
|
||||
passed: boolean,
|
||||
message: string
|
||||
) {
|
||||
if (testStack.length === 0) return
|
||||
|
||||
const currentTest = testStack[testStack.length - 1]
|
||||
currentTest.expectResults.push({
|
||||
status: passed ? "pass" : "fail",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a message for an assertion
|
||||
* Tries to match the format expected by tests
|
||||
*/
|
||||
function buildMessage(
|
||||
assertion: any, // Chai assertion object - dynamic API, must be any
|
||||
method: string,
|
||||
args: SandboxValue[],
|
||||
value: SandboxValue,
|
||||
_failed: boolean
|
||||
): string {
|
||||
const flags = assertion.__flags || {}
|
||||
const valueStr = formatValue(value)
|
||||
|
||||
let message = `Expected ${valueStr}`
|
||||
|
||||
// Add "to" or "to not"
|
||||
if (flags.negate) {
|
||||
message += " to not"
|
||||
} else {
|
||||
message += " to"
|
||||
}
|
||||
|
||||
// Add modifiers
|
||||
if (flags.deep) message += " deep"
|
||||
if (flags.own) message += " own"
|
||||
if (flags.nested) message += " nested"
|
||||
|
||||
// Add the method name
|
||||
message += ` ${method}`
|
||||
|
||||
// Add arguments
|
||||
if (args.length > 0) {
|
||||
const argStrs = args.map(formatValue)
|
||||
message += ` ${argStrs.join(", ")}`
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a clean error message from a Chai assertion error
|
||||
*/
|
||||
function extractErrorMessage(error: any): string {
|
||||
if (!error) return "Assertion failed"
|
||||
|
||||
// Chai errors have a message property
|
||||
let message = error.message || String(error)
|
||||
|
||||
// Remove stack traces and extra info
|
||||
const lines = message.split("\n")
|
||||
if (lines.length > 0) {
|
||||
message = lines[0]
|
||||
}
|
||||
|
||||
// Clean up Chai's "expected X to Y" format
|
||||
// Chai uses lowercase "expected", we want "Expected"
|
||||
if (message.startsWith("expected ")) {
|
||||
message = "E" + message.substring(1)
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a value for display in messages
|
||||
*/
|
||||
function formatValue(val: SandboxValue): string {
|
||||
if (val === null) return "null"
|
||||
if (val === undefined) return "undefined"
|
||||
if (typeof val === "string") return `'${val}'`
|
||||
if (typeof val === "number") {
|
||||
if (isNaN(val)) return "NaN"
|
||||
if (val === Infinity) return "Infinity"
|
||||
if (val === -Infinity) return "-Infinity"
|
||||
return String(val)
|
||||
}
|
||||
if (typeof val === "boolean") return String(val)
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) return "[]"
|
||||
const items = val.slice(0, 10).map(formatValue)
|
||||
return `[${items.join(", ")}]`
|
||||
}
|
||||
if (typeof val === "object") {
|
||||
try {
|
||||
const keys = Object.keys(val)
|
||||
if (keys.length === 0) return "{}"
|
||||
const pairs = keys.slice(0, 5).map((k) => `${k}: ${formatValue(val[k])}`)
|
||||
return `{${pairs.join(", ")}}`
|
||||
} catch {
|
||||
return "[object Object]"
|
||||
}
|
||||
}
|
||||
if (typeof val === "function") {
|
||||
return val.name || "[Function]"
|
||||
}
|
||||
return String(val)
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ export const getRequestSetterMethods = (request: HoppRESTRequest) => {
|
|||
}
|
||||
|
||||
const setMethod = (method: string) => {
|
||||
updatedRequest.method = method.toUpperCase()
|
||||
// NOTE: Postman does NOT normalize method to uppercase, so we preserve the original case
|
||||
updatedRequest.method = method
|
||||
}
|
||||
const setHeader = (name: string, value: string) => {
|
||||
const headers = [...updatedRequest.headers]
|
||||
|
|
@ -45,7 +46,9 @@ export const getRequestSetterMethods = (request: HoppRESTRequest) => {
|
|||
}
|
||||
|
||||
const removeHeader = (key: string) => {
|
||||
updatedRequest.headers = updatedRequest.headers.filter((h) => h.key !== key)
|
||||
updatedRequest.headers = updatedRequest.headers.filter(
|
||||
(h) => h.key.toLowerCase() !== key.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
const setParam = (name: string, value: string) => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
SelectedEnvItem,
|
||||
TestDescriptor,
|
||||
TestResult,
|
||||
SandboxValue,
|
||||
} from "../types"
|
||||
|
||||
export type EnvSource = "active" | "global" | "all"
|
||||
|
|
@ -57,7 +58,7 @@ const findEnvIndex = (
|
|||
|
||||
const setEnv = (
|
||||
envName: string,
|
||||
envValue: string,
|
||||
envValue: SandboxValue,
|
||||
envs: TestResult["envs"],
|
||||
options: { setInitialValue?: boolean; source: EnvSource } = {
|
||||
setInitialValue: false,
|
||||
|
|
@ -154,6 +155,7 @@ export function getSharedEnvMethods(
|
|||
setInitial: (key: string, value: string, options?: EnvAPIOptions) => void
|
||||
}
|
||||
}
|
||||
pmSetAny: (key: string, value: SandboxValue, options?: EnvAPIOptions) => void
|
||||
updatedEnvs: TestResult["envs"]
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +189,7 @@ export function getSharedEnvMethods(
|
|||
let updatedEnvs = envs
|
||||
|
||||
const envGetFn = (
|
||||
key: any,
|
||||
key: unknown,
|
||||
options: EnvAPIOptions = { fallbackToNull: false, source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
|
|
@ -198,7 +200,7 @@ export function getSharedEnvMethods(
|
|||
getEnv(key, updatedEnvs, options),
|
||||
O.fold(
|
||||
() => (options.fallbackToNull ? null : undefined),
|
||||
(env) => String(env.currentValue)
|
||||
(env) => env.currentValue // Return value as-is (PM namespace preserves types)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -206,7 +208,7 @@ export function getSharedEnvMethods(
|
|||
}
|
||||
|
||||
const envGetResolveFn = (
|
||||
key: any,
|
||||
key: unknown,
|
||||
options: EnvAPIOptions = { fallbackToNull: false, source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
|
|
@ -225,13 +227,17 @@ export function getSharedEnvMethods(
|
|||
getEnv(key, updatedEnvs, options),
|
||||
E.fromOption(() => "INVALID_KEY" as const),
|
||||
|
||||
E.map((e) =>
|
||||
pipe(
|
||||
parseTemplateStringE(e.currentValue, envVars), // If the recursive resolution failed, return the unresolved value
|
||||
E.getOrElse(() => e.currentValue)
|
||||
)
|
||||
),
|
||||
E.map((x) => String(x)),
|
||||
E.map((e) => {
|
||||
// Only resolve templates if the value is a string (PM namespace may have non-strings)
|
||||
if (typeof e.currentValue === "string") {
|
||||
return pipe(
|
||||
parseTemplateStringE(e.currentValue, envVars),
|
||||
E.getOrElse(() => e.currentValue)
|
||||
)
|
||||
}
|
||||
// Return non-string values as-is (arrays, objects, null, etc.)
|
||||
return e.currentValue
|
||||
}),
|
||||
|
||||
E.getOrElseW(() => (options.fallbackToNull ? null : undefined))
|
||||
)
|
||||
|
|
@ -240,8 +246,8 @@ export function getSharedEnvMethods(
|
|||
}
|
||||
|
||||
const envSetFn = (
|
||||
key: any,
|
||||
value: any,
|
||||
key: unknown,
|
||||
value: unknown,
|
||||
options: EnvAPIOptions = { source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
|
|
@ -257,7 +263,26 @@ export function getSharedEnvMethods(
|
|||
return undefined
|
||||
}
|
||||
|
||||
const envUnsetFn = (key: any, options: EnvAPIOptions = { source: "all" }) => {
|
||||
// PM namespace-specific setter that accepts any type (for Postman compatibility)
|
||||
const envSetAnyFn = (
|
||||
key: unknown,
|
||||
value: SandboxValue, // Intentionally SandboxValue for PM namespace type preservation
|
||||
options: EnvAPIOptions = { source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
throw new Error("Expected key to be a string")
|
||||
}
|
||||
|
||||
// PM namespace preserves ALL types (arrays, objects, primitives, null, undefined)
|
||||
updatedEnvs = setEnv(key, value, updatedEnvs, options)
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
const envUnsetFn = (
|
||||
key: unknown,
|
||||
options: EnvAPIOptions = { source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
throw new Error("Expected key to be a string")
|
||||
}
|
||||
|
|
@ -267,7 +292,7 @@ export function getSharedEnvMethods(
|
|||
return undefined
|
||||
}
|
||||
|
||||
const envResolveFn = (value: any) => {
|
||||
const envResolveFn = (value: unknown) => {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Expected value to be a string")
|
||||
}
|
||||
|
|
@ -317,7 +342,7 @@ export function getSharedEnvMethods(
|
|||
}
|
||||
|
||||
const envGetInitialRawFn = (
|
||||
key: any,
|
||||
key: unknown,
|
||||
options: EnvAPIOptions = { source: "all" }
|
||||
) => {
|
||||
if (typeof key !== "string") {
|
||||
|
|
@ -328,7 +353,7 @@ export function getSharedEnvMethods(
|
|||
getEnv(key, updatedEnvs, options),
|
||||
O.fold(
|
||||
() => undefined,
|
||||
(env) => String(env.initialValue)
|
||||
(env) => env.initialValue // Return as-is (PM namespace preserves types)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -375,7 +400,8 @@ export function getSharedEnvMethods(
|
|||
setInitial: envSetInitialFn,
|
||||
},
|
||||
},
|
||||
|
||||
// Expose PM-specific setter that accepts any type
|
||||
pmSetAny: envSetAnyFn,
|
||||
updatedEnvs,
|
||||
}
|
||||
}
|
||||
|
|
@ -408,7 +434,7 @@ export const getSharedCookieMethods = (cookies: Cookie[] | null) => {
|
|||
}
|
||||
}
|
||||
|
||||
const cookieGetFn = (domain: any, name: any): Cookie | null => {
|
||||
const cookieGetFn = (domain: unknown, name: unknown): Cookie | null => {
|
||||
throwIfCookiesUnsupported()
|
||||
|
||||
if (typeof domain !== "string" || typeof name !== "string") {
|
||||
|
|
@ -492,7 +518,7 @@ export const getSharedCookieMethods = (cookies: Cookie[] | null) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getResolvedExpectValue = (expectVal: any) => {
|
||||
const getResolvedExpectValue = (expectVal: SandboxValue) => {
|
||||
if (typeof expectVal !== "string") {
|
||||
return expectVal
|
||||
}
|
||||
|
|
@ -549,14 +575,14 @@ export function preventCyclicObjects<T extends object = Record<string, any>>(
|
|||
* @returns Object with the expectation methods
|
||||
*/
|
||||
export const createExpectation = (
|
||||
expectVal: any,
|
||||
expectVal: SandboxValue,
|
||||
negated: boolean,
|
||||
currTestStack: TestDescriptor[]
|
||||
): Expectation => {
|
||||
// Non-primitive values supplied are stringified in the isolate context
|
||||
const resolvedExpectVal = getResolvedExpectValue(expectVal)
|
||||
|
||||
const toBeFn = (expectedVal: any) => {
|
||||
const toBeFn = (expectedVal: SandboxValue) => {
|
||||
let assertion = resolvedExpectVal === expectedVal
|
||||
|
||||
if (negated) {
|
||||
|
|
@ -616,7 +642,7 @@ export const createExpectation = (
|
|||
const toBeLevel4xxFn = () => toBeLevelXxx("400", 400, 499)
|
||||
const toBeLevel5xxFn = () => toBeLevelXxx("500", 500, 599)
|
||||
|
||||
const toBeTypeFn = (expectedType: any) => {
|
||||
const toBeTypeFn = (expectedType: SandboxValue) => {
|
||||
if (
|
||||
[
|
||||
"string",
|
||||
|
|
@ -656,7 +682,7 @@ export const createExpectation = (
|
|||
return undefined
|
||||
}
|
||||
|
||||
const toHaveLengthFn = (expectedLength: any) => {
|
||||
const toHaveLengthFn = (expectedLength: SandboxValue) => {
|
||||
if (
|
||||
!(
|
||||
Array.isArray(resolvedExpectVal) ||
|
||||
|
|
@ -700,7 +726,7 @@ export const createExpectation = (
|
|||
return undefined
|
||||
}
|
||||
|
||||
const toIncludeFn = (needle: any) => {
|
||||
const toIncludeFn = (needle: SandboxValue) => {
|
||||
if (
|
||||
!(
|
||||
Array.isArray(resolvedExpectVal) ||
|
||||
|
|
@ -806,7 +832,7 @@ export const getTestRunnerScriptMethods = (envs: TestResult["envs"]) => {
|
|||
testRunStack[testRunStack.length - 1].children.push(child)
|
||||
}
|
||||
|
||||
const expectFn = (expectVal: any) =>
|
||||
const expectFn = (expectVal: unknown) =>
|
||||
createExpectation(expectVal, false, testRunStack)
|
||||
|
||||
const { methods, updatedEnvs } = getSharedEnvMethods(cloneDeep(envs))
|
||||
|
|
@ -824,30 +850,42 @@ export const getTestRunnerScriptMethods = (envs: TestResult["envs"]) => {
|
|||
* Compiles shared scripting API properties (scoped to requests) for use in both pre and post request scripts
|
||||
* Extracts shared properties from a request object
|
||||
* @param request The request object to extract shared properties from
|
||||
* @param getUpdatedRequest Optional function to get the updated request (for pre-request mutations)
|
||||
* @returns An object containing the shared properties of the request
|
||||
*/
|
||||
export const getSharedRequestProps = (request: HoppRESTRequest) => {
|
||||
export const getSharedRequestProps = (
|
||||
request: HoppRESTRequest,
|
||||
getUpdatedRequest?: () => HoppRESTRequest
|
||||
) => {
|
||||
return {
|
||||
get url() {
|
||||
return request.endpoint
|
||||
// For pre-request scripts, read from updated request to see mutations
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.endpoint
|
||||
},
|
||||
get method() {
|
||||
return request.method
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.method
|
||||
},
|
||||
get params() {
|
||||
return request.params
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.params
|
||||
},
|
||||
get headers() {
|
||||
return request.headers
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.headers
|
||||
},
|
||||
get body() {
|
||||
return request.body
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.body
|
||||
},
|
||||
get auth() {
|
||||
return request.auth
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.auth
|
||||
},
|
||||
get requestVariables() {
|
||||
return request.requestVariables
|
||||
const currentRequest = getUpdatedRequest ? getUpdatedRequest() : request
|
||||
return currentRequest.requestVariables
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
192
packages/hoppscotch-js-sandbox/src/utils/test-helpers.ts
Normal file
192
packages/hoppscotch-js-sandbox/src/utils/test-helpers.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* Consolidated test helpers for all namespace tests
|
||||
*
|
||||
* This file provides reusable helper functions to eliminate duplication
|
||||
* across 45+ test files that previously had inline `func` definitions.
|
||||
*/
|
||||
|
||||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { runTestScript, runPreRequestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
// Default fixtures used across test files
|
||||
export const defaultRequest = getDefaultRESTRequest()
|
||||
export const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 0,
|
||||
body: "hoi",
|
||||
headers: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test script and return the test results
|
||||
*
|
||||
* This is the most common pattern used across all test files.
|
||||
* Replaces the inline `func` helper pattern.
|
||||
*
|
||||
* @param script - The test script to execute
|
||||
* @param envs - Environment variables (defaults to empty)
|
||||
* @param response - Response object (defaults to fakeResponse)
|
||||
* @param request - Request object (defaults to defaultRequest)
|
||||
* @returns TaskEither containing test results
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* test("pm.expect assertion", () => {
|
||||
* return expect(
|
||||
* runTest(`pm.test("test", () => pm.expect(1).to.equal(1))`, {
|
||||
* global: [],
|
||||
* selected: []
|
||||
* })()
|
||||
* ).resolves.toEqualRight([...])
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const runTest = (
|
||||
script: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse = fakeResponse,
|
||||
request: ReturnType<typeof getDefaultRESTRequest> = defaultRequest
|
||||
) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request,
|
||||
response,
|
||||
}),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
/**
|
||||
* Run a pre-request script and return the environment variables
|
||||
*
|
||||
* Used for testing pre-request scripts that modify environment variables.
|
||||
*
|
||||
* @param script - The pre-request script to execute
|
||||
* @param envs - Initial environment variables (defaults to empty)
|
||||
* @param request - Request object (defaults to defaultRequest)
|
||||
* @returns TaskEither containing environment variables
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* test("pm.environment.set in pre-request", () => {
|
||||
* return expect(
|
||||
* runPreRequest(
|
||||
* `pm.environment.set("key", "value")`,
|
||||
* { global: [], selected: [] }
|
||||
* )()
|
||||
* ).resolves.toEqualRight({
|
||||
* global: [],
|
||||
* selected: [{ key: "key", value: "value", secret: false }]
|
||||
* })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const runPreRequest = (
|
||||
script: string,
|
||||
envs: TestResult["envs"],
|
||||
request: ReturnType<typeof getDefaultRESTRequest> = defaultRequest
|
||||
) =>
|
||||
pipe(
|
||||
runPreRequestScript(script, {
|
||||
envs,
|
||||
request,
|
||||
}),
|
||||
TE.map((x) => x.updatedEnvs)
|
||||
)
|
||||
|
||||
/**
|
||||
* Run a test script with custom response
|
||||
*
|
||||
* Convenience wrapper when you only need to customize the response.
|
||||
*
|
||||
* @param script - The test script to execute
|
||||
* @param response - Custom response object
|
||||
* @param envs - Environment variables (defaults to empty)
|
||||
* @returns TaskEither containing test results
|
||||
*/
|
||||
export const runTestWithResponse = (
|
||||
script: string,
|
||||
response: TestResponse,
|
||||
envs: TestResult["envs"] = { global: [], selected: [] }
|
||||
) => runTest(script, envs, response)
|
||||
|
||||
/**
|
||||
* Run a test script with custom request
|
||||
*
|
||||
* Convenience wrapper when you only need to customize the request.
|
||||
*
|
||||
* @param script - The test script to execute
|
||||
* @param request - Custom request object
|
||||
* @param envs - Environment variables (defaults to empty)
|
||||
* @param response - Response object (defaults to fakeResponse)
|
||||
* @returns TaskEither containing test results
|
||||
*/
|
||||
export const runTestWithRequest = (
|
||||
script: string,
|
||||
request: ReturnType<typeof getDefaultRESTRequest>,
|
||||
envs: TestResult["envs"] = { global: [], selected: [] },
|
||||
response: TestResponse = fakeResponse
|
||||
) => runTest(script, envs, response, request)
|
||||
|
||||
/**
|
||||
* Run a test script with empty environments
|
||||
*
|
||||
* Convenience wrapper for the most common case (no environment variables).
|
||||
*
|
||||
* @param script - The test script to execute
|
||||
* @param response - Response object (defaults to fakeResponse)
|
||||
* @returns TaskEither containing test results
|
||||
*/
|
||||
export const runTestWithEmptyEnv = (
|
||||
script: string,
|
||||
response: TestResponse = fakeResponse
|
||||
) => runTest(script, { global: [], selected: [] }, response)
|
||||
|
||||
/**
|
||||
* Run a test script and return the environment variables (not test results)
|
||||
*
|
||||
* Used for testing scripts that modify environment variables but you want
|
||||
* to inspect the final env state rather than test results.
|
||||
*
|
||||
* This is different from runPreRequest which uses runPreRequestScript.
|
||||
* This uses runTestScript but extracts envs instead of tests.
|
||||
*
|
||||
* @param script - The test script to execute
|
||||
* @param envs - Initial environment variables
|
||||
* @param response - Response object (defaults to fakeResponse)
|
||||
* @param request - Request object (defaults to defaultRequest)
|
||||
* @returns TaskEither containing environment variables
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* test("env mutation in test script", () => {
|
||||
* return expect(
|
||||
* runTestAndGetEnvs(
|
||||
* `pw.env.set("key", "value")`,
|
||||
* { global: [], selected: [] }
|
||||
* )()
|
||||
* ).resolves.toEqualRight({
|
||||
* global: [],
|
||||
* selected: [{ key: "key", value: "value", secret: false }]
|
||||
* })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const runTestAndGetEnvs = (
|
||||
script: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse = fakeResponse,
|
||||
request: ReturnType<typeof getDefaultRESTRequest> = defaultRequest
|
||||
) =>
|
||||
pipe(
|
||||
runTestScript(script, {
|
||||
envs,
|
||||
request,
|
||||
response,
|
||||
}),
|
||||
TE.map((x: TestResult) => x.envs)
|
||||
)
|
||||
|
|
@ -1158,6 +1158,9 @@ importers:
|
|||
'@types/lodash-es':
|
||||
specifier: 4.17.12
|
||||
version: 4.17.12
|
||||
chai:
|
||||
specifier: 6.2.0
|
||||
version: 6.2.0
|
||||
faraday-cage:
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0
|
||||
|
|
@ -1180,6 +1183,9 @@ importers:
|
|||
'@relmify/jest-fp-ts':
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1(fp-ts@2.16.11)(io-ts@2.2.22(fp-ts@2.16.11))
|
||||
'@types/chai':
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
'@types/jest':
|
||||
specifier: 30.0.0
|
||||
version: 30.0.0
|
||||
|
|
@ -8870,6 +8876,10 @@ packages:
|
|||
resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chai@6.2.0:
|
||||
resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chalk@2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -24957,6 +24967,8 @@ snapshots:
|
|||
loupe: 3.2.0
|
||||
pathval: 2.0.1
|
||||
|
||||
chai@6.2.0: {}
|
||||
|
||||
chalk@2.4.2:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue