fix(http): correctly cache blob responses in transfer cache (#67002)

Previously, Blob values were passed to `Uint8Array` this resulted in silently producing an empty array (length = 0) without throwing an error, leading to empty cached data

PR Close #67002
This commit is contained in:
SkyZeroZx 2026-02-11 14:19:17 -05:00 committed by Jessica Janiuk
parent aeb9b819d2
commit 277ade97ac
3 changed files with 205 additions and 144 deletions

View file

@ -21,7 +21,7 @@ import {
ɵRuntimeError as RuntimeError,
} from '@angular/core';
import {Observable, of} from 'rxjs';
import {tap} from 'rxjs/operators';
import {concatMap} from 'rxjs/operators';
import {RuntimeErrorCode} from './errors';
import {HttpHeaders} from './headers';
@ -267,14 +267,24 @@ export function transferCacheInterceptorFn(
if (typeof ngServerMode !== 'undefined' && ngServerMode) {
// Request not found in cache. Make the request and cache it if on the server.
return event$.pipe(
tap((event: HttpEvent<unknown>) => {
concatMap(async (event: HttpEvent<unknown>) => {
// Only cache successful HTTP responses.
if (event instanceof HttpResponse) {
let body = event.body;
if (req.responseType === 'blob') {
// Note: Blob is converted to ArrayBuffer because Uint8Array constructor
// doesn't accept Blob directly, which would result in an empty array.
// Type assertion is safe here: when responseType is 'blob', the body is guaranteed to be a Blob
const arrayBuffer = await (event.body as Blob).arrayBuffer();
body = toBase64(arrayBuffer);
} else if (req.responseType === 'arraybuffer') {
// For arraybuffer, convert to base64; for other types (json, text), store as-is.
// Type assertion is safe here: when responseType is 'arraybuffer', the body is
// guaranteed to be an ArrayBuffer
body = toBase64(event.body as ArrayBufferLike);
}
transferState.set<TransferHttpResponse>(storeKey, {
[BODY]:
req.responseType === 'arraybuffer' || req.responseType === 'blob'
? toBase64(event.body as ArrayBufferLike)
: event.body,
[BODY]: body,
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
[STATUS]: event.status,
[STATUS_TEXT]: event.statusText,
@ -282,6 +292,7 @@ export function transferCacheInterceptorFn(
[RESPONSE_TYPE]: req.responseType,
});
}
return event;
}),
);
}

View file

@ -17,7 +17,7 @@ import {
} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {useAutoTick, timeout, withBody} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';
import {BehaviorSubject, firstValueFrom, lastValueFrom} from 'rxjs';
import {HttpClient, HttpResponse, provideHttpClient} from '../public_api';
import {
@ -39,6 +39,7 @@ interface RequestParams {
transferCache?: {includeHeaders: string[]} | boolean;
headers?: {[key: string]: string};
body?: RequestBody;
responseType?: 'arraybuffer' | 'blob' | 'json' | 'text';
}
type RequestBody =
@ -63,35 +64,41 @@ describe('TransferCache', () => {
describe('withHttpTransferCache', () => {
let isStable: BehaviorSubject<boolean>;
function makeRequestAndExpectOne(
async function makeRequestAndExpectOne<T = string>(
url: string,
body: RequestBody,
params?: RequestParams,
): string;
function makeRequestAndExpectOne(
): Promise<T>;
async function makeRequestAndExpectOne<T = string>(
url: string,
body: RequestBody,
params?: RequestParams & {observe: 'response'},
): HttpResponse<string>;
function makeRequestAndExpectOne(url: string, body: RequestBody, params?: RequestParams): any {
let response!: any;
TestBed.inject(HttpClient)
.request(params?.method ?? 'GET', url, params)
.subscribe((r) => (response = r));
): Promise<HttpResponse<T>>;
async function makeRequestAndExpectOne(
url: string,
body: RequestBody,
params?: RequestParams,
): Promise<HttpResponse<any> | any> {
const response = lastValueFrom(
TestBed.inject(HttpClient).request(params?.method ?? 'GET', url, params),
);
TestBed.inject(HttpTestingController).expectOne(url).flush(body, {headers: params?.headers});
return response;
}
function makeRequestAndExpectNone(
async function makeRequestAndExpectNone<T = string>(
url: string,
method: string = 'GET',
params?: RequestParams,
): HttpResponse<string> {
let response!: HttpResponse<string>;
TestBed.inject(HttpClient)
.request(method, url, {observe: 'response', ...params})
.subscribe((r) => (response = r));
): Promise<HttpResponse<T>> {
const response = lastValueFrom(
TestBed.inject(HttpClient).request(method, url, {observe: 'response', ...params}),
);
TestBed.inject(HttpTestingController).expectNone(url);
return response;
}
@ -132,53 +139,86 @@ describe('TransferCache', () => {
}),
);
it('should store HTTP calls in cache when application is not stable', () => {
makeRequestAndExpectOne('/test', 'foo');
it('should store HTTP calls in cache when application is not stable', async () => {
await makeRequestAndExpectOne('/test', 'foo');
const transferState = TestBed.inject(TransferState);
const key = makeStateKey(Object.keys((transferState as any).store)[0]);
expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({[BODY]: 'foo'}));
});
it('should cache arraybuffer responses correctly', () => {
const testData = new Uint8Array([1, 2, 3, 4, 5]).buffer;
let response!: ArrayBuffer;
TestBed.inject(HttpClient)
.get('/test-arraybuffer', {responseType: 'arraybuffer'})
.subscribe((r) => (response = r));
TestBed.inject(HttpTestingController).expectOne('/test-arraybuffer').flush(testData);
it('should cache arraybuffer responses correctly', async () => {
const testData = new Uint8Array([1, 2, 3, 4, 5]);
const response = await makeRequestAndExpectOne<ArrayBuffer>(
'/test-arraybuffer',
testData.buffer,
{
responseType: 'arraybuffer',
},
);
expect(new Uint8Array(response)).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
expect(response).toEqual(testData.buffer);
let cachedResponse!: ArrayBuffer;
TestBed.inject(HttpClient)
.get('/test-arraybuffer', {responseType: 'arraybuffer'})
.subscribe((r) => (cachedResponse = r));
TestBed.inject(HttpTestingController).expectNone('/test-arraybuffer');
const cachedResponse = await makeRequestAndExpectNone<ArrayBuffer>(
'/test-arraybuffer',
'GET',
{responseType: 'arraybuffer'},
);
expect(new Uint8Array(cachedResponse)).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
expect(new Uint8Array(cachedResponse.body!)).toEqual(testData);
});
it('should cache blob responses correctly', () => {
const testData = new Uint8Array([10, 20, 30, 40, 50]).buffer;
let response!: Blob;
TestBed.inject(HttpClient)
.get('/test-blob', {responseType: 'blob'})
.subscribe((r) => (response = r));
TestBed.inject(HttpTestingController).expectOne('/test-blob').flush(testData);
it('should cache blob responses correctly (with ArrayBuffer)', async () => {
const testData = new Uint8Array([10, 20, 30, 40, 50]);
const response = await makeRequestAndExpectOne<Blob>('/test-blob', testData.buffer, {
responseType: 'blob',
});
expect(response instanceof Blob).toBeTrue();
expect(response).toBeInstanceOf(Blob);
expect(response.size).toBe(5);
const cachedResponse = await makeRequestAndExpectNone<Blob>('/test-blob', 'GET', {
responseType: 'blob',
});
expect(cachedResponse.body).toBeInstanceOf(Blob);
expect(cachedResponse.body!.size).toBe(5);
const cachedArrayBuffer = await cachedResponse.body!.arrayBuffer();
expect(new Uint8Array(cachedArrayBuffer)).toEqual(testData);
});
it('should cache blob responses correctly (with Blob)', async () => {
const data = new Uint8Array([65, 66, 67, 68, 69, 70]);
const blobData = new Blob([data], {
type: 'application/octet-stream',
});
const response = await makeRequestAndExpectOne<Blob>('/test-blob-direct', blobData, {
responseType: 'blob',
});
expect(response).toBeInstanceOf(Blob);
expect(response.size).toBe(6);
expect(response.type).toBe('application/octet-stream');
const cachedResponse = await makeRequestAndExpectNone<Blob>('/test-blob-direct', 'GET', {
responseType: 'blob',
});
expect(cachedResponse.body).toBeInstanceOf(Blob);
expect(cachedResponse.body!.size).toBe(6);
const cachedArrayBuffer = await cachedResponse.body!.arrayBuffer();
expect(new Uint8Array(cachedArrayBuffer)).toEqual(data);
});
it('should stop storing HTTP calls in `TransferState` after application becomes stable', async () => {
makeRequestAndExpectOne('/test-1', 'foo');
makeRequestAndExpectOne('/test-2', 'buzz');
await makeRequestAndExpectOne('/test-1', 'foo');
await makeRequestAndExpectOne('/test-2', 'buzz');
isStable.next(true);
await timeout();
makeRequestAndExpectOne('/test-3', 'bar');
await makeRequestAndExpectOne('/test-3', 'bar');
const transferState = TestBed.inject(TransferState);
expect(JSON.parse(transferState.toJson()) as Record<string, unknown>).toEqual({
@ -201,76 +241,79 @@ describe('TransferCache', () => {
});
});
it(`should use calls from cache when present and application is not stable`, () => {
makeRequestAndExpectOne('/test-1', 'foo');
it(`should use calls from cache when present and application is not stable`, async () => {
await makeRequestAndExpectOne('/test-1', 'foo');
// Do the same call, this time it should served from cache.
makeRequestAndExpectNone('/test-1');
await makeRequestAndExpectNone('/test-1');
});
it(`should not use calls from cache when present and application is stable`, async () => {
makeRequestAndExpectOne('/test-1', 'foo');
await makeRequestAndExpectOne('/test-1', 'foo');
isStable.next(true);
await timeout();
// Do the same call, this time it should go through as application is stable.
makeRequestAndExpectOne('/test-1', 'foo');
await makeRequestAndExpectOne('/test-1', 'foo');
});
it(`should differentiate calls with different parameters`, async () => {
// make calls with different parameters. All of which should be saved in the state.
makeRequestAndExpectOne('/test-1?foo=1', 'foo');
makeRequestAndExpectOne('/test-1', 'foo');
makeRequestAndExpectOne('/test-1?foo=2', 'buzz');
await makeRequestAndExpectOne('/test-1?foo=1', 'foo');
await makeRequestAndExpectOne('/test-1', 'foo');
await makeRequestAndExpectOne('/test-1?foo=2', 'buzz');
makeRequestAndExpectNone('/test-1?foo=1');
await makeRequestAndExpectNone('/test-1?foo=1');
await expectAsync(TestBed.inject(HttpClient).get('/test-1?foo=1').toPromise()).toBeResolvedTo(
'foo',
);
});
it('should skip cache when specified', () => {
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {transferCache: false});
it('should skip cache when specified', async () => {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {transferCache: false});
// The previous request wasn't cached so this one can't use the cache
makeRequestAndExpectOne('/test-1?foo=1', 'foo');
await makeRequestAndExpectOne('/test-1?foo=1', 'foo');
// But this one will
makeRequestAndExpectNone('/test-1?foo=1');
await makeRequestAndExpectNone('/test-1?foo=1');
});
it('should not cache a POST even with filter true specified', () => {
makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST'});
it('should not cache a POST even with filter true specified', async () => {
await makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST'});
// Previous POST request wasn't cached
makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'});
await makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'});
// filter => true won't cache neither
makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST', transferCache: true});
await makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {
method: 'POST',
transferCache: true,
});
const response = makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'});
const response = await makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'});
expect(response).toBe('body2');
});
it('should not cache headers', async () => {
// HttpTransferCacheOptions: true = fallback to default = headers won't be cached
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {
headers: {foo: 'foo', bar: 'bar'},
transferCache: true,
});
// request returns the cache without any header.
const response2 = makeRequestAndExpectNone('/test-1?foo=1');
const response2 = await makeRequestAndExpectNone('/test-1?foo=1');
expect(response2.headers.keys().length).toBe(0);
});
it('should cache with headers', async () => {
// headers are case not sensitive
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {
headers: {foo: 'foo', bar: 'bar', 'BAZ': 'baz'},
transferCache: {includeHeaders: ['foo', 'baz']},
});
const consoleWarnSpy = spyOn(console, 'warn');
// request returns the cache with only 2 header entries.
const response = makeRequestAndExpectNone('/test-1?foo=1', 'GET', {
const response = await makeRequestAndExpectNone('/test-1?foo=1', 'GET', {
transferCache: {includeHeaders: ['foo', 'baz']},
});
expect(response.headers.keys().length).toBe(2);
@ -299,69 +342,77 @@ describe('TransferCache', () => {
expect(consoleWarnSpy.calls.count()).toBe(3);
});
it('should not cache POST by default', () => {
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'});
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'});
it('should not cache POST by default', async () => {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'});
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'});
});
it('should cache POST with the transferCache option', () => {
makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST', transferCache: true});
makeRequestAndExpectNone('/test-1?foo=1', 'POST', {transferCache: true});
it('should cache POST with the transferCache option', async () => {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST', transferCache: true});
await makeRequestAndExpectNone('/test-1?foo=1', 'POST', {transferCache: true});
makeRequestAndExpectOne('/test-2?foo=1', 'foo', {
await makeRequestAndExpectOne('/test-2?foo=1', 'foo', {
method: 'POST',
transferCache: {includeHeaders: []},
});
makeRequestAndExpectNone('/test-2?foo=1', 'POST', {transferCache: true});
await makeRequestAndExpectNone('/test-2?foo=1', 'POST', {transferCache: true});
});
it('should not cache request that requires authorization by default', async () => {
makeRequestAndExpectOne('/test-auth', 'foo', {
await makeRequestAndExpectOne('/test-auth', 'foo', {
headers: {Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'},
});
makeRequestAndExpectOne('/test-auth', 'foo');
await makeRequestAndExpectOne('/test-auth', 'foo');
});
it('should not cache request that requires proxy authorization by default', async () => {
makeRequestAndExpectOne('/test-auth', 'foo', {
await makeRequestAndExpectOne('/test-auth', 'foo', {
headers: {'Proxy-Authorization': 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'},
});
makeRequestAndExpectOne('/test-auth', 'foo');
await makeRequestAndExpectOne('/test-auth', 'foo');
});
it('should cache POST with the differing body in string form', () => {
makeRequestAndExpectOne('/test-1', null, {method: 'POST', transferCache: true, body: 'foo'});
makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: 'foo'});
makeRequestAndExpectOne('/test-1', null, {method: 'POST', transferCache: true, body: 'bar'});
it('should cache POST with the differing body in string form', async () => {
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: 'foo',
});
await makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: 'foo'});
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: 'bar',
});
});
it('should cache POST with the differing body in object form', () => {
makeRequestAndExpectOne('/test-1', null, {
it('should cache POST with the differing body in object form', async () => {
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: {foo: true},
});
makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: {foo: true}});
makeRequestAndExpectOne('/test-1', null, {
await makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: {foo: true}});
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: {foo: false},
});
});
it('should cache POST with the differing body in URLSearchParams form', () => {
makeRequestAndExpectOne('/test-1', null, {
it('should cache POST with the differing body in URLSearchParams form', async () => {
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: new URLSearchParams('foo=1'),
});
makeRequestAndExpectNone('/test-1', 'POST', {
await makeRequestAndExpectNone('/test-1', 'POST', {
transferCache: true,
body: new URLSearchParams('foo=1'),
});
makeRequestAndExpectOne('/test-1', null, {
await makeRequestAndExpectOne('/test-1', null, {
method: 'POST',
transferCache: true,
body: new URLSearchParams('foo=2'),
@ -407,9 +458,9 @@ describe('TransferCache', () => {
}),
);
it('should skip storing in transfer cache when platform is browser', () => {
makeRequestAndExpectOne('/test-1?foo=1', 'foo');
makeRequestAndExpectOne('/test-1?foo=1', 'foo');
it('should skip storing in transfer cache when platform is browser', async () => {
await makeRequestAndExpectOne('/test-1?foo=1', 'foo');
await makeRequestAndExpectOne('/test-1?foo=1', 'foo');
});
});
@ -457,58 +508,58 @@ describe('TransferCache', () => {
}),
);
it('should cache because of global filter', () => {
makeRequestAndExpectOne('/include?foo=1', 'foo');
makeRequestAndExpectNone('/include?foo=1');
it('should cache because of global filter', async () => {
await makeRequestAndExpectOne('/include?foo=1', 'foo');
await makeRequestAndExpectNone('/include?foo=1');
});
it('should not cache because of global filter', () => {
makeRequestAndExpectOne('/exclude?foo=1', 'foo');
makeRequestAndExpectOne('/exclude?foo=1', 'foo');
it('should not cache because of global filter', async () => {
await makeRequestAndExpectOne('/exclude?foo=1', 'foo');
await makeRequestAndExpectOne('/exclude?foo=1', 'foo');
});
it(`should cache request that requires authorization when 'includeRequestsWithAuthHeaders' is 'true'`, async () => {
makeRequestAndExpectOne('/test-auth', 'foo', {
await makeRequestAndExpectOne('/test-auth', 'foo', {
headers: {Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'},
});
makeRequestAndExpectNone('/test-auth');
await makeRequestAndExpectNone('/test-auth');
});
it(`should cache request that requires proxy authorization when 'includeRequestsWithAuthHeaders' is 'true'`, async () => {
makeRequestAndExpectOne('/test-auth', 'foo', {
await makeRequestAndExpectOne('/test-auth', 'foo', {
headers: {'Proxy-Authorization': 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'},
});
makeRequestAndExpectNone('/test-auth');
await makeRequestAndExpectNone('/test-auth');
});
it('should cache a POST request', () => {
makeRequestAndExpectOne('/include?foo=1', 'post-body', {method: 'POST'});
it('should cache a POST request', async () => {
await makeRequestAndExpectOne('/include?foo=1', 'post-body', {method: 'POST'});
// Previous POST request wasn't cached
const response = makeRequestAndExpectNone('/include?foo=1', 'POST');
const response = await makeRequestAndExpectNone('/include?foo=1', 'POST');
expect(response.body).toBe('post-body');
});
it('should cache with headers', () => {
it('should cache with headers', async () => {
// nothing specified, should use global options = callback => include + headers
makeRequestAndExpectOne('/include?foo=1', 'foo', {headers: {foo: 'foo', bar: 'bar'}});
await makeRequestAndExpectOne('/include?foo=1', 'foo', {headers: {foo: 'foo', bar: 'bar'}});
// This one was cached with headers
const response = makeRequestAndExpectNone('/include?foo=1');
const response = await makeRequestAndExpectNone('/include?foo=1');
expect(response.headers.keys().length).toBe(2);
});
it('should cache without headers because overridden', () => {
it('should cache without headers because overridden', async () => {
// nothing specified, should use global options = callback => include + headers
makeRequestAndExpectOne('/include?foo=1', 'foo', {
await makeRequestAndExpectOne('/include?foo=1', 'foo', {
headers: {foo: 'foo', bar: 'bar'},
transferCache: {includeHeaders: []},
});
// This one was cached with headers
const response = makeRequestAndExpectNone('/include?foo=1');
const response = await makeRequestAndExpectNone('/include?foo=1');
expect(response.headers.keys().length).toBe(0);
});
});
@ -550,17 +601,17 @@ describe('TransferCache', () => {
}),
);
it('should cache with public origin', () => {
makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo');
const cachedRequest = makeRequestAndExpectNone(
it('should cache with public origin', async () => {
await makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo');
const cachedRequest = await makeRequestAndExpectNone(
'https://external-domain.net:443/test-1?foo=1',
);
expect(cachedRequest.url).toBe('https://external-domain.net:443/test-1?foo=1');
});
it('should cache normally when there is no mapping defined for the origin', () => {
makeRequestAndExpectOne('https://other.internal-domain.com:1234/test-1?foo=1', 'foo');
makeRequestAndExpectNone('https://other.internal-domain.com:1234/test-1?foo=1');
it('should cache normally when there is no mapping defined for the origin', async () => {
await makeRequestAndExpectOne('https://other.internal-domain.com:1234/test-1?foo=1', 'foo');
await makeRequestAndExpectNone('https://other.internal-domain.com:1234/test-1?foo=1');
});
describe('when the origin map is configured with extra paths', () => {
@ -617,7 +668,7 @@ describe('TransferCache', () => {
describe('on the client', () => {
beforeEach(
withBody('<test-app-http></test-app-http>', () => {
withBody('<test-app-http></test-app-http>', async () => {
TestBed.resetTestingModule();
isStable = new BehaviorSubject<boolean>(false);
@ -647,7 +698,7 @@ describe('TransferCache', () => {
});
// Make a request on the server to fill the transfer state then reuse it in the browser
makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo');
await makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo');
const transferState = TestBed.inject(TransferState);
TestBed.resetTestingModule();

View file

@ -18,7 +18,7 @@ import {
} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {withBody} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';
import {BehaviorSubject, lastValueFrom} from 'rxjs';
import {provideClientHydration, withNoHttpTransferCache} from '../public_api';
import {withHttpTransferCacheOptions} from '../src/hydration';
@ -31,21 +31,23 @@ describe('provideClientHydration', () => {
})
class SomeComponent {}
function makeRequestAndExpectOne(
async function makeRequestAndExpectOne(
url: string,
body: string,
options: HttpTransferCacheOptions | boolean = true,
): void {
TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe();
): Promise<Object> {
const response = lastValueFrom(TestBed.inject(HttpClient).get(url, {transferCache: options}));
TestBed.inject(HttpTestingController).expectOne(url).flush(body);
return response;
}
function makeRequestAndExpectNone(
async function makeRequestAndExpectNone(
url: string,
options: HttpTransferCacheOptions | boolean = true,
): void {
TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe();
): Promise<Object> {
const response = lastValueFrom(TestBed.inject(HttpClient).get(url, {transferCache: options}));
TestBed.inject(HttpTestingController).expectNone(url);
return response;
}
@Injectable()
@ -88,10 +90,10 @@ describe('provideClientHydration', () => {
),
);
it(`should use cached HTTP calls`, () => {
makeRequestAndExpectOne('/test-1', 'foo');
it(`should use cached HTTP calls`, async () => {
await makeRequestAndExpectOne('/test-1', 'foo');
// Do the same call, this time it should served from cache.
makeRequestAndExpectNone('/test-1');
await makeRequestAndExpectNone('/test-1');
});
});
@ -120,10 +122,10 @@ describe('provideClientHydration', () => {
),
);
it(`should not cache HTTP calls`, () => {
makeRequestAndExpectOne('/test-1', 'foo', false);
it(`should not cache HTTP calls`, async () => {
await makeRequestAndExpectOne('/test-1', 'foo', false);
// Do the same call, this time should pass through as cache is disabled.
makeRequestAndExpectOne('/test-1', 'foo');
await makeRequestAndExpectOne('/test-1', 'foo');
});
});
@ -154,14 +156,11 @@ describe('provideClientHydration', () => {
),
);
it(`should cache HTTP POST calls`, () => {
it(`should cache HTTP POST calls`, async () => {
const url = '/test-1';
const body = 'foo';
TestBed.inject(HttpClient).post(url, body).subscribe();
TestBed.inject(HttpTestingController).expectOne(url).flush(body);
TestBed.inject(HttpClient).post(url, body).subscribe();
TestBed.inject(HttpTestingController).expectNone(url);
await makeRequestAndExpectOne(url, body);
await makeRequestAndExpectNone(url);
});
});
});