Skip to content

Commit 96b78f7

Browse files
committed
🔧 update(tests): enhance mocks for TOTP and backup codes in setup tests
1 parent 0e0d369 commit 96b78f7

3 files changed

Lines changed: 82 additions & 24 deletions

File tree

src/cli/tests/commands/setup.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ mock.module('@tinyclaw/config', () => ({
8888
// ── Mock @tinyclaw/core ─────────────────────────────────────────────
8989

9090
const mockIsAvailable = mock(() => Promise.resolve(true));
91+
const mockGenerateTotpSecret = mock(() => 'JBSWY3DPEHPK3PXP');
92+
const mockCreateTotpUri = mock(() => 'otpauth://totp/TinyClaw?secret=JBSWY3DPEHPK3PXP');
93+
const mockVerifyTotpCode = mock(() => Promise.resolve(true));
94+
const mockGenerateBackupCodes = mock(() => Array.from({ length: 10 }, (_, i) => `BACKUP${String(i).padStart(2, '0')}`));
95+
const mockGenerateRecoveryToken = mock(() => 'RECOVERYTOKEN'.repeat(5));
96+
const mockSha256 = mock(() => Promise.resolve('abc123'));
9197

9298
mock.module('@tinyclaw/core', () => ({
9399
createOllamaProvider: mock(() => ({
@@ -96,6 +102,49 @@ mock.module('@tinyclaw/core', () => ({
96102
DEFAULT_PROVIDER: 'ollama',
97103
DEFAULT_MODEL: 'kimi-k2.5:cloud',
98104
DEFAULT_BASE_URL: 'https://ollama.com',
105+
SECURITY_WARNING_TITLE: 'Security Warning',
106+
SECURITY_WARNING_BODY: 'Test body',
107+
SECURITY_LICENSE: 'Test license',
108+
SECURITY_WARRANTY: 'Test warranty',
109+
SECURITY_SAFETY_TITLE: 'Safety',
110+
SECURITY_SAFETY_PRACTICES: ['Practice 1'],
111+
SECURITY_CONFIRM: 'Do you accept?',
112+
defaultModelNote: mock((model: string) => `Using ${model}`),
113+
TOTP_SETUP_TITLE: 'TOTP Setup',
114+
TOTP_SETUP_BODY: 'Set up 2FA',
115+
BACKUP_CODES_INTRO: 'Save these codes',
116+
BACKUP_CODES_HINT: 'Keep them safe',
117+
RECOVERY_TOKEN_HINT: 'Store securely',
118+
generateTotpSecret: mockGenerateTotpSecret,
119+
createTotpUri: mockCreateTotpUri,
120+
verifyTotpCode: mockVerifyTotpCode,
121+
generateBackupCodes: mockGenerateBackupCodes,
122+
generateRecoveryToken: mockGenerateRecoveryToken,
123+
sha256: mockSha256,
124+
BACKUP_CODES_COUNT: 10,
125+
}));
126+
127+
// ── Mock @tinyclaw/web ──────────────────────────────────────────────
128+
129+
mock.module('@tinyclaw/web', () => ({
130+
createWebUI: mock(() => ({
131+
start: mock(() => Promise.resolve()),
132+
stop: mock(() => Promise.resolve()),
133+
})),
134+
}));
135+
136+
// ── Mock qrcode ─────────────────────────────────────────────────────
137+
138+
mock.module('qrcode', () => ({
139+
default: {
140+
toString: mock(() => Promise.resolve('██ QR CODE ██')),
141+
},
142+
}));
143+
144+
// ── Mock child_process (prevents execSync clipboard calls) ──────────
145+
146+
mock.module('child_process', () => ({
147+
execSync: mock(() => {}),
99148
}));
100149

101150
// ── Mock @tinyclaw/heartware ────────────────────────────────────────
@@ -171,6 +220,12 @@ beforeEach(() => {
171220
mockConfigSet.mockClear();
172221
mockConfigClose.mockClear();
173222
mockIsAvailable.mockClear();
223+
mockGenerateTotpSecret.mockClear();
224+
mockCreateTotpUri.mockClear();
225+
mockVerifyTotpCode.mockClear();
226+
mockGenerateBackupCodes.mockClear();
227+
mockGenerateRecoveryToken.mockClear();
228+
mockSha256.mockClear();
174229
mockParseSeed.mockClear();
175230
mockGenerateRandomSeed.mockClear();
176231
mockGenerateSoul.mockClear();
@@ -183,6 +238,12 @@ beforeEach(() => {
183238
mockText.mockImplementation(() => '');
184239
mockSelect.mockImplementation(() => 'keep');
185240
mockIsAvailable.mockImplementation(() => Promise.resolve(true));
241+
mockGenerateTotpSecret.mockImplementation(() => 'JBSWY3DPEHPK3PXP');
242+
mockCreateTotpUri.mockImplementation(() => 'otpauth://totp/TinyClaw?secret=JBSWY3DPEHPK3PXP');
243+
mockVerifyTotpCode.mockImplementation(() => Promise.resolve(true));
244+
mockGenerateBackupCodes.mockImplementation(() => Array.from({ length: 10 }, (_, i) => `BACKUP${String(i).padStart(2, '0')}`));
245+
mockGenerateRecoveryToken.mockImplementation(() => 'RECOVERYTOKEN'.repeat(5));
246+
mockSha256.mockImplementation(() => Promise.resolve('abc123'));
186247
mockParseSeed.mockImplementation((input: unknown) => {
187248
const n = Number(input);
188249
if (!input || isNaN(n)) return 42;

src/cli/tests/commands/start.test.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,6 @@
88

99
import { afterEach, beforeAll, beforeEach, describe, expect, test, mock, jest } from 'bun:test';
1010

11-
// ── Mock picocolors (used by local CLI theme) ───────────────────────
12-
13-
mock.module('picocolors', () => ({
14-
default: {
15-
cyan: (text: string) => text,
16-
green: (text: string) => text,
17-
yellow: (text: string) => text,
18-
red: (text: string) => text,
19-
dim: (text: string) => text,
20-
bold: (text: string) => text,
21-
white: (text: string) => text,
22-
},
23-
}));
24-
2511
// ── Mock @tinyclaw/secrets ───────────────────────────────────────────
2612

2713
const mockSecretsCheck = mock(() => Promise.resolve(true));
@@ -46,6 +32,8 @@ mock.module('@tinyclaw/secrets', () => ({
4632
const mockConfigGet = mock((key: string) => {
4733
if (key === 'providers.starterBrain.model') return 'kimi-k2.5:cloud';
4834
if (key === 'providers.starterBrain.baseUrl') return 'https://ollama.com';
35+
if (key === 'heartware.seed') return 42;
36+
if (key === 'owner.ownerId') return 'cli:owner';
4937
return undefined;
5038
});
5139
const mockConfigClose = mock(() => {});
@@ -129,10 +117,10 @@ mock.module('@tinyclaw/queue', () => ({
129117

130118
mock.module('@tinyclaw/logger', () => ({
131119
logger: {
132-
log: mock(() => {}),
133-
info: mock(() => {}),
134-
warn: mock(() => {}),
135-
error: mock(() => {}),
120+
log: mock((...args: any[]) => { console.log(...args); }),
121+
info: mock((...args: any[]) => { console.log(...args); }),
122+
warn: mock((...args: any[]) => { console.log(...args); }),
123+
error: mock((...args: any[]) => { console.log(...args); }),
136124
debug: mock(() => {}),
137125
},
138126
setLogMode: mock(() => {}),
@@ -290,11 +278,17 @@ beforeEach(() => {
290278

291279
process.exit = ((code?: number) => {
292280
exitCode = code ?? 0;
293-
throw new Error(`process.exit(${code})`);
294281
}) as never;
295282

296283
// Reset to defaults
297284
mockSecretsCheck.mockImplementation(() => Promise.resolve(true));
285+
mockConfigGet.mockImplementation((key: string) => {
286+
if (key === 'providers.starterBrain.model') return 'kimi-k2.5:cloud';
287+
if (key === 'providers.starterBrain.baseUrl') return 'https://ollama.com';
288+
if (key === 'heartware.seed') return 42;
289+
if (key === 'owner.ownerId') return 'cli:owner';
290+
return undefined;
291+
});
298292
});
299293

300294
afterEach(() => {

src/web/tests/server.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ async function generateTotp(secret: string): Promise<string> {
7575
}
7676

7777
function extractBootstrapSecret(logs: string[]): string {
78-
const full = logs.join('\n');
79-
const match = full.match(/Secret:\s+([A-Z2-9]{30})/);
78+
const full = logs.join('\n').replace(/\x1b\[[0-9;]*m/g, '');
79+
const match = full.match(/secret:\s+([A-Z2-9]{30})/i);
8080
if (!match) throw new Error('bootstrap secret not found in logs');
8181
return match[1];
8282
}
@@ -134,6 +134,10 @@ describe('setup and MFA flow', () => {
134134
async store(key: string, value: string) {
135135
storedSecrets.push({ key, value });
136136
},
137+
async retrieve(key: string): Promise<string | undefined> {
138+
const entry = [...storedSecrets].reverse().find(s => s.key === key);
139+
return entry?.value;
140+
},
137141
};
138142

139143
afterEach(async () => {
@@ -202,9 +206,8 @@ describe('setup and MFA flow', () => {
202206
expect(configStore.get('heartware.seed')).toBe(8675309);
203207
expect(configStore.get('owner.backupCodesRemaining')).toBe(10);
204208
expect(typeof configStore.get('owner.recoveryTokenHash')).toBe('string');
205-
expect(storedSecrets).toEqual([
206-
{ key: 'provider.ollama.apiKey', value: 'ollama-test-key' },
207-
]);
209+
expect(storedSecrets.some(s => s.key === 'provider.ollama.apiKey' && s.value === 'ollama-test-key')).toBe(true);
210+
expect(storedSecrets.some(s => s.key === 'owner.totpSecret')).toBe(true);
208211
});
209212

210213
test('login only accepts TOTP — rejects backup code via login endpoint', async () => {

0 commit comments

Comments
 (0)