From c37b28c58ab1db97ebe112ea598e2f23b879c02d Mon Sep 17 00:00:00 2001 From: Brendan Burns <5751682+brendandburns@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:54:31 +0000 Subject: [PATCH 1/3] Add unit tests for log.ts --- package.json | 1 + src/log.ts | 9 ++- src/log_test.ts | 173 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index aff86a68520..251d684d3ac 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ ], "exclude": [ "src/gen/*/**.ts", + "src/gen/**.ts", "src/index.ts", "src/*_test.ts", "src/test" diff --git a/src/log.ts b/src/log.ts index e209c3dadc5..419c8b9a0c3 100644 --- a/src/log.ts +++ b/src/log.ts @@ -142,7 +142,7 @@ export class Log { // TODO: the follow search param still has the stream close prematurely based on my testing response.body!.pipe(stream); } else if (status === 500) { - const v1status = response.body as V1Status; + const v1status = (await response.json()) as V1Status; const v1code = v1status.code; const v1message = v1status.message; if (v1code !== undefined && v1message !== undefined) { @@ -152,6 +152,13 @@ export class Log { v1status, normalizeResponseHeaders(response), ); + } else { + throw new ApiException( + status, + 'Error occurred in log request', + undefined, + normalizeResponseHeaders(response), + ); } } else { throw new ApiException( diff --git a/src/log_test.ts b/src/log_test.ts index ef5862aaf75..0ea9390e4ef 100644 --- a/src/log_test.ts +++ b/src/log_test.ts @@ -1,8 +1,177 @@ -import { strictEqual, throws } from 'node:assert'; -import { AddOptionsToSearchParams, LogOptions } from './log.js'; +import { strictEqual, rejects, throws } from 'node:assert'; +import nock from 'nock'; +import { AddOptionsToSearchParams, Log, LogOptions } from './log.js'; +import { KubeConfig } from './config.js'; +import { Writable } from 'node:stream'; describe('Log', () => { + describe('Constructor', () => { + it('should work', () => { + const config = new KubeConfig(); + config.addCluster({ + name: 'foo', + server: 'https://example.com', + caData: 'certificate-authority-data', + skipTLSVerify: false, + }); + const log = new Log(config); + strictEqual(log.config, config); + }); + }); + describe('log', () => { + const config = new KubeConfig(); + config.addCluster({ + name: 'foo', + server: 'https://example.com', + caData: 'certificate-authority-data', + skipTLSVerify: false, + }); + config.addContext({ + name: 'foo', + cluster: 'foo', + user: 'foo', + }); + config.setCurrentContext('foo'); + const log = new Log(config); + + afterEach(() => { + nock.cleanAll(); + }); + + it('should make a request with correct parameters', async () => { + const namespace = 'default'; + const podName = 'mypod'; + const containerName = 'mycontainer'; + const stream = new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }); + const options: LogOptions = { + follow: true, + limitBytes: 100, + pretty: true, + previous: true, + sinceSeconds: 1, + tailLines: 1, + timestamps: true, + }; + + nock('https://example.com') + .get('/api/v1/namespaces/default/pods/mypod/log') + .query({ + container: 'mycontainer', + follow: 'true', + limitBytes: '100', + pretty: 'true', + previous: 'true', + sinceSeconds: '1', + tailLines: '1', + timestamps: 'true', + }) + .reply(200, 'log data'); + + const controller = await log.log(namespace, podName, containerName, stream, options); + strictEqual(controller instanceof AbortController, true); + }); + + it('should throw an error if no active cluster', async () => { + const configWithoutCluster = new KubeConfig(); + const logWithoutCluster = new Log(configWithoutCluster); + const namespace = 'default'; + const podName = 'mypod'; + const containerName = 'mycontainer'; + const stream = new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }); + + rejects(async () => { + await logWithoutCluster.log(namespace, podName, containerName, stream); + }, /No currently active cluster/); + }); + + it('should handle API exceptions on non-500', async () => { + const namespace = 'default'; + const podName = 'mypod'; + const containerName = 'mycontainer'; + const stream = new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }); + + nock('https://example.com') + .get('/api/v1/namespaces/default/pods/mypod/log') + .query({ container: 'mycontainer' }) + .reply(501, { message: 'Error occurred in log request' }); + + rejects(async () => { + await log.log(namespace, podName, containerName, stream); + }, /Error occurred in log request/); + }); + + it('should handle API exceptions on 500', async () => { + const namespace = 'default'; + const podName = 'mypod'; + const containerName = 'mycontainer'; + const stream = new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }); + + nock('https://example.com') + .get('/api/v1/namespaces/default/pods/mypod/log') + .query({ container: 'mycontainer' }) + .reply(500, { message: 'Error occurred in log request' }); + + rejects(async () => { + await log.log(namespace, podName, containerName, stream); + }, /Error occurred in log request/); + }); + + it('should handle V1Status with code and message', async () => { + const namespace = 'default'; + const podName = 'mypod'; + const containerName = 'mycontainer'; + const stream = new Writable({ + write(chunk, encoding, callback) { + callback(); + }, + }); + + const v1Status = { + kind: 'Status', + apiVersion: 'v1', + metadata: {}, + status: 'Failure', + message: 'Pod not found', + reason: 'NotFound', + details: { + name: podName, + kind: 'pods', + }, + code: 404, + }; + + nock('https://example.com') + .get('/api/v1/namespaces/default/pods/mypod/log') + .query({ container: 'mycontainer' }) + .reply(500, v1Status); + + await rejects(async () => { + await log.log(namespace, podName, containerName, stream); + }, /Pod not found/); + }); + }); describe('AddOptionsToSearchParams', () => { + it('should handle no log options', () => { + const searchParams = new URLSearchParams(); + AddOptionsToSearchParams(undefined, searchParams); + // verify that it doesn't throw. + }); it('should add options to search params', () => { let searchParams = new URLSearchParams(); let options: LogOptions = { From 25c736b6e5caebd787dfcb0957090b0602334da9 Mon Sep 17 00:00:00 2001 From: Brendan Burns <5751682+brendandburns@users.noreply.github.com> Date: Wed, 26 Feb 2025 01:45:16 +0000 Subject: [PATCH 2/3] Address comments --- src/log_test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/log_test.ts b/src/log_test.ts index 0ea9390e4ef..20f0de223dd 100644 --- a/src/log_test.ts +++ b/src/log_test.ts @@ -87,7 +87,7 @@ describe('Log', () => { }, }); - rejects(async () => { + await rejects(async () => { await logWithoutCluster.log(namespace, podName, containerName, stream); }, /No currently active cluster/); }); @@ -107,7 +107,7 @@ describe('Log', () => { .query({ container: 'mycontainer' }) .reply(501, { message: 'Error occurred in log request' }); - rejects(async () => { + await rejects(async () => { await log.log(namespace, podName, containerName, stream); }, /Error occurred in log request/); }); @@ -127,7 +127,7 @@ describe('Log', () => { .query({ container: 'mycontainer' }) .reply(500, { message: 'Error occurred in log request' }); - rejects(async () => { + await rejects(async () => { await log.log(namespace, podName, containerName, stream); }, /Error occurred in log request/); }); From 45bdb7584270799778b1cf0cf1dbd7718029db78 Mon Sep 17 00:00:00 2001 From: Brendan Burns <5751682+brendandburns@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:17:28 +0000 Subject: [PATCH 3/3] Update glob. --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 251d684d3ac..9a29b0d1ee8 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ "src/**/*.ts" ], "exclude": [ - "src/gen/*/**.ts", - "src/gen/**.ts", + "src/gen/**/*.ts", "src/index.ts", "src/*_test.ts", "src/test"