@memnexus-ai/cli 0.1.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.
- package/.env.example +13 -0
- package/.eslintrc.js +24 -0
- package/.github/ISSUE_TEMPLATE/phase-1-foundation.md +1078 -0
- package/.github/workflows/publish.yml +277 -0
- package/.github/workflows/test-app-token.yml +54 -0
- package/.npmrc.backup +3 -0
- package/.npmrc.example +6 -0
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +138 -0
- package/PLATFORM_TESTING.md +243 -0
- package/README.md +986 -0
- package/RELEASE.md +428 -0
- package/RELEASE_READINESS.md +253 -0
- package/USAGE.md +1373 -0
- package/bin/mx.js +2 -0
- package/dist/commands/apikeys.d.ts +7 -0
- package/dist/commands/apikeys.d.ts.map +1 -0
- package/dist/commands/apikeys.js +133 -0
- package/dist/commands/apikeys.js.map +1 -0
- package/dist/commands/artifacts.d.ts +7 -0
- package/dist/commands/artifacts.d.ts.map +1 -0
- package/dist/commands/artifacts.js +277 -0
- package/dist/commands/artifacts.js.map +1 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +119 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/communities.d.ts +7 -0
- package/dist/commands/communities.d.ts.map +1 -0
- package/dist/commands/communities.js +137 -0
- package/dist/commands/communities.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +138 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/conversations.d.ts +7 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/facts.d.ts +7 -0
- package/dist/commands/facts.d.ts.map +1 -0
- package/dist/commands/facts.js +298 -0
- package/dist/commands/facts.js.map +1 -0
- package/dist/commands/graphrag.d.ts +7 -0
- package/dist/commands/graphrag.d.ts.map +1 -0
- package/dist/commands/graphrag.js +139 -0
- package/dist/commands/graphrag.js.map +1 -0
- package/dist/commands/memories.d.ts +7 -0
- package/dist/commands/memories.d.ts.map +1 -0
- package/dist/commands/memories.js +304 -0
- package/dist/commands/memories.js.map +1 -0
- package/dist/commands/patterns.d.ts +7 -0
- package/dist/commands/patterns.d.ts.map +1 -0
- package/dist/commands/patterns.js +227 -0
- package/dist/commands/patterns.js.map +1 -0
- package/dist/commands/system.d.ts +7 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +97 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/commands/topics.d.ts +7 -0
- package/dist/commands/topics.d.ts.map +1 -0
- package/dist/commands/topics.js +314 -0
- package/dist/commands/topics.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +29 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +64 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth.d.ts +10 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +47 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +19 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +59 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +7 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +133 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/formatters.d.ts +12 -0
- package/dist/lib/formatters.d.ts.map +1 -0
- package/dist/lib/formatters.js +103 -0
- package/dist/lib/formatters.js.map +1 -0
- package/dist/lib/spinner.d.ts +54 -0
- package/dist/lib/spinner.d.ts.map +1 -0
- package/dist/lib/spinner.js +108 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/validators.d.ts +92 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +257 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/docs/README.md +219 -0
- package/docs/code-generation-strategy.md +560 -0
- package/docs/prd.md +748 -0
- package/docs/sync-strategy.md +533 -0
- package/jest.config.js +30 -0
- package/package.json +67 -0
- package/scripts/install-deps.sh +38 -0
- package/src/commands/apikeys.ts +144 -0
- package/src/commands/artifacts.ts +296 -0
- package/src/commands/auth.ts +122 -0
- package/src/commands/communities.ts +153 -0
- package/src/commands/config.ts +144 -0
- package/src/commands/conversations.ts +176 -0
- package/src/commands/facts.ts +320 -0
- package/src/commands/graphrag.ts +149 -0
- package/src/commands/memories.ts +332 -0
- package/src/commands/patterns.ts +251 -0
- package/src/commands/system.ts +102 -0
- package/src/commands/topics.ts +354 -0
- package/src/index.ts +43 -0
- package/src/lib/api-client.ts +68 -0
- package/src/lib/auth.ts +42 -0
- package/src/lib/config.ts +68 -0
- package/src/lib/errors.ts +143 -0
- package/src/lib/formatters.ts +123 -0
- package/src/lib/spinner.ts +113 -0
- package/src/lib/validators.ts +302 -0
- package/src/types/index.ts +17 -0
- package/tests/__mocks__/chalk.ts +16 -0
- package/tests/__mocks__/cli-table3.ts +37 -0
- package/tests/__mocks__/configstore.ts +38 -0
- package/tests/commands/apikeys.test.ts +179 -0
- package/tests/commands/artifacts.test.ts +194 -0
- package/tests/commands/auth.test.ts +120 -0
- package/tests/commands/communities.test.ts +154 -0
- package/tests/commands/config.test.ts +154 -0
- package/tests/commands/conversations.test.ts +136 -0
- package/tests/commands/facts.test.ts +210 -0
- package/tests/commands/graphrag.test.ts +194 -0
- package/tests/commands/memories.test.ts +215 -0
- package/tests/commands/patterns.test.ts +201 -0
- package/tests/commands/system.test.ts +172 -0
- package/tests/commands/topics.test.ts +274 -0
- package/tests/lib/auth.test.ts +77 -0
- package/tests/lib/config.test.ts +50 -0
- package/tests/lib/errors.test.ts +126 -0
- package/tests/lib/formatters.test.ts +87 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Mock cli-table3 for Jest tests
|
|
2
|
+
interface TableOptions {
|
|
3
|
+
head?: string[];
|
|
4
|
+
colWidths?: number[];
|
|
5
|
+
wordWrap?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class MockTable {
|
|
9
|
+
private rows: unknown[][] = [];
|
|
10
|
+
private headers: string[] = [];
|
|
11
|
+
|
|
12
|
+
constructor(options?: TableOptions) {
|
|
13
|
+
if (options?.head) {
|
|
14
|
+
this.headers = options.head;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
push(row: unknown[]): void {
|
|
19
|
+
this.rows.push(row);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toString(): string {
|
|
23
|
+
const lines: string[] = [];
|
|
24
|
+
|
|
25
|
+
// Add header row if present
|
|
26
|
+
if (this.headers.length > 0) {
|
|
27
|
+
lines.push(this.headers.join(' | '));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Add data rows
|
|
31
|
+
lines.push(...this.rows.map((row) => row.join(' | ')));
|
|
32
|
+
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default MockTable;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Mock Configstore for Jest tests
|
|
2
|
+
class MockConfigstore {
|
|
3
|
+
private store: Record<string, unknown> = {};
|
|
4
|
+
private defaults: Record<string, unknown> = {};
|
|
5
|
+
|
|
6
|
+
constructor(_name: string, defaults?: Record<string, unknown>) {
|
|
7
|
+
if (defaults) {
|
|
8
|
+
this.defaults = { ...defaults };
|
|
9
|
+
this.store = { ...defaults };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get all() {
|
|
14
|
+
return this.store;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set all(value: Record<string, unknown>) {
|
|
18
|
+
this.store = { ...value };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get(key: string): unknown {
|
|
22
|
+
return this.store[key];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
set(key: string, value: unknown): void {
|
|
26
|
+
this.store[key] = value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
delete(key: string): void {
|
|
30
|
+
delete this.store[key];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clear(): void {
|
|
34
|
+
this.store = {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default MockConfigstore;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getApiClient } from '../../src/lib/api-client';
|
|
2
|
+
|
|
3
|
+
// Mock the API client
|
|
4
|
+
jest.mock('../../src/lib/api-client');
|
|
5
|
+
|
|
6
|
+
// Mock chalk
|
|
7
|
+
jest.mock('chalk', () => ({
|
|
8
|
+
default: {
|
|
9
|
+
green: (str: string) => str,
|
|
10
|
+
red: (str: string) => str,
|
|
11
|
+
yellow: (str: string) => str,
|
|
12
|
+
cyan: (str: string) => str,
|
|
13
|
+
gray: (str: string) => str,
|
|
14
|
+
},
|
|
15
|
+
green: (str: string) => str,
|
|
16
|
+
red: (str: string) => str,
|
|
17
|
+
yellow: (str: string) => str,
|
|
18
|
+
cyan: (str: string) => str,
|
|
19
|
+
gray: (str: string) => str,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
// Mock ora
|
|
23
|
+
jest.mock('ora', () => {
|
|
24
|
+
return jest.fn(() => ({
|
|
25
|
+
start: jest.fn(function() { return this; }),
|
|
26
|
+
succeed: jest.fn(function() { return this; }),
|
|
27
|
+
fail: jest.fn(function() { return this; }),
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('API Keys Commands', () => {
|
|
32
|
+
let mockClient: any;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
|
|
37
|
+
mockClient = {
|
|
38
|
+
apikeys: {
|
|
39
|
+
list: jest.fn(),
|
|
40
|
+
get: jest.fn(),
|
|
41
|
+
create: jest.fn(),
|
|
42
|
+
delete: jest.fn(),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
(getApiClient as jest.Mock).mockReturnValue(mockClient);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('apikeys list', () => {
|
|
50
|
+
it('should list API keys with metadata only', async () => {
|
|
51
|
+
const mockResponse = {
|
|
52
|
+
data: [
|
|
53
|
+
{
|
|
54
|
+
id: 'key_1',
|
|
55
|
+
name: 'Production Key',
|
|
56
|
+
status: 'active',
|
|
57
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
58
|
+
lastUsed: '2024-01-10T00:00:00Z',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
pagination: {
|
|
62
|
+
limit: 20,
|
|
63
|
+
offset: 0,
|
|
64
|
+
count: 1,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
mockClient.apikeys.list.mockResolvedValue(mockResponse);
|
|
69
|
+
|
|
70
|
+
const result = await mockClient.apikeys.list({ limit: 20, offset: 0 });
|
|
71
|
+
|
|
72
|
+
expect(mockClient.apikeys.list).toHaveBeenCalledWith({ limit: 20, offset: 0 });
|
|
73
|
+
expect(result.data).toHaveLength(1);
|
|
74
|
+
expect(result.data[0].name).toBe('Production Key');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('apikeys get', () => {
|
|
79
|
+
it('should get API key metadata without value', async () => {
|
|
80
|
+
const mockKey = {
|
|
81
|
+
id: 'key_1',
|
|
82
|
+
name: 'Production Key',
|
|
83
|
+
status: 'active',
|
|
84
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
85
|
+
value: 'sk_live_secret_key_value', // Should not be returned
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
mockClient.apikeys.get.mockResolvedValue(mockKey);
|
|
89
|
+
|
|
90
|
+
const result = await mockClient.apikeys.get('key_1');
|
|
91
|
+
|
|
92
|
+
expect(mockClient.apikeys.get).toHaveBeenCalledWith('key_1');
|
|
93
|
+
expect(result.id).toBe('key_1');
|
|
94
|
+
expect(result.name).toBe('Production Key');
|
|
95
|
+
// Value should be present in mock but should be filtered in actual command
|
|
96
|
+
expect(result.value).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('apikeys create', () => {
|
|
101
|
+
it('should create API key and show value once', async () => {
|
|
102
|
+
const mockResult = {
|
|
103
|
+
id: 'key_1',
|
|
104
|
+
name: 'New Key',
|
|
105
|
+
value: 'sk_live_new_secret_key_value',
|
|
106
|
+
status: 'active',
|
|
107
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
108
|
+
scopes: ['read', 'write'],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
mockClient.apikeys.create.mockResolvedValue(mockResult);
|
|
112
|
+
|
|
113
|
+
const result = await mockClient.apikeys.create({
|
|
114
|
+
name: 'New Key',
|
|
115
|
+
scopes: ['read', 'write'],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(mockClient.apikeys.create).toHaveBeenCalledWith({
|
|
119
|
+
name: 'New Key',
|
|
120
|
+
scopes: ['read', 'write'],
|
|
121
|
+
});
|
|
122
|
+
expect(result.id).toBe('key_1');
|
|
123
|
+
expect(result.value).toBe('sk_live_new_secret_key_value');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should create API key with expiration', async () => {
|
|
127
|
+
const mockResult = {
|
|
128
|
+
id: 'key_1',
|
|
129
|
+
name: 'Temporary Key',
|
|
130
|
+
value: 'sk_live_temp_key_value',
|
|
131
|
+
expiresAt: '2024-02-01T00:00:00Z',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
mockClient.apikeys.create.mockResolvedValue(mockResult);
|
|
135
|
+
|
|
136
|
+
const result = await mockClient.apikeys.create({
|
|
137
|
+
name: 'Temporary Key',
|
|
138
|
+
expiresIn: 30,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(mockClient.apikeys.create).toHaveBeenCalledWith({
|
|
142
|
+
name: 'Temporary Key',
|
|
143
|
+
expiresIn: 30,
|
|
144
|
+
});
|
|
145
|
+
expect(result.expiresAt).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('apikeys delete', () => {
|
|
150
|
+
it('should delete API key', async () => {
|
|
151
|
+
mockClient.apikeys.delete.mockResolvedValue({ success: true });
|
|
152
|
+
|
|
153
|
+
const result = await mockClient.apikeys.delete('key_1');
|
|
154
|
+
|
|
155
|
+
expect(mockClient.apikeys.delete).toHaveBeenCalledWith('key_1');
|
|
156
|
+
expect(result.success).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('security considerations', () => {
|
|
161
|
+
it('should never store or log key values', async () => {
|
|
162
|
+
const mockKey = {
|
|
163
|
+
id: 'key_1',
|
|
164
|
+
name: 'Secure Key',
|
|
165
|
+
value: 'sk_live_secret_value',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
mockClient.apikeys.get.mockResolvedValue(mockKey);
|
|
169
|
+
|
|
170
|
+
const result = await mockClient.apikeys.get('key_1');
|
|
171
|
+
|
|
172
|
+
// The actual command should filter out the value
|
|
173
|
+
// This test verifies the mock returns it, but the command filters it
|
|
174
|
+
expect(result.value).toBeDefined();
|
|
175
|
+
expect(result.id).toBe('key_1');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { getApiClient } from '../../src/lib/api-client';
|
|
2
|
+
|
|
3
|
+
// Mock the API client
|
|
4
|
+
jest.mock('../../src/lib/api-client');
|
|
5
|
+
|
|
6
|
+
// Mock chalk
|
|
7
|
+
jest.mock('chalk', () => ({
|
|
8
|
+
default: {
|
|
9
|
+
green: (str: string) => str,
|
|
10
|
+
red: (str: string) => str,
|
|
11
|
+
yellow: (str: string) => str,
|
|
12
|
+
cyan: (str: string) => str,
|
|
13
|
+
gray: (str: string) => str,
|
|
14
|
+
},
|
|
15
|
+
green: (str: string) => str,
|
|
16
|
+
red: (str: string) => str,
|
|
17
|
+
yellow: (str: string) => str,
|
|
18
|
+
cyan: (str: string) => str,
|
|
19
|
+
gray: (str: string) => str,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
// Mock ora
|
|
23
|
+
jest.mock('ora', () => {
|
|
24
|
+
return jest.fn(() => ({
|
|
25
|
+
start: jest.fn(function() { return this; }),
|
|
26
|
+
succeed: jest.fn(function() { return this; }),
|
|
27
|
+
fail: jest.fn(function() { return this; }),
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('Artifacts Commands', () => {
|
|
32
|
+
let mockClient: any;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
|
|
37
|
+
mockClient = {
|
|
38
|
+
artifacts: {
|
|
39
|
+
list: jest.fn(),
|
|
40
|
+
get: jest.fn(),
|
|
41
|
+
create: jest.fn(),
|
|
42
|
+
update: jest.fn(),
|
|
43
|
+
delete: jest.fn(),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
(getApiClient as jest.Mock).mockReturnValue(mockClient);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('artifacts list', () => {
|
|
51
|
+
it('should list artifacts with default pagination', async () => {
|
|
52
|
+
const mockResponse = {
|
|
53
|
+
data: [
|
|
54
|
+
{
|
|
55
|
+
id: 'art_1',
|
|
56
|
+
name: 'Artifact 1',
|
|
57
|
+
type: 'document',
|
|
58
|
+
size: 1024,
|
|
59
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
pagination: {
|
|
63
|
+
limit: 20,
|
|
64
|
+
offset: 0,
|
|
65
|
+
count: 1,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
mockClient.artifacts.list.mockResolvedValue(mockResponse);
|
|
70
|
+
|
|
71
|
+
const result = await mockClient.artifacts.list({ limit: 20, offset: 0 });
|
|
72
|
+
|
|
73
|
+
expect(mockClient.artifacts.list).toHaveBeenCalledWith({ limit: 20, offset: 0 });
|
|
74
|
+
expect(result.data).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should filter by type', async () => {
|
|
78
|
+
const mockResponse = {
|
|
79
|
+
data: [],
|
|
80
|
+
pagination: { limit: 20, offset: 0, count: 0 },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
mockClient.artifacts.list.mockResolvedValue(mockResponse);
|
|
84
|
+
|
|
85
|
+
await mockClient.artifacts.list({ limit: 20, offset: 0, type: 'document' });
|
|
86
|
+
|
|
87
|
+
expect(mockClient.artifacts.list).toHaveBeenCalledWith({
|
|
88
|
+
limit: 20,
|
|
89
|
+
offset: 0,
|
|
90
|
+
type: 'document',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('artifacts get', () => {
|
|
96
|
+
it('should get artifact details', async () => {
|
|
97
|
+
const mockArtifact = {
|
|
98
|
+
id: 'art_1',
|
|
99
|
+
name: 'Artifact 1',
|
|
100
|
+
type: 'document',
|
|
101
|
+
size: 1024,
|
|
102
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
mockClient.artifacts.get.mockResolvedValue(mockArtifact);
|
|
106
|
+
|
|
107
|
+
const result = await mockClient.artifacts.get('art_1');
|
|
108
|
+
|
|
109
|
+
expect(mockClient.artifacts.get).toHaveBeenCalledWith('art_1');
|
|
110
|
+
expect(result.id).toBe('art_1');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('artifacts create', () => {
|
|
115
|
+
it('should create artifact', async () => {
|
|
116
|
+
const mockResult = {
|
|
117
|
+
id: 'art_1',
|
|
118
|
+
name: 'New Artifact',
|
|
119
|
+
type: 'document',
|
|
120
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
mockClient.artifacts.create.mockResolvedValue(mockResult);
|
|
124
|
+
|
|
125
|
+
const result = await mockClient.artifacts.create({
|
|
126
|
+
name: 'New Artifact',
|
|
127
|
+
type: 'document',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(mockClient.artifacts.create).toHaveBeenCalledWith({
|
|
131
|
+
name: 'New Artifact',
|
|
132
|
+
type: 'document',
|
|
133
|
+
});
|
|
134
|
+
expect(result.id).toBe('art_1');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should create artifact with metadata', async () => {
|
|
138
|
+
const mockResult = {
|
|
139
|
+
id: 'art_1',
|
|
140
|
+
name: 'New Artifact',
|
|
141
|
+
type: 'document',
|
|
142
|
+
metadata: { key: 'value' },
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
mockClient.artifacts.create.mockResolvedValue(mockResult);
|
|
146
|
+
|
|
147
|
+
const result = await mockClient.artifacts.create({
|
|
148
|
+
name: 'New Artifact',
|
|
149
|
+
type: 'document',
|
|
150
|
+
metadata: { key: 'value' },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(mockClient.artifacts.create).toHaveBeenCalledWith({
|
|
154
|
+
name: 'New Artifact',
|
|
155
|
+
type: 'document',
|
|
156
|
+
metadata: { key: 'value' },
|
|
157
|
+
});
|
|
158
|
+
expect(result.metadata).toEqual({ key: 'value' });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('artifacts update', () => {
|
|
163
|
+
it('should update artifact', async () => {
|
|
164
|
+
const mockResult = {
|
|
165
|
+
id: 'art_1',
|
|
166
|
+
name: 'Updated Artifact',
|
|
167
|
+
type: 'document',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
mockClient.artifacts.update.mockResolvedValue(mockResult);
|
|
171
|
+
|
|
172
|
+
const result = await mockClient.artifacts.update('art_1', {
|
|
173
|
+
name: 'Updated Artifact',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(mockClient.artifacts.update).toHaveBeenCalledWith('art_1', {
|
|
177
|
+
name: 'Updated Artifact',
|
|
178
|
+
});
|
|
179
|
+
expect(result.name).toBe('Updated Artifact');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('artifacts delete', () => {
|
|
184
|
+
it('should delete artifact', async () => {
|
|
185
|
+
mockClient.artifacts.delete.mockResolvedValue({ success: true });
|
|
186
|
+
|
|
187
|
+
const result = await mockClient.artifacts.delete('art_1');
|
|
188
|
+
|
|
189
|
+
expect(mockClient.artifacts.delete).toHaveBeenCalledWith('art_1');
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { storeApiKey, removeApiKey, getApiKeyStatus } from '../../src/lib/auth';
|
|
2
|
+
import { config } from '../../src/lib/config';
|
|
3
|
+
|
|
4
|
+
// Mock the config module
|
|
5
|
+
jest.mock('../../src/lib/config', () => ({
|
|
6
|
+
config: {
|
|
7
|
+
setApiKey: jest.fn(),
|
|
8
|
+
delete: jest.fn(),
|
|
9
|
+
get: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock chalk to avoid color codes in tests
|
|
14
|
+
jest.mock('chalk', () => ({
|
|
15
|
+
default: {
|
|
16
|
+
green: (str: string) => str,
|
|
17
|
+
red: (str: string) => str,
|
|
18
|
+
yellow: (str: string) => str,
|
|
19
|
+
cyan: (str: string) => str,
|
|
20
|
+
gray: (str: string) => str,
|
|
21
|
+
},
|
|
22
|
+
green: (str: string) => str,
|
|
23
|
+
red: (str: string) => str,
|
|
24
|
+
yellow: (str: string) => str,
|
|
25
|
+
cyan: (str: string) => str,
|
|
26
|
+
gray: (str: string) => str,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
describe('Auth Commands', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
// Clear environment variables
|
|
33
|
+
delete process.env.MX_API_KEY;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('storeApiKey', () => {
|
|
37
|
+
it('should store a valid API key', () => {
|
|
38
|
+
const apiKey = 'test_api_key_12345';
|
|
39
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
40
|
+
|
|
41
|
+
storeApiKey(apiKey);
|
|
42
|
+
|
|
43
|
+
expect(config.setApiKey).toHaveBeenCalledWith(apiKey);
|
|
44
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('API key saved successfully'));
|
|
45
|
+
|
|
46
|
+
consoleSpy.mockRestore();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should throw error for empty API key', () => {
|
|
50
|
+
expect(() => storeApiKey('')).toThrow('API key cannot be empty');
|
|
51
|
+
expect(config.setApiKey).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should throw error for whitespace-only API key', () => {
|
|
55
|
+
expect(() => storeApiKey(' ')).toThrow('API key cannot be empty');
|
|
56
|
+
expect(config.setApiKey).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('removeApiKey', () => {
|
|
61
|
+
it('should remove stored API key', () => {
|
|
62
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
63
|
+
|
|
64
|
+
removeApiKey();
|
|
65
|
+
|
|
66
|
+
expect(config.delete).toHaveBeenCalledWith('apiKey');
|
|
67
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('API key removed'));
|
|
68
|
+
|
|
69
|
+
consoleSpy.mockRestore();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('getApiKeyStatus', () => {
|
|
74
|
+
it('should return env source when API key is in environment', () => {
|
|
75
|
+
process.env.MX_API_KEY = 'env_api_key';
|
|
76
|
+
|
|
77
|
+
const status = getApiKeyStatus();
|
|
78
|
+
|
|
79
|
+
expect(status).toEqual({
|
|
80
|
+
configured: true,
|
|
81
|
+
source: 'env',
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return config source when API key is in config file', () => {
|
|
86
|
+
(config.get as jest.Mock).mockReturnValue('config_api_key');
|
|
87
|
+
|
|
88
|
+
const status = getApiKeyStatus();
|
|
89
|
+
|
|
90
|
+
expect(status).toEqual({
|
|
91
|
+
configured: true,
|
|
92
|
+
source: 'config',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return none when no API key is configured', () => {
|
|
97
|
+
(config.get as jest.Mock).mockReturnValue(undefined);
|
|
98
|
+
|
|
99
|
+
const status = getApiKeyStatus();
|
|
100
|
+
|
|
101
|
+
expect(status).toEqual({
|
|
102
|
+
configured: false,
|
|
103
|
+
source: 'none',
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should prioritize environment variable over config file', () => {
|
|
108
|
+
process.env.MX_API_KEY = 'env_api_key';
|
|
109
|
+
(config.get as jest.Mock).mockReturnValue('config_api_key');
|
|
110
|
+
|
|
111
|
+
const status = getApiKeyStatus();
|
|
112
|
+
|
|
113
|
+
expect(status).toEqual({
|
|
114
|
+
configured: true,
|
|
115
|
+
source: 'env',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|