api-client/packages/hoppscotch-js-sandbox/src/__tests__/pm-namespace/sendRequest.spec.ts

682 lines
23 KiB
TypeScript

import { describe, expect, test, vi } from "vitest"
import { runTest } from "~/utils/test-helpers"
import type { HoppFetchHook } from "~/types"
/**
* Tests for pm.sendRequest() functionality
*
* NOTE: These unit tests validate API availability but have limited coverage
* due to QuickJS async callback timing issues. Callback assertions don't
* execute reliably in the test context.
*
* For production validation, see the comprehensive E2E tests in:
* packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json
*
* The E2E tests make real HTTP requests and fully validate:
* - String URL format
* - Request object format
* - URL-encoded body
* - Response format validation
* - HTTP error status codes
* - Environment variable integration
* - Store response in environment
*/
describe("pm.sendRequest()", () => {
describe("Basic functionality", () => {
test("pm.sendRequest should execute callback with response data", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ success: true, data: "test" }), {
status: 200,
statusText: "OK",
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("sendRequest with callback", () => {
pm.sendRequest("https://api.example.com/data", (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.status).toBe("OK")
pm.expect(response.json().success).toBe(true)
pm.expect(response.json().data).toBe("test")
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "sendRequest with callback",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected 'OK' to be 'OK'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
{ status: "pass", message: "Expected 'test' to be 'test'" },
],
}),
],
}),
])
})
test("pm.sendRequest should handle errors in callback", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
throw new Error("Network error")
})
await expect(
runTest(
`
pm.test("sendRequest with error", () => {
pm.sendRequest("https://api.example.com/fail", (error, response) => {
pm.expect(error).not.toBe(null)
pm.expect(response).toBe(null)
pm.expect(error.message).toBe("Network error")
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "sendRequest with error",
expectResults: [
expect.objectContaining({ status: "pass" }),
{ status: "pass", message: "Expected 'null' to be 'null'" },
{
status: "pass",
message: "Expected 'Network error' to be 'Network error'",
},
],
}),
],
}),
])
})
})
describe("Request object format", () => {
test("pm.sendRequest accepts request object format with POST (array headers)", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ created: true, id: 123 }), {
status: 201,
statusText: "Created",
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("request object format", () => {
pm.sendRequest({
url: "https://api.example.com/items",
method: "POST",
header: [
{ key: "Content-Type", value: "application/json" }
],
body: {
mode: "raw",
raw: JSON.stringify({ name: "test" })
}
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(201)
pm.expect(response.status).toBe("Created")
pm.expect(response.json().created).toBe(true)
pm.expect(response.json().id).toBe(123)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "request object format",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '201' to be '201'" },
{
status: "pass",
message: "Expected 'Created' to be 'Created'",
},
{ status: "pass", message: "Expected 'true' to be 'true'" },
{ status: "pass", message: "Expected '123' to be '123'" },
],
}),
],
}),
])
})
test("pm.sendRequest accepts request object format with object headers (RFC pattern)", async () => {
const mockFetch: HoppFetchHook = vi.fn(async (_url, options) => {
// Verify headers were properly passed as object
expect(options?.headers).toEqual({
"Content-Type": "application/json",
Authorization: "Bearer test-token",
})
return new Response(JSON.stringify({ success: true, userId: 456 }), {
status: 200,
statusText: "OK",
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("RFC pattern - object headers", () => {
const requestObject = {
url: 'https://api.example.com/users',
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test-token'
},
body: {
mode: 'raw',
raw: JSON.stringify({ name: 'John Doe' })
}
}
pm.sendRequest(requestObject, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().success).toBe(true)
pm.expect(response.json().userId).toBe(456)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "RFC pattern - object headers",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
{ status: "pass", message: "Expected '456' to be '456'" },
],
}),
],
}),
])
})
})
describe("Body modes", () => {
test("pm.sendRequest handles urlencoded body mode", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(
JSON.stringify({ authenticated: true, token: "abc123" }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
)
})
await expect(
runTest(
`
pm.test("urlencoded body", () => {
pm.sendRequest({
url: "https://api.example.com/login",
method: "POST",
body: {
mode: "urlencoded",
urlencoded: [
{ key: "username", value: "john" },
{ key: "password", value: "secret123" }
]
}
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().authenticated).toBe(true)
pm.expect(response.json().token).toBeType("string")
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "urlencoded body",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
{
status: "pass",
message: "Expected 'abc123' to be type 'string'",
},
],
}),
],
}),
])
})
})
describe("Integration with environment variables", () => {
test("pm.sendRequest works with environment variables", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(
JSON.stringify({ data: "secured_data", user: "john" }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
)
})
await expect(
runTest(
`
pm.test("environment variables in sendRequest", () => {
// Set environment variables
pm.environment.set("API_URL", "https://api.example.com")
pm.environment.set("AUTH_TOKEN", "Bearer token123")
// Use variables in request
const url = pm.environment.get("API_URL") + "/data"
const token = pm.environment.get("AUTH_TOKEN")
pm.sendRequest({
url: url,
header: [
{ key: "Authorization", value: token }
]
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().data).toBe("secured_data")
pm.expect(response.json().user).toBe("john")
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "environment variables in sendRequest",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{
status: "pass",
message: "Expected 'secured_data' to be 'secured_data'",
},
{ status: "pass", message: "Expected 'john' to be 'john'" },
],
}),
],
}),
])
})
})
describe("Multiple requests in same test", () => {
test("pm.sendRequest supports multiple async requests", async () => {
let callCount = 0
const mockFetch: HoppFetchHook = vi.fn(async () => {
callCount++
return new Response(
JSON.stringify({ request: callCount, data: `response${callCount}` }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
)
})
await expect(
runTest(
`
pm.test("multiple sendRequests", () => {
// First request
pm.sendRequest("https://api.example.com/first", (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().request).toBe(1)
})
// Second request
pm.sendRequest("https://api.example.com/second", (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().request).toBe(2)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "multiple sendRequests",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected '1' to be '1'" },
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected '2' to be '2'" },
],
}),
],
}),
])
})
})
describe("Additional body modes and content types", () => {
test("pm.sendRequest with formdata body mode", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ uploaded: true, files: 1 }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("formdata body", () => {
pm.sendRequest({
url: "https://api.example.com/upload",
method: "POST",
body: {
mode: "formdata",
formdata: [
{ key: "file", value: "example.txt" },
{ key: "description", value: "test upload" }
]
}
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().uploaded).toBe(true)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "formdata body",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
],
}),
],
}),
])
})
})
describe("HTTP methods coverage", () => {
test("pm.sendRequest with PUT method", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ updated: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("PUT request", () => {
pm.sendRequest({
url: "https://api.example.com/resource/123",
method: "PUT",
header: { "Content-Type": "application/json" },
body: { mode: "raw", raw: JSON.stringify({ name: "updated" }) }
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
pm.expect(response.json().updated).toBe(true)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "PUT request",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
],
}),
],
}),
])
})
test("pm.sendRequest with PATCH method", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ patched: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("PATCH request", () => {
pm.sendRequest({
url: "https://api.example.com/resource/456",
method: "PATCH",
header: { "Content-Type": "application/json" },
body: { mode: "raw", raw: JSON.stringify({ status: "active" }) }
}, (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.code).toBe(200)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "PATCH request",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '200' to be '200'" },
],
}),
],
}),
])
})
})
describe("Response header validation", () => {
test("pm.sendRequest response headers access", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ data: "test" }), {
status: 200,
headers: {
"Content-Type": "application/json",
"X-Request-Id": "abc123",
"X-Rate-Limit": "100",
},
})
})
await expect(
runTest(
`
pm.test("response headers parsing", () => {
pm.sendRequest("https://api.example.com/data", (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.headers.has("Content-Type")).toBe(true)
pm.expect(response.headers.get("X-Request-Id")).toBe("abc123")
pm.expect(response.headers.has("X-Rate-Limit")).toBe(true)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "response headers parsing",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
{ status: "pass", message: "Expected 'abc123' to be 'abc123'" },
{ status: "pass", message: "Expected 'true' to be 'true'" },
],
}),
],
}),
])
})
})
describe("Cookie handling", () => {
test("pm.sendRequest should handle empty cookies gracefully", async () => {
const mockFetch: HoppFetchHook = vi.fn(async () => {
return new Response(JSON.stringify({ success: true }), {
status: 200,
statusText: "OK",
headers: { "Content-Type": "application/json" },
})
})
await expect(
runTest(
`
pm.test("sendRequest without cookies", () => {
pm.sendRequest("https://api.example.com/data", (error, response) => {
pm.expect(error).toBe(null)
pm.expect(response.cookies.has("anything")).toBe(false)
pm.expect(response.cookies.get("anything")).toBe(null)
const cookiesObj = response.cookies.toObject()
pm.expect(Object.keys(cookiesObj).length).toBe(0)
})
})
`,
{ global: [], selected: [] },
undefined,
undefined,
mockFetch
)()
).resolves.toEqualRight([
expect.objectContaining({
descriptor: "root",
children: [
expect.objectContaining({
descriptor: "sendRequest without cookies",
expectResults: [
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected 'false' to be 'false'" },
{ status: "pass", message: "Expected 'null' to be 'null'" },
{ status: "pass", message: "Expected '0' to be '0'" },
],
}),
],
}),
])
})
})
describe("E2E test reference", () => {
test("comprehensive validation in E2E tests", () => {
// This is a documentation test - no actual execution needed
// For comprehensive validation including:
// - HTTP methods (GET, POST, PUT, DELETE, PATCH)
// - Body modes (raw, urlencoded, formdata)
// - Response header parsing
// - Multi-request workflows
// - Store response in environment
//
// See E2E tests in:
// packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json
//
// Run with: pnpm --filter @hoppscotch/cli test:e2e
expect(true).toBe(true)
})
})
})