ultraclaude-agent 0.0.22 → 0.0.23
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/__tests__/daemon-reconcile.test.ts +1 -0
- package/__tests__/daemon.test.ts +1 -0
- package/__tests__/hide-branches.test.ts +129 -0
- package/__tests__/logger-multistream.test.ts +151 -0
- package/__tests__/repl-reset.test.ts +3 -0
- package/__tests__/repl-status-account.test.ts +3 -0
- package/__tests__/repl.test.ts +7 -2
- package/__tests__/snapshot-sync.test.ts +6 -6
- package/__tests__/status-command.test.ts +479 -0
- package/__tests__/status-service-type.test.ts +177 -0
- package/__tests__/sync-bugs.test.ts +8 -7
- package/__tests__/sync-queue-credentials.test.ts +4 -4
- package/__tests__/sync-reorder.test.ts +8 -8
- package/__tests__/sync.test.ts +4 -3
- package/__tests__/version-check.test.ts +1 -1
- package/__tests__/version-watcher.test.ts +8 -2
- package/__tests__/watcher-branch.test.ts +68 -0
- package/dist/cli.js +6 -96
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +3 -70
- package/dist/daemon.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +6 -143
- package/dist/repl.js.map +1 -1
- package/dist/sync.d.ts +13 -8
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +45 -21
- package/dist/sync.js.map +1 -1
- package/dist/watcher.d.ts +6 -0
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +92 -7
- package/dist/watcher.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js +8 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.d.ts +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.js +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.d.ts +3 -3
- package/node_modules/@ultra-claude/shared/dist/index.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.js +2 -2
- package/node_modules/@ultra-claude/shared/dist/index.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/types.d.ts +0 -32
- package/node_modules/@ultra-claude/shared/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +6 -120
- package/src/daemon.ts +3 -82
- package/src/repl.ts +6 -166
- package/src/sync.ts +56 -14
- package/src/watcher.ts +101 -7
- package/__tests__/claude-profiles-ops.test.ts +0 -441
- package/__tests__/claude-profiles.test.ts +0 -407
- package/__tests__/credential-watcher.test.ts +0 -229
- package/dist/claude-profiles.d.ts +0 -83
- package/dist/claude-profiles.d.ts.map +0 -1
- package/dist/claude-profiles.js +0 -548
- package/dist/claude-profiles.js.map +0 -1
- package/src/claude-profiles.ts +0 -650
|
@@ -77,6 +77,7 @@ vi.mock('../src/sync.js', () => ({
|
|
|
77
77
|
const mockStartProjectWatcher = vi.fn();
|
|
78
78
|
vi.mock('../src/watcher.js', () => ({
|
|
79
79
|
startProjectWatcher: (...args: unknown[]) => mockStartProjectWatcher(...args),
|
|
80
|
+
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
80
81
|
}));
|
|
81
82
|
|
|
82
83
|
// Mock usage-sync
|
package/__tests__/daemon.test.ts
CHANGED
|
@@ -75,6 +75,7 @@ vi.mock('../src/sync.js', () => ({
|
|
|
75
75
|
const mockStartProjectWatcher = vi.fn();
|
|
76
76
|
vi.mock('../src/watcher.js', () => ({
|
|
77
77
|
startProjectWatcher: (...args: unknown[]) => mockStartProjectWatcher(...args),
|
|
78
|
+
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
78
79
|
}));
|
|
79
80
|
|
|
80
81
|
// Mock usage-sync
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for hideBranches() in sync.ts (Plan 032, Task 1).
|
|
3
|
+
*
|
|
4
|
+
* Verifies branch hide notification is sent correctly to the server.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
|
|
9
|
+
// Mock fetch globally
|
|
10
|
+
const mockFetch = vi.fn();
|
|
11
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
12
|
+
|
|
13
|
+
// Mock config module
|
|
14
|
+
vi.mock('../src/config.js', () => ({
|
|
15
|
+
loadCredentials: vi.fn().mockResolvedValue({
|
|
16
|
+
apiKey: 'test-api-key',
|
|
17
|
+
userId: 'test-user',
|
|
18
|
+
serverUrl: 'http://localhost:3000',
|
|
19
|
+
}),
|
|
20
|
+
getServerUrl: vi.fn().mockReturnValue('http://localhost:3000'),
|
|
21
|
+
getProjectId: vi.fn().mockResolvedValue('test-project-id'),
|
|
22
|
+
writeProjectId: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
paths: {
|
|
24
|
+
claudeProjects: '/tmp/test-claude-projects',
|
|
25
|
+
projectIdFile: '.claude/ultra/project-id',
|
|
26
|
+
oldConfigDir: '/tmp/test-old-config',
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Mock logger
|
|
31
|
+
vi.mock('../src/logger.js', () => ({
|
|
32
|
+
logger: {
|
|
33
|
+
child: () => ({
|
|
34
|
+
info: vi.fn(),
|
|
35
|
+
warn: vi.fn(),
|
|
36
|
+
error: vi.fn(),
|
|
37
|
+
debug: vi.fn(),
|
|
38
|
+
}),
|
|
39
|
+
info: vi.fn(),
|
|
40
|
+
warn: vi.fn(),
|
|
41
|
+
error: vi.fn(),
|
|
42
|
+
debug: vi.fn(),
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const testCreds = { apiKey: 'test-api-key', userId: 'test-user', serverUrl: 'http://localhost:3000' };
|
|
47
|
+
|
|
48
|
+
describe('hideBranches', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.resetModules();
|
|
51
|
+
mockFetch.mockReset();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('POSTs to /api/sync/branch-hide with projectId and branches', async () => {
|
|
55
|
+
const { hideBranches } = await import('../src/sync.js');
|
|
56
|
+
|
|
57
|
+
mockFetch.mockResolvedValueOnce({
|
|
58
|
+
ok: true,
|
|
59
|
+
json: async () => ({ data: {} }),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = await hideBranches('proj-uuid', ['feature/old', 'plan/done'], testCreds);
|
|
63
|
+
expect(result).toBe(true);
|
|
64
|
+
|
|
65
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
66
|
+
const [url, options] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
67
|
+
expect(url).toContain('/api/sync/branch-hide');
|
|
68
|
+
expect(options.method).toBe('POST');
|
|
69
|
+
|
|
70
|
+
const body = JSON.parse(options.body as string) as { projectId: string; branches: string[] };
|
|
71
|
+
expect(body.projectId).toBe('proj-uuid');
|
|
72
|
+
expect(body.branches).toEqual(['feature/old', 'plan/done']);
|
|
73
|
+
|
|
74
|
+
const headers = options.headers as Record<string, string>;
|
|
75
|
+
expect(headers['Idempotency-Key']).toBeDefined();
|
|
76
|
+
expect(headers['Idempotency-Key']).toMatch(
|
|
77
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns false when server returns error', async () => {
|
|
82
|
+
const { hideBranches } = await import('../src/sync.js');
|
|
83
|
+
|
|
84
|
+
mockFetch.mockResolvedValueOnce({
|
|
85
|
+
ok: false,
|
|
86
|
+
status: 500,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const result = await hideBranches('proj-uuid', ['feature/old'], testCreds);
|
|
90
|
+
expect(result).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns false when network is unreachable', async () => {
|
|
94
|
+
const { hideBranches } = await import('../src/sync.js');
|
|
95
|
+
|
|
96
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
97
|
+
|
|
98
|
+
const result = await hideBranches('proj-uuid', ['feature/old'], testCreds);
|
|
99
|
+
expect(result).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns false when server returns 404 (project not found)', async () => {
|
|
103
|
+
const { hideBranches } = await import('../src/sync.js');
|
|
104
|
+
|
|
105
|
+
mockFetch.mockResolvedValueOnce({
|
|
106
|
+
ok: false,
|
|
107
|
+
status: 404,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const result = await hideBranches('proj-uuid', ['main'], testCreds);
|
|
111
|
+
expect(result).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('sends multiple branches in a single request', async () => {
|
|
115
|
+
const { hideBranches } = await import('../src/sync.js');
|
|
116
|
+
|
|
117
|
+
mockFetch.mockResolvedValueOnce({
|
|
118
|
+
ok: true,
|
|
119
|
+
json: async () => ({ data: {} }),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const branches = ['feature/a', 'feature/b', 'plan/001', 'plan/002'];
|
|
123
|
+
await hideBranches('proj-uuid', branches, testCreds);
|
|
124
|
+
|
|
125
|
+
const body = JSON.parse((mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string) as { branches: string[] };
|
|
126
|
+
expect(body.branches).toHaveLength(4);
|
|
127
|
+
expect(body.branches).toEqual(branches);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for unified logging setup (initMultistreamLogger / initFileLogger).
|
|
3
|
+
* Plan 030 Task 1 regression: "daemon.log contains structured JSON in both foreground and background mode"
|
|
4
|
+
* and "Foreground mode also outputs to stdout".
|
|
5
|
+
*
|
|
6
|
+
* Verifies that:
|
|
7
|
+
* - initMultistreamLogger sets up pino with BOTH file (JSON) and stdout (pretty) transports
|
|
8
|
+
* - initFileLogger sets up pino with ONLY a file (JSON) transport
|
|
9
|
+
* - Both write to daemon.log in the correct log directory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
13
|
+
|
|
14
|
+
// Mock pino so we don't spin up real worker-thread file transports
|
|
15
|
+
const mockTransport = vi.fn();
|
|
16
|
+
const mockPinoInstance = {
|
|
17
|
+
info: vi.fn(),
|
|
18
|
+
warn: vi.fn(),
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
debug: vi.fn(),
|
|
21
|
+
child: vi.fn().mockReturnThis(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockPino = vi.fn().mockReturnValue(mockPinoInstance);
|
|
25
|
+
(mockPino as unknown as { transport: typeof mockTransport }).transport = mockTransport;
|
|
26
|
+
mockTransport.mockReturnValue('mock-transport');
|
|
27
|
+
|
|
28
|
+
vi.mock('pino', () => ({
|
|
29
|
+
default: Object.assign(mockPino, { transport: mockTransport }),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// Mock resolveServerPaths to return controlled paths
|
|
33
|
+
const mockResolveServerPaths = vi.fn().mockReturnValue({
|
|
34
|
+
logDir: '/tmp/test-logs',
|
|
35
|
+
accountsDir: '/tmp/accounts',
|
|
36
|
+
configFile: '/tmp/config.json',
|
|
37
|
+
statusFile: '/tmp/status.json',
|
|
38
|
+
pidFile: '/tmp/daemon.pid',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
vi.mock('../src/config.js', () => ({
|
|
42
|
+
resolveServerPaths: (...args: unknown[]) => mockResolveServerPaths(...args),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
describe('logger — initMultistreamLogger (foreground mode)', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
mockTransport.mockReturnValue('mock-transport');
|
|
49
|
+
(mockPino as unknown as { transport: typeof mockTransport }).transport = mockTransport;
|
|
50
|
+
mockPino.mockReturnValue(mockPinoInstance);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
vi.resetModules();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('calls pino.transport with two targets (file + stdout)', async () => {
|
|
58
|
+
const { initMultistreamLogger } = await import('../src/logger.js');
|
|
59
|
+
initMultistreamLogger('http://localhost:3000');
|
|
60
|
+
|
|
61
|
+
expect(mockTransport).toHaveBeenCalledOnce();
|
|
62
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { targets: Array<{ target: string; options?: Record<string, unknown> }> };
|
|
63
|
+
expect(transportArgs).toHaveProperty('targets');
|
|
64
|
+
expect(transportArgs.targets).toHaveLength(2);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('includes pino/file target writing to daemon.log', async () => {
|
|
68
|
+
const { initMultistreamLogger } = await import('../src/logger.js');
|
|
69
|
+
initMultistreamLogger('http://localhost:3000');
|
|
70
|
+
|
|
71
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { targets: Array<{ target: string; options?: Record<string, unknown> }> };
|
|
72
|
+
const fileTarget = transportArgs.targets.find((t) => t.target === 'pino/file');
|
|
73
|
+
expect(fileTarget).toBeDefined();
|
|
74
|
+
expect(fileTarget!.options?.destination).toMatch(/daemon\.log$/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('includes pino-pretty target writing to stdout (destination: 1)', async () => {
|
|
78
|
+
const { initMultistreamLogger } = await import('../src/logger.js');
|
|
79
|
+
initMultistreamLogger('http://localhost:3000');
|
|
80
|
+
|
|
81
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { targets: Array<{ target: string; options?: Record<string, unknown> }> };
|
|
82
|
+
const prettyTarget = transportArgs.targets.find((t) => t.target === 'pino-pretty');
|
|
83
|
+
expect(prettyTarget).toBeDefined();
|
|
84
|
+
expect(prettyTarget!.options?.destination).toBe(1); // stdout fd
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('creates the pino logger with the transport stream', async () => {
|
|
88
|
+
const { initMultistreamLogger } = await import('../src/logger.js');
|
|
89
|
+
initMultistreamLogger('http://localhost:3000');
|
|
90
|
+
|
|
91
|
+
// pino() should have been called with the transport returned by pino.transport()
|
|
92
|
+
expect(mockPino).toHaveBeenCalledWith(
|
|
93
|
+
expect.objectContaining({ level: expect.any(String) }),
|
|
94
|
+
'mock-transport',
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('writes to log dir from resolveServerPaths', async () => {
|
|
99
|
+
const { initMultistreamLogger } = await import('../src/logger.js');
|
|
100
|
+
initMultistreamLogger('http://localhost:3000');
|
|
101
|
+
|
|
102
|
+
expect(mockResolveServerPaths).toHaveBeenCalledWith('http://localhost:3000');
|
|
103
|
+
|
|
104
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { targets: Array<{ target: string; options?: { destination?: string } }> };
|
|
105
|
+
const fileTarget = transportArgs.targets.find((t) => t.target === 'pino/file');
|
|
106
|
+
expect(fileTarget?.options?.destination).toContain('/tmp/test-logs');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('logger — initFileLogger (background mode)', () => {
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
vi.clearAllMocks();
|
|
113
|
+
mockTransport.mockReturnValue('mock-transport');
|
|
114
|
+
(mockPino as unknown as { transport: typeof mockTransport }).transport = mockTransport;
|
|
115
|
+
mockPino.mockReturnValue(mockPinoInstance);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
vi.resetModules();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('calls pino.transport with a single pino/file target (no stdout)', async () => {
|
|
123
|
+
const { initFileLogger } = await import('../src/logger.js');
|
|
124
|
+
initFileLogger('http://localhost:3000');
|
|
125
|
+
|
|
126
|
+
expect(mockTransport).toHaveBeenCalledOnce();
|
|
127
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { target?: string; targets?: unknown[] };
|
|
128
|
+
|
|
129
|
+
// Single target form (not array) — background daemon writes only to file
|
|
130
|
+
expect(transportArgs.target).toBe('pino/file');
|
|
131
|
+
expect(transportArgs.targets).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('writes to daemon.log in log directory', async () => {
|
|
135
|
+
const { initFileLogger } = await import('../src/logger.js');
|
|
136
|
+
initFileLogger('http://localhost:3000');
|
|
137
|
+
|
|
138
|
+
const transportArgs = mockTransport.mock.calls[0]![0] as { options?: { destination?: string } };
|
|
139
|
+
expect(transportArgs.options?.destination).toMatch(/daemon\.log$/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('creates pino instance with the file transport', async () => {
|
|
143
|
+
const { initFileLogger } = await import('../src/logger.js');
|
|
144
|
+
initFileLogger('http://localhost:3000');
|
|
145
|
+
|
|
146
|
+
expect(mockPino).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({ level: expect.any(String) }),
|
|
148
|
+
'mock-transport',
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -93,6 +93,9 @@ vi.mock('../src/sync.js', () => ({
|
|
|
93
93
|
initialSync: vi.fn().mockResolvedValue(undefined),
|
|
94
94
|
createSnapshot: vi.fn().mockResolvedValue(true),
|
|
95
95
|
}));
|
|
96
|
+
vi.mock('../src/watcher.js', () => ({
|
|
97
|
+
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
98
|
+
}));
|
|
96
99
|
|
|
97
100
|
vi.mock('../src/logger.js', () => ({
|
|
98
101
|
logger: {
|
|
@@ -73,6 +73,9 @@ vi.mock('../src/sync.js', () => ({
|
|
|
73
73
|
initialSync: vi.fn().mockResolvedValue(undefined),
|
|
74
74
|
createSnapshot: vi.fn().mockResolvedValue(true),
|
|
75
75
|
}));
|
|
76
|
+
vi.mock('../src/watcher.js', () => ({
|
|
77
|
+
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
78
|
+
}));
|
|
76
79
|
vi.mock('../src/logger.js', () => ({
|
|
77
80
|
logger: {
|
|
78
81
|
child: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }),
|
package/__tests__/repl.test.ts
CHANGED
|
@@ -91,6 +91,11 @@ vi.mock('../src/sync.js', () => ({
|
|
|
91
91
|
createSnapshot: (...args: unknown[]) => mockCreateSnapshot(...args),
|
|
92
92
|
}));
|
|
93
93
|
|
|
94
|
+
// Mock watcher (getCurrentBranch is imported by repl.ts)
|
|
95
|
+
vi.mock('../src/watcher.js', () => ({
|
|
96
|
+
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
97
|
+
}));
|
|
98
|
+
|
|
94
99
|
// Mock logger
|
|
95
100
|
vi.mock('../src/logger.js', () => ({
|
|
96
101
|
logger: {
|
|
@@ -775,7 +780,7 @@ describe('repl — start/stop/status/push/snapshot commands', () => {
|
|
|
775
780
|
try {
|
|
776
781
|
await startRepl(SERVER_URL);
|
|
777
782
|
await runCommand('push');
|
|
778
|
-
expect(mockInitialSync).toHaveBeenCalledWith('proj-uuid-1', '/home/user/projects/my-app', alice);
|
|
783
|
+
expect(mockInitialSync).toHaveBeenCalledWith('proj-uuid-1', '/home/user/projects/my-app', alice, 'main');
|
|
779
784
|
} finally {
|
|
780
785
|
consoleSpy.mockRestore();
|
|
781
786
|
}
|
|
@@ -824,7 +829,7 @@ describe('repl — start/stop/status/push/snapshot commands', () => {
|
|
|
824
829
|
expect(mockCreateSnapshot).toHaveBeenCalledWith(
|
|
825
830
|
'proj-uuid-1',
|
|
826
831
|
expect.stringContaining('release v1.0'),
|
|
827
|
-
undefined, undefined, alice,
|
|
832
|
+
'main', undefined, undefined, alice,
|
|
828
833
|
);
|
|
829
834
|
} finally {
|
|
830
835
|
consoleSpy.mockRestore();
|
|
@@ -65,7 +65,7 @@ describe('createSnapshot — Idempotency-Key header (Fix A)', () => {
|
|
|
65
65
|
json: async () => ({ data: { id: 'snap-uuid' } }),
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
await createSnapshot('proj-uuid', 'test snapshot', 'abc123', 'git_commit', testCreds);
|
|
68
|
+
await createSnapshot('proj-uuid', 'test snapshot', 'main', 'abc123', 'git_commit', testCreds);
|
|
69
69
|
|
|
70
70
|
expect(mockFetch).toHaveBeenCalledOnce();
|
|
71
71
|
const [, options] = mockFetch.mock.calls[0];
|
|
@@ -86,7 +86,7 @@ describe('createSnapshot — Idempotency-Key header (Fix A)', () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
// No credentials provided — uses apiRequestWithDiskCredentials
|
|
89
|
-
await createSnapshot('proj-uuid', 'test snapshot');
|
|
89
|
+
await createSnapshot('proj-uuid', 'test snapshot', 'main');
|
|
90
90
|
|
|
91
91
|
expect(mockFetch).toHaveBeenCalledOnce();
|
|
92
92
|
const [, options] = mockFetch.mock.calls[0];
|
|
@@ -269,7 +269,7 @@ describe('flushQueue — corrupted item isolation (Fix C)', () => {
|
|
|
269
269
|
await writeFile(filePath, '# Valid\n\nContent.');
|
|
270
270
|
|
|
271
271
|
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
272
|
-
await syncMarkdownFile('test-project', tempDir, filePath, testCreds);
|
|
272
|
+
await syncMarkdownFile('test-project', tempDir, filePath, testCreds, 'main');
|
|
273
273
|
expect(getQueueSize()).toBe(1);
|
|
274
274
|
|
|
275
275
|
// Now corrupt the queued item's payload by directly manipulating the queue
|
|
@@ -280,7 +280,7 @@ describe('flushQueue — corrupted item isolation (Fix C)', () => {
|
|
|
280
280
|
await writeFile(filePath2, '# Valid2\n\nContent2.');
|
|
281
281
|
|
|
282
282
|
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
283
|
-
await syncMarkdownFile('test-project', tempDir, filePath2, testCreds);
|
|
283
|
+
await syncMarkdownFile('test-project', tempDir, filePath2, testCreds, 'main');
|
|
284
284
|
expect(getQueueSize()).toBe(2);
|
|
285
285
|
|
|
286
286
|
// On flush, make first apiRequest call throw unexpectedly (simulating corrupted processing)
|
|
@@ -315,7 +315,7 @@ describe('flushQueue — corrupted item isolation (Fix C)', () => {
|
|
|
315
315
|
|
|
316
316
|
// Queue an item
|
|
317
317
|
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
318
|
-
await syncMarkdownFile('test-project', tempDir, filePath, testCreds);
|
|
318
|
+
await syncMarkdownFile('test-project', tempDir, filePath, testCreds, 'main');
|
|
319
319
|
expect(getQueueSize()).toBe(1);
|
|
320
320
|
|
|
321
321
|
// On flush, return 400 (permanent failure) — item should be dropped
|
|
@@ -361,7 +361,7 @@ describe('regression — syncMarkdownFile still sends Idempotency-Key', () => {
|
|
|
361
361
|
json: async () => ({ data: { upserted: 1 } }),
|
|
362
362
|
});
|
|
363
363
|
|
|
364
|
-
await syncMarkdownFile('test-project', tempDir, filePath, testCreds);
|
|
364
|
+
await syncMarkdownFile('test-project', tempDir, filePath, testCreds, 'main');
|
|
365
365
|
|
|
366
366
|
const postCall = mockFetch.mock.calls.find(
|
|
367
367
|
(call: unknown[]) =>
|