|
| 1 | +/** |
| 2 | + * Where `wunderland start` listens. |
| 3 | + * |
| 4 | + * The CLI's own dependencies come from the parent monorepo, so this job |
| 5 | + * cannot import src/cli/commands/start.ts. It binds real servers through the |
| 6 | + * helper start.ts uses and reads start.ts to check that it uses it. |
| 7 | + */ |
| 8 | +import { readFileSync } from 'node:fs'; |
| 9 | +import { createServer, type Server } from 'node:http'; |
| 10 | +import path from 'node:path'; |
| 11 | +import { afterEach, describe, expect, it } from 'vitest'; |
| 12 | + |
| 13 | +import { |
| 14 | + DEFAULT_LISTEN_HOST, |
| 15 | + displayHost, |
| 16 | + isLoopbackHost, |
| 17 | + listenOn, |
| 18 | + resolveListenHost, |
| 19 | +} from '../../../src/cli/config/listen-host'; |
| 20 | + |
| 21 | +const REPO_ROOT = path.resolve(__dirname, '..', '..', '..'); |
| 22 | +const START_SOURCE = path.join(REPO_ROOT, 'src', 'cli', 'commands', 'start.ts'); |
| 23 | + |
| 24 | +const servers: Server[] = []; |
| 25 | + |
| 26 | +function newServer(): Server { |
| 27 | + const server = createServer((_req, res) => res.end('ok')); |
| 28 | + servers.push(server); |
| 29 | + return server; |
| 30 | +} |
| 31 | + |
| 32 | +afterEach(async () => { |
| 33 | + await Promise.all( |
| 34 | + servers.splice(0).map((server) => new Promise<void>((resolve) => server.close(() => resolve()))), |
| 35 | + ); |
| 36 | +}); |
| 37 | + |
| 38 | +describe('wunderland start listen address', () => { |
| 39 | + it('listens on 127.0.0.1 when --host is not given', async () => { |
| 40 | + expect(DEFAULT_LISTEN_HOST).toBe('127.0.0.1'); |
| 41 | + const address = await listenOn(newServer(), 0, resolveListenHost(undefined)); |
| 42 | + expect(address.address).toBe('127.0.0.1'); |
| 43 | + }); |
| 44 | + |
| 45 | + it('listens on the address --host names', async () => { |
| 46 | + const address = await listenOn(newServer(), 0, resolveListenHost('0.0.0.0')); |
| 47 | + expect(address.address).toBe('0.0.0.0'); |
| 48 | + }); |
| 49 | + |
| 50 | + it('drops the brackets from an IPv6 --host, which server.listen cannot resolve', () => { |
| 51 | + expect(resolveListenHost('[::1]')).toBe('::1'); |
| 52 | + expect(resolveListenHost('::1')).toBe('::1'); |
| 53 | + expect(resolveListenHost('[not-an-address]')).toBe('[not-an-address]'); |
| 54 | + }); |
| 55 | + |
| 56 | + it('refuses --host without an address instead of guessing one', () => { |
| 57 | + expect(() => resolveListenHost(true)).toThrow(/--host needs an address/); |
| 58 | + expect(() => resolveListenHost(' ')).toThrow(/--host needs an address/); |
| 59 | + }); |
| 60 | + |
| 61 | + it('rejects when the port is taken', async () => { |
| 62 | + const first = await listenOn(newServer(), 0, DEFAULT_LISTEN_HOST); |
| 63 | + await expect(listenOn(newServer(), first.port, DEFAULT_LISTEN_HOST)).rejects.toMatchObject({ |
| 64 | + code: 'EADDRINUSE', |
| 65 | + }); |
| 66 | + }); |
| 67 | + |
| 68 | + it('tells loopback addresses from addresses other machines can reach', () => { |
| 69 | + for (const host of ['127.0.0.1', '127.0.0.2', 'localhost', '::1', '[::1]']) { |
| 70 | + expect(isLoopbackHost(host), host).toBe(true); |
| 71 | + } |
| 72 | + for (const host of ['0.0.0.0', '::', '192.168.1.20', 'example.com']) { |
| 73 | + expect(isLoopbackHost(host), host).toBe(false); |
| 74 | + } |
| 75 | + }); |
| 76 | + |
| 77 | + it('prints a wildcard address as localhost and brackets IPv6', () => { |
| 78 | + expect(displayHost('0.0.0.0')).toBe('localhost'); |
| 79 | + expect(displayHost('::')).toBe('localhost'); |
| 80 | + expect(displayHost('127.0.0.1')).toBe('127.0.0.1'); |
| 81 | + expect(displayHost('::1')).toBe('[::1]'); |
| 82 | + }); |
| 83 | + |
| 84 | + it('start.ts listens through the helper on the --host address, never a fixed 0.0.0.0', () => { |
| 85 | + const source = readFileSync(START_SOURCE, 'utf8'); |
| 86 | + expect(source).not.toContain("'0.0.0.0'"); |
| 87 | + expect(source).toContain("resolveListenHost(flags['host'])"); |
| 88 | + expect(source).toContain('listenOn(server, port, host)'); |
| 89 | + }); |
| 90 | +}); |
0 commit comments