@rushstack/rush-http-build-cache-plugin
Version:
Rush plugin for generic HTTP cloud build cache
129 lines (112 loc) • 4.46 kB
text/typescript
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
jest.mock('@rushstack/rush-sdk/lib/utilities/WebClient', () => {
return jest.requireActual('@microsoft/rush-lib/lib/utilities/WebClient');
});
import { type RushSession, EnvironmentConfiguration } from '@rushstack/rush-sdk';
import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal';
import { WebClient } from '@rushstack/rush-sdk/lib/utilities/WebClient';
import { HttpBuildCacheProvider, type IHttpBuildCacheProviderOptions } from '../HttpBuildCacheProvider';
const EXAMPLE_OPTIONS: IHttpBuildCacheProviderOptions = {
url: 'https://buildcache.example.acme.com',
tokenHandler: {
exec: 'node',
args: ['tokenHandler.js']
},
uploadMethod: 'POST',
isCacheWriteAllowed: false,
pluginName: 'example-plugin',
rushJsonFolder: '/repo',
minHttpRetryDelayMs: 1
};
type FetchFnType = Parameters<typeof WebClient.mockRequestFn>[0];
describe('HttpBuildCacheProvider', () => {
let terminalBuffer: StringBufferTerminalProvider;
let terminal!: Terminal;
let fetchFn: jest.Mock;
beforeEach(() => {
terminalBuffer = new StringBufferTerminalProvider();
terminal = new Terminal(terminalBuffer);
fetchFn = jest.fn();
WebClient.mockRequestFn(fetchFn as unknown as FetchFnType);
});
afterEach(() => {
WebClient.resetMockRequestFn();
});
describe('tryGetCacheEntryBufferByIdAsync', () => {
it('prints warning if read credentials are not available', async () => {
jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);
const session: RushSession = {} as RushSession;
const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);
mocked(fetchFn).mockResolvedValue({
status: 401,
statusText: 'Unauthorized',
ok: false
});
const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
expect(result).toBe(undefined);
expect(fetchFn).toHaveBeenCalledTimes(1);
expect(fetchFn).toHaveBeenNthCalledWith(
1,
'https://buildcache.example.acme.com/some-key',
expect.objectContaining({
method: 'GET',
redirect: 'follow'
})
);
expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(
`"Error getting cache entry: Error: Credentials for https://buildcache.example.acme.com/ have not been provided.[n]In CI, verify that RUSH_BUILD_CACHE_CREDENTIAL contains a valid Authorization header value.[n][n]For local developers, run:[n][n] rush update-cloud-credentials --interactive[n][n]"`
);
});
it('attempts up to 3 times to download a cache entry', async () => {
jest.spyOn(EnvironmentConfiguration, 'buildCacheCredential', 'get').mockReturnValue(undefined);
const session: RushSession = {} as RushSession;
const provider = new HttpBuildCacheProvider(EXAMPLE_OPTIONS, session);
mocked(fetchFn).mockResolvedValueOnce({
status: 500,
statusText: 'InternalServiceError',
ok: false
});
mocked(fetchFn).mockResolvedValueOnce({
status: 503,
statusText: 'ServiceUnavailable',
ok: false
});
mocked(fetchFn).mockResolvedValueOnce({
status: 504,
statusText: 'BadGateway',
ok: false
});
const result = await provider.tryGetCacheEntryBufferByIdAsync(terminal, 'some-key');
expect(result).toBe(undefined);
expect(fetchFn).toHaveBeenCalledTimes(3);
expect(fetchFn).toHaveBeenNthCalledWith(
1,
'https://buildcache.example.acme.com/some-key',
expect.objectContaining({
method: 'GET',
redirect: 'follow'
})
);
expect(fetchFn).toHaveBeenNthCalledWith(
2,
'https://buildcache.example.acme.com/some-key',
expect.objectContaining({
method: 'GET',
redirect: 'follow'
})
);
expect(fetchFn).toHaveBeenNthCalledWith(
3,
'https://buildcache.example.acme.com/some-key',
expect.objectContaining({
method: 'GET',
redirect: 'follow'
})
);
expect(terminalBuffer.getWarningOutput()).toMatchInlineSnapshot(
`"Could not get cache entry: HTTP 504: BadGateway[n]"`
);
});
});
});