682 lines
23 KiB
TypeScript
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)
|
|
})
|
|
})
|
|
})
|