diff --git a/goatherd_test.go b/goatherd_test.go
index 1692204acf33ecc7e0a31e482df13c41c42485f6..526517bc78078e0fb22738ebaa71cd6e9e4f504a 100644
--- a/goatherd_test.go
+++ b/goatherd_test.go
@@ -132,38 +132,55 @@ func check_offer_t(t *testing.T) {
 
 }
 
+type client_t func(r *io.PipeReader, w *io.PipeWriter)
 
-func mock_conn(name string, offer string) (delay *sync.Mutex, answer string) {
-    in_r, in_w := io.Pipe()
-    in_r_buf := bufio.NewReader(in_r)
-    defer in_r.Close()
-    out_r, out_w := io.Pipe()
-    out_r_buf, out_w_buf := bufio.NewReader(out_r), bufio.NewWriter(out_w)
-    defer out_r.Close()
+func mock_conn(client client_t) (delay *sync.Mutex) {
+    server_r, client_w := io.Pipe()
+    server_r_buf := bufio.NewReader(server_r)
+    defer server_r.Close()
+    client_r, server_w := io.Pipe()
+    server_w_buf := bufio.NewWriter(server_w)
+    defer client_r.Close()
 
-    answer_c := make(chan string)
+    go client(client_r, client_w)
 
-    go func() {
-        in_w.Write(append([]byte(username), '\n'))
-        in_w.Write(append([]byte(offer), '\n'))
-        answer, _ := get_line(out_r_buf)
-        answer_c <- answer
-    }()
-
-    delay = handle_conn(db, "handle_conn_t", in_r_buf, out_w_buf)
-    answer = <-answer_c
+    delay = handle_conn(db, "handle_conn_t", server_r_buf, server_w_buf)
 
     return
 }
 
+func interact(name string, offer string, recv chan string) client_t {
+    return func(r *io.PipeReader, w *io.PipeWriter) {
+        w.Write(append([]byte(name), '\n'))
+        w.Write(append([]byte(offer), '\n'))
+        answer, _ := get_line(bufio.NewReader(r))
+        recv <- answer
+    }
+}
+
 
 func handle_conn_t(t *testing.T) {
     // zero value Mutex is unlocked, can be compared to a given Mutex to
     // (unreliably?) check if it is locked
     unlocked := sync.Mutex{}
 
+    t.Run("dummy_user", func(t *testing.T) {
+        recv := make(chan string)
+        delay, answer := mock_conn(interact("dummy user", "dummy offer", recv)), <-recv
+        if delay != nil {
+            t.Fail()
+        }
+        if answer != "" {
+            t.Error("answer:", answer)
+        }
+        if faildelay.userlocks["dummy user"] != nil {
+            t.Error("userlock created for dummy user")
+        }
+    })
+
     t.Run("dummy_offer", func(t *testing.T) {
-        delay, answer := mock_conn(username, "dummy offer")
+        recv := make(chan string)
+        delay, answer := mock_conn(interact(username, "dummy offer", recv)), <-recv
         if delay == nil {
             t.Fail()
         } else if *delay == unlocked {
@@ -174,16 +191,21 @@ func handle_conn_t(t *testing.T) {
         if answer != "FAIL" {
             t.Error("answer:", answer)
         }
+        if faildelay.userlocks[username] == nil {
+            t.Error("userlock not in map")
+        }
     })
 
-    t.Run("ok", func(t *testing.T) {
-        tx, err := db.Begin()
-        t_err_fatal(t, err)
-        otp, err := get_otp(tx, username)
-        t_err_fatal(t, err)
-        tx.Commit()
+    tx, err := db.Begin()
+    t_err_fatal(t, err)
+    otp, err := get_otp(tx, username)
+    t_err_fatal(t, err)
+    tx.Commit()
 
-        delay, answer := mock_conn(username, otp.OTP())
+    t.Run("ok", func(t *testing.T) {
+        recv := make(chan string)
+        delay := mock_conn(interact(username, otp.OTP(), recv))
+        answer := <-recv
         if delay != nil {
             t.Fail()
         }
@@ -194,6 +216,66 @@ func handle_conn_t(t *testing.T) {
             t.Error("answer:", answer)
         }
     })
+    otp.Increment()
+
+    t.Run("read_error", func(t *testing.T) {
+        t.Run("close_immediately",  func(t *testing.T) {
+            delay := mock_conn(func(r *io.PipeReader, w *io.PipeWriter) {
+                w.Close()
+            })
+            if delay != nil {
+                t.Fail()
+            }
+            if *faildelay.userlocks[username] != unlocked {
+                t.Error("userlock not unlocked")
+            }
+        })
+
+        t.Run("close_after_username",  func(t *testing.T) {
+            delay := mock_conn(func(r *io.PipeReader, w *io.PipeWriter) {
+                w.Write(append([]byte(username), '\n'))
+                w.Close()
+            })
+            if delay != nil {
+                t.Fail()
+            }
+            if *faildelay.userlocks[username] != unlocked {
+                t.Error("userlock not unlocked")
+            }
+        })
+    })
+
+    t.Run("write_error", func(t *testing.T) {
+        t.Run("FAIL", func(t *testing.T) {
+            delay := mock_conn(func(r *io.PipeReader, w *io.PipeWriter) {
+                r.Close()
+                w.Write(append([]byte(username), '\n'))
+                w.Write(append([]byte("dummy offer"), '\n'))
+            })
+            if delay == nil {
+                t.Fail()
+            } else if *delay == unlocked {
+                t.Error("delay mutex is unlocked")
+            } else {
+                delay.Unlock()
+            }
+        })
+
+        t.Run("OK", func(t *testing.T) {
+            delay := mock_conn(func(r *io.PipeReader, w *io.PipeWriter) {
+                r.Close()
+                w.Write(append([]byte(username), '\n'))
+                w.Write(append([]byte(otp.OTP()), '\n'))
+            })
+            if delay != nil {
+                t.Fail()
+            }
+            if *faildelay.userlocks[username] != unlocked {
+                t.Error("userlock not unlocked")
+            }
+        })
+        otp.Increment()
+    })
 }