WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
1import { execSync } from 'node:child_process';
2import { beforeEach, describe, expect, it, vi } from 'vitest';
3import { createSshKeyCommand } from '../../src/commands/ssh-key.js';
4import * as sessionModule from '../../src/lib/session.js';
5
6vi.mock('node:child_process');
7vi.mock('../../src/lib/session.js');
8
9describe('SSH Key Commands', () => {
10 let consoleLogSpy: ReturnType<typeof vi.fn>;
11 let consoleErrorSpy: ReturnType<typeof vi.fn>;
12 let processExitSpy: ReturnType<typeof vi.fn>;
13
14 beforeEach(() => {
15 vi.clearAllMocks();
16
17 // Mock console methods
18 consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) as never;
19 consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) as never;
20
21 // Mock process.exit to throw to stop execution
22 processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => {
23 throw new Error(`process.exit(${code})`);
24 }) as never;
25 });
26
27 describe('verify command', () => {
28 it('should parse DID from successful SSH response', async () => {
29 // Mock successful SSH response with actual format from tangled.org
30 const mockSshOutput =
31 "Hi @did:plc:b2mcbcamkwyznc5fkplwlxbf! You've successfully authenticated.\n";
32
33 vi.mocked(execSync).mockImplementation(() => {
34 // ssh -T returns non-zero exit code even on success, throw with stderr
35 const error = new Error('SSH command') as Error & { stderr: string };
36 error.stderr = mockSshOutput;
37 throw error;
38 });
39
40 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue(null);
41
42 const sshKey = createSshKeyCommand();
43 await sshKey.parseAsync(['node', 'test', 'verify']);
44
45 expect(consoleLogSpy).toHaveBeenCalledWith(
46 expect.stringContaining('SSH authentication successful')
47 );
48 expect(consoleLogSpy).toHaveBeenCalledWith(
49 expect.stringContaining('did:plc:b2mcbcamkwyznc5fkplwlxbf')
50 );
51 expect(consoleLogSpy).toHaveBeenCalledWith(
52 expect.stringContaining('Your SSH setup is working correctly')
53 );
54 });
55
56 it('should show handle when logged in user matches SSH DID', async () => {
57 const mockDid = 'did:plc:b2mcbcamkwyznc5fkplwlxbf';
58 const mockSshOutput = `Hi @${mockDid}! You've successfully authenticated.\n`;
59
60 vi.mocked(execSync).mockImplementation(() => {
61 const error = new Error('SSH command') as Error & { stderr: string };
62 error.stderr = mockSshOutput;
63 throw error;
64 });
65
66 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue({
67 handle: 'user.bsky.social',
68 did: mockDid,
69 pds: 'https://bsky.social',
70 lastUsed: new Date().toISOString(),
71 });
72
73 const sshKey = createSshKeyCommand();
74 await sshKey.parseAsync(['node', 'test', 'verify']);
75
76 expect(consoleLogSpy).toHaveBeenCalledWith(
77 expect.stringContaining('did:plc:b2mcbcamkwyznc5fkplwlxbf')
78 );
79 expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('@user.bsky.social'));
80 });
81
82 it('should not show handle when logged in user does not match SSH DID', async () => {
83 const mockSshOutput =
84 "Hi @did:plc:b2mcbcamkwyznc5fkplwlxbf! You've successfully authenticated.\n";
85
86 vi.mocked(execSync).mockImplementation(() => {
87 const error = new Error('SSH command') as Error & { stderr: string };
88 error.stderr = mockSshOutput;
89 throw error;
90 });
91
92 vi.mocked(sessionModule.getCurrentSessionMetadata).mockResolvedValue({
93 handle: 'otheruser.bsky.social',
94 did: 'did:plc:differentuser',
95 pds: 'https://bsky.social',
96 lastUsed: new Date().toISOString(),
97 });
98
99 const sshKey = createSshKeyCommand();
100 await sshKey.parseAsync(['node', 'test', 'verify']);
101
102 expect(consoleLogSpy).toHaveBeenCalledWith(
103 expect.stringContaining('did:plc:b2mcbcamkwyznc5fkplwlxbf')
104 );
105 expect(consoleLogSpy).not.toHaveBeenCalledWith(
106 expect.stringContaining('@otheruser.bsky.social')
107 );
108 });
109
110 it('should handle SSH authentication failure', async () => {
111 const mockSshOutput = 'Permission denied (publickey).\n';
112
113 vi.mocked(execSync).mockImplementation(() => {
114 const error = new Error('SSH command') as Error & { stderr: string };
115 error.stderr = mockSshOutput;
116 throw error;
117 });
118
119 const sshKey = createSshKeyCommand();
120 await expect(sshKey.parseAsync(['node', 'test', 'verify'])).rejects.toThrow('process.exit');
121
122 expect(consoleErrorSpy).toHaveBeenCalledWith(
123 expect.stringContaining('SSH authentication failed')
124 );
125 expect(consoleErrorSpy).toHaveBeenCalledWith(
126 expect.stringContaining('Could not find authenticated DID')
127 );
128 expect(processExitSpy).toHaveBeenCalledWith(1);
129 });
130
131 it('should provide helpful error message on failure', async () => {
132 const mockSshOutput = 'Connection refused';
133
134 vi.mocked(execSync).mockImplementation(() => {
135 const error = new Error('SSH command') as Error & { stderr: string };
136 error.stderr = mockSshOutput;
137 throw error;
138 });
139
140 const sshKey = createSshKeyCommand();
141 await expect(sshKey.parseAsync(['node', 'test', 'verify'])).rejects.toThrow('process.exit');
142
143 expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Generated an SSH key'));
144 expect(consoleErrorSpy).toHaveBeenCalledWith(
145 expect.stringContaining('tangled.org/settings/keys')
146 );
147 expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('SSH agent is running'));
148 });
149 });
150});