diff --git a/package.json b/package.json index aff86a6852..9a29b0d1ee 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "src/**/*.ts" ], "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 e209c3dadc..419c8b9a0c 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 ef5862aaf7..20f0de223d 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(); + }, + }); + + await 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' }); + + await 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' }); + + await 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 = {