k6ctl 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +168 -4
  2. package/dist/cli.js +10 -5
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/delete.d.ts +4 -1
  5. package/dist/commands/delete.d.ts.map +1 -1
  6. package/dist/commands/delete.js +26 -5
  7. package/dist/commands/delete.js.map +1 -1
  8. package/dist/commands/list.js +6 -6
  9. package/dist/commands/list.js.map +1 -1
  10. package/dist/commands/run.d.ts +2 -0
  11. package/dist/commands/run.d.ts.map +1 -1
  12. package/dist/commands/run.js +191 -1
  13. package/dist/commands/run.js.map +1 -1
  14. package/dist/commands/status.d.ts.map +1 -1
  15. package/dist/commands/status.js +4 -0
  16. package/dist/commands/status.js.map +1 -1
  17. package/dist/services/kubernetes.service.d.ts +12 -5
  18. package/dist/services/kubernetes.service.d.ts.map +1 -1
  19. package/dist/services/kubernetes.service.js +134 -6
  20. package/dist/services/kubernetes.service.js.map +1 -1
  21. package/dist/services/script.service.d.ts +3 -1
  22. package/dist/services/script.service.d.ts.map +1 -1
  23. package/dist/services/script.service.js +115 -1
  24. package/dist/services/script.service.js.map +1 -1
  25. package/dist/types/kubernetes.types.d.ts +5 -0
  26. package/dist/types/kubernetes.types.d.ts.map +1 -1
  27. package/dist/types/lastRun.types.d.ts +2 -1
  28. package/dist/types/lastRun.types.d.ts.map +1 -1
  29. package/dist/types/script.types.d.ts +39 -0
  30. package/dist/types/script.types.d.ts.map +1 -1
  31. package/dist/types/testRunManifest.types.d.ts +2 -4
  32. package/dist/types/testRunManifest.types.d.ts.map +1 -1
  33. package/dist/utils/testRunManifestBuilder.d.ts +14 -1
  34. package/dist/utils/testRunManifestBuilder.d.ts.map +1 -1
  35. package/dist/utils/testRunManifestBuilder.js +70 -14
  36. package/dist/utils/testRunManifestBuilder.js.map +1 -1
  37. package/package.json +2 -1
  38. package/src/cli.ts +10 -5
  39. package/src/commands/delete.ts +35 -8
  40. package/src/commands/list.ts +7 -7
  41. package/src/commands/run.ts +216 -2
  42. package/src/commands/status.ts +4 -0
  43. package/src/services/kubernetes.service.ts +161 -16
  44. package/src/services/script.service.ts +102 -4
  45. package/src/types/kubernetes.types.ts +6 -0
  46. package/src/types/lastRun.types.ts +2 -1
  47. package/src/types/script.types.ts +40 -0
  48. package/src/types/testRunManifest.types.ts +3 -4
  49. package/src/utils/testRunManifestBuilder.ts +80 -18
  50. package/test/integration/kubernetes.service.test.ts +63 -9
  51. package/test/unit/kubernetes.service.test.ts +15 -7
  52. package/test/unit/script.service.test.ts +11 -4
@@ -1,23 +1,20 @@
1
- import { afterAll, describe, expect, test } from '@jest/globals';
1
+ import { describe, expect, test } from '@jest/globals';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { existsSync } from 'node:fs';
4
- import { unlink } from 'node:fs/promises';
5
- import { createDefaultKubernetesService, printConfigMapsTable } from '../../src/services/kubernetes.service';
4
+ import { createDefaultKubernetesService } from '../../src/services/kubernetes.service';
6
5
  import { ScriptService } from '../../src/services/script.service';
7
- import { ConfigMapResult } from '../../src/types/kubernetes.types';
6
+ import { ConfigMapResult, VolumeClaimResult } from '../../src/types/kubernetes.types';
8
7
  import { ArchiveResult } from '../../src/types/script.types';
9
8
  import logger from '../../src/utils/logger';
10
9
  import { TestRunManifest } from '../../src/types/testRunManifest.types';
11
10
  import { K6Config } from '../../src/types/config.types';
12
11
  import { loadK6Config } from '../../src/utils/configLoader';
