|
| 1 | +import { expect, it, vi } from 'vite-plus/test'; |
| 2 | +import { |
| 3 | + cloudflareDeviceCode, |
| 4 | + printCloudflareLoginPrompt, |
| 5 | +} from '../../src/cli/cloudflare-login-output.ts'; |
| 6 | + |
| 7 | +it('extracts the device code from cf output, including streamed and colored output', () => { |
| 8 | + expect(cloudflareDeviceCode('and enter the code:\n\n a1B2c3D4\n')).toBe('a1B2c3D4'); |
| 9 | + expect(cloudflareDeviceCode('and enter the code:\n')).toBeNull(); |
| 10 | + expect(cloudflareDeviceCode('and enter the code:\n \u001b[34ma1B2c3D4\u001b[0m\n')).toBe( |
| 11 | + 'a1B2c3D4', |
| 12 | + ); |
| 13 | + expect( |
| 14 | + cloudflareDeviceCode( |
| 15 | + 'Opening a link in your default browser: https://dash.cloudflare.com/oauth2/device/verify?user_code=a1B2c3D4\n', |
| 16 | + ), |
| 17 | + ).toBe('a1B2c3D4'); |
| 18 | +}); |
| 19 | + |
| 20 | +it('prints only the short Void prompt with the actual code', () => { |
| 21 | + const printed = vi.spyOn(console, 'log').mockImplementation(() => {}); |
| 22 | + try { |
| 23 | + printCloudflareLoginPrompt('a1B2c3D4'); |
| 24 | + expect(printed).toHaveBeenCalledTimes(1); |
| 25 | + expect(printed.mock.calls[0]?.[0]).toContain( |
| 26 | + 'Opening Cloudflare login in your browser. Confirm the code a1B2c3D4 in your browser. Press Ctrl+C to cancel.', |
| 27 | + ); |
| 28 | + } finally { |
| 29 | + printed.mockRestore(); |
| 30 | + } |
| 31 | +}); |
| 32 | + |
| 33 | +it('hides cf login chatter and prints the code before the child exits', async () => { |
| 34 | + const directory = mkdtempSync(join(tmpdir(), 'void-login-output-')); |
| 35 | + try { |
| 36 | + const preload = join(directory, 'mock-login.mjs'); |
| 37 | + writeFileSync( |
| 38 | + preload, |
| 39 | + `import child from 'node:child_process'; |
| 40 | +import { EventEmitter } from 'node:events'; |
| 41 | +import { syncBuiltinESMExports } from 'node:module'; |
| 42 | +child.spawn = () => { |
| 43 | + const process = new EventEmitter(); |
| 44 | + process.stdout = new EventEmitter(); |
| 45 | + process.stderr = new EventEmitter(); |
| 46 | + setImmediate(() => { |
| 47 | + process.stderr.emit('data', Buffer.from('Attempting to login via OAuth Device Authorization Grant...\\nand enter the code:\\n a1B2')); |
| 48 | + process.stderr.emit('data', Buffer.from('c3D4\\nOpening a link in your default browser: https://dash.cloudflare.com/oauth2/device/verify?user_code=a1B2c3D4\\nSuccessfully logged in.\\nConfig file: sample\\n')); |
| 49 | + process.emit('close', 0, null); |
| 50 | + }); |
| 51 | + return process; |
| 52 | +}; |
| 53 | +syncBuiltinESMExports(); |
| 54 | +`, |
| 55 | + ); |
| 56 | + const entry = join(import.meta.dirname, '..', '..', 'src', 'cli', 'cf-compat.ts'); |
| 57 | + const output = await new Promise<{ status: number; stdout: string; stderr: string }>( |
| 58 | + (resolve) => { |
| 59 | + execFile( |
| 60 | + process.execPath, |
| 61 | + ['--import', pathToFileURL(preload).href, entry, 'login'], |
| 62 | + { cwd: directory, encoding: 'utf8', timeout: 10_000 }, |
| 63 | + (error, stdout, stderr) => resolve({ status: error ? 1 : 0, stdout, stderr }), |
| 64 | + ); |
| 65 | + }, |
| 66 | + ); |
| 67 | + expect(output.status, output.stderr).toBe(0); |
| 68 | + expect(output.stdout.trim()).toBe( |
| 69 | + 'Opening Cloudflare login in your browser. Confirm the code a1B2c3D4 in your browser. Press Ctrl+C to cancel.', |
| 70 | + ); |
| 71 | + expect(output.stdout + output.stderr).not.toContain('Attempting to login'); |
| 72 | + expect(output.stdout + output.stderr).not.toContain('Config file:'); |
| 73 | + expect(output.stdout + output.stderr).not.toContain('Successfully logged in.'); |
| 74 | + } finally { |
| 75 | + rmSync(directory, { recursive: true, force: true }); |
| 76 | + } |
| 77 | +}); |
| 78 | +import { execFile } from 'node:child_process'; |
| 79 | +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; |
| 80 | +import { tmpdir } from 'node:os'; |
| 81 | +import { join } from 'node:path'; |
| 82 | +import { pathToFileURL } from 'node:url'; |
0 commit comments