package imapx import ( "io" "net" "testing" "time" "context" ) // A server that sends the IMAP greeting, accepts one command, then goes silent // forever must NOT wedge the client: the idle read-timeout has to abort the // blocked read so the command returns an error instead of hanging. This is the // exact production failure (account stuck in "running" with zero progress). func TestConnectIdleReadTimeoutUnwedgesSilentServer(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } defer ln.Close() go func() { c, err := ln.Accept() if err != nil { return } defer c.Close() _, _ = io.WriteString(c, "* OK IMAP4rev2 ready\r\n") // Read the LOGIN command bytes, then never answer. buf := make([]byte, 512) _, _ = c.Read(buf) // Block until the client gives up and closes the connection. _, _ = c.Read(buf) }() old := idleReadTimeout idleReadTimeout = 150 * time.Millisecond defer func() { idleReadTimeout = old }() port := ln.Addr().(*net.TCPAddr).Port ep := Endpoint{Host: "127.0.0.1", Port: port, TLSMode: "plain"} c, err := Connect(context.Background(), ep) if err != nil { t.Fatalf("Connect: %v", err) } done := make(chan error, 1) go func() { done <- c.Login("user", "pass").Wait() }() select { case err := <-done: if err == nil { t.Fatal("expected an error from Login against a silent server, got nil") } case <-time.After(3 * time.Second): t.Fatal("Login did not return: idle read-timeout was not enforced (connection wedged)") } }