claude-login.test.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import fs from 'node:fs'
  2. import os from 'node:os'
  3. import path from 'node:path'
  4. import { describe, expect, it } from 'vitest'
  5. import { ClaudeLoginManager } from '../src/services/claude-login'
  6. // 模拟 `claude auth login` 的非交互行为:输出网址 → 读授权码 → 无效则提示并继续等待,有效则成功退出
  7. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-claude-'))
  8. const fake = path.join(dir, 'claude')
  9. fs.writeFileSync(fake, `#!/usr/bin/env node
  10. process.stdout.write('Opening browser to sign in…\\nIf the browser didn\\'t open, visit: https://claude.com/cai/oauth/authorize?code=true&state=abc\\nPaste code here if prompted > ')
  11. let buf = ''
  12. process.stdin.on('data', d => {
  13. buf += d
  14. const lines = buf.split('\\n'); buf = lines.pop()
  15. for (const l of lines) {
  16. if (l === 'good-code') { console.log('Login successful.'); process.exit(0) }
  17. else console.error('Invalid code. Please make sure the full code was copied.')
  18. }
  19. })
  20. `, { mode: 0o755 })
  21. describe('ClaudeLoginManager', () => {
  22. const audits: string[] = []
  23. const mgr = new ClaudeLoginManager(() => fake, s => audits.push(s))
  24. it('启动后返回登录网址;无效授权码可以重试;有效授权码完成登录', async () => {
  25. const start = await mgr.start({})
  26. expect(start.url).toBe('https://claude.com/cai/oauth/authorize?code=true&state=abc')
  27. await expect(mgr.start({})).rejects.toThrow(/已有一个登录流程/)
  28. const bad = await mgr.submitCode(start.id, 'wrong')
  29. expect(bad).toEqual({ ok: false, retry: true, message: expect.stringContaining('Invalid code') })
  30. const good = await mgr.submitCode(start.id, 'good-code')
  31. expect(good.ok).toBe(true)
  32. expect(mgr.current()).toBeNull()
  33. // 审计中不包含授权码
  34. expect(audits.join('\n')).not.toMatch(/good-code|wrong/)
  35. })
  36. it('取消后流程结束,旧 ID 不能再提交', async () => {
  37. const start = await mgr.start({ sso: true, email: 'a@b.com' })
  38. mgr.cancel(start.id)
  39. expect(mgr.current()).toBeNull()
  40. await expect(mgr.submitCode(start.id, 'good-code')).rejects.toThrow(/不存在/)
  41. })
  42. it('拒绝格式不正确的输入', async () => {
  43. await expect(mgr.start({ email: 'not-an-email' })).rejects.toThrow(/邮箱/)
  44. const start = await mgr.start({})
  45. await expect(mgr.submitCode(start.id, 'has space')).rejects.toThrow(/格式/)
  46. mgr.cancel(start.id)
  47. })
  48. })