aws-cdk
Version:
CDK Toolkit, the command line tool for CDK apps
631 lines (622 loc) • 102 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable import/order */
const https = require("https");
const os = require("os");
const path = require("path");
const fs = require("fs-extra");
const nock = require("nock");
const logging = require("../lib/logging");
const notices_1 = require("../lib/notices");
const version = require("../lib/cli/version");
const settings_1 = require("../lib/api/settings");
const context_1 = require("../lib/api/context");
const BASIC_BOOTSTRAP_NOTICE = {
title: 'Exccessive permissions on file asset publishing role',
issueNumber: 16600,
overview: 'FilePublishingRoleDefaultPolicy has too many permissions',
components: [{
name: 'bootstrap',
version: '<25',
}],
schemaVersion: '1',
};
const BOOTSTRAP_NOTICE_V10 = {
title: 'Bootstrap version 10 is no good',
issueNumber: 16600,
overview: 'overview',
components: [{
name: 'bootstrap',
version: '=10',
}],
schemaVersion: '1',
};
const BOOTSTRAP_NOTICE_V11 = {
title: 'Bootstrap version 11 is no good',
issueNumber: 16600,
overview: 'overview',
components: [{
name: 'bootstrap',
version: '=11',
}],
schemaVersion: '1',
};
const BASIC_DYNAMIC_NOTICE = {
title: 'Toggling off auto_delete_objects for Bucket empties the bucket',
issueNumber: 16603,
overview: '{resolve:DYNAMIC1} this is a notice with dynamic values {resolve:DYNAMIC2}',
components: [{
name: 'cli',
version: '<=1.126.0',
}],
schemaVersion: '1',
};
const BASIC_NOTICE = {
title: 'Toggling off auto_delete_objects for Bucket empties the bucket',
issueNumber: 16603,
overview: 'If a stack is deployed with an S3 bucket with auto_delete_objects=True, and then re-deployed with auto_delete_objects=False, all the objects in the bucket will be deleted.',
components: [{
name: 'cli',
version: '<=1.126.0',
}],
schemaVersion: '1',
};
const BASIC_WARNING_NOTICE = {
title: 'Toggling off auto_delete_objects for Bucket empties the bucket',
issueNumber: 16603,
overview: 'If a stack is deployed with an S3 bucket with auto_delete_objects=True, and then re-deployed with auto_delete_objects=False, all the objects in the bucket will be deleted.',
components: [{
name: 'cli',
version: '<=1.126.0',
}],
schemaVersion: '1',
severity: 'warning',
};
const BASIC_ERROR_NOTICE = {
title: 'Toggling off auto_delete_objects for Bucket empties the bucket',
issueNumber: 16603,
overview: 'If a stack is deployed with an S3 bucket with auto_delete_objects=True, and then re-deployed with auto_delete_objects=False, all the objects in the bucket will be deleted.',
components: [{
name: 'cli',
version: '<=1.126.0',
}],
schemaVersion: '1',
severity: 'error',
};
const MULTIPLE_AFFECTED_VERSIONS_NOTICE = {
title: 'Error when building EKS cluster with monocdk import',
issueNumber: 17061,
overview: 'When using monocdk/aws-eks to build a stack containing an EKS cluster, error is thrown about missing lambda-layer-node-proxy-agent/layer/package.json.',
components: [{
name: 'cli',
version: '<1.130.0 >=1.126.0',
}],
schemaVersion: '1',
};
const FRAMEWORK_2_1_0_AFFECTED_NOTICE = {
title: 'Regression on module foobar',
issueNumber: 1234,
overview: 'Some bug description',
components: [{
name: 'framework',
version: '<= 2.1.0',
}],
schemaVersion: '1',
};
const NOTICE_FOR_APIGATEWAYV2 = {
title: 'Regression on module foobar',
issueNumber: 1234,
overview: 'Some bug description',
components: [{
name: '@aws-cdk/aws-apigatewayv2-alpha.',
version: '<= 2.13.0-alpha.0',
}],
schemaVersion: '1',
};
const NOTICE_FOR_APIGATEWAY = {
title: 'Regression on module foobar',
issueNumber: 1234,
overview: 'Some bug description',
components: [{
name: '@aws-cdk/aws-apigateway',
version: '<= 2.13.0-alpha.0',
}],
schemaVersion: '1',
};
const NOTICE_FOR_APIGATEWAYV2_CFN_STAGE = {
title: 'Regression on module foobar',
issueNumber: 1234,
overview: 'Some bug description',
components: [{
name: 'aws-cdk-lib.aws_apigatewayv2.CfnStage',
version: '<= 2.13.0-alpha.0',
}],
schemaVersion: '1',
};
describe(notices_1.FilteredNotice, () => {
describe('format', () => {
test('resolves dynamic values', () => {
const filteredNotice = new notices_1.FilteredNotice(BASIC_DYNAMIC_NOTICE);
filteredNotice.addDynamicValue('DYNAMIC1', 'dynamic-value1');
filteredNotice.addDynamicValue('DYNAMIC2', 'dynamic-value2');
expect(filteredNotice.format()).toMatchInlineSnapshot(`
"16603 Toggling off auto_delete_objects for Bucket empties the bucket
Overview: dynamic-value1 this is a notice with dynamic values
dynamic-value2
Affected versions: cli: <=1.126.0
More information at: https://github.com/aws/aws-cdk/issues/16603
"
`);
});
test('single version range', () => {
expect(new notices_1.FilteredNotice(BASIC_NOTICE).format()).toMatchInlineSnapshot(`
"16603 Toggling off auto_delete_objects for Bucket empties the bucket
Overview: If a stack is deployed with an S3 bucket with
auto_delete_objects=True, and then re-deployed with
auto_delete_objects=False, all the objects in the bucket
will be deleted.
Affected versions: cli: <=1.126.0
More information at: https://github.com/aws/aws-cdk/issues/16603
"
`);
});
test('multiple version ranges', () => {
expect(new notices_1.FilteredNotice(MULTIPLE_AFFECTED_VERSIONS_NOTICE).format()).toMatchInlineSnapshot(`
"17061 Error when building EKS cluster with monocdk import
Overview: When using monocdk/aws-eks to build a stack containing an
EKS cluster, error is thrown about missing
lambda-layer-node-proxy-agent/layer/package.json.
Affected versions: cli: <1.130.0 >=1.126.0
More information at: https://github.com/aws/aws-cdk/issues/17061
"
`);
});
});
});
describe(notices_1.NoticesFilter, () => {
describe('filter', () => {
test('cli', async () => {
const notices = [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE];
// doesn't matter for this test because our data only has CLI notices
const outDir = path.join(__dirname, 'cloud-assembly-trees', 'built-with-2_12_0');
expect(notices_1.NoticesFilter.filter({ data: notices, bootstrappedEnvironments: [], outDir, cliVersion: '1.0.0' }).map(f => f.notice)).toEqual([BASIC_NOTICE]);
expect(notices_1.NoticesFilter.filter({ data: notices, bootstrappedEnvironments: [], outDir, cliVersion: '1.129.0' }).map(f => f.notice)).toEqual([MULTIPLE_AFFECTED_VERSIONS_NOTICE]);
expect(notices_1.NoticesFilter.filter({ data: notices, bootstrappedEnvironments: [], outDir, cliVersion: '1.126.0' }).map(f => f.notice)).toEqual([BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE]);
expect(notices_1.NoticesFilter.filter({ data: notices, bootstrappedEnvironments: [], outDir, cliVersion: '1.130.0' }).map(f => f.notice)).toEqual([]);
});
test('framework', () => {
const notices = [FRAMEWORK_2_1_0_AFFECTED_NOTICE];
// doesn't matter for this test because our data only has framework notices
const cliVersion = '1.0.0';
expect(notices_1.NoticesFilter.filter({ data: notices, cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'built-with-2_12_0') }).map(f => f.notice)).toEqual([]);
expect(notices_1.NoticesFilter.filter({ data: notices, cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'built-with-1_144_0') }).map(f => f.notice)).toEqual([FRAMEWORK_2_1_0_AFFECTED_NOTICE]);
});
test('module', () => {
// doesn't matter for this test because our data only has framework notices
const cliVersion = '1.0.0';
// module-level match
expect(notices_1.NoticesFilter.filter({ data: [NOTICE_FOR_APIGATEWAYV2], cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'experimental-module') }).map(f => f.notice)).toEqual([NOTICE_FOR_APIGATEWAYV2]);
// no apigatewayv2 in the tree
expect(notices_1.NoticesFilter.filter({ data: [NOTICE_FOR_APIGATEWAYV2], cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'built-with-2_12_0') }).map(f => f.notice)).toEqual([]);
// module name mismatch: apigateway != apigatewayv2
expect(notices_1.NoticesFilter.filter({ data: [NOTICE_FOR_APIGATEWAY], cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'experimental-module') }).map(f => f.notice)).toEqual([]);
// construct-level match
expect(notices_1.NoticesFilter.filter({ data: [NOTICE_FOR_APIGATEWAYV2_CFN_STAGE], cliVersion, bootstrappedEnvironments: [], outDir: path.join(__dirname, 'cloud-assembly-trees', 'experimental-module') }).map(f => f.notice)).toEqual([NOTICE_FOR_APIGATEWAYV2_CFN_STAGE]);
});
test('bootstrap', () => {
// doesn't matter for this test because our data only has bootstrap notices
const outDir = path.join(__dirname, 'cloud-assembly-trees', 'built-with-2_12_0');
const cliVersion = '1.0.0';
const bootstrappedEnvironments = [
{
// affected
bootstrapStackVersion: 22,
environment: {
account: 'account',
region: 'region1',
name: 'env1',
},
},
{
// affected
bootstrapStackVersion: 21,
environment: {
account: 'account',
region: 'region2',
name: 'env2',
},
},
{
// not affected
bootstrapStackVersion: 28,
environment: {
account: 'account',
region: 'region3',
name: 'env3',
},
},
];
expect(notices_1.NoticesFilter.filter({
data: [BASIC_BOOTSTRAP_NOTICE],
cliVersion,
outDir,
bootstrappedEnvironments: bootstrappedEnvironments,
}).map(f => f.notice)).toEqual([BASIC_BOOTSTRAP_NOTICE]);
});
test('ignores invalid bootstrap versions', () => {
// doesn't matter for this test because our data only has bootstrap notices
const outDir = path.join(__dirname, 'cloud-assembly-trees', 'built-with-2_12_0');
const cliVersion = '1.0.0';
expect(notices_1.NoticesFilter.filter({
data: [BASIC_BOOTSTRAP_NOTICE],
cliVersion,
outDir,
bootstrappedEnvironments: [{ bootstrapStackVersion: NaN, environment: { account: 'account', region: 'region', name: 'env' } }],
}).map(f => f.notice)).toEqual([]);
});
});
});
describe(notices_1.WebsiteNoticeDataSource, () => {
const dataSource = new notices_1.WebsiteNoticeDataSource();
test('returns data when download succeeds', async () => {
const result = await mockCall(200, {
notices: [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE],
});
expect(result).toEqual([BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE]);
});
test('returns appropriate error when the server returns an unexpected status code', async () => {
const result = mockCall(500, {
notices: [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE],
});
await expect(result).rejects.toThrow(/500/);
});
test('returns appropriate error when the server returns an unexpected structure', async () => {
const result = mockCall(200, {
foo: [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE],
});
await expect(result).rejects.toThrow(/key is missing/);
});
test('returns appropriate error when the server returns invalid json', async () => {
const result = mockCall(200, '-09aiskjkj838');
await expect(result).rejects.toThrow(/Failed to parse/);
});
test('returns appropriate error when HTTPS call throws', async () => {
const mockGet = jest.spyOn(https, 'get')
.mockImplementation(() => { throw new Error('No connection'); });
const result = dataSource.fetch();
await expect(result).rejects.toThrow(/No connection/);
mockGet.mockRestore();
});
test('returns appropriate error when the request has an error', async () => {
nock('https://cli.cdk.dev-tools.aws.dev')
.get('/notices.json')
.replyWithError('DNS resolution failed');
const result = dataSource.fetch();
await expect(result).rejects.toThrow(/DNS resolution failed/);
});
test('returns appropriate error when the connection stays idle for too long', async () => {
nock('https://cli.cdk.dev-tools.aws.dev')
.get('/notices.json')
.delayConnection(3500)
.reply(200, {
notices: [BASIC_NOTICE],
});
const result = dataSource.fetch();
await expect(result).rejects.toThrow(/timed out/);
});
test('returns empty array when the request takes too long to finish', async () => {
nock('https://cli.cdk.dev-tools.aws.dev')
.get('/notices.json')
.delayBody(3500)
.reply(200, {
notices: [BASIC_NOTICE],
});
const result = dataSource.fetch();
await expect(result).rejects.toThrow(/timed out/);
});
function mockCall(statusCode, body) {
nock('https://cli.cdk.dev-tools.aws.dev')
.get('/notices.json')
.reply(statusCode, body);
return dataSource.fetch();
}
});
describe(notices_1.CachedDataSource, () => {
const fileName = path.join(os.tmpdir(), 'cache.json');
const cachedData = [BASIC_NOTICE];
const freshData = [MULTIPLE_AFFECTED_VERSIONS_NOTICE];
beforeEach(() => {
fs.writeFileSync(fileName, '');
});
test('retrieves data from the delegate cache when the file is empty', async () => {
const dataSource = dataSourceWithDelegateReturning(freshData);
const notices = await dataSource.fetch();
expect(notices).toEqual(freshData);
});
test('retrieves data from the file when the data is still valid', async () => {
fs.writeJsonSync(fileName, {
notices: cachedData,
expiration: Date.now() + 10000,
});
const dataSource = dataSourceWithDelegateReturning(freshData);
const notices = await dataSource.fetch();
expect(notices).toEqual(cachedData);
});
test('retrieves data from the delegate when the data is expired', async () => {
fs.writeJsonSync(fileName, {
notices: cachedData,
expiration: 0,
});
const dataSource = dataSourceWithDelegateReturning(freshData);
const notices = await dataSource.fetch();
expect(notices).toEqual(freshData);
});
test('retrieves data from the delegate when the file cannot be read', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-test'));
try {
const debugSpy = jest.spyOn(logging, 'debug');
const dataSource = dataSourceWithDelegateReturning(freshData, `${tmpDir}/does-not-exist.json`);
const notices = await dataSource.fetch();
expect(notices).toEqual(freshData);
expect(debugSpy).not.toHaveBeenCalled();
debugSpy.mockRestore();
}
finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
test('retrieved data from the delegate when it is configured to ignore the cache', async () => {
fs.writeJsonSync(fileName, {
notices: cachedData,
expiration: Date.now() + 10000,
});
const dataSource = dataSourceWithDelegateReturning(freshData, fileName, true);
const notices = await dataSource.fetch();
expect(notices).toEqual(freshData);
});
test('error in delegate gets turned into empty result by cached source', async () => {
// GIVEN
const delegate = {
fetch: jest.fn().mockRejectedValue(new Error('fetching failed')),
};
const dataSource = new notices_1.CachedDataSource(fileName, delegate, true);
// WHEN
const notices = await dataSource.fetch();
// THEN
expect(notices).toEqual([]);
});
function dataSourceWithDelegateReturning(notices, file = fileName, ignoreCache = false) {
const delegate = {
fetch: jest.fn(),
};
delegate.fetch.mockResolvedValue(notices);
return new notices_1.CachedDataSource(file, delegate, ignoreCache);
}
});
describe(notices_1.Notices, () => {
beforeEach(() => {
// disable caching
jest.spyOn(notices_1.CachedDataSource.prototype, 'save').mockImplementation((_) => Promise.resolve());
jest.spyOn(notices_1.CachedDataSource.prototype, 'load').mockImplementation(() => Promise.resolve({ expiration: 0, notices: [] }));
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('addBootstrapVersion', () => {
test('can add multiple values', async () => {
const notices = notices_1.Notices.create({ context: new context_1.Context() });
notices.addBootstrappedEnvironment({ bootstrapStackVersion: 10, environment: { account: 'account', region: 'region', name: 'env' } });
notices.addBootstrappedEnvironment({ bootstrapStackVersion: 11, environment: { account: 'account', region: 'region', name: 'env' } });
await notices.refresh({
dataSource: { fetch: async () => [BOOTSTRAP_NOTICE_V10, BOOTSTRAP_NOTICE_V11] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenCalledWith(new notices_1.FilteredNotice(BOOTSTRAP_NOTICE_V10).format());
expect(print).toHaveBeenCalledWith(new notices_1.FilteredNotice(BOOTSTRAP_NOTICE_V11).format());
});
test('deduplicates', async () => {
const notices = notices_1.Notices.create({ context: new context_1.Context() });
notices.addBootstrappedEnvironment({ bootstrapStackVersion: 10, environment: { account: 'account', region: 'region', name: 'env' } });
notices.addBootstrappedEnvironment({ bootstrapStackVersion: 10, environment: { account: 'account', region: 'region', name: 'env' } });
// mock cli version number
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
notices.display();
const filter = jest.spyOn(notices_1.NoticesFilter, 'filter');
notices.display();
expect(filter).toHaveBeenCalledTimes(1);
expect(filter).toHaveBeenCalledWith({
bootstrappedEnvironments: [{
bootstrapStackVersion: 10,
environment: {
account: 'account',
region: 'region',
name: 'env',
},
}],
cliVersion: '1.0.0',
data: [],
outDir: 'cdk.out',
});
});
});
describe('refresh', () => {
test('deduplicates notices', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, BASIC_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenCalledWith(new notices_1.FilteredNotice(BASIC_NOTICE).format());
});
test('clears notices if empty', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [] },
});
const print = jest.spyOn(logging, 'info');
notices.display({ showTotal: true });
expect(print).toHaveBeenNthCalledWith(1, '');
expect(print).toHaveBeenNthCalledWith(2, 'There are 0 unacknowledged notice(s).');
expect(print).toHaveBeenCalledTimes(2);
});
test('doesnt throw', async () => {
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: {
fetch: async () => {
throw new Error('Should not fail refresh');
},
},
});
});
test('does nothing when we shouldnt display', async () => {
let refreshCalled = false;
const notices = notices_1.Notices.create({ context: new context_1.Context(), shouldDisplay: false });
await notices.refresh({
dataSource: {
fetch: async () => {
refreshCalled = true;
return Promise.resolve([]);
},
},
});
expect(refreshCalled).toBeFalsy();
});
test('filters out acknowledged notices by default', async () => {
// within the affected version range of both notices
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.126.0');
const context = new context_1.Context({ bag: new settings_1.Settings({ 'acknowledged-issue-numbers': [MULTIPLE_AFFECTED_VERSIONS_NOTICE.issueNumber] }) });
const notices = notices_1.Notices.create({ context });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(4, new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(print).toHaveBeenNthCalledWith(6, 'If you don’t want to see a notice anymore, use \"cdk acknowledge <id>\". For example, \"cdk acknowledge 16603\".');
});
test('preserves acknowledged notices if requested', async () => {
// within the affected version range of both notices
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.126.0');
const context = new context_1.Context({ bag: new settings_1.Settings({ 'acknowledged-issue-numbers': [MULTIPLE_AFFECTED_VERSIONS_NOTICE.issueNumber] }) });
const notices = notices_1.Notices.create({ context, includeAcknowledged: true });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenCalledWith(new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(print).toHaveBeenCalledWith(new notices_1.FilteredNotice(MULTIPLE_AFFECTED_VERSIONS_NOTICE).format());
});
});
describe('display', () => {
test('notices envelop', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, BASIC_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(2, 'NOTICES (What\'s this? https://github.com/aws/aws-cdk/wiki/CLI-Notices)');
expect(print).toHaveBeenNthCalledWith(6, 'If you don’t want to see a notice anymore, use \"cdk acknowledge <id>\". For example, \"cdk acknowledge 16603\".');
});
test('deduplicates notices', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, BASIC_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(4, new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(print).toHaveBeenNthCalledWith(6, 'If you don’t want to see a notice anymore, use \"cdk acknowledge <id>\". For example, \"cdk acknowledge 16603\".');
});
test('does nothing when we shouldnt display', async () => {
const notices = notices_1.Notices.create({ context: new context_1.Context(), shouldDisplay: false });
await notices.refresh({ dataSource: { fetch: async () => [BASIC_NOTICE] } });
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenCalledTimes(0);
});
test('nothing when there are no notices', async () => {
const print = jest.spyOn(logging, 'info');
notices_1.Notices.create({ context: new context_1.Context() }).display();
expect(print).toHaveBeenCalledTimes(0);
});
test('total count when show total is true', async () => {
const print = jest.spyOn(logging, 'info');
notices_1.Notices.create({ context: new context_1.Context() }).display({ showTotal: true });
expect(print).toHaveBeenNthCalledWith(2, 'There are 0 unacknowledged notice(s).');
});
test('warning', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_WARNING_NOTICE] },
});
const warning = jest.spyOn(logging, 'warning');
notices.display();
expect(warning).toHaveBeenNthCalledWith(1, new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(warning).toHaveBeenCalledTimes(1);
});
test('error', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_ERROR_NOTICE] },
});
const error = jest.spyOn(logging, 'error');
notices.display();
expect(error).toHaveBeenNthCalledWith(1, new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(error).toHaveBeenCalledTimes(1);
});
test('only relevant notices', async () => {
// within the affected version range of the notice
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.0.0');
const notices = notices_1.Notices.create({ context: new context_1.Context() });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(4, new notices_1.FilteredNotice(BASIC_NOTICE).format());
});
test('only unacknowledged notices', async () => {
// within the affected version range of both notices
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.126.0');
const context = new context_1.Context({ bag: new settings_1.Settings({ 'acknowledged-issue-numbers': [MULTIPLE_AFFECTED_VERSIONS_NOTICE.issueNumber] }) });
const notices = notices_1.Notices.create({ context });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(4, new notices_1.FilteredNotice(BASIC_NOTICE).format());
});
test('can include acknowledged notices if requested', async () => {
// within the affected version range of both notices
jest.spyOn(version, 'versionNumber').mockImplementation(() => '1.126.0');
const context = new context_1.Context({ bag: new settings_1.Settings({ 'acknowledged-issue-numbers': [MULTIPLE_AFFECTED_VERSIONS_NOTICE.issueNumber] }) });
const notices = notices_1.Notices.create({ context, includeAcknowledged: true });
await notices.refresh({
dataSource: { fetch: async () => [BASIC_NOTICE, MULTIPLE_AFFECTED_VERSIONS_NOTICE] },
});
const print = jest.spyOn(logging, 'info');
notices.display();
expect(print).toHaveBeenNthCalledWith(4, new notices_1.FilteredNotice(BASIC_NOTICE).format());
expect(print).toHaveBeenNthCalledWith(6, new notices_1.FilteredNotice(MULTIPLE_AFFECTED_VERSIONS_NOTICE).format());
});
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm90aWNlcy50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibm90aWNlcy50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsaUNBQWlDO0FBQ2pDLCtCQUErQjtBQUMvQix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtCQUErQjtBQUMvQiw2QkFBNkI7QUFDN0IsMENBQTBDO0FBQzFDLDRDQVF3QjtBQUN4Qiw4Q0FBOEM7QUFDOUMsa0RBQStDO0FBQy9DLGdEQUE2QztBQUU3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzdCLEtBQUssRUFBRSxzREFBc0Q7SUFDN0QsV0FBVyxFQUFFLEtBQUs7SUFDbEIsUUFBUSxFQUFFLDBEQUEwRDtJQUNwRSxVQUFVLEVBQUUsQ0FBQztZQUNYLElBQUksRUFBRSxXQUFXO1lBQ2pCLE9BQU8sRUFBRSxLQUFLO1NBQ2YsQ0FBQztJQUNGLGFBQWEsRUFBRSxHQUFHO0NBQ25CLENBQUM7QUFFRixNQUFNLG9CQUFvQixHQUFHO0lBQzNCLEtBQUssRUFBRSxpQ0FBaUM7SUFDeEMsV0FBVyxFQUFFLEtBQUs7SUFDbEIsUUFBUSxFQUFFLFVBQVU7SUFDcEIsVUFBVSxFQUFFLENBQUM7WUFDWCxJQUFJLEVBQUUsV0FBVztZQUNqQixPQUFPLEVBQUUsS0FBSztTQUNmLENBQUM7SUFDRixhQUFhLEVBQUUsR0FBRztDQUNuQixDQUFDO0FBRUYsTUFBTSxvQkFBb0IsR0FBRztJQUMzQixLQUFLLEVBQUUsaUNBQWlDO0lBQ3hDLFdBQVcsRUFBRSxLQUFLO0lBQ2xCLFFBQVEsRUFBRSxVQUFVO0lBQ3BCLFVBQVUsRUFBRSxDQUFDO1lBQ1gsSUFBSSxFQUFFLFdBQVc7WUFDakIsT0FBTyxFQUFFLEtBQUs7U0FDZixDQUFDO0lBQ0YsYUFBYSxFQUFFLEdBQUc7Q0FDbkIsQ0FBQztBQUVGLE1BQU0sb0JBQW9CLEdBQUc7SUFDM0IsS0FBSyxFQUFFLGdFQUFnRTtJQUN2RSxXQUFXLEVBQUUsS0FBSztJQUNsQixRQUFR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