Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kitops/kitops-ts",
"version": "0.0.4",
"version": "0.1.0",
"description": "TypeScript library for KitOps CLI",
"type": "module",
"main": "./dist/index.js",
Expand Down
154 changes: 44 additions & 110 deletions src/commands/__tests__/pack.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { pack } from '../pack';
import { runCommand, prepareArgs } from '../../core/exec';

vi.mock('../../core/exec');
vi.mock('../../core/exec', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../core/exec')>();
return { ...actual, runCommand: vi.fn(), prepareArgs: vi.fn() };
});

const mockRunCommand = vi.mocked(runCommand);
const mockPrepareArgs = vi.mocked(prepareArgs);

describe('pack', () => {
beforeEach(() => {
vi.clearAllMocks();
// Mock prepareArgs to return the expected array format
mockPrepareArgs.mockImplementation((options) => {
const args: string[] = [];
Object.entries(options).forEach(([key, value]) => {
Expand All @@ -22,39 +24,29 @@ describe('pack', () => {
});
return args;
});
mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', exitCode: 0 });
});

it('should call runCommand with default directory when no arguments provided', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});
it('should return a CancellablePromise with a cancel method', () => {
const op = pack();
expect(op).toBeInstanceOf(Promise);
expect(typeof op.cancel).toBe('function');
return op;
});

it('should call runCommand with default directory when no arguments provided', async () => {
await pack();

expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.']);
expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.'], undefined, { signal: expect.any(AbortSignal) });
});

it('should call runCommand with specified directory', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

await pack('/path/to/project');

expect(mockRunCommand).toHaveBeenCalledWith('pack', ['/path/to/project']);
expect(mockRunCommand).toHaveBeenCalledWith('pack', ['/path/to/project'], undefined, { signal: expect.any(AbortSignal) });
});

it('should handle pack with all flags', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const flags = {
file: 'Kitfile',
tag: 'my-model:v1.0.0',
Expand All @@ -71,16 +63,10 @@ describe('pack', () => {
'--tag', 'my-model:v1.0.0',
'--compression', 'gzip',
'--useModelPack'
]);
], undefined, { signal: expect.any(AbortSignal) });
});

it('should handle pack with partial flags', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const flags = {
file: 'CustomKitfile',
tag: 'test-model:latest',
Expand All @@ -96,24 +82,17 @@ describe('pack', () => {
'--file', 'CustomKitfile',
'--tag', 'test-model:latest',
'--compression', 'none'
]);
], undefined, { signal: expect.any(AbortSignal) });
});

it('should handle pack with only file flag', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const flags = {
file: 'MyKitfile',
tag: '',
compression: '',
useModelPack: false
};

// Mock prepareArgs to handle empty strings appropriately
mockPrepareArgs.mockReturnValue(['--file', 'MyKitfile']);

await pack('./models', flags);
Expand All @@ -122,16 +101,10 @@ describe('pack', () => {
expect(mockRunCommand).toHaveBeenCalledWith('pack', [
'./models',
'--file', 'MyKitfile'
]);
], undefined, { signal: expect.any(AbortSignal) });
});

it('should handle pack with only useModelPack flag', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const flags = {
file: '',
tag: '',
Expand All @@ -147,33 +120,21 @@ describe('pack', () => {
expect(mockRunCommand).toHaveBeenCalledWith('pack', [
'.',
'--useModelPack'
]);
], undefined, { signal: expect.any(AbortSignal) });
});

it('should propagate errors from runCommand', async () => {
const errorMessage = 'Kit command failed with exit code 1: Kitfile not found';
mockRunCommand.mockRejectedValue(new Error(errorMessage));

await expect(pack('./nonexistent'))
.rejects.toThrow(errorMessage);
await expect(pack('./nonexistent')).rejects.toThrow(errorMessage);
});

it('should handle different compression types', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const compressionTypes = ['gzip', 'zstd', 'none'];

for (const compression of compressionTypes) {
const flags = {
file: 'Kitfile',
tag: 'model:latest',
compression,
useModelPack: false
};
const flags = { file: 'Kitfile', tag: 'model:latest', compression, useModelPack: false };

mockPrepareArgs.mockReturnValue([
'--file', 'Kitfile',
Expand All @@ -188,17 +149,11 @@ describe('pack', () => {
'--file', 'Kitfile',
'--tag', 'model:latest',
'--compression', compression
]);
], undefined, { signal: expect.any(AbortSignal) });
}
});

it('should handle different tag formats', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const tagFormats = [
'simple-model:v1.0.0',
'registry.example.com/org/model:latest',
Expand All @@ -208,12 +163,7 @@ describe('pack', () => {
];

for (const tag of tagFormats) {
const flags = {
file: 'Kitfile',
tag,
compression: 'gzip',
useModelPack: false
};
const flags = { file: 'Kitfile', tag, compression: 'gzip', useModelPack: false };

mockPrepareArgs.mockReturnValue([
'--file', 'Kitfile',
Expand All @@ -228,34 +178,21 @@ describe('pack', () => {
'--file', 'Kitfile',
'--tag', tag,
'--compression', 'gzip'
]);
], undefined, { signal: expect.any(AbortSignal) });
}
});

it('should handle relative and absolute directory paths', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Pack completed successfully',
stderr: '',
exitCode: 0
});

const directories = [
'.',
'./src',
'../project',
'/absolute/path/to/project',
'~/home/user/project'
];
const directories = ['.', './src', '../project', '/absolute/path/to/project', '~/home/user/project'];

for (const directory of directories) {
await pack(directory);
expect(mockRunCommand).toHaveBeenCalledWith('pack', [directory]);
expect(mockRunCommand).toHaveBeenCalledWith('pack', [directory], undefined, { signal: expect.any(AbortSignal) });
}
});

it('should handle pack command execution failure', async () => {
const execError = 'Failed to execute kit command: permission denied';
mockRunCommand.mockRejectedValue(new Error(execError));
mockRunCommand.mockRejectedValue(new Error('Failed to execute kit command: permission denied'));

await expect(pack('./restricted-dir'))
.rejects.toThrow('Failed to execute kit command: permission denied');
Expand All @@ -265,31 +202,28 @@ describe('pack', () => {
const kitfileError = 'Kit command failed with exit code 1: invalid Kitfile path';
mockRunCommand.mockRejectedValue(new Error(kitfileError));

const flags = {
file: '/invalid/path/Kitfile',
tag: 'model:latest',
compression: 'gzip',
useModelPack: false
};
const flags = { file: '/invalid/path/Kitfile', tag: 'model:latest', compression: 'gzip', useModelPack: false };

await expect(pack('.', flags))
.rejects.toThrow('Kit command failed with exit code 1: invalid Kitfile path');
await expect(pack('.', flags)).rejects.toThrow('Kit command failed with exit code 1: invalid Kitfile path');
});

it('should handle successful pack operation', async () => {
mockRunCommand.mockResolvedValue({
stdout: 'Successfully packed model:v1.0.0',
stderr: '',
exitCode: 0
});

const flags = {
file: 'Kitfile',
tag: 'model:v1.0.0',
compression: 'gzip',
useModelPack: true
};
const flags = { file: 'Kitfile', tag: 'model:v1.0.0', compression: 'gzip', useModelPack: true };

await expect(pack('./project', flags)).resolves.not.toThrow();
});
});

it('should abort the signal when cancel is called', () => {
let capturedSignal: AbortSignal | undefined;

mockRunCommand.mockImplementation((_cmd, _args, _stdin, opts) => {
capturedSignal = opts?.signal;
return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 });
});

const op = pack('.');
expect(capturedSignal?.aborted).toBe(false);
op.cancel();
expect(capturedSignal?.aborted).toBe(true);
});
});
52 changes: 44 additions & 8 deletions src/commands/__tests__/pull.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { pull } from '../pull';
import { runCommand, prepareArgs } from '../../core/exec';

vi.mock('../../core/exec');
vi.mock('../../core/exec', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../core/exec')>();
return { ...actual, runCommand: vi.fn(), prepareArgs: vi.fn() };
});

const mockRunCommand = vi.mocked(runCommand);
const mockPrepareArgs = vi.mocked(prepareArgs);
Expand All @@ -14,27 +17,46 @@ describe('pull', () => {
mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 });
});

it('should call runCommand with the reference', async () => {
it('should return a CancellablePromise with a cancel method', () => {
const op = pull('registry.example.com/org/my-model:v1.0.0');
expect(op).toBeInstanceOf(Promise);
expect(typeof op.cancel).toBe('function');
return op;
});

it('should call runCommand with the reference and an AbortSignal', async () => {
await pull('registry.example.com/org/my-model:v1.0.0');

expect(mockRunCommand).toHaveBeenCalledWith('pull', ['registry.example.com/org/my-model:v1.0.0']);
expect(mockRunCommand).toHaveBeenCalledWith(
'pull',
['registry.example.com/org/my-model:v1.0.0'],
undefined,
{ signal: expect.any(AbortSignal) },
);
});

it('should forward TLS flags', async () => {
mockPrepareArgs.mockReturnValue(['--tls-cert=/path/to/cert.pem']);

await pull('registry.example.com/org/my-model:v1.0.0', { tlsCert: '/path/to/cert.pem' });

expect(mockRunCommand).toHaveBeenCalledWith('pull', [
'registry.example.com/org/my-model:v1.0.0',
'--tls-cert=/path/to/cert.pem',
]);
expect(mockRunCommand).toHaveBeenCalledWith(
'pull',
['registry.example.com/org/my-model:v1.0.0', '--tls-cert=/path/to/cert.pem'],
undefined,
{ signal: expect.any(AbortSignal) },
);
});

it('should work without flags', async () => {
await pull('my-model:latest');

expect(mockRunCommand).toHaveBeenCalledWith('pull', ['my-model:latest']);
expect(mockRunCommand).toHaveBeenCalledWith(
'pull',
['my-model:latest'],
undefined,
{ signal: expect.any(AbortSignal) },
);
});

it('should propagate errors from runCommand', async () => {
Expand All @@ -43,4 +65,18 @@ describe('pull', () => {
await expect(pull('registry.example.com/org/my-model:v1.0.0'))
.rejects.toThrow('Kit command failed with exit code 1: not found');
});

it('should abort the signal when cancel is called', () => {
let capturedSignal: AbortSignal | undefined;

mockRunCommand.mockImplementation((_cmd, _args, _stdin, opts) => {
capturedSignal = opts?.signal;
return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 });
});

const op = pull('registry.example.com/org/my-model:v1.0.0');
expect(capturedSignal?.aborted).toBe(false);
op.cancel();
expect(capturedSignal?.aborted).toBe(true);
});
});
Loading
Loading