-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Adding bulk upgrade api * Addressing comments * Removing todo * Changing body field * Adding helper for getting the bulk install route * Adding request spec * Pulling in Johns changes * Removing test for same package upgraded multiple times * Adding upgrade to setup route * Adding setup integration test * Clean up error handling * Beginning to add tests * Failing jest mock tests * Break up tests & modules for easier testing. Deal with issue described in jestjs/jest#1075 (comment) epm/packages/install has functions a, b, c which are independent but a can also call b and c function a() { b(); c(); } The linked FB issue describes the cause and rationale (Jest works on "module" boundary) but TL;DR: it's easier if you split up your files Some related links I found during this journey * https://medium.com/@qjli/how-to-mock-specific-module-function-in-jest-715e39a391f4 * https://stackoverflow.com/questions/52650367/jestjs-how-to-test-function-being-called-inside-another-function * https://stackoverflow.com/questions/50854440/spying-on-an-imported-function-that-calls-another-function-in-jest/50855968#50855968 * Add test confirming update error result will throw * Keep orig error. Add status code in http handler * Leave error as-is * Removing accidental code changes. File rename. * Missed a function when moving to a new file * Add missing type imports * Lift .map lambda into named outer function * Adding additional test * Fixing type error Co-authored-by: John Schulz <john.schulz@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: John Schulz <john.schulz@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
f9abd17
commit 0ea9480
Showing
11 changed files
with
426 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
x-pack/plugins/ingest_manager/server/services/epm/packages/bulk_install_packages.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { SavedObjectsClientContract } from 'src/core/server'; | ||
import { CallESAsCurrentUser } from '../../../types'; | ||
import * as Registry from '../registry'; | ||
import { getInstallationObject } from './index'; | ||
import { BulkInstallResponse, IBulkInstallPackageError, upgradePackage } from './install'; | ||
|
||
interface BulkInstallPackagesParams { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
packagesToUpgrade: string[]; | ||
callCluster: CallESAsCurrentUser; | ||
} | ||
|
||
export async function bulkInstallPackages({ | ||
savedObjectsClient, | ||
packagesToUpgrade, | ||
callCluster, | ||
}: BulkInstallPackagesParams): Promise<BulkInstallResponse[]> { | ||
const installedAndLatestPromises = packagesToUpgrade.map((pkgToUpgrade) => | ||
Promise.all([ | ||
getInstallationObject({ savedObjectsClient, pkgName: pkgToUpgrade }), | ||
Registry.fetchFindLatestPackage(pkgToUpgrade), | ||
]) | ||
); | ||
const installedAndLatestResults = await Promise.allSettled(installedAndLatestPromises); | ||
const installResponsePromises = installedAndLatestResults.map(async (result, index) => { | ||
const pkgToUpgrade = packagesToUpgrade[index]; | ||
if (result.status === 'fulfilled') { | ||
const [installedPkg, latestPkg] = result.value; | ||
return upgradePackage({ | ||
savedObjectsClient, | ||
callCluster, | ||
installedPkg, | ||
latestPkg, | ||
pkgToUpgrade, | ||
}); | ||
} else { | ||
return { name: pkgToUpgrade, error: result.reason }; | ||
} | ||
}); | ||
const installResults = await Promise.allSettled(installResponsePromises); | ||
const installResponses = installResults.map((result, index) => { | ||
const pkgToUpgrade = packagesToUpgrade[index]; | ||
if (result.status === 'fulfilled') { | ||
return result.value; | ||
} else { | ||
return { name: pkgToUpgrade, error: result.reason }; | ||
} | ||
}); | ||
|
||
return installResponses; | ||
} | ||
|
||
export function isBulkInstallError(test: any): test is IBulkInstallPackageError { | ||
return 'error' in test && test.error instanceof Error; | ||
} |
144 changes: 144 additions & 0 deletions
144
...ins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { ElasticsearchAssetType, Installation, KibanaAssetType } from '../../../types'; | ||
import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; | ||
|
||
jest.mock('./install'); | ||
jest.mock('./bulk_install_packages'); | ||
jest.mock('./get'); | ||
|
||
import { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages'; | ||
const { ensureInstalledDefaultPackages } = jest.requireActual('./install'); | ||
const { isBulkInstallError: actualIsBulkInstallError } = jest.requireActual( | ||
'./bulk_install_packages' | ||
); | ||
import { getInstallation } from './get'; | ||
import { savedObjectsClientMock } from 'src/core/server/mocks'; | ||
import { appContextService } from '../../app_context'; | ||
import { createAppContextStartContractMock } from '../../../mocks'; | ||
|
||
// if we add this assertion, TS will type check the return value | ||
// and the editor will also know about .mockImplementation, .mock.calls, etc | ||
const mockedBulkInstallPackages = bulkInstallPackages as jest.MockedFunction< | ||
typeof bulkInstallPackages | ||
>; | ||
const mockedIsBulkInstallError = isBulkInstallError as jest.MockedFunction< | ||
typeof isBulkInstallError | ||
>; | ||
const mockedGetInstallation = getInstallation as jest.MockedFunction<typeof getInstallation>; | ||
|
||
// I was unable to get the actual implementation set in the `jest.mock()` call at the top to work | ||
// so this will set the `isBulkInstallError` function back to the actual implementation | ||
mockedIsBulkInstallError.mockImplementation(actualIsBulkInstallError); | ||
|
||
const mockInstallation: SavedObject<Installation> = { | ||
id: 'test-pkg', | ||
references: [], | ||
type: 'epm-packages', | ||
attributes: { | ||
id: 'test-pkg', | ||
installed_kibana: [{ type: KibanaAssetType.dashboard, id: 'dashboard-1' }], | ||
installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], | ||
es_index_patterns: { pattern: 'pattern-name' }, | ||
name: 'test package', | ||
version: '1.0.0', | ||
install_status: 'installed', | ||
install_version: '1.0.0', | ||
install_started_at: new Date().toISOString(), | ||
}, | ||
}; | ||
|
||
describe('ensureInstalledDefaultPackages', () => { | ||
let soClient: jest.Mocked<SavedObjectsClientContract>; | ||
beforeEach(async () => { | ||
soClient = savedObjectsClientMock.create(); | ||
appContextService.start(createAppContextStartContractMock()); | ||
}); | ||
afterEach(async () => { | ||
appContextService.stop(); | ||
}); | ||
it('should return an array of Installation objects when successful', async () => { | ||
mockedGetInstallation.mockImplementation(async () => { | ||
return mockInstallation.attributes; | ||
}); | ||
mockedBulkInstallPackages.mockImplementationOnce(async function () { | ||
return [ | ||
{ | ||
name: mockInstallation.attributes.name, | ||
assets: [], | ||
newVersion: '', | ||
oldVersion: '', | ||
statusCode: 200, | ||
}, | ||
]; | ||
}); | ||
const resp = await ensureInstalledDefaultPackages(soClient, jest.fn()); | ||
expect(resp).toEqual([mockInstallation.attributes]); | ||
}); | ||
it('should throw the first Error it finds', async () => { | ||
class SomeCustomError extends Error {} | ||
mockedGetInstallation.mockImplementation(async () => { | ||
return mockInstallation.attributes; | ||
}); | ||
mockedBulkInstallPackages.mockImplementationOnce(async function () { | ||
return [ | ||
{ | ||
name: 'success one', | ||
assets: [], | ||
newVersion: '', | ||
oldVersion: '', | ||
statusCode: 200, | ||
}, | ||
{ | ||
name: 'success two', | ||
assets: [], | ||
newVersion: '', | ||
oldVersion: '', | ||
statusCode: 200, | ||
}, | ||
{ | ||
name: 'failure one', | ||
error: new SomeCustomError('abc 123'), | ||
}, | ||
{ | ||
name: 'success three', | ||
assets: [], | ||
newVersion: '', | ||
oldVersion: '', | ||
statusCode: 200, | ||
}, | ||
{ | ||
name: 'failure two', | ||
error: new Error('zzz'), | ||
}, | ||
]; | ||
}); | ||
const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn()); | ||
expect.assertions(2); | ||
expect(installPromise).rejects.toThrow(SomeCustomError); | ||
expect(installPromise).rejects.toThrow('abc 123'); | ||
}); | ||
it('should throw an error when get installation returns undefined', async () => { | ||
mockedGetInstallation.mockImplementation(async () => { | ||
return undefined; | ||
}); | ||
mockedBulkInstallPackages.mockImplementationOnce(async function () { | ||
return [ | ||
{ | ||
name: 'undefined package', | ||
assets: [], | ||
newVersion: '', | ||
oldVersion: '', | ||
statusCode: 200, | ||
}, | ||
]; | ||
}); | ||
const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn()); | ||
expect.assertions(1); | ||
expect(installPromise).rejects.toThrow(); | ||
}); | ||
}); |
Oops, something went wrong.