-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
425 additions
and
76 deletions.
There are no files selected for viewing
202 changes: 202 additions & 0 deletions
202
.../plugins/security_solution/scripts/endpoint/agent_downloader_cli/agent_downloader.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,202 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { getAgentDownloadUrl, getAgentFileName } from '../common/fleet_services'; | ||
import { downloadAndStoreAgent } from '../common/agent_downloads_service'; | ||
import type { ToolingLog } from '@kbn/tooling-log'; | ||
import { agentDownloaderRunner } from './agent_downloader'; | ||
|
||
jest.mock('../common/fleet_services'); | ||
jest.mock('../common/agent_downloads_service'); | ||
|
||
describe('agentDownloaderRunner', () => { | ||
let log: ToolingLog; | ||
|
||
beforeEach(() => { | ||
log = { | ||
info: jest.fn(), | ||
error: jest.fn(), | ||
} as unknown as ToolingLog; | ||
|
||
jest.clearAllMocks(); | ||
}); | ||
|
||
const version = '8.15.0'; | ||
let closestMatch = false; | ||
const url = 'http://example.com/agent.tar.gz'; | ||
const fileName = 'elastic-agent-8.15.0.tar.gz'; | ||
|
||
it('downloads and stores the specified version', async () => { | ||
(getAgentDownloadUrl as jest.Mock).mockResolvedValue({ url }); | ||
(getAgentFileName as jest.Mock).mockReturnValue('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock).mockResolvedValue(undefined); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version, closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledWith(version, closestMatch, log); | ||
expect(getAgentFileName).toHaveBeenCalledWith(version); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, fileName); | ||
expect(log.info).toHaveBeenCalledWith('Successfully downloaded and stored version 8.15.0'); | ||
}); | ||
|
||
it('logs an error if the download fails', async () => { | ||
(getAgentDownloadUrl as jest.Mock).mockResolvedValue({ url }); | ||
(getAgentFileName as jest.Mock).mockReturnValue('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock).mockRejectedValue(new Error('Download failed')); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version, closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledWith(version, closestMatch, log); | ||
expect(getAgentFileName).toHaveBeenCalledWith(version); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, fileName); | ||
expect(log.error).toHaveBeenCalledWith( | ||
'Failed to download or store version 8.15.0: Download failed' | ||
); | ||
}); | ||
|
||
it('downloads and stores the previous patch version if the specified version fails', async () => { | ||
const fallbackVersion = '8.15.0'; | ||
const fallbackFileName = 'elastic-agent-8.15.0.tar.gz'; | ||
|
||
(getAgentDownloadUrl as jest.Mock) | ||
.mockResolvedValueOnce({ url }) | ||
.mockResolvedValueOnce({ url }); | ||
(getAgentFileName as jest.Mock) | ||
.mockReturnValueOnce('elastic-agent-8.15.1') | ||
.mockReturnValueOnce('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock) | ||
.mockRejectedValueOnce(new Error('Download failed')) | ||
.mockResolvedValueOnce(undefined); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version: '8.15.1', closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledWith('8.15.1', closestMatch, log); | ||
expect(getAgentDownloadUrl).toHaveBeenCalledWith(fallbackVersion, closestMatch, log); | ||
expect(getAgentFileName).toHaveBeenCalledWith('8.15.1'); | ||
expect(getAgentFileName).toHaveBeenCalledWith(fallbackVersion); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, 'elastic-agent-8.15.1.tar.gz'); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, fallbackFileName); | ||
expect(log.error).toHaveBeenCalledWith( | ||
'Failed to download or store version 8.15.1: Download failed' | ||
); | ||
expect(log.info).toHaveBeenCalledWith('Successfully downloaded and stored version 8.15.0'); | ||
}); | ||
|
||
it('logs an error if all downloads fail', async () => { | ||
(getAgentDownloadUrl as jest.Mock).mockResolvedValue({ url }); | ||
(getAgentFileName as jest.Mock) | ||
.mockReturnValueOnce('elastic-agent-8.15.1') | ||
.mockReturnValueOnce('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock) | ||
.mockRejectedValueOnce(new Error('Download failed')) | ||
.mockRejectedValueOnce(new Error('Download failed')); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version: '8.15.1', closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledWith('8.15.1', closestMatch, log); | ||
expect(getAgentDownloadUrl).toHaveBeenCalledWith('8.15.0', closestMatch, log); | ||
expect(getAgentFileName).toHaveBeenCalledWith('8.15.1'); | ||
expect(getAgentFileName).toHaveBeenCalledWith('8.15.0'); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, 'elastic-agent-8.15.1.tar.gz'); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, 'elastic-agent-8.15.0.tar.gz'); | ||
expect(log.error).toHaveBeenCalledWith( | ||
'Failed to download or store version 8.15.1: Download failed' | ||
); | ||
expect(log.error).toHaveBeenCalledWith( | ||
'Failed to download or store version 8.15.0: Download failed' | ||
); | ||
}); | ||
|
||
it('does not attempt fallback when patch version is 0', async () => { | ||
(getAgentDownloadUrl as jest.Mock).mockResolvedValue({ url }); | ||
(getAgentFileName as jest.Mock).mockReturnValue('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock).mockResolvedValue(undefined); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version: '8.15.0', closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledTimes(1); // Only one call for 8.15.0 | ||
expect(getAgentFileName).toHaveBeenCalledTimes(1); | ||
expect(downloadAndStoreAgent).toHaveBeenCalledWith(url, fileName); | ||
expect(log.info).toHaveBeenCalledWith('Successfully downloaded and stored version 8.15.0'); | ||
}); | ||
|
||
it('logs an error for an invalid version format', async () => { | ||
const invalidVersion = '7.x.x'; | ||
|
||
await expect( | ||
agentDownloaderRunner({ | ||
flags: { version: invalidVersion, closestMatch }, | ||
log, | ||
}) | ||
).rejects.toThrow('Invalid version format'); | ||
}); | ||
|
||
it('passes the closestMatch flag correctly', async () => { | ||
closestMatch = true; | ||
|
||
(getAgentDownloadUrl as jest.Mock).mockResolvedValue({ url }); | ||
(getAgentFileName as jest.Mock).mockReturnValue('elastic-agent-8.15.0'); | ||
(downloadAndStoreAgent as jest.Mock).mockResolvedValue(undefined); | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version, closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(getAgentDownloadUrl).toHaveBeenCalledWith(version, closestMatch, log); | ||
}); | ||
|
||
it('throws an error when version is not provided', async () => { | ||
await expect( | ||
agentDownloaderRunner({ | ||
flags: { closestMatch }, | ||
log, | ||
}) | ||
).rejects.toThrow('version argument is required'); | ||
}); | ||
|
||
it('logs the correct messages when both version and fallback version are processed', async () => { | ||
const primaryVersion = '8.15.1'; | ||
|
||
(getAgentDownloadUrl as jest.Mock) | ||
.mockResolvedValueOnce({ url }) | ||
.mockResolvedValueOnce({ url }); | ||
|
||
(getAgentFileName as jest.Mock) | ||
.mockReturnValueOnce('elastic-agent-8.15.1') | ||
.mockReturnValueOnce('elastic-agent-8.15.0'); | ||
|
||
(downloadAndStoreAgent as jest.Mock) | ||
.mockRejectedValueOnce(new Error('Download failed')) // Fail on primary | ||
.mockResolvedValueOnce(undefined); // Success on fallback | ||
|
||
await agentDownloaderRunner({ | ||
flags: { version: primaryVersion, closestMatch }, | ||
log, | ||
}); | ||
|
||
expect(log.error).toHaveBeenCalledWith( | ||
'Failed to download or store version 8.15.1: Download failed' | ||
); | ||
expect(log.info).toHaveBeenCalledWith('Successfully downloaded and stored version 8.15.0'); | ||
}); | ||
}); |
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
82 changes: 82 additions & 0 deletions
82
x-pack/plugins/security_solution/scripts/endpoint/common/agent_downloads_service.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,82 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
// Adjust path if needed | ||
|
||
import { downloadAndStoreAgent, isAgentDownloadFromDiskAvailable } from './agent_downloads_service'; | ||
import { createToolingLogger } from '../../../common/endpoint/data_loaders/utils'; | ||
import fs from 'fs'; | ||
import nodeFetch from 'node-fetch'; | ||
import { finished } from 'stream/promises'; | ||
|
||
jest.mock('fs'); | ||
jest.mock('node-fetch'); | ||
jest.mock('stream/promises', () => ({ | ||
finished: jest.fn(), | ||
})); | ||
jest.mock('../../../common/endpoint/data_loaders/utils', () => ({ | ||
createToolingLogger: jest.fn(() => ({ | ||
debug: jest.fn(), | ||
info: jest.fn(), | ||
error: jest.fn(), | ||
})), | ||
})); | ||
|
||
describe('AgentDownloadStorage', () => { | ||
let log; | ||
const url = 'http://example.com/agent.tar.gz'; | ||
const fileName = 'elastic-agent-7.10.0.tar.gz'; | ||
const fullFilePath = `/path/to/${fileName}`; | ||
|
||
beforeEach(() => { | ||
log = createToolingLogger(); | ||
jest.clearAllMocks(); // Ensure no previous test state affects the current one | ||
}); | ||
|
||
it('downloads and stores the agent if not cached', async () => { | ||
fs.existsSync.mockReturnValue(false); | ||
fs.createWriteStream.mockReturnValue({ | ||
on: jest.fn(), | ||
end: jest.fn(), | ||
}); | ||
nodeFetch.mockResolvedValue({ body: { pipe: jest.fn() } }); | ||
finished.mockResolvedValue(undefined); | ||
|
||
const result = await downloadAndStoreAgent(url, fileName); | ||
|
||
expect(result).toEqual({ | ||
url, | ||
filename: fileName, | ||
directory: expect.any(String), | ||
fullFilePath: expect.stringContaining(fileName), // Dynamically match the file path | ||
}); | ||
}); | ||
|
||
it('reuses cached agent if available', async () => { | ||
fs.existsSync.mockReturnValue(true); | ||
|
||
const result = await downloadAndStoreAgent(url, fileName); | ||
|
||
expect(result).toEqual({ | ||
url, | ||
filename: fileName, | ||
directory: expect.any(String), | ||
fullFilePath: expect.stringContaining(fileName), // Dynamically match the path | ||
}); | ||
}); | ||
|
||
it('checks if agent download is available from disk', () => { | ||
fs.existsSync.mockReturnValue(true); | ||
|
||
const result = isAgentDownloadFromDiskAvailable(fileName); | ||
|
||
expect(result).toEqual({ | ||
filename: fileName, | ||
directory: expect.any(String), | ||
fullFilePath: expect.stringContaining(fileName), // Dynamically match the path | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.