import { describe, it, expect, vi, beforeEach } from 'vitest'; vi.mock('../../auth.js', () => ({ loadSession: vi.fn(), validateSession: vi.fn(), signIn: vi.fn(), saveSession: vi.fn(), })); vi.mock('./login.js', () => ({ getGatewayUrl: vi.fn().mockReturnValue('http://localhost:14242'), promptLine: vi.fn(), promptSecret: vi.fn(), })); vi.mock('./piped-credentials.js', () => ({ readCredentialsFromPipedStdin: vi.fn(), })); vi.mock('./daemon.js', () => ({ readMeta: vi.fn(), writeMeta: vi.fn(), })); import { ensureSession } from './token-ops.js'; import { loadSession, validateSession, signIn, saveSession } from '../../auth.js'; import { promptLine, promptSecret } from './login.js'; import { readCredentialsFromPipedStdin } from './piped-credentials.js'; const URL = 'http://localhost:14242'; function asNonTTY(): void { Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true }); } describe('ensureSession — #1394 credential precedence (flag > piped stdin > prompt)', () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(loadSession).mockReturnValue(null); asNonTTY(); }); it('stored valid session wins; no credentials touched', async () => { vi.mocked(loadSession).mockReturnValue({ cookie: 'SESS', email: 'a@b.c' } as never); vi.mocked(validateSession).mockResolvedValue(true); await expect(ensureSession(URL)).resolves.toBe('SESS'); expect(signIn).not.toHaveBeenCalled(); }); it('flag email + stdin password: FLAG wins for email, stdin supplies the password', async () => { vi.mocked(signIn).mockResolvedValue({ cookie: 'NEW', email: 'flag@b.c' } as never); vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({ email: 'stdin@b.c', password: 'stdin-pw', }); await ensureSession(URL, { email: 'flag@b.c' }); expect(signIn).toHaveBeenCalledWith(URL, 'flag@b.c', 'stdin-pw'); expect(promptLine).not.toHaveBeenCalled(); expect(promptSecret).not.toHaveBeenCalled(); }); it('stdin-only path (no flag): both credentials from piped lines', async () => { vi.mocked(signIn).mockResolvedValue({ cookie: 'NEW2', email: 's@b.c' } as never); vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({ email: 's@b.c', password: 'spw', }); await ensureSession(URL); expect(signIn).toHaveBeenCalledWith(URL, 's@b.c', 'spw'); }); it('no credentials headless → exit(2) with --email guidance; signIn untouched', async () => { vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({ email: null, password: null }); const exit = vi.spyOn(process, 'exit').mockImplementation((() => { throw new Error('EXIT'); }) as never); const err = vi.spyOn(console, 'error').mockImplementation(() => {}); await expect(ensureSession(URL)).rejects.toThrow('EXIT'); expect(exit).toHaveBeenCalledWith(2); expect(err).toHaveBeenCalledWith(expect.stringContaining('--email')); expect(signIn).not.toHaveBeenCalled(); exit.mockRestore(); err.mockRestore(); }); it('successful sign-in persists the session', async () => { vi.mocked(signIn).mockResolvedValue({ cookie: 'C', email: 'a@b.c' } as never); vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({ email: 'a@b.c', password: 'pw', }); await ensureSession(URL); expect(saveSession).toHaveBeenCalledWith(URL, expect.anything()); }); });