197 lines
4.9 KiB
Go
197 lines
4.9 KiB
Go
package navicat
|
|
|
|
import (
|
|
"context"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"mysql-tunnel/internal/tunnel"
|
|
)
|
|
|
|
func testClient(t *testing.T, opts ...MockOption) (*Client, *httptest.Server) {
|
|
t.Helper()
|
|
srv := httptest.NewServer(NewMockHandler(opts...))
|
|
t.Cleanup(srv.Close)
|
|
httpClient := tunnel.NewHTTPClient(tunnel.Options{Timeout: 5 * time.Second})
|
|
c := NewClient(httpClient, ClientOptions{
|
|
URL: srv.URL + "?token=secret",
|
|
Target: Target{
|
|
Host: "mysql",
|
|
Port: 3306,
|
|
User: "root",
|
|
Password: "remote-pass",
|
|
Database: "app",
|
|
Charset: "utf8mb4",
|
|
},
|
|
})
|
|
return c, srv
|
|
}
|
|
|
|
func TestClientConnectAndSelect1(t *testing.T) {
|
|
var cap CapturedRequest
|
|
c, _ := testClient(t, WithToken("secret"), WithCapture(&cap))
|
|
ctx := context.Background()
|
|
|
|
conn, err := c.Connect(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if conn.Conn.ServerInfo != "8.0.36-mock" {
|
|
t.Fatalf("%+v", conn.Conn)
|
|
}
|
|
if cap.Action != ActionConnect || cap.Host != "mysql" || cap.Login != "root" {
|
|
t.Fatalf("connect capture %+v", cap)
|
|
}
|
|
if cap.Port != "3306" {
|
|
t.Fatalf("port %s", cap.Port)
|
|
}
|
|
if !cap.HasPassword {
|
|
t.Fatal("password field missing")
|
|
}
|
|
|
|
q, err := c.Query(ctx, "", "SELECT 1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, ok := LastResultSet(q)
|
|
if !ok || res.Rows[0][0].Text() != "1" {
|
|
t.Fatalf("select 1: %+v", q)
|
|
}
|
|
if cap.Action != ActionQuery {
|
|
t.Fatalf("query capture %+v", cap)
|
|
}
|
|
joined := strings.Join(cap.Queries, " | ")
|
|
if !strings.Contains(joined, "SET NAMES utf8mb4") {
|
|
t.Fatalf("expected SET NAMES in %q", joined)
|
|
}
|
|
if !strings.Contains(joined, "SELECT 1") {
|
|
t.Fatalf("expected SELECT 1 in %q", joined)
|
|
}
|
|
}
|
|
|
|
func TestClientTypesOverHTTP(t *testing.T) {
|
|
c, _ := testClient(t, WithToken("secret"))
|
|
ctx := context.Background()
|
|
|
|
q, err := c.Query(ctx, "app", "SELECT NULL AS n")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, _ := LastResultSet(q)
|
|
if !res.Rows[0][0].Null {
|
|
t.Fatal("NULL")
|
|
}
|
|
|
|
q, err = c.Query(ctx, "", "SELECT bigint_val")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, _ = LastResultSet(q)
|
|
got := res.Rows[0][0].Text()
|
|
if got != "18446744073709551615" {
|
|
t.Fatalf("bigint %q", got)
|
|
}
|
|
if _, err := strconv.ParseInt(got, 10, 64); err == nil {
|
|
t.Fatal("test value should overflow int64")
|
|
}
|
|
|
|
q, err = c.Query(ctx, "", "SELECT dec_val")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, _ = LastResultSet(q)
|
|
if res.Fields[0].Type != 246 || res.Rows[0][0].Text() != "1234567890.123456789" {
|
|
t.Fatalf("decimal %+v", res)
|
|
}
|
|
|
|
q, err = c.Query(ctx, "", "SELECT zh_val")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, _ = LastResultSet(q)
|
|
if res.Rows[0][0].Text() != "你好世界" {
|
|
t.Fatalf("zh %q", res.Rows[0][0].Text())
|
|
}
|
|
}
|
|
|
|
func TestClientSQLError(t *testing.T) {
|
|
c, _ := testClient(t, WithToken("secret"))
|
|
q, err := c.Query(context.Background(), "", "SELECT SYNTAX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, ok := LastResultSet(q)
|
|
if !ok || !res.IsError() || res.Errno != 1064 {
|
|
t.Fatalf("%+v", q)
|
|
}
|
|
}
|
|
|
|
func TestClientDML(t *testing.T) {
|
|
c, _ := testClient(t, WithToken("secret"))
|
|
q, err := c.Query(context.Background(), "", "UPDATE product SET price=1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res := q.Results[len(q.Results)-1]
|
|
if res.IsResultSet() || res.AffectedRows != 1 || res.InsertID != 42 {
|
|
t.Fatalf("%+v", res)
|
|
}
|
|
}
|
|
|
|
func TestClientBadTokenPOST(t *testing.T) {
|
|
srv := httptest.NewServer(NewMockHandler(WithToken("right")))
|
|
t.Cleanup(srv.Close)
|
|
httpClient := tunnel.NewHTTPClient(tunnel.Options{Timeout: 5 * time.Second})
|
|
c := NewClient(httpClient, ClientOptions{
|
|
URL: srv.URL + "?token=wrong",
|
|
Target: Target{Host: "mysql", Port: 3306, User: "root"},
|
|
})
|
|
_, err := c.Connect(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected token error")
|
|
}
|
|
ne, ok := err.(*Error)
|
|
if !ok || ne.Errno != ErrnoInvalidToken {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestClientConnectDenied(t *testing.T) {
|
|
c, _ := testClient(t, WithToken("secret"), WithConnectError(1045, "Access denied for user 'root'@'localhost'"))
|
|
_, err := c.Connect(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected deny")
|
|
}
|
|
if !strings.Contains(err.Error(), "1045") {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(err.Error(), "remote-pass") {
|
|
t.Fatal("password leaked")
|
|
}
|
|
}
|
|
|
|
func TestEncodeBase64Query(t *testing.T) {
|
|
var cap CapturedRequest
|
|
srv := httptest.NewServer(NewMockHandler(WithToken("secret"), WithCapture(&cap)))
|
|
t.Cleanup(srv.Close)
|
|
httpClient := tunnel.NewHTTPClient(tunnel.Options{Timeout: 5 * time.Second})
|
|
c := NewClient(httpClient, ClientOptions{
|
|
URL: srv.URL + "?token=secret",
|
|
Target: Target{Host: "mysql", Port: 3306, User: "root", Charset: "utf8"},
|
|
EncodeBase64: true,
|
|
})
|
|
_, err := c.Query(context.Background(), "", "SELECT 1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cap.EncodeBase64 != "1" {
|
|
t.Fatalf("%+v", cap)
|
|
}
|
|
if strings.Contains(strings.Join(cap.Queries, ""), "SELECT 1") {
|
|
t.Fatalf("wire should be base64, got %v", cap.Queries)
|
|
}
|
|
}
|