Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/shared-fetch-utils/common/test/httpResponse.spec.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { describe, expect, it } from 'vitest';
7
import type { HttpHeaders } from '../fetchTypes';
8
import { cloneResponse, type CloneableResponse } from '../httpResponse';
9
10
// ── Helpers ─────────────────────────────────────────────────────────────
11
12
function makeHeaders(entries: Record<string, string> = {}): HttpHeaders {
13
const map = new Map(Object.entries(entries));
14
return { get: (name: string) => map.get(name.toLowerCase()) ?? null };
15
}
16
17
function textStream(text: string): ReadableStream<Uint8Array> {
18
return new ReadableStream({
19
start(controller) {
20
controller.enqueue(new TextEncoder().encode(text));
21
controller.close();
22
},
23
});
24
}
25
26
function multiChunkStream(chunks: string[]): ReadableStream<Uint8Array> {
27
return new ReadableStream({
28
start(controller) {
29
for (const chunk of chunks) {
30
controller.enqueue(new TextEncoder().encode(chunk));
31
}
32
controller.close();
33
},
34
});
35
}
36
37
/** Simulates the platform `DestroyableStream` which exposes `toReadableStream()`. */
38
function fakeDestroyableStream(text: string) {
39
return { toReadableStream: () => textStream(text) };
40
}
41
42
function makeResponse(status: number, headers: HttpHeaders, body: ReadableStream<Uint8Array> | null): CloneableResponse {
43
return { status, headers, body };
44
}
45
46
// ── cloneResponse ───────────────────────────────────────────────────────
47
48
describe('cloneResponse', () => {
49
50
describe('null body', () => {
51
it('returns two independent responses with null body', () => {
52
const headers = makeHeaders({ 'x-test': '1' });
53
const [a, b] = cloneResponse(makeResponse(200, headers, null));
54
55
expect(a.status).toBe(200);
56
expect(b.status).toBe(200);
57
expect(a.body).toBeNull();
58
expect(b.body).toBeNull();
59
expect(a).not.toBe(b);
60
});
61
62
it('text() returns empty string for null body', async () => {
63
const [a, b] = cloneResponse(makeResponse(204, makeHeaders(), null));
64
expect(await a.text()).toBe('');
65
expect(await b.text()).toBe('');
66
});
67
});
68
69
describe('ReadableStream body', () => {
70
it('produces two independently consumable streams', async () => {
71
const response = makeResponse(200, makeHeaders(), textStream('hello'));
72
const [a, b] = cloneResponse(response);
73
74
expect(await a.text()).toBe('hello');
75
expect(await b.text()).toBe('hello');
76
});
77
78
it('text() handles multi-chunk streams', async () => {
79
const response = makeResponse(200, makeHeaders(), multiChunkStream(['hel', 'lo ', 'world']));
80
const [a, b] = cloneResponse(response);
81
82
expect(await a.text()).toBe('hello world');
83
expect(await b.text()).toBe('hello world');
84
});
85
86
it('json() parses body as JSON', async () => {
87
const payload = { key: 'value', n: 42 };
88
const response = makeResponse(200, makeHeaders(), textStream(JSON.stringify(payload)));
89
const [a, b] = cloneResponse(response);
90
91
expect(await a.json()).toEqual(payload);
92
expect(await b.json()).toEqual(payload);
93
});
94
95
it('preserves status and headers on both clones', () => {
96
const headers = makeHeaders({ 'content-type': 'application/json', 'etag': '"v1"' });
97
const response = makeResponse(200, headers, textStream('{}'));
98
const [a, b] = cloneResponse(response);
99
100
expect(a.status).toBe(200);
101
expect(b.status).toBe(200);
102
expect(a.headers.get('content-type')).toBe('application/json');
103
expect(b.headers.get('etag')).toBe('"v1"');
104
});
105
});
106
107
describe('DestroyableStream body (toReadableStream)', () => {
108
it('unwraps DestroyableStream and produces two consumable clones', async () => {
109
const response: CloneableResponse = {
110
status: 200,
111
headers: makeHeaders(),
112
body: fakeDestroyableStream('from destroyable'),
113
};
114
const [a, b] = cloneResponse(response);
115
116
expect(await a.text()).toBe('from destroyable');
117
expect(await b.text()).toBe('from destroyable');
118
});
119
});
120
121
describe('repeated cloning', () => {
122
it('can clone a clone', async () => {
123
const response = makeResponse(200, makeHeaders(), textStream('original'));
124
const [first, second] = cloneResponse(response);
125
126
// Clone one of the halves again
127
const [firstA, firstB] = cloneResponse(first);
128
129
expect(await firstA.text()).toBe('original');
130
expect(await firstB.text()).toBe('original');
131
expect(await second.text()).toBe('original');
132
});
133
});
134
});
135
136