@agents-at-scale/ark 0.1.53 → 0.1.55
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.
- package/dist/commands/export/index.js +6 -4
- package/dist/commands/generate/generators/agent.js +2 -0
- package/dist/commands/generate/generators/marketplace.js +2 -0
- package/dist/commands/generate/generators/mcpserver.js +2 -0
- package/dist/commands/generate/generators/project.js +9 -2
- package/dist/commands/generate/generators/query.js +2 -0
- package/dist/commands/generate/generators/team.js +2 -0
- package/dist/commands/generate/templateDiscovery.js +1 -0
- package/dist/commands/generate/templateEngine.js +1 -3
- package/dist/commands/import/index.js +1 -1
- package/dist/commands/install/index.js +2 -1
- package/dist/commands/models/kubernetes/manifest-builder.js +27 -10
- package/dist/commands/models/providers/azure.d.ts +10 -7
- package/dist/commands/models/providers/azure.js +83 -21
- package/dist/commands/uninstall/index.js +1 -1
- package/dist/components/ChatUI.js +17 -16
- package/dist/components/statusChecker.js +3 -3
- package/dist/lib/arkApiClient.js +11 -9
- package/dist/lib/arkApiProxy.js +1 -0
- package/dist/lib/arkServiceProxy.js +5 -1
- package/dist/lib/chatClient.js +9 -0
- package/dist/lib/config.js +8 -3
- package/dist/lib/errors.js +3 -0
- package/dist/ui/asyncOperations/connectingToArk.js +2 -2
- package/package.json +16 -12
- package/dist/arkServices.spec.d.ts +0 -1
- package/dist/arkServices.spec.js +0 -138
- package/dist/commands/agents/index.spec.d.ts +0 -1
- package/dist/commands/agents/index.spec.js +0 -67
- package/dist/commands/cluster/get.spec.d.ts +0 -1
- package/dist/commands/cluster/get.spec.js +0 -92
- package/dist/commands/cluster/index.spec.d.ts +0 -1
- package/dist/commands/cluster/index.spec.js +0 -24
- package/dist/commands/completion/index.spec.d.ts +0 -1
- package/dist/commands/completion/index.spec.js +0 -34
- package/dist/commands/config/index.spec.d.ts +0 -1
- package/dist/commands/config/index.spec.js +0 -78
- package/dist/commands/evaluation/index.spec.d.ts +0 -1
- package/dist/commands/evaluation/index.spec.js +0 -161
- package/dist/commands/export/index.spec.d.ts +0 -1
- package/dist/commands/export/index.spec.js +0 -145
- package/dist/commands/import/index.spec.d.ts +0 -1
- package/dist/commands/import/index.spec.js +0 -46
- package/dist/commands/install/index.spec.d.ts +0 -1
- package/dist/commands/install/index.spec.js +0 -286
- package/dist/commands/marketplace/index.spec.d.ts +0 -1
- package/dist/commands/marketplace/index.spec.js +0 -88
- package/dist/commands/memory/index.spec.d.ts +0 -1
- package/dist/commands/memory/index.spec.js +0 -124
- package/dist/commands/models/create.spec.d.ts +0 -1
- package/dist/commands/models/create.spec.js +0 -167
- package/dist/commands/models/index.spec.d.ts +0 -1
- package/dist/commands/models/index.spec.js +0 -96
- package/dist/commands/models/providers/azure.spec.d.ts +0 -1
- package/dist/commands/models/providers/azure.spec.js +0 -232
- package/dist/commands/models/providers/bedrock.spec.d.ts +0 -1
- package/dist/commands/models/providers/bedrock.spec.js +0 -241
- package/dist/commands/models/providers/openai.spec.d.ts +0 -1
- package/dist/commands/models/providers/openai.spec.js +0 -180
- package/dist/commands/queries/delete.spec.d.ts +0 -1
- package/dist/commands/queries/delete.spec.js +0 -74
- package/dist/commands/queries/index.spec.d.ts +0 -1
- package/dist/commands/queries/index.spec.js +0 -167
- package/dist/commands/queries/list.spec.d.ts +0 -1
- package/dist/commands/queries/list.spec.js +0 -170
- package/dist/commands/queries/validation.spec.d.ts +0 -1
- package/dist/commands/queries/validation.spec.js +0 -27
- package/dist/commands/query/index.spec.d.ts +0 -1
- package/dist/commands/query/index.spec.js +0 -104
- package/dist/commands/targets/index.spec.d.ts +0 -1
- package/dist/commands/targets/index.spec.js +0 -154
- package/dist/commands/teams/index.spec.d.ts +0 -1
- package/dist/commands/teams/index.spec.js +0 -70
- package/dist/commands/tools/index.spec.d.ts +0 -1
- package/dist/commands/tools/index.spec.js +0 -70
- package/dist/commands/uninstall/index.spec.d.ts +0 -1
- package/dist/commands/uninstall/index.spec.js +0 -125
- package/dist/lib/arkServiceProxy.spec.d.ts +0 -1
- package/dist/lib/arkServiceProxy.spec.js +0 -100
- package/dist/lib/arkStatus.spec.d.ts +0 -1
- package/dist/lib/arkStatus.spec.js +0 -49
- package/dist/lib/chatClient.spec.d.ts +0 -1
- package/dist/lib/chatClient.spec.js +0 -108
- package/dist/lib/cluster.spec.d.ts +0 -1
- package/dist/lib/cluster.spec.js +0 -338
- package/dist/lib/commands.spec.d.ts +0 -1
- package/dist/lib/commands.spec.js +0 -146
- package/dist/lib/config.spec.d.ts +0 -1
- package/dist/lib/config.spec.js +0 -202
- package/dist/lib/duration.spec.d.ts +0 -1
- package/dist/lib/duration.spec.js +0 -13
- package/dist/lib/errors.spec.d.ts +0 -1
- package/dist/lib/errors.spec.js +0 -221
- package/dist/lib/executeQuery.spec.d.ts +0 -1
- package/dist/lib/executeQuery.spec.js +0 -325
- package/dist/lib/kubectl.spec.d.ts +0 -1
- package/dist/lib/kubectl.spec.js +0 -192
- package/dist/lib/marketplaceFetcher.spec.d.ts +0 -1
- package/dist/lib/marketplaceFetcher.spec.js +0 -225
- package/dist/lib/nextSteps.spec.d.ts +0 -1
- package/dist/lib/nextSteps.spec.js +0 -59
- package/dist/lib/output.spec.d.ts +0 -1
- package/dist/lib/output.spec.js +0 -123
- package/dist/lib/startup.spec.d.ts +0 -1
- package/dist/lib/startup.spec.js +0 -152
- package/dist/lib/stdin.spec.d.ts +0 -1
- package/dist/lib/stdin.spec.js +0 -82
- package/dist/lib/timeout.spec.d.ts +0 -1
- package/dist/lib/timeout.spec.js +0 -14
- package/dist/lib/waitForReady.spec.d.ts +0 -1
- package/dist/lib/waitForReady.spec.js +0 -104
- package/dist/marketplaceServices.spec.d.ts +0 -1
- package/dist/marketplaceServices.spec.js +0 -74
- package/dist/ui/statusFormatter.spec.d.ts +0 -1
- package/dist/ui/statusFormatter.spec.js +0 -58
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
// Mock execa to avoid real kubectl calls
|
|
4
|
-
jest.unstable_mockModule('execa', () => ({
|
|
5
|
-
execa: jest.fn(),
|
|
6
|
-
}));
|
|
7
|
-
const mockOutput = {
|
|
8
|
-
warning: jest.fn(),
|
|
9
|
-
error: jest.fn(),
|
|
10
|
-
};
|
|
11
|
-
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
12
|
-
default: mockOutput,
|
|
13
|
-
}));
|
|
14
|
-
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
15
|
-
throw new Error('process.exit called');
|
|
16
|
-
}));
|
|
17
|
-
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
18
|
-
const { execa } = await import('execa');
|
|
19
|
-
const mockExeca = execa;
|
|
20
|
-
const { createTargetsCommand } = await import('./index.js');
|
|
21
|
-
describe('targets command', () => {
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
jest.clearAllMocks();
|
|
24
|
-
});
|
|
25
|
-
it('creates command with correct structure', () => {
|
|
26
|
-
const command = createTargetsCommand({});
|
|
27
|
-
expect(command).toBeInstanceOf(Command);
|
|
28
|
-
expect(command.name()).toBe('targets');
|
|
29
|
-
});
|
|
30
|
-
it('lists targets in text format', async () => {
|
|
31
|
-
// Mock kubectl responses for each resource type (order: model, agent, team, tool)
|
|
32
|
-
mockExeca
|
|
33
|
-
.mockResolvedValueOnce({
|
|
34
|
-
stdout: JSON.stringify({
|
|
35
|
-
items: [{ metadata: { name: 'gpt-4' }, status: { available: true } }],
|
|
36
|
-
}),
|
|
37
|
-
})
|
|
38
|
-
.mockResolvedValueOnce({
|
|
39
|
-
stdout: JSON.stringify({
|
|
40
|
-
items: [
|
|
41
|
-
{ metadata: { name: 'gpt-assistant' }, status: { phase: 'ready' } },
|
|
42
|
-
],
|
|
43
|
-
}),
|
|
44
|
-
})
|
|
45
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
46
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
47
|
-
const command = createTargetsCommand({});
|
|
48
|
-
await command.parseAsync(['node', 'test']);
|
|
49
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'models', '-o', 'json'], {
|
|
50
|
-
stdio: 'pipe',
|
|
51
|
-
});
|
|
52
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'agents', '-o', 'json'], {
|
|
53
|
-
stdio: 'pipe',
|
|
54
|
-
});
|
|
55
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt-assistant');
|
|
56
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('model/gpt-4');
|
|
57
|
-
});
|
|
58
|
-
it('lists targets in json format', async () => {
|
|
59
|
-
// Order: model, agent, team, tool
|
|
60
|
-
mockExeca
|
|
61
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
62
|
-
.mockResolvedValueOnce({
|
|
63
|
-
stdout: JSON.stringify({
|
|
64
|
-
items: [{ metadata: { name: 'gpt' }, status: { phase: 'ready' } }],
|
|
65
|
-
}),
|
|
66
|
-
})
|
|
67
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
68
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
69
|
-
const command = createTargetsCommand({});
|
|
70
|
-
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
71
|
-
const expectedTargets = [
|
|
72
|
-
{ type: 'agent', name: 'gpt', id: 'agent/gpt', available: true },
|
|
73
|
-
];
|
|
74
|
-
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(expectedTargets, null, 2));
|
|
75
|
-
});
|
|
76
|
-
it('filters targets by type', async () => {
|
|
77
|
-
mockExeca.mockResolvedValueOnce({
|
|
78
|
-
stdout: JSON.stringify({
|
|
79
|
-
items: [
|
|
80
|
-
{ metadata: { name: 'gpt' }, status: { phase: 'ready' } },
|
|
81
|
-
{ metadata: { name: 'helper' }, status: { phase: 'ready' } },
|
|
82
|
-
],
|
|
83
|
-
}),
|
|
84
|
-
});
|
|
85
|
-
const command = createTargetsCommand({});
|
|
86
|
-
await command.parseAsync(['node', 'test', '-t', 'agent']);
|
|
87
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'agents', '-o', 'json'], {
|
|
88
|
-
stdio: 'pipe',
|
|
89
|
-
});
|
|
90
|
-
expect(mockExeca).toHaveBeenCalledTimes(1); // Only agents, not other types
|
|
91
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt');
|
|
92
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('agent/helper');
|
|
93
|
-
});
|
|
94
|
-
it('sorts targets by type then name', async () => {
|
|
95
|
-
// Order: model, agent, team, tool
|
|
96
|
-
mockExeca
|
|
97
|
-
.mockResolvedValueOnce({
|
|
98
|
-
stdout: JSON.stringify({
|
|
99
|
-
items: [
|
|
100
|
-
{ metadata: { name: 'b' }, status: { available: true } },
|
|
101
|
-
{ metadata: { name: 'a' }, status: { available: true } },
|
|
102
|
-
],
|
|
103
|
-
}),
|
|
104
|
-
})
|
|
105
|
-
.mockResolvedValueOnce({
|
|
106
|
-
stdout: JSON.stringify({
|
|
107
|
-
items: [
|
|
108
|
-
{ metadata: { name: 'z' }, status: { phase: 'ready' } },
|
|
109
|
-
{ metadata: { name: 'a' }, status: { phase: 'ready' } },
|
|
110
|
-
],
|
|
111
|
-
}),
|
|
112
|
-
})
|
|
113
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
114
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
115
|
-
const command = createTargetsCommand({});
|
|
116
|
-
await command.parseAsync(['node', 'test']);
|
|
117
|
-
// Check order of calls - sorted by type then name
|
|
118
|
-
const calls = mockConsoleLog.mock.calls
|
|
119
|
-
.filter((call) => call[0] && call[0].includes('/'))
|
|
120
|
-
.map((call) => call[0]);
|
|
121
|
-
expect(calls).toEqual(['agent/a', 'agent/z', 'model/a', 'model/b']);
|
|
122
|
-
});
|
|
123
|
-
it('shows warning when no targets', async () => {
|
|
124
|
-
// All resource types return empty (order: model, agent, team, tool)
|
|
125
|
-
mockExeca
|
|
126
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
127
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
128
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
129
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
130
|
-
const command = createTargetsCommand({});
|
|
131
|
-
await command.parseAsync(['node', 'test']);
|
|
132
|
-
expect(mockOutput.warning).toHaveBeenCalledWith('no targets available');
|
|
133
|
-
});
|
|
134
|
-
it('handles errors', async () => {
|
|
135
|
-
mockExeca.mockRejectedValue(new Error('kubectl not found'));
|
|
136
|
-
const command = createTargetsCommand({});
|
|
137
|
-
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
138
|
-
expect(mockOutput.error).toHaveBeenCalledWith('fetching targets:', 'kubectl not found');
|
|
139
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
140
|
-
});
|
|
141
|
-
it('list subcommand works', async () => {
|
|
142
|
-
// Order: model, agent, team, tool
|
|
143
|
-
mockExeca
|
|
144
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
145
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
146
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
147
|
-
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
148
|
-
const command = createTargetsCommand({});
|
|
149
|
-
await command.parseAsync(['node', 'test', 'list']);
|
|
150
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'models', '-o', 'json'], {
|
|
151
|
-
stdio: 'pipe',
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
const mockExeca = jest.fn();
|
|
4
|
-
jest.unstable_mockModule('execa', () => ({
|
|
5
|
-
execa: mockExeca,
|
|
6
|
-
}));
|
|
7
|
-
const mockOutput = {
|
|
8
|
-
info: jest.fn(),
|
|
9
|
-
error: jest.fn(),
|
|
10
|
-
};
|
|
11
|
-
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
12
|
-
default: mockOutput,
|
|
13
|
-
}));
|
|
14
|
-
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
15
|
-
throw new Error('process.exit called');
|
|
16
|
-
}));
|
|
17
|
-
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
18
|
-
const { createTeamsCommand } = await import('./index.js');
|
|
19
|
-
describe('teams command', () => {
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
it('creates command with correct structure', () => {
|
|
24
|
-
const command = createTeamsCommand({});
|
|
25
|
-
expect(command).toBeInstanceOf(Command);
|
|
26
|
-
expect(command.name()).toBe('teams');
|
|
27
|
-
});
|
|
28
|
-
it('lists teams in text format', async () => {
|
|
29
|
-
const mockTeams = {
|
|
30
|
-
items: [
|
|
31
|
-
{ metadata: { name: 'engineering' } },
|
|
32
|
-
{ metadata: { name: 'data-science' } },
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTeams) });
|
|
36
|
-
const command = createTeamsCommand({});
|
|
37
|
-
await command.parseAsync(['node', 'test']);
|
|
38
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'teams', '-o', 'json'], { stdio: 'pipe' });
|
|
39
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('engineering');
|
|
40
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('data-science');
|
|
41
|
-
});
|
|
42
|
-
it('lists teams in json format', async () => {
|
|
43
|
-
const mockTeams = {
|
|
44
|
-
items: [{ metadata: { name: 'engineering' } }],
|
|
45
|
-
};
|
|
46
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTeams) });
|
|
47
|
-
const command = createTeamsCommand({});
|
|
48
|
-
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
49
|
-
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockTeams.items, null, 2));
|
|
50
|
-
});
|
|
51
|
-
it('shows info when no teams', async () => {
|
|
52
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
53
|
-
const command = createTeamsCommand({});
|
|
54
|
-
await command.parseAsync(['node', 'test']);
|
|
55
|
-
expect(mockOutput.info).toHaveBeenCalledWith('No teams found');
|
|
56
|
-
});
|
|
57
|
-
it('handles errors', async () => {
|
|
58
|
-
mockExeca.mockRejectedValue(new Error('kubectl failed'));
|
|
59
|
-
const command = createTeamsCommand({});
|
|
60
|
-
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
61
|
-
expect(mockOutput.error).toHaveBeenCalledWith('fetching teams:', 'kubectl failed');
|
|
62
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
63
|
-
});
|
|
64
|
-
it('list subcommand works', async () => {
|
|
65
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
66
|
-
const command = createTeamsCommand({});
|
|
67
|
-
await command.parseAsync(['node', 'test', 'list']);
|
|
68
|
-
expect(mockExeca).toHaveBeenCalled();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
const mockExeca = jest.fn();
|
|
4
|
-
jest.unstable_mockModule('execa', () => ({
|
|
5
|
-
execa: mockExeca,
|
|
6
|
-
}));
|
|
7
|
-
const mockOutput = {
|
|
8
|
-
info: jest.fn(),
|
|
9
|
-
error: jest.fn(),
|
|
10
|
-
};
|
|
11
|
-
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
12
|
-
default: mockOutput,
|
|
13
|
-
}));
|
|
14
|
-
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
15
|
-
throw new Error('process.exit called');
|
|
16
|
-
}));
|
|
17
|
-
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
18
|
-
const { createToolsCommand } = await import('./index.js');
|
|
19
|
-
describe('tools command', () => {
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
it('creates command with correct structure', () => {
|
|
24
|
-
const command = createToolsCommand({});
|
|
25
|
-
expect(command).toBeInstanceOf(Command);
|
|
26
|
-
expect(command.name()).toBe('tools');
|
|
27
|
-
});
|
|
28
|
-
it('lists tools in text format', async () => {
|
|
29
|
-
const mockTools = {
|
|
30
|
-
items: [
|
|
31
|
-
{ metadata: { name: 'github-mcp' } },
|
|
32
|
-
{ metadata: { name: 'slack-mcp' } },
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTools) });
|
|
36
|
-
const command = createToolsCommand({});
|
|
37
|
-
await command.parseAsync(['node', 'test']);
|
|
38
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'mcpservers', '-o', 'json'], { stdio: 'pipe' });
|
|
39
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('github-mcp');
|
|
40
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('slack-mcp');
|
|
41
|
-
});
|
|
42
|
-
it('lists tools in json format', async () => {
|
|
43
|
-
const mockTools = {
|
|
44
|
-
items: [{ metadata: { name: 'github-mcp' } }],
|
|
45
|
-
};
|
|
46
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTools) });
|
|
47
|
-
const command = createToolsCommand({});
|
|
48
|
-
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
49
|
-
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockTools.items, null, 2));
|
|
50
|
-
});
|
|
51
|
-
it('shows info when no tools', async () => {
|
|
52
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
53
|
-
const command = createToolsCommand({});
|
|
54
|
-
await command.parseAsync(['node', 'test']);
|
|
55
|
-
expect(mockOutput.info).toHaveBeenCalledWith('No tools found');
|
|
56
|
-
});
|
|
57
|
-
it('handles errors', async () => {
|
|
58
|
-
mockExeca.mockRejectedValue(new Error('kubectl failed'));
|
|
59
|
-
const command = createToolsCommand({});
|
|
60
|
-
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
61
|
-
expect(mockOutput.error).toHaveBeenCalledWith('fetching tools:', 'kubectl failed');
|
|
62
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
63
|
-
});
|
|
64
|
-
it('list subcommand works', async () => {
|
|
65
|
-
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
66
|
-
const command = createToolsCommand({});
|
|
67
|
-
await command.parseAsync(['node', 'test', 'list']);
|
|
68
|
-
expect(mockExeca).toHaveBeenCalled();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
const mockExeca = jest.fn(() => Promise.resolve());
|
|
4
|
-
jest.unstable_mockModule('execa', () => ({
|
|
5
|
-
execa: mockExeca,
|
|
6
|
-
}));
|
|
7
|
-
const mockGetClusterInfo = jest.fn();
|
|
8
|
-
jest.unstable_mockModule('../../lib/cluster.js', () => ({
|
|
9
|
-
getClusterInfo: mockGetClusterInfo,
|
|
10
|
-
}));
|
|
11
|
-
const mockGetInstallableServices = jest.fn();
|
|
12
|
-
const mockArkServices = {};
|
|
13
|
-
jest.unstable_mockModule('../../arkServices.js', () => ({
|
|
14
|
-
getInstallableServices: mockGetInstallableServices,
|
|
15
|
-
arkServices: mockArkServices,
|
|
16
|
-
}));
|
|
17
|
-
const mockOutput = {
|
|
18
|
-
error: jest.fn(),
|
|
19
|
-
info: jest.fn(),
|
|
20
|
-
success: jest.fn(),
|
|
21
|
-
warning: jest.fn(),
|
|
22
|
-
};
|
|
23
|
-
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
24
|
-
default: mockOutput,
|
|
25
|
-
}));
|
|
26
|
-
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
27
|
-
throw new Error('process.exit called');
|
|
28
|
-
}));
|
|
29
|
-
jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
30
|
-
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
31
|
-
const { createUninstallCommand } = await import('./index.js');
|
|
32
|
-
describe('uninstall command', () => {
|
|
33
|
-
const mockConfig = {
|
|
34
|
-
clusterInfo: {
|
|
35
|
-
context: 'test-cluster',
|
|
36
|
-
type: 'minikube',
|
|
37
|
-
namespace: 'default',
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
jest.clearAllMocks();
|
|
42
|
-
mockGetClusterInfo.mockResolvedValue({
|
|
43
|
-
context: 'test-cluster',
|
|
44
|
-
type: 'minikube',
|
|
45
|
-
namespace: 'default',
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
it('creates command with correct structure', () => {
|
|
49
|
-
const command = createUninstallCommand(mockConfig);
|
|
50
|
-
expect(command).toBeInstanceOf(Command);
|
|
51
|
-
expect(command.name()).toBe('uninstall');
|
|
52
|
-
});
|
|
53
|
-
it('uninstalls single service with correct helm parameters', async () => {
|
|
54
|
-
const mockService = {
|
|
55
|
-
name: 'ark-api',
|
|
56
|
-
helmReleaseName: 'ark-api',
|
|
57
|
-
namespace: 'ark-system',
|
|
58
|
-
};
|
|
59
|
-
mockGetInstallableServices.mockReturnValue({
|
|
60
|
-
'ark-api': mockService,
|
|
61
|
-
});
|
|
62
|
-
const command = createUninstallCommand(mockConfig);
|
|
63
|
-
await command.parseAsync(['node', 'test', 'ark-api']);
|
|
64
|
-
expect(mockExeca).toHaveBeenCalledWith('helm', [
|
|
65
|
-
'uninstall',
|
|
66
|
-
'ark-api',
|
|
67
|
-
'--ignore-not-found',
|
|
68
|
-
'--namespace',
|
|
69
|
-
'ark-system',
|
|
70
|
-
], {
|
|
71
|
-
stdio: 'inherit',
|
|
72
|
-
});
|
|
73
|
-
expect(mockOutput.success).toHaveBeenCalledWith('ark-api uninstalled successfully');
|
|
74
|
-
});
|
|
75
|
-
it('shows error when service not found', async () => {
|
|
76
|
-
mockGetInstallableServices.mockReturnValue({
|
|
77
|
-
'ark-api': { name: 'ark-api' },
|
|
78
|
-
'ark-controller': { name: 'ark-controller' },
|
|
79
|
-
});
|
|
80
|
-
const command = createUninstallCommand(mockConfig);
|
|
81
|
-
await expect(command.parseAsync(['node', 'test', 'invalid-service'])).rejects.toThrow('process.exit called');
|
|
82
|
-
expect(mockOutput.error).toHaveBeenCalledWith("service 'invalid-service' not found");
|
|
83
|
-
expect(mockOutput.info).toHaveBeenCalledWith('available services:');
|
|
84
|
-
expect(mockOutput.info).toHaveBeenCalledWith(' ark-api');
|
|
85
|
-
expect(mockOutput.info).toHaveBeenCalledWith(' ark-controller');
|
|
86
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
87
|
-
});
|
|
88
|
-
it('handles service without namespace (uses current context)', async () => {
|
|
89
|
-
const mockService = {
|
|
90
|
-
name: 'ark-dashboard',
|
|
91
|
-
helmReleaseName: 'ark-dashboard',
|
|
92
|
-
// namespace is undefined - should use current context
|
|
93
|
-
};
|
|
94
|
-
mockGetInstallableServices.mockReturnValue({
|
|
95
|
-
'ark-dashboard': mockService,
|
|
96
|
-
});
|
|
97
|
-
const command = createUninstallCommand(mockConfig);
|
|
98
|
-
await command.parseAsync(['node', 'test', 'ark-dashboard']);
|
|
99
|
-
// Should NOT include --namespace flag
|
|
100
|
-
expect(mockExeca).toHaveBeenCalledWith('helm', ['uninstall', 'ark-dashboard', '--ignore-not-found'], {
|
|
101
|
-
stdio: 'inherit',
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
it('handles helm uninstall error gracefully', async () => {
|
|
105
|
-
const mockService = {
|
|
106
|
-
name: 'ark-api',
|
|
107
|
-
helmReleaseName: 'ark-api',
|
|
108
|
-
namespace: 'ark-system',
|
|
109
|
-
};
|
|
110
|
-
mockGetInstallableServices.mockReturnValue({
|
|
111
|
-
'ark-api': mockService,
|
|
112
|
-
});
|
|
113
|
-
mockExeca.mockRejectedValue(new Error('helm failed'));
|
|
114
|
-
const command = createUninstallCommand(mockConfig);
|
|
115
|
-
await expect(command.parseAsync(['node', 'test', 'ark-api'])).rejects.toThrow('process.exit called');
|
|
116
|
-
expect(mockOutput.error).toHaveBeenCalledWith('failed to uninstall ark-api');
|
|
117
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
118
|
-
});
|
|
119
|
-
it('exits when cluster not connected', async () => {
|
|
120
|
-
mockGetClusterInfo.mockResolvedValue({ error: true });
|
|
121
|
-
const command = createUninstallCommand({});
|
|
122
|
-
await expect(command.parseAsync(['node', 'test', 'ark-api'])).rejects.toThrow('process.exit called');
|
|
123
|
-
expect(mockExit).toHaveBeenCalledWith(1);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { Buffer } from 'buffer';
|
|
3
|
-
const mockFind = jest.fn();
|
|
4
|
-
jest.unstable_mockModule('find-process', () => ({
|
|
5
|
-
default: mockFind,
|
|
6
|
-
}));
|
|
7
|
-
const mockSpawn = jest.fn();
|
|
8
|
-
const mockChildProcess = {
|
|
9
|
-
spawn: mockSpawn,
|
|
10
|
-
};
|
|
11
|
-
jest.unstable_mockModule('child_process', () => ({
|
|
12
|
-
...mockChildProcess,
|
|
13
|
-
spawn: mockSpawn,
|
|
14
|
-
}));
|
|
15
|
-
const { ArkServiceProxy } = await import('./arkServiceProxy.js');
|
|
16
|
-
describe('ArkServiceProxy', () => {
|
|
17
|
-
const mockService = {
|
|
18
|
-
name: 'test-service',
|
|
19
|
-
helmReleaseName: 'test-service',
|
|
20
|
-
description: 'Test service',
|
|
21
|
-
k8sServiceName: 'test-service-k8s',
|
|
22
|
-
k8sServicePort: 8080,
|
|
23
|
-
namespace: 'default',
|
|
24
|
-
enabled: true,
|
|
25
|
-
category: 'test',
|
|
26
|
-
};
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
jest.clearAllMocks();
|
|
29
|
-
});
|
|
30
|
-
describe('port-forward reuse', () => {
|
|
31
|
-
it('creates new port-forward when reuse is disabled', async () => {
|
|
32
|
-
const proxy = new ArkServiceProxy(mockService, 3000, false);
|
|
33
|
-
const mockProcess = {
|
|
34
|
-
stdout: { on: jest.fn() },
|
|
35
|
-
stderr: { on: jest.fn() },
|
|
36
|
-
on: jest.fn(),
|
|
37
|
-
kill: jest.fn(),
|
|
38
|
-
};
|
|
39
|
-
mockSpawn.mockReturnValue(mockProcess);
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
const stdoutCallback = mockProcess.stdout.on.mock.calls.find((call) => call[0] === 'data')?.[1];
|
|
42
|
-
if (stdoutCallback) {
|
|
43
|
-
stdoutCallback(Buffer.from('Forwarding from 127.0.0.1:3000'));
|
|
44
|
-
}
|
|
45
|
-
}, 10);
|
|
46
|
-
const url = await proxy.start();
|
|
47
|
-
expect(url).toBe('http://localhost:3000');
|
|
48
|
-
expect(mockFind).not.toHaveBeenCalled();
|
|
49
|
-
expect(mockSpawn).toHaveBeenCalledWith('kubectl', ['port-forward', 'service/test-service-k8s', '3000:8080', '--namespace', 'default'], expect.any(Object));
|
|
50
|
-
});
|
|
51
|
-
it('reuses existing kubectl port-forward when reuse is enabled', async () => {
|
|
52
|
-
mockFind.mockResolvedValue([
|
|
53
|
-
{
|
|
54
|
-
pid: 12345,
|
|
55
|
-
name: 'kubectl',
|
|
56
|
-
cmd: 'kubectl port-forward service/test-service-k8s 3000:8080',
|
|
57
|
-
},
|
|
58
|
-
]);
|
|
59
|
-
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
60
|
-
const url = await proxy.start();
|
|
61
|
-
expect(url).toBe('http://localhost:3000');
|
|
62
|
-
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
63
|
-
expect(mockSpawn).not.toHaveBeenCalled();
|
|
64
|
-
});
|
|
65
|
-
it('creates new port-forward when port is not in use', async () => {
|
|
66
|
-
mockFind.mockResolvedValue([]);
|
|
67
|
-
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
68
|
-
const mockProcess = {
|
|
69
|
-
stdout: { on: jest.fn() },
|
|
70
|
-
stderr: { on: jest.fn() },
|
|
71
|
-
on: jest.fn(),
|
|
72
|
-
kill: jest.fn(),
|
|
73
|
-
};
|
|
74
|
-
mockSpawn.mockReturnValue(mockProcess);
|
|
75
|
-
setTimeout(() => {
|
|
76
|
-
const stdoutCallback = mockProcess.stdout.on.mock.calls.find((call) => call[0] === 'data')?.[1];
|
|
77
|
-
if (stdoutCallback) {
|
|
78
|
-
stdoutCallback(Buffer.from('Forwarding from 127.0.0.1:3000'));
|
|
79
|
-
}
|
|
80
|
-
}, 10);
|
|
81
|
-
const url = await proxy.start();
|
|
82
|
-
expect(url).toBe('http://localhost:3000');
|
|
83
|
-
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
84
|
-
expect(mockSpawn).toHaveBeenCalled();
|
|
85
|
-
});
|
|
86
|
-
it('throws error when port is in use by non-kubectl process', async () => {
|
|
87
|
-
mockFind.mockResolvedValue([
|
|
88
|
-
{
|
|
89
|
-
pid: 54321,
|
|
90
|
-
name: 'node',
|
|
91
|
-
cmd: 'node server.js',
|
|
92
|
-
},
|
|
93
|
-
]);
|
|
94
|
-
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
95
|
-
await expect(proxy.start()).rejects.toThrow('test-service port forward failed: port 3000 is already in use by node (PID: 54321)');
|
|
96
|
-
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
97
|
-
expect(mockSpawn).not.toHaveBeenCalled();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
-
// Mock execa using unstable_mockModule
|
|
3
|
-
jest.unstable_mockModule('execa', () => ({
|
|
4
|
-
execa: jest.fn(),
|
|
5
|
-
}));
|
|
6
|
-
// Dynamic imports after mock
|
|
7
|
-
const { execa } = await import('execa');
|
|
8
|
-
const { isArkReady } = await import('./arkStatus.js');
|
|
9
|
-
describe('arkStatus with __mocks__', () => {
|
|
10
|
-
describe('isArkReady', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
jest.clearAllMocks();
|
|
13
|
-
});
|
|
14
|
-
it('should return true when ark-controller is deployed and ready', async () => {
|
|
15
|
-
// Mock successful kubectl response with ready deployment
|
|
16
|
-
const mockDeployment = {
|
|
17
|
-
metadata: { name: 'ark-controller' },
|
|
18
|
-
spec: { replicas: 3 },
|
|
19
|
-
status: {
|
|
20
|
-
readyReplicas: 3,
|
|
21
|
-
availableReplicas: 3,
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
execa.mockResolvedValue({
|
|
25
|
-
stdout: JSON.stringify(mockDeployment),
|
|
26
|
-
stderr: '',
|
|
27
|
-
exitCode: 0,
|
|
28
|
-
failed: false,
|
|
29
|
-
});
|
|
30
|
-
const result = await isArkReady();
|
|
31
|
-
expect(result).toBe(true);
|
|
32
|
-
expect(execa).toHaveBeenCalledWith('kubectl', [
|
|
33
|
-
'get',
|
|
34
|
-
'deployment',
|
|
35
|
-
'ark-controller',
|
|
36
|
-
'-n',
|
|
37
|
-
'ark-system',
|
|
38
|
-
'-o',
|
|
39
|
-
'json',
|
|
40
|
-
], { stdio: 'pipe' });
|
|
41
|
-
});
|
|
42
|
-
it('should return false when kubectl fails', async () => {
|
|
43
|
-
// Mock kubectl failure
|
|
44
|
-
execa.mockRejectedValue(new Error('kubectl not found'));
|
|
45
|
-
const result = await isArkReady();
|
|
46
|
-
expect(result).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|