@sumicom/quicksave 0.1.0 → 0.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.
@@ -1,294 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { MessageHandler } from './messageHandler.js';
3
- import { createMessage } from '@sumicom/quicksave-shared';
4
- import { mkdir, writeFile, rm } from 'fs/promises';
5
- import { join } from 'path';
6
- import { tmpdir } from 'os';
7
- import { simpleGit } from 'simple-git';
8
-
9
- describe('MessageHandler', () => {
10
- let testRepoPath: string;
11
- let handler: MessageHandler;
12
- let defaultBranch: string;
13
-
14
- beforeEach(async () => {
15
- // Create temporary test repo
16
- testRepoPath = join(tmpdir(), `quicksave-handler-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
17
- await mkdir(testRepoPath, { recursive: true });
18
-
19
- // Initialize git repo
20
- const git = simpleGit(testRepoPath);
21
- await git.init();
22
- await git.addConfig('user.email', 'test@test.com');
23
- await git.addConfig('user.name', 'Test User');
24
-
25
- // Create initial commit
26
- await writeFile(join(testRepoPath, 'README.md'), '# Test Repo\n');
27
- await git.add('README.md');
28
- await git.commit('Initial commit');
29
-
30
- // Get the default branch name (could be 'main' or 'master' depending on git config)
31
- const status = await git.status();
32
- defaultBranch = status.current || 'main';
33
-
34
- handler = new MessageHandler([{ path: testRepoPath, name: 'test-repo' }]);
35
- });
36
-
37
- afterEach(async () => {
38
- try {
39
- await rm(testRepoPath, { recursive: true, force: true });
40
- } catch {
41
- // Ignore cleanup errors
42
- }
43
- });
44
-
45
- describe('handleMessage - ping/pong', () => {
46
- it('should respond to ping with pong', async () => {
47
- const message = createMessage('ping', { timestamp: Date.now() });
48
- const response = await handler.handleMessage(message);
49
-
50
- expect(response.type).toBe('pong');
51
- expect(response.payload).toHaveProperty('timestamp');
52
- });
53
- });
54
-
55
- describe('handleMessage - handshake', () => {
56
- it('should respond to handshake with ack', async () => {
57
- const message = createMessage('handshake', { publicKey: 'test-key' });
58
- const response = await handler.handleMessage(message);
59
-
60
- expect(response.type).toBe('handshake:ack');
61
- expect(response.id).toBe(message.id);
62
- expect((response.payload as any).success).toBe(true);
63
- expect((response.payload as any).agentVersion).toBe('0.1.0');
64
- expect((response.payload as any).repoPath).toBe(testRepoPath);
65
- });
66
- });
67
-
68
- describe('handleMessage - git:status', () => {
69
- it('should return git status', async () => {
70
- const message = createMessage('git:status', {});
71
- const response = await handler.handleMessage(message);
72
-
73
- expect(response.type).toBe('git:status:response');
74
- expect(response.id).toBe(message.id);
75
-
76
- const payload = response.payload as any;
77
- expect(payload.branch).toBe(defaultBranch);
78
- expect(Array.isArray(payload.staged)).toBe(true);
79
- expect(Array.isArray(payload.unstaged)).toBe(true);
80
- expect(Array.isArray(payload.untracked)).toBe(true);
81
- });
82
-
83
- it('should detect changes in status', async () => {
84
- await writeFile(join(testRepoPath, 'newfile.txt'), 'content');
85
-
86
- const message = createMessage('git:status', {});
87
- const response = await handler.handleMessage(message);
88
-
89
- const payload = response.payload as any;
90
- expect(payload.untracked).toContain('newfile.txt');
91
- });
92
- });
93
-
94
- describe('handleMessage - git:diff', () => {
95
- it('should return diff for modified file', async () => {
96
- await writeFile(join(testRepoPath, 'README.md'), '# Modified\n');
97
-
98
- const message = createMessage('git:diff', { path: 'README.md', staged: false });
99
- const response = await handler.handleMessage(message);
100
-
101
- expect(response.type).toBe('git:diff:response');
102
- expect(response.id).toBe(message.id);
103
-
104
- const payload = response.payload as any;
105
- expect(payload.path).toBe('README.md');
106
- expect(payload.hunks.length).toBeGreaterThan(0);
107
- });
108
- });
109
-
110
- describe('handleMessage - git:stage', () => {
111
- it('should stage files successfully', async () => {
112
- await writeFile(join(testRepoPath, 'newfile.txt'), 'content');
113
-
114
- const message = createMessage('git:stage', { paths: ['newfile.txt'] });
115
- const response = await handler.handleMessage(message);
116
-
117
- expect(response.type).toBe('git:stage:response');
118
- expect((response.payload as any).success).toBe(true);
119
-
120
- // Verify file was staged
121
- const statusMsg = createMessage('git:status', {});
122
- const statusResp = await handler.handleMessage(statusMsg);
123
- const status = statusResp.payload as any;
124
- expect(status.staged.some((f: any) => f.path === 'newfile.txt')).toBe(true);
125
- });
126
-
127
- it('should return error for invalid path', async () => {
128
- const message = createMessage('git:stage', { paths: ['nonexistent.txt'] });
129
- const response = await handler.handleMessage(message);
130
-
131
- expect(response.type).toBe('git:stage:response');
132
- expect((response.payload as any).success).toBe(false);
133
- expect((response.payload as any).error).toBeDefined();
134
- });
135
- });
136
-
137
- describe('handleMessage - git:unstage', () => {
138
- it('should unstage files successfully', async () => {
139
- await writeFile(join(testRepoPath, 'newfile.txt'), 'content');
140
- await simpleGit(testRepoPath).add('newfile.txt');
141
-
142
- const message = createMessage('git:unstage', { paths: ['newfile.txt'] });
143
- const response = await handler.handleMessage(message);
144
-
145
- expect(response.type).toBe('git:unstage:response');
146
- expect((response.payload as any).success).toBe(true);
147
- });
148
- });
149
-
150
- describe('handleMessage - git:commit', () => {
151
- it('should create commit successfully', async () => {
152
- await writeFile(join(testRepoPath, 'newfile.txt'), 'content');
153
- await simpleGit(testRepoPath).add('newfile.txt');
154
-
155
- const message = createMessage('git:commit', { message: 'Test commit' });
156
- const response = await handler.handleMessage(message);
157
-
158
- expect(response.type).toBe('git:commit:response');
159
- expect((response.payload as any).success).toBe(true);
160
- expect((response.payload as any).hash).toBeDefined();
161
- });
162
-
163
- it('should create commit with description', async () => {
164
- await writeFile(join(testRepoPath, 'newfile.txt'), 'content');
165
- await simpleGit(testRepoPath).add('newfile.txt');
166
-
167
- const message = createMessage('git:commit', {
168
- message: 'Title',
169
- description: 'Extended description',
170
- });
171
- const response = await handler.handleMessage(message);
172
-
173
- expect(response.type).toBe('git:commit:response');
174
- expect((response.payload as any).success).toBe(true);
175
- });
176
-
177
- it('should handle empty commit gracefully', async () => {
178
- const message = createMessage('git:commit', { message: 'Empty commit' });
179
- const response = await handler.handleMessage(message);
180
-
181
- // The behavior depends on git - it might succeed with empty commit or fail
182
- // We just verify it returns a proper response
183
- expect(response.type).toBe('git:commit:response');
184
- });
185
- });
186
-
187
- describe('handleMessage - git:log', () => {
188
- it('should return commit log', async () => {
189
- const message = createMessage('git:log', { limit: 10 });
190
- const response = await handler.handleMessage(message);
191
-
192
- expect(response.type).toBe('git:log:response');
193
- expect(response.id).toBe(message.id);
194
-
195
- const payload = response.payload as any;
196
- expect(Array.isArray(payload.commits)).toBe(true);
197
- expect(payload.commits.length).toBeGreaterThan(0);
198
- expect(payload.commits[0].message).toBe('Initial commit');
199
- });
200
-
201
- it('should use default limit when not specified', async () => {
202
- const message = createMessage('git:log', {});
203
- const response = await handler.handleMessage(message);
204
-
205
- expect(response.type).toBe('git:log:response');
206
- });
207
- });
208
-
209
- describe('handleMessage - git:branches', () => {
210
- it('should return branches', async () => {
211
- const message = createMessage('git:branches', {});
212
- const response = await handler.handleMessage(message);
213
-
214
- expect(response.type).toBe('git:branches:response');
215
-
216
- const payload = response.payload as any;
217
- expect(Array.isArray(payload.branches)).toBe(true);
218
- expect(payload.current).toBe(defaultBranch);
219
- });
220
- });
221
-
222
- describe('handleMessage - git:checkout', () => {
223
- it('should checkout existing branch', async () => {
224
- await simpleGit(testRepoPath).checkoutLocalBranch('feature');
225
-
226
- const message = createMessage('git:checkout', { branch: defaultBranch });
227
- const response = await handler.handleMessage(message);
228
-
229
- expect(response.type).toBe('git:checkout:response');
230
- expect((response.payload as any).success).toBe(true);
231
- });
232
-
233
- it('should create and checkout new branch', async () => {
234
- const message = createMessage('git:checkout', { branch: 'new-feature', create: true });
235
- const response = await handler.handleMessage(message);
236
-
237
- expect(response.type).toBe('git:checkout:response');
238
- expect((response.payload as any).success).toBe(true);
239
-
240
- // Verify branch was created
241
- const branchMsg = createMessage('git:branches', {});
242
- const branchResp = await handler.handleMessage(branchMsg);
243
- expect((branchResp.payload as any).current).toBe('new-feature');
244
- });
245
-
246
- it('should fail for non-existent branch', async () => {
247
- const message = createMessage('git:checkout', { branch: 'nonexistent' });
248
- const response = await handler.handleMessage(message);
249
-
250
- expect(response.type).toBe('git:checkout:response');
251
- expect((response.payload as any).success).toBe(false);
252
- });
253
- });
254
-
255
- describe('handleMessage - git:discard', () => {
256
- it('should discard changes', async () => {
257
- await writeFile(join(testRepoPath, 'README.md'), '# Modified\n');
258
-
259
- const message = createMessage('git:discard', { paths: ['README.md'] });
260
- const response = await handler.handleMessage(message);
261
-
262
- expect(response.type).toBe('git:discard:response');
263
- expect((response.payload as any).success).toBe(true);
264
-
265
- // Verify changes were discarded
266
- const statusMsg = createMessage('git:status', {});
267
- const statusResp = await handler.handleMessage(statusMsg);
268
- expect((statusResp.payload as any).unstaged).toHaveLength(0);
269
- });
270
- });
271
-
272
- describe('handleMessage - unknown type', () => {
273
- it('should return error for unknown message type', async () => {
274
- const message = createMessage('unknown:type' as any, {});
275
- const response = await handler.handleMessage(message);
276
-
277
- expect(response.type).toBe('error');
278
- expect((response.payload as any).code).toBe('UNKNOWN_MESSAGE_TYPE');
279
- });
280
- });
281
-
282
- describe('handleMessage - error handling', () => {
283
- it('should preserve message ID in responses', async () => {
284
- const message = createMessage('ping', {});
285
- message.id = 'custom-id-123';
286
-
287
- const response = await handler.handleMessage(message);
288
-
289
- // pong doesn't preserve ID in current implementation
290
- // but status and other git ops should
291
- expect(response).toBeDefined();
292
- });
293
- });
294
- });