UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

631 lines (622 loc) 102 kB
"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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