- src/upstream/status.test.ts: 16 tests for getUpstreamStatus() covering disabled, not_configured, up_to_date, and updates_available paths - src/channels/registry.test.ts: 6 tests for registerChannel() and getAllChannelFactories() module-level registry operations - setup/telegram-auth.test.ts: 12 tests for run() covering missing env, missing token, invalid token, valid token, and parseEnv edge cases Total: 91 test files / 1527 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- Build: FAIL | Tests: pass — Tests 1527 passed (1527)
209 lines
6.8 KiB
TypeScript
209 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' }),
|
|
);
|
|
});
|
|
});
|