Repository: enzonotario/vitepress-openapi (ts)

Issue:
Incorrect Content-Type Parsing in POST Requests Causes Guaranteed Failure
### Current behavior

I noticed that in the latest version, after executing onSubmit in OAPlayground.vue, the Content-Type header in the POST request observed in the browser console is incorrectly parsed as: `application/json, application/json`

This issue causes all POST endpoints to fail.

### Desired behavior

Manually correcting the Content-Type header to a valid format resolves the issue:

```
curl 'https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums' \
  -H 'accept: */*' \
  -H 'accept-language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7' \
  -H 'authorization: Bearer Token' \
  -H 'content-type: application/json' \
  -H 'origin: https://vitepress-openapi.vercel.app' \
  -H 'priority: u=1, i' \
  -H 'referer: https://vitepress-openapi.vercel.app/' \
  -H 'sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Microsoft Edge";v="134"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0' \
  --data-raw '{"id":1,"name":"La Máquina de Hacer Pájaros","year":1976,"image":"https://cdn.rock-legends.com/photos/la-maquina.jpg"}'
```

Returns the expected response:
```
{
  "id": 1,
  "name": "La Máquina de Hacer Pájaros",
  "year": 1976,
  "image": "https://cdn.rock-legends.com/photos/la-maquina.jpg"
}
```

### Reproduction

_No response_

### Steps to reproduce

Reproduction Steps

This can be verified by copying the generated cURL request. For example, sending a request via the sandbox at:
https://vitepress-openapi.vercel.app/sandbox/#createArtist

The generated request:
```
curl 'https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums' \
  -H 'accept: */*' \
  -H 'accept-language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7' \
  -H 'authorization: Bearer Token' \
  -H 'content-type: application/json, application/json' \
  -H 'origin: https://vitepress-openapi.vercel.app' \
  -H 'priority: u=1, i' \
  -H 'referer: https://vitepress-openapi.vercel.app/' \
  -H 'sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Microsoft Edge";v="134"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0' \
  --data-raw '{"id":1,"name":"La Máquina de Hacer Pájaros","year":1976,"image":"https://cdn.rock-legends.com/photos/la-maquina.jpg"}'
```
Results in a 500 Internal Server Error.

### Logs and Error Messages

_No response_

### Other Information

Ensure that the Content-Type header is set correctly in POST requests to avoid duplicate values.

Pull request description:
fix(Playground): remove duplicated Content-Type header
<!-- Provide a general summary of your changes in the title above. -->

# Description

The playground was adding the `Content-Type` header twice. Once as `content-type` and once as `Content-Type`. This resulted in incorrect headers being sent with the request, causing the requests to fail.

## Related issues/external references

Fixes #192 

## Types of changes
- Bug fix

Patch:
diff --git a/package.json b/package.json
index 58d0c67..cffec36 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "vitepress-openapi",
   "type": "module",
-  "version": "0.0.3-alpha.69",
+  "version": "0.0.3-alpha.70",
   "packageManager": "pnpm@9.15.9",
   "homepage": "https://vitepress-openapi.vercel.app/",
   "repository": {
diff --git a/src/components/Playground/OAPlayground.vue b/src/components/Playground/OAPlayground.vue
index 51f8a53..f412989 100644
--- a/src/components/Playground/OAPlayground.vue
+++ b/src/components/Playground/OAPlayground.vue
@@ -1,18 +1,12 @@
 <script setup lang="ts">
 import type { OpenAPIV3 } from '@scalar/openapi-types'
-import { computed, defineEmits, defineProps, onBeforeUnmount, ref } from 'vue'
+import { computed, defineEmits, defineProps, onBeforeUnmount } from 'vue'
+import { usePlayground } from '../../composables/usePlayground'
 import OAHeading from '../Common/OAHeading.vue'
 import { Button } from '../ui/button'
 import OAPlaygroundParameters from './OAPlaygroundParameters.vue'
 import OAPlaygroundResponse from './OAPlaygroundResponse.vue'
 
-interface PlaygroundResponse {
-  body: any
-  type: string
-  time: string | null
-  status: number | null
-}
-
 const props = defineProps({
   operationId: {
     type: String,
@@ -70,13 +64,7 @@ const emits = defineEmits([
   'update:selectedServer',
 ])
 
-const defaultRequestUrl = `${props.baseUrl}${props.path}`
-
-const loading = ref(false)
-
-const response = ref<PlaygroundResponse | null>(null)
-
-const imageUrls = ref<string[]>([])
+const { loading, response, submitRequest, cleanupImageUrls } = usePlayground()
 
 const hasBody = computed(() =>
   Boolean(props.requestBody),
@@ -95,89 +83,17 @@ async function onSubmit() {
     return
   }
 
-  response.value = null
-
-  const innerResponse: PlaygroundResponse = {
-    body: null,
-    type: '',
-    time: null,
-    status: null,
-  }
-
-  trackEvent()
-
-  const start = performance.now()
-  const controller = new AbortController()
-  const timeoutId = setTimeout(() => controller.abort(), 30000) // 30s timeout
-
-  try {
-    innerResponse.time = null
-    innerResponse.body = '{}'
-    loading.value = true
-
-    const headers = props.request.headers ?? {}
-    if (props.request.body && !headers['Content-Type']) {
-      headers['Content-Type'] = 'application/json'
-    }
-
-    const url = new URL(props.request.url ?? defaultRequestUrl)
-    for (const [key, value] of Object.entries(props.request.query)) {
-      url.searchParams.set(key, String(value))
-    }
-
-    const data = await fetch(url.toString(), {
-      method: props.method.toUpperCase(),
-      headers,
-      body: props.request.body ? JSON.stringify(props.request.body) : null,
-      signal: controller.signal,
-    })
-
-    const contentType = data.headers.get('Content-Type') || 'text/plain'
-    innerResponse.type = contentType
-
-    if (/json/i.test(contentType)) {
-      innerResponse.body = await data.json()
-    } else if (/xml/i.test(contentType) || /html/i.test(contentType) || /text\/plain/.test(contentType)) {
-      innerResponse.body = await data.text()
-    } else if (/^image\//i.test(contentType)) {
-      const blob = await data.blob()
-      innerResponse.body = URL.createObjectURL(blob)
-      // Store the blob URL to release it later.
-      imageUrls.value.push(innerResponse.body)
-    } else if (/^audio\//i.test(contentType)) {
-      innerResponse.body = await data.blob()
-    } else {
-      innerResponse.body = await data.text()
-    }
-
-    innerResponse.status = data.status
-  } catch (error: any) {
-    innerResponse.body = error?.message
-    innerResponse.type = 'text/plain'
-    innerResponse.status = 500
-  } finally {
-    clearTimeout(timeoutId)
-    loading.value = false
-    const end = performance.now()
-    innerResponse.time = (end - start).toFixed(2)
-
-    response.value = innerResponse
-  }
-}
-
-function trackEvent() {
-  try {
-    // @ts-expect-error: gtag is defined in the global scope
-    window.gtag('event', 'try_it', {
-      event_category: 'api',
-      event_label: props.operationId,
-    })
-  } catch { }
+  await submitRequest({
+    request: props.request,
+    method: props.method,
+    baseUrl: props.baseUrl,
+    path: props.path,
+    operationId: props.operationId,
+  })
 }
 
 onBeforeUnmount(() => {
-  // Release the blob URLs to prevent memory leaks.
-  imageUrls.value.forEach(URL.revokeObjectURL)
+  cleanupImageUrls()
 })
 </script>
 
diff --git a/src/composables/usePlayground.ts b/src/composables/usePlayground.ts
index 8f01393..468ae7f 100644
--- a/src/composables/usePlayground.ts
+++ b/src/composables/usePlayground.ts
@@ -1,6 +1,7 @@
 import type { OpenAPIV3 } from '@scalar/openapi-types'
+import { ref } from 'vue'
 
-interface SecuritySchemeDefaultValues {
+export interface SecuritySchemeDefaultValues {
   'http-basic': string
   'http-bearer': string
   'apiKey': string | null
@@ -8,6 +9,21 @@ interface SecuritySchemeDefaultValues {
   'oauth2': string
 }
 
+export interface PlaygroundResponse {
+  body: any
+  type: string
+  time: string | null
+  status: number | null
+}
+
+export interface SubmitOptions {
+  request: any
+  method: string
+  baseUrl: string
+  path: string
+  operationId: string
+}
+
 let securitySchemeDefaultValues: SecuritySchemeDefaultValues = {
   'http-basic': 'Basic Auth',
   'http-bearer': 'Token',
@@ -17,6 +33,10 @@ let securitySchemeDefaultValues: SecuritySchemeDefaultValues = {
 }
 
 export function usePlayground() {
+  const loading = ref(false)
+  const response = ref<PlaygroundResponse | null>(null)
+  const imageUrls = ref<string[]>([])
+
   function setSecuritySchemeDefaultValues(values: Partial<SecuritySchemeDefaultValues>) {
     securitySchemeDefaultValues = {
       ...securitySchemeDefaultValues,
@@ -42,8 +62,102 @@ export function usePlayground() {
     return ''
   }
 
+  async function submitRequest({ request, method, baseUrl, path, operationId }: SubmitOptions) {
+    if (!request) {
+      return null
+    }
+
+    response.value = null
+    const defaultRequestUrl = `${baseUrl}${path}`
+
+    const innerResponse: PlaygroundResponse = {
+      body: null,
+      type: '',
+      time: null,
+      status: null,
+    }
+
+    trackEvent(operationId)
+
+    const start = performance.now()
+    const controller = new AbortController()
+    const timeoutId = setTimeout(() => controller.abort(), 30000) // 30s timeout
+
+    try {
+      innerResponse.time = null
+      innerResponse.body = '{}'
+      loading.value = true
+
+      const url = new URL(request.url ?? defaultRequestUrl)
+      for (const [key, value] of Object.entries(request.query)) {
+        url.searchParams.set(key, String(value))
+      }
+
+      const data = await fetch(url.toString(), {
+        method: method.toUpperCase(),
+        headers: request.headers ?? {},
+        body: (typeof request.body === 'string' || request.body instanceof Blob) ? request.body : JSON.stringify(request.body),
+        signal: controller.signal,
+      })
+
+      const contentType = data.headers.get('Content-Type') || 'text/plain'
+      innerResponse.type = contentType
+
+      if (/json/i.test(contentType)) {
+        innerResponse.body = await data.json()
+      } else if (/xml/i.test(contentType) || /html/i.test(contentType) || /text\/plain/.test(contentType)) {
+        innerResponse.body = await data.text()
+      } else if (/^image\//i.test(contentType)) {
+        const blob = await data.blob()
+        innerResponse.body = URL.createObjectURL(blob)
+        // Store the blob URL to release it later.
+        imageUrls.value.push(innerResponse.body)
+      } else if (/^audio\//i.test(contentType)) {
+        innerResponse.body = await data.blob()
+      } else {
+        innerResponse.body = await data.text()
+      }
+
+      innerResponse.status = data.status
+    } catch (error: any) {
+      innerResponse.body = error?.message
+      innerResponse.type = 'text/plain'
+      innerResponse.status = 500
+    } finally {
+      clearTimeout(timeoutId)
+      loading.value = false
+      const end = performance.now()
+      innerResponse.time = (end - start).toFixed(2)
+
+      response.value = innerResponse
+    }
+
+    return innerResponse
+  }
+
+  function trackEvent(operationId: string) {
+    try {
+      // @ts-expect-error: gtag is defined in the global scope
+      window.gtag('event', 'try_it', {
+        event_category: 'api',
+        event_label: operationId,
+      })
+    } catch { }
+  }
+
+  function cleanupImageUrls() {
+    // Release the blob URLs to prevent memory leaks.
+    imageUrls.value.forEach(URL.revokeObjectURL)
+    imageUrls.value = []
+  }
+
   return {
+    loading,
+    response,
+    imageUrls,
     setSecuritySchemeDefaultValues,
     getSecuritySchemeDefaultValue,
+    submitRequest,
+    cleanupImageUrls,
   }
 }
diff --git a/src/lib/codeSamples/buildHarRequest.ts b/src/lib/codeSamples/buildHarRequest.ts
index e9ed8dd..49b2b12 100644
--- a/src/lib/codeSamples/buildHarRequest.ts
+++ b/src/lib/codeSamples/buildHarRequest.ts
@@ -66,12 +66,5 @@ export function buildHarRequest(
     }
   }
 
-  if (oaRequest.body && !harRequest.headers.some(header => header.name === 'Content-Type')) {
-    harRequest.headers.push({
-      name: 'Content-Type',
-      value: oaRequest.contentType || 'application/json',
-    })
-  }
-
   return harRequest
 }


Test patch:
diff --git a/test/composables/usePlayground.submitRequest.test.ts b/test/composables/usePlayground.submitRequest.test.ts
new file mode 100644
index 0000000..c5e5eaf
--- /dev/null
+++ b/test/composables/usePlayground.submitRequest.test.ts
@@ -0,0 +1,410 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { usePlayground } from '../../src/composables/usePlayground'
+import { buildRequest } from '../../src/lib/codeSamples/buildRequest'
+
+describe('usePlayground', () => {
+  const mockFetch = vi.fn()
+
+  beforeEach(() => {
+    globalThis.fetch = mockFetch
+    vi.spyOn(performance, 'now').mockReturnValueOnce(0).mockReturnValueOnce(100)
+    globalThis.URL.createObjectURL = vi.fn().mockReturnValue('blob:url')
+    globalThis.URL.revokeObjectURL = vi.fn()
+  })
+
+  afterEach(() => {
+    vi.clearAllMocks()
+  })
+
+  describe('submitRequest', () => {
+    it('should handle request body correctly', async () => {
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ success: true }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const requestBody = {
+        name: 'Test User',
+        email: 'test@example.com',
+        age: 30,
+      }
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users',
+        method: 'POST',
+        body: requestBody,
+        parameters: [],
+        variables: {},
+      })
+
+      await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'createUserOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users')
+      expect(mockFetch.mock.calls[0][1]).toEqual({
+        method: 'POST',
+        headers: {
+          'content-type': 'application/json',
+        },
+        body: JSON.stringify(requestBody),
+        signal: expect.any(AbortSignal),
+      })
+      expect(mockResponse.json).toHaveBeenCalled()
+    })
+
+    it('should handle GET requests with query parameters', async () => {
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ data: [{ id: 1, name: 'Test' }] }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      request.query = { page: 1, limit: 10, search: 'test' }
+
+      await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'getUsersOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users?page=1&limit=10&search=test')
+      expect(mockFetch.mock.calls[0][1]).toEqual({
+        method: 'GET',
+        headers: {},
+        body: undefined,
+        signal: expect.any(AbortSignal),
+      })
+      expect(mockResponse.json).toHaveBeenCalled()
+    })
+
+    it('should handle XML response content type', async () => {
+      const xmlContent = '<response><status>success</status><message>Data retrieved</message></response>'
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/xml'),
+        },
+        json: vi.fn(),
+        text: vi.fn().mockResolvedValue(xmlContent),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const playground = usePlayground()
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/data',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      const result = await playground.submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'getXmlDataOperation',
+      })
+
+      expect(mockFetch).toHaveBeenCalled()
+      expect(mockResponse.text).toHaveBeenCalled()
+      expect(mockResponse.json).not.toHaveBeenCalled()
+      expect(result?.body).toBe(xmlContent)
+      expect(result?.type).toBe('application/xml')
+      expect(playground.response.value?.body).toBe(xmlContent)
+    })
+
+    it('should handle image response content type', async () => {
+      const mockBlob = new Blob(['image data'], { type: 'image/png' })
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('image/png'),
+        },
+        json: vi.fn(),
+        text: vi.fn(),
+        blob: vi.fn().mockResolvedValue(mockBlob),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const playground = usePlayground()
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/image',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      const result = await playground.submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'getImageOperation',
+      })
+
+      expect(mockFetch).toHaveBeenCalled()
+      expect(mockResponse.blob).toHaveBeenCalled()
+      expect(globalThis.URL.createObjectURL).toHaveBeenCalledWith(mockBlob)
+      expect(result?.body).toBe('blob:url')
+      expect(result?.type).toBe('image/png')
+      expect(playground.imageUrls.value).toContain('blob:url')
+    })
+
+    it('should handle network errors', async () => {
+      mockFetch.mockRejectedValue(new Error('Network error'))
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      const result = await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'failedOperation',
+      })
+
+      expect(mockFetch).toHaveBeenCalled()
+      expect(result?.status).toBe(500)
+      expect(result?.body).toBe('Network error')
+      expect(result?.type).toBe('text/plain')
+    })
+
+    it('should handle custom headers in request', async () => {
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ success: true }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      // Add custom headers
+      request.headers = {
+        'Authorization': 'Bearer token123',
+        'X-API-Key': 'abc123',
+        'Accept-Language': 'en-US',
+      }
+
+      await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'getUsersWithHeadersOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users')
+      expect(mockFetch.mock.calls[0][1].headers).toEqual({
+        'Authorization': 'Bearer token123',
+        'X-API-Key': 'abc123',
+        'Accept-Language': 'en-US',
+      })
+      expect(mockResponse.json).toHaveBeenCalled()
+    })
+
+    it('should handle 404 error responses', async () => {
+      const mockResponse = {
+        status: 404,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ error: 'Resource not found' }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const playground = usePlayground()
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/nonexistent',
+        method: 'GET',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      const result = await playground.submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'notFoundOperation',
+      })
+
+      expect(mockFetch).toHaveBeenCalled()
+      expect(mockResponse.json).toHaveBeenCalled()
+      expect(result?.status).toBe(404)
+      expect(result?.body).toEqual({ error: 'Resource not found' })
+      expect(playground.response.value?.status).toBe(404)
+    })
+
+    it('should handle PUT requests with body', async () => {
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ id: 1, name: 'Updated User', email: 'updated@example.com' }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const requestBody = {
+        name: 'Updated User',
+        email: 'updated@example.com',
+      }
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users/1',
+        method: 'PUT',
+        body: requestBody,
+        parameters: [],
+        variables: {},
+      })
+
+      await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'updateUserOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users/1')
+      expect(mockFetch.mock.calls[0][1]).toEqual({
+        method: 'PUT',
+        headers: {
+          'content-type': 'application/json',
+        },
+        body: JSON.stringify(requestBody),
+        signal: expect.any(AbortSignal),
+      })
+      expect(mockResponse.json).toHaveBeenCalled()
+    })
+
+    it('should handle DELETE requests', async () => {
+      const mockResponse = {
+        status: 204,
+        headers: {
+          get: vi.fn().mockReturnValue(null),
+        },
+        json: vi.fn(),
+        text: vi.fn().mockResolvedValue(''),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users/1',
+        method: 'DELETE',
+        body: null,
+        parameters: [],
+        variables: {},
+      })
+
+      const result = await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'deleteUserOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users/1')
+      expect(mockFetch.mock.calls[0][1]).toEqual({
+        method: 'DELETE',
+        headers: {},
+        body: undefined,
+        signal: expect.any(AbortSignal),
+      })
+      expect(result?.status).toBe(204)
+      expect(result?.body).toBe('')
+    })
+
+    it('should handle path parameters correctly', async () => {
+      const mockResponse = {
+        status: 200,
+        headers: {
+          get: vi.fn().mockReturnValue('application/json'),
+        },
+        json: vi.fn().mockResolvedValue({ id: 123, name: 'Test User' }),
+        text: vi.fn(),
+      }
+      mockFetch.mockResolvedValue(mockResponse)
+
+      const request = buildRequest({
+        baseUrl: 'https://api.example.com',
+        path: '/users/{userId}/profile',
+        method: 'GET',
+        body: null,
+        parameters: [
+          { name: 'userId', in: 'path', value: '123' },
+        ],
+        variables: {},
+      })
+
+      await usePlayground().submitRequest({
+        request,
+        method: request.method,
+        baseUrl: 'https://api.example.com',
+        path: request.path,
+        operationId: 'getUserProfileOperation',
+      })
+
+      expect(mockFetch.mock.calls[0][0].toString()).toBe('https://api.example.com/users/%7BuserId%7D/profile')
+      expect(mockResponse.json).toHaveBeenCalled()
+    })
+  })
+})