210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
|
|
/**
|
||
|
|
* 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' }),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|