13
- import { buildTestRunManifest } from '../../src/utils/testRunManifestBuilder';
14
- import { printTestRunsTable, printPodsTable } from '../../src/services/kubernetes.service';
12
+ import { buildTestRunManifest, buildTestRunManifestWithVolumeClaim } from '../../src/utils/testRunManifestBuilder';
15
13
  import { saveLastRun } from '../../src/utils/lastRunStore';
16
14
  import { status } from '../../src/commands/status';
17
15
  import { logs } from '../../src/commands/logs';
18
16
  import { deleteLastRun } from '../../src/commands/delete';
19
17
 
20
-
21
18
  const samplesPath = resolve(__dirname, '..', 'samples');
22
19
  const scriptSample1 = join(samplesPath, 'k6_script_sample_1.js');
23
20
  const scriptSample2 = join(samplesPath, 'k6_script_sample_2.js');
@@ -29,8 +26,11 @@ const kubernetesService = createDefaultKubernetesService();
29
26
  let archiveOutput: ArchiveResult;
30
27
  let configMapResult: ConfigMapResult;
31
28
 
32
- describe('KubernetesService integration tests', () => {
29
+ let volumeArchiveOutput: ArchiveResult;
30
+ let volumeClaimResult: VolumeClaimResult;
31
+ let volumeTestRunManifest: TestRunManifest;
33
32
 
33
+ describe('KubernetesService integration tests', () => {
34
34
  test('create config map from archived script', async () => {
35
35
  archiveOutput = await scriptService.archiveTest(scriptSample2);
36
36
  expect(existsSync(archiveOutput.archivePath)).toBe(true);
@@ -72,4 +72,58 @@ describe('KubernetesService integration tests', () => {
72
72
  // Clean up the TestRun after successful creation and execution
73
73
  await deleteLastRun({ namespace: testRunManifest.metadata.namespace });
74
74
  }, 120000);
75
- });
75
+
76
+ test('archives a script and creates PVC', async () => {
77
+ volumeArchiveOutput = await scriptService.archiveTest(scriptSample1);
78
+ expect(existsSync(volumeArchiveOutput.archivePath)).toBe(true);
79
+
80
+ volumeClaimResult = await kubernetesService.createPVCWithArchive(volumeArchiveOutput, 'default');
81
+
82
+ expect(volumeClaimResult).toBeDefined();
83
+ expect(volumeClaimResult.volumeClaimName).toMatch(/^archive-/);
84
+ expect(volumeClaimResult.namespace).toBe('default');
85
+ expect(volumeClaimResult.archiveFilename).toMatch(/\.tar$/);
86
+
87
+ // createPVCWithArchive deletes the local archive file after uploading
88
+ expect(existsSync(volumeArchiveOutput.archivePath)).toBe(false);
89
+
90
+ logger.info(`PVC created: ${JSON.stringify(volumeClaimResult, null, 2)}`);
91
+ }, 120000);
92
+
93
+ test('creates and runs a TestRun from the PVC', async () => {
94
+ const cfg: K6Config = loadK6Config();
95
+ logger.debug('Loaded K6 Config:', JSON.stringify(cfg, null, 2));
96
+
97
+ volumeTestRunManifest = buildTestRunManifestWithVolumeClaim(volumeClaimResult, cfg);
98
+
99
+ expect(volumeTestRunManifest.spec.script.volumeClaim).toBeDefined();
100
+ expect(volumeTestRunManifest.spec.script.configMap).toBeUndefined();
101
+ expect(volumeTestRunManifest.spec.script.volumeClaim?.name).toBe(volumeClaimResult.volumeClaimName);
102
+ expect(volumeTestRunManifest.spec.script.volumeClaim?.file).toBe(volumeClaimResult.archiveFilename);
103
+
104
+ const response = await kubernetesService.createTestRun(volumeTestRunManifest);
105
+ expect(response).toBeDefined();
106
+ logger.info('TestRun result:', JSON.stringify(response));
107
+
108
+ await saveLastRun({
109
+ testRunName: volumeTestRunManifest.metadata.name,
110
+ namespace: volumeTestRunManifest.metadata.namespace,
111
+ volumeClaimName: volumeClaimResult.volumeClaimName,
112
+ scriptPath: volumeArchiveOutput.scriptPath,
113
+ createdAt: new Date().toISOString(),
114
+ });
115
+ logger.info(`Last run saved: ${volumeTestRunManifest.metadata.name} (namespace: ${volumeTestRunManifest.metadata.namespace})`);
116
+
117
+ // Wait for the TestRun to be created and start running
118
+ await new Promise((resolve) => setTimeout(resolve, 60000));
119
+
120
+ // Check status command output
121
+ await status({ namespace: volumeTestRunManifest.metadata.namespace });
122
+
123
+ // Check logs command output
124
+ await logs({ namespace: volumeTestRunManifest.metadata.namespace });
125
+
126
+ // Clean up the TestRun and PVC
127
+ await deleteLastRun({ namespace: volumeTestRunManifest.metadata.namespace });
128
+ }, 120000);
129
+ });
@@ -5,13 +5,19 @@ import logger from '../../src/utils/logger';
5
5
  import { KubernetesService, createDefaultKubernetesService } from '../../src/services/kubernetes.service';
6
6
  import type { ArchivedFile } from '../../src/types/kubernetes.types';
7
7
 
8
- jest.mock('fs', () => ({
9
- existsSync: jest.fn(),
10
- promises: {
11
- stat: jest.fn(),
12
- readFile: jest.fn(),
13
- },
14
- }));
8
+ jest.mock('fs', () => {
9
+ const actualFs = jest.requireActual<typeof import('fs')>('fs');
10
+ return {
11
+ ...actualFs,
12
+ existsSync: jest.fn(),
13
+ promises: {
14
+ ...actualFs.promises,
15
+ stat: jest.fn(),
16
+ readFile: jest.fn(),
17
+ unlink: jest.fn(),
18
+ },
19
+ };
20
+ });
15
21
 
16
22
  jest.mock('@kubernetes/client-node', () => {
17
23
  const mockApiClient = {
@@ -49,6 +55,7 @@ jest.mock('../../src/utils/logger', () => ({
49
55
  const mockedExistsSync = existsSync as unknown as jest.Mock;
50
56
  const mockedStat = fs_promises.stat as unknown as jest.Mock;
51
57
  const mockedReadFile = fs_promises.readFile as unknown as jest.Mock;
58
+ const mockedUnlink = fs_promises.unlink as unknown as jest.MockedFunction<typeof fs_promises.unlink>;
52
59
  const mockedLoggerInfo = logger.info as unknown as jest.Mock;
53
60
 
54
61
  const mockedK8sModule = k8s as unknown as {
@@ -119,6 +126,7 @@ describe('KubernetesService', () => {
119
126
  });
120
127
 
121
128
  test('creates configmap with binaryData and returns namespace/name', async () => {
129
+ mockedUnlink.mockResolvedValue(undefined);
122
130
  mockedExistsSync.mockReturnValue(true);
123
131
  mockedStat.mockImplementation(async () => ({ size: 512 }));
124
132
  mockedReadFile.mockImplementation(async () => 'YmFzZTY0');
@@ -1,11 +1,16 @@
1
1
  import { beforeEach, describe, expect, jest, test } from '@jest/globals';
2
2
  import { ScriptService } from '../../src/services/script.service';
3
- import { existsSync } from 'fs';
3
+ import { existsSync, statSync } from 'fs';
4
4
  import type { ExecFn } from '../../src/types/script.types';
5
5
 
6
- jest.mock('fs', () => ({
7
- existsSync: jest.fn(),
8
- }));
6
+ jest.mock('fs', () => {
7
+ const actualFs = jest.requireActual<typeof import('fs')>('fs');
8
+ return {
9
+ ...actualFs,
10
+ existsSync: jest.fn(),
11
+ statSync: jest.fn(),
12
+ };
13
+ });
9
14
 
10
15
  jest.mock('../../src/utils/logger', () => ({
11
16
  __esModule: true,
@@ -13,6 +18,7 @@ jest.mock('../../src/utils/logger', () => ({
13
18
  }));
14
19
 
15
20
  const mockedExistsSync = existsSync as unknown as jest.Mock;
21
+ const mockedStatSync = statSync as unknown as jest.Mock;
16
22
 
17
23
  describe('ScriptService', () => {
18
24
  let mockExec: jest.MockedFunction<ExecFn>;
@@ -22,6 +28,7 @@ describe('ScriptService', () => {
22
28
  jest.resetAllMocks();
23
29
  mockExec = jest.fn<ExecFn>();
24
30
  service = new ScriptService(mockExec);
31
+ mockedStatSync.mockReturnValue({ size: 1024 });
25
32
  });
26
33
 
27
34
  describe('archiveTest', () => {