diff --git a/resp/reply_test.go b/resp/reply_test.go new file mode 100644 index 0000000..8f80d0d --- /dev/null +++ b/resp/reply_test.go @@ -0,0 +1,54 @@ +package resp + +import ( + "io" + "net" + "testing" +) + +// TestReplyCommand tests the ReplyCommand function for +// all possible valid and invalid inputs +func TestReplyCommand(t *testing.T) { + tests := []struct { + reply []byte + want string + }{ + {[]byte("+PONG\r\n"), "+PONG\r\n"}, + {[]byte("+OK\r\n"), "+OK\r\n"}, + {[]byte("$5\r\nhello\r\n"), "$5\r\nhello\r\n"}, + {[]byte("-error message\r\n"), "-error message\r\n"}, + } + for _, test := range tests { + server, client := net.Pipe() + + ch := make(chan []byte, 1) + go func() { + b, _ := io.ReadAll(client) + ch <- b + }() + + ReplyCommand(test.reply, server) + server.Close() + + got := string(<-ch) + if got != test.want { + t.Errorf("ReplyCommand(%q) = %q; want %q", test.reply, got, test.want) + } + client.Close() + } +} + +// TestReplyCommand_ClosedConn tests that ReplyCommand does not panic +// when the connection is already closed +func TestReplyCommand_ClosedConn(t *testing.T) { + server, client := net.Pipe() + client.Close() + + defer func() { + if r := recover(); r != nil { + t.Errorf("ReplyCommand panicked on closed connection: %v", r) + } + }() + ReplyCommand([]byte("+PONG\r\n"), server) + server.Close() +} diff --git a/resp/request_test.go b/resp/request_test.go new file mode 100644 index 0000000..b49b175 --- /dev/null +++ b/resp/request_test.go @@ -0,0 +1,62 @@ +package resp + +import ( + "io" + "net" + "testing" +) + +// newPipe returns a connected pair of net.Conn. server is passed to the function +// under test; client is used to read back what was written. +func newPipe(t *testing.T) (server, client net.Conn) { + t.Helper() + server, client = net.Pipe() + t.Cleanup(func() { + server.Close() + client.Close() + }) + return +} + +// readReply reads from the client side of the pipe in a goroutine and returns +// the bytes via a channel. The server side must be closed (or the write must +// complete) before this unblocks. +func readReply(client net.Conn) <-chan []byte { + ch := make(chan []byte, 1) + go func() { + b, _ := io.ReadAll(client) + ch <- b + }() + return ch +} + +// TestPerformRequest tests the PerformRequest function for +// all possible valid and invalid inputs +func TestPerformRequest(t *testing.T) { + tests := []struct { + input []byte + want string + }{ + // Valid commands + {[]byte("*1\r\n$4\r\nPING\r\n"), "+PONG\r\n"}, + {[]byte("*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n"), "$5\r\nhello\r\n"}, + // Invalid RESP prefix + {[]byte("%1\r\n$4\r\nPING\r\n"), "-invalid request\r\n"}, + // Unknown command + {[]byte("*1\r\n$3\r\nFOO\r\n"), "-unsupported command\r\n"}, + // nil buffer — ParseCommand returns zero-value Cmd with empty Name, nothing written + {nil, ""}, + } + for _, test := range tests { + server, client := newPipe(t) + ch := readReply(client) + + PerformRequest(test.input, server) + server.Close() + + got := string(<-ch) + if got != test.want { + t.Errorf("PerformRequest(%q) = %q; want %q", test.input, got, test.want) + } + } +}