clawdie-ai/setup/telegram-auth.test.ts

210 lines
6.8 KiB
TypeScript
Raw Normal View History

/**
* setup/telegram-auth.test.ts telegram-auth run() unit tests.
*
* Tests env-missing, token-missing, invalid-token, and valid-token paths
* by mocking fs, fetch, emitStatus, and logger.
*
* Run with: npx vitest run setup/telegram-auth.test.ts
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------
vi.mock('../src/logger.js', () => ({
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
const emitStatusMock = vi.hoisted(() => vi.fn());
vi.mock('./status.js', () => ({ emitStatus: emitStatusMock }));
const fsMocks = vi.hoisted(() => ({
existsSync: vi.fn<[string], boolean>(() => false),
readFileSync: vi.fn<[string, string], string>(() => ''),
}));
vi.mock('fs', async () => {
const actual = await vi.importActual<typeof import('fs')>('fs');
const mocked = { ...actual, ...fsMocks };
return { ...mocked, default: mocked };
});
// Capture process.exit calls without terminating
const exitMock = vi.spyOn(process, 'exit').mockImplementation((_code?: number) => {
throw new Error(`process.exit(${_code})`);
});
import { run } from './telegram-auth.js';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeSuccessFetch(username = 'mybot'): typeof fetch {
return vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ ok: true, result: { username } }),
status: 200,
}) as unknown as typeof fetch;
}
function makeFailFetch(description = 'Unauthorized'): typeof fetch {
return vi.fn().mockResolvedValue({
ok: false,
json: async () => ({ ok: false, description }),
status: 401,
}) as unknown as typeof fetch;
}
// ---------------------------------------------------------------------------
// env missing
// ---------------------------------------------------------------------------
describe('run — env file missing', () => {
beforeEach(() => {
emitStatusMock.mockReset();
fsMocks.existsSync.mockReset().mockReturnValue(false);
fsMocks.readFileSync.mockReset();
exitMock.mockClear();
});
it('calls emitStatus with STATUS=failed', async () => {
await expect(run([])).rejects.toThrow('process.exit');
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'failed', ERROR: 'env_missing' }),
);
});
it('calls process.exit(4)', async () => {
await expect(run([])).rejects.toThrow('process.exit(4)');
});
});
// ---------------------------------------------------------------------------
// token missing
// ---------------------------------------------------------------------------
describe('run — token missing from .env', () => {
beforeEach(() => {
emitStatusMock.mockReset();
fsMocks.existsSync.mockReset().mockReturnValue(true);
fsMocks.readFileSync.mockReset().mockReturnValue('SOME_OTHER_KEY=value\n');
exitMock.mockClear();
});
it('calls emitStatus with missing_telegram_bot_token error', async () => {
await expect(run([])).rejects.toThrow('process.exit');
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'failed', ERROR: 'missing_telegram_bot_token' }),
);
});
it('calls process.exit(4)', async () => {
await expect(run([])).rejects.toThrow('process.exit(4)');
});
});
// ---------------------------------------------------------------------------
// invalid token
// ---------------------------------------------------------------------------
describe('run — invalid Telegram token', () => {
beforeEach(() => {
emitStatusMock.mockReset();
fsMocks.existsSync.mockReset().mockReturnValue(true);
fsMocks.readFileSync.mockReset().mockReturnValue('TELEGRAM_BOT_TOKEN=badtoken\n');
exitMock.mockClear();
vi.stubGlobal('fetch', makeFailFetch('Unauthorized'));
});
it('calls emitStatus with AUTH_STATUS=invalid', async () => {
await expect(run([])).rejects.toThrow('process.exit');
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'failed', AUTH_STATUS: 'invalid' }),
);
});
it('calls process.exit(1)', async () => {
await expect(run([])).rejects.toThrow('process.exit(1)');
});
});
// ---------------------------------------------------------------------------
// valid token
// ---------------------------------------------------------------------------
describe('run — valid Telegram token', () => {
beforeEach(() => {
emitStatusMock.mockReset();
fsMocks.existsSync.mockReset().mockReturnValue(true);
fsMocks.readFileSync.mockReset().mockReturnValue('TELEGRAM_BOT_TOKEN=goodtoken123\n');
exitMock.mockClear();
vi.stubGlobal('fetch', makeSuccessFetch('clawdiebot'));
});
it('calls emitStatus with STATUS=success', async () => {
await run([]);
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'success', AUTH_STATUS: 'verified' }),
);
});
it('includes bot username in status', async () => {
await run([]);
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ BOT_USERNAME: 'clawdiebot' }),
);
});
it('does not call process.exit', async () => {
await run([]);
expect(exitMock).not.toHaveBeenCalled();
});
it('parses quoted token values correctly', async () => {
fsMocks.readFileSync.mockReturnValue("TELEGRAM_BOT_TOKEN='quotedtoken'\n");
await run([]);
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'success' }),
);
});
});
// ---------------------------------------------------------------------------
// parseEnv edge cases (tested via run() behaviour)
// ---------------------------------------------------------------------------
describe('run — parseEnv edge cases', () => {
beforeEach(() => {
emitStatusMock.mockReset();
fsMocks.existsSync.mockReset().mockReturnValue(true);
exitMock.mockClear();
vi.stubGlobal('fetch', makeSuccessFetch('bot'));
});
it('ignores comment lines in .env', async () => {
fsMocks.readFileSync.mockReturnValue('# This is a comment\nTELEGRAM_BOT_TOKEN=tok123\n');
await run([]);
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'success' }),
);
});
it('ignores blank lines in .env', async () => {
fsMocks.readFileSync.mockReturnValue('\n\nTELEGRAM_BOT_TOKEN=tok456\n\n');
await run([]);
expect(emitStatusMock).toHaveBeenCalledWith(
'AUTH_TELEGRAM',
expect.objectContaining({ STATUS: 'success' }),
);
});
});