Compare commits

...

1 Commits

Author SHA1 Message Date
Zecheng Zhang 0450046e94 feat(integ): let a fake bind an address other than the loopback
gws, notion and github each take a --port and then hardcode 127.0.0.1.
That is right for every direct invocation and wrong inside a container:
the published port accepts the connection and then answers nothing, which
reaches the caller as an empty reply with no error anywhere explaining it.

--host leaves the default alone, so nothing that runs these on a host
changes. github also takes --advertise, because the base URL it writes
into html_url and clone_url is not always where it bound -- a container
binds 0.0.0.0, and 0.0.0.0 in a URL sends the caller nowhere.

The remaining integ servers carry the same hardcoded bind and are left
alone here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 21:27:00 -07:00
3 changed files with 39 additions and 11 deletions
+23 -5
View File
@@ -1898,8 +1898,13 @@ def seed_state(state: FakeGitHub, repos: list[str], metadata: list[str],
seed_commits(state, full_name, fixtures / fixture)
async def _serve(port: int, repos: list[str], metadata: list[str],
commits: list[str], create_repos: bool) -> None:
async def _serve(port: int,
repos: list[str],
metadata: list[str],
commits: list[str],
create_repos: bool,
host: str = "127.0.0.1",
advertise: str | None = None) -> None:
state = FakeGitHub()
state.create_repos = create_repos
state.seed = (repos, metadata, commits)
@@ -1907,9 +1912,13 @@ async def _serve(port: int, repos: list[str], metadata: list[str],
server = GitHubServer(state)
runner = web.AppRunner(build_app(server))
await runner.setup()
site = web.TCPSite(runner, "127.0.0.1", port)
site = web.TCPSite(runner, host, port)
await site.start()
state.base = f"http://127.0.0.1:{port}"
# What the fake calls itself in every html_url and clone_url it hands
# out, which is not always where it binds: a container binds 0.0.0.0
# and is reached on a published loopback port, and 0.0.0.0 in a URL
# sends the caller nowhere.
state.base = (advertise or f"http://127.0.0.1:{port}").rstrip("/")
print(f"GITHUB_ENDPOINT={state.base}", flush=True)
await asyncio.Event().wait()
@@ -1942,10 +1951,19 @@ def main() -> None:
help="refuse POST /user/repos with 403, the way a fine-grained "
"token without Administration:write does; fork and rename stay "
"available")
parser.add_argument("--host",
default="127.0.0.1",
help="bind address; 0.0.0.0 in a container, where "
"binding the loopback publishes a port that accepts "
"the connection and then answers nothing")
parser.add_argument("--advertise",
help="base URL to put in html_url and clone_url, "
"when callers reach this fake by a name other than "
"loopback (default http://127.0.0.1:<port>)")
args = parser.parse_args()
asyncio.run(
_serve(args.port, args.repo, args.metadata, args.commits,
not args.no_create_repos))
not args.no_create_repos, args.host, args.advertise))
if __name__ == "__main__":
+8 -3
View File
@@ -2915,7 +2915,7 @@ function rangeLabelFor(range: A1Range, requested: string): string {
return `${range.tab.title}!A1:Z1000`
}
export function startServer(port: number): Promise<http.Server> {
export function startServer(port: number, host = '127.0.0.1'): Promise<http.Server> {
const server = http.createServer((req, res) => {
const chunks: Buffer[] = []
req.on('data', (chunk: Buffer) => chunks.push(chunk))
@@ -2953,7 +2953,7 @@ export function startServer(port: number): Promise<http.Server> {
})
})
return new Promise((resolve) => {
server.listen(port, '127.0.0.1', () => resolve(server))
server.listen(port, host, () => resolve(server))
})
}
@@ -2961,7 +2961,12 @@ const isMain = process.argv[1] !== undefined && process.argv[1].endsWith('gws_se
if (isMain) {
const portArg = process.argv.indexOf('--port')
const port = portArg !== -1 ? parseInt(process.argv[portArg + 1] as string, 10) : 19999
void startServer(port).then(() => {
// Loopback by default, which is every direct invocation. A container has
// to bind 0.0.0.0 or its published port accepts the connection and then
// answers nothing -- an empty reply with no error anywhere to explain it.
const hostArg = process.argv.indexOf('--host')
const host = hostArg !== -1 ? (process.argv[hostArg + 1] as string) : '127.0.0.1'
void startServer(port, host).then(() => {
console.log(`GWS_URL=http://127.0.0.1:${String(port)}`)
})
}
+8 -3
View File
@@ -1693,11 +1693,11 @@ function serve(db: PrismaClient, fx: Fixture): Server {
})
}
export async function startServer(port: number): Promise<Server> {
export async function startServer(port: number, host = '127.0.0.1'): Promise<Server> {
const { db, fx } = await createStore(`rest-${String(port)}`)
const server = serve(db, fx)
return new Promise((resolve) => {
server.listen(port, '127.0.0.1', () => resolve(server))
server.listen(port, host, () => resolve(server))
})
}
@@ -1851,7 +1851,12 @@ if (isMain) {
const portArg = process.argv.indexOf('--port')
const port =
portArg !== -1 ? Number.parseInt(process.argv[portArg + 1] as string, 10) : DEFAULT_PORT
void startServer(port).then(() => {
// Loopback by default, which is every direct invocation. A container has
// to bind 0.0.0.0 or its published port accepts the connection and then
// answers nothing -- an empty reply with no error anywhere to explain it.
const hostArg = process.argv.indexOf('--host')
const host = hostArg !== -1 ? (process.argv[hostArg + 1] as string) : '127.0.0.1'
void startServer(port, host).then(() => {
console.log(`NOTION_URL=http://127.0.0.1:${String(port)}`)
})
}