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/fetchedValue.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 { beforeEach, describe, expect, it } from 'vitest';
7
import { FetchedValue, FetchedValueOptions } from '../fetchedValue';
8
import { FetchBlockedError } from '../fetchTypes';
9
10
interface TestToken {
11
value: string;
12
expiresAt: number;
13
}
14
15
describe('FetchedValue', () => {
16
let fetchCount: number;
17
let nextToken: TestToken;
18
let fetchedValue: FetchedValue<TestToken>;
19
20
function createFetchedValue(overrides?: Partial<FetchedValueOptions<TestToken>>): FetchedValue<TestToken> {
21
return new FetchedValue({
22
fetch: async () => {
23
fetchCount++;
24
return nextToken;
25
},
26
isStale: token => token.expiresAt < Date.now(),
27
...overrides,
28
});
29
}
30
31
beforeEach(() => {
32
fetchCount = 0;
33
nextToken = { value: 'token-1', expiresAt: Date.now() + 60_000 };
34
fetchedValue = createFetchedValue();
35
});
36
37
it('value is undefined before first resolve', () => {
38
expect(fetchedValue.value).toBeUndefined();
39
});
40
41
it('resolve fetches and caches the value', async () => {
42
const result = await fetchedValue.resolve();
43
expect(result).toBe(nextToken);
44
expect(fetchedValue.value).toBe(nextToken);
45
expect(fetchCount).toBe(1);
46
});
47
48
it('resolve returns cached value when not stale', async () => {
49
await fetchedValue.resolve();
50
nextToken = { value: 'token-2', expiresAt: Date.now() + 60_000 };
51
const result = await fetchedValue.resolve();
52
expect(result.value).toBe('token-1');
53
expect(fetchCount).toBe(1);
54
});
55
56
it('resolve re-fetches when value is stale', async () => {
57
nextToken = { value: 'token-1', expiresAt: Date.now() - 1 };
58
await fetchedValue.resolve();
59
expect(fetchCount).toBe(1);
60
61
nextToken = { value: 'token-2', expiresAt: Date.now() + 60_000 };
62
const result = await fetchedValue.resolve();
63
expect(result.value).toBe('token-2');
64
expect(fetchCount).toBe(2);
65
});
66
67
it('resolve with force bypasses staleness check', async () => {
68
await fetchedValue.resolve();
69
nextToken = { value: 'token-2', expiresAt: Date.now() + 60_000 };
70
71
const result = await fetchedValue.resolve(true);
72
expect(result.value).toBe('token-2');
73
expect(fetchCount).toBe(2);
74
});
75
76
it('concurrent resolves coalesce into a single fetch', async () => {
77
const [a, b, c] = await Promise.all([
78
fetchedValue.resolve(),
79
fetchedValue.resolve(),
80
fetchedValue.resolve(),
81
]);
82
expect(fetchCount).toBe(1);
83
expect(a).toBe(b);
84
expect(b).toBe(c);
85
});
86
87
it('fetch error propagates and does not cache', async () => {
88
const fv = createFetchedValue({
89
fetch: async () => { throw new Error('network failure'); },
90
});
91
92
await expect(fv.resolve()).rejects.toThrow('network failure');
93
expect(fv.value).toBeUndefined();
94
});
95
96
it('FetchBlockedError returns cached value when one exists', async () => {
97
let shouldBlock = false;
98
const fv = new FetchedValue<TestToken>({
99
fetch: async () => {
100
if (shouldBlock) {
101
throw new FetchBlockedError('blocked', 5000);
102
}
103
return { value: 'good-value', expiresAt: Date.now() + 60_000 };
104
},
105
isStale: () => true,
106
});
107
await fv.resolve();
108
expect(fv.value!.value).toBe('good-value');
109
110
shouldBlock = true;
111
const result = await fv.resolve();
112
expect(result.value).toBe('good-value');
113
});
114
115
it('FetchBlockedError propagates when no cached value exists', async () => {
116
const fv = new FetchedValue<TestToken>({
117
fetch: async () => { throw new FetchBlockedError('blocked', 5000); },
118
isStale: () => true,
119
});
120
121
await expect(fv.resolve()).rejects.toThrow('blocked');
122
expect(fv.value).toBeUndefined();
123
});
124
125
it('dispose prevents further resolves', async () => {
126
fetchedValue.dispose();
127
await expect(fetchedValue.resolve()).rejects.toThrow('disposed');
128
});
129
130
describe('when T includes undefined', () => {
131
it('does not re-fetch when the fetched value is undefined', async () => {
132
let undefinedFetchCount = 0;
133
const fv = new FetchedValue<string | undefined>({
134
fetch: async () => {
135
undefinedFetchCount++;
136
return undefined;
137
},
138
isStale: () => false,
139
});
140
141
const first = await fv.resolve();
142
expect(first).toBeUndefined();
143
expect(undefinedFetchCount).toBe(1);
144
145
const second = await fv.resolve();
146
expect(second).toBeUndefined();
147
expect(undefinedFetchCount).toBe(1); // should not re-fetch
148
});
149
});
150
});
151
152