package http import ( "crypto/tls" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" "testing" "time" "github.com/rqlite/rqlite/command" "github.com/rqlite/rqlite/store" "github.com/rqlite/rqlite/testdata/x509" "golang.org/x/net/http2" ) func Test_ResponseJSONMarshal(t *testing.T) { resp := NewResponse() b, err := json.Marshal(resp) if err != nil { t.Fatalf("error JSON marshaling empty Response: %s", err.Error()) } if exp, got := `{"results":[]}`, string(b); exp != got { t.Fatalf("Incorrect marshal, exp: %s, got: %s", exp, got) } resp = NewResponse() resp.Results.ExecuteResult = []*command.ExecuteResult{{ LastInsertId: 39, RowsAffected: 45, Time: 1234, }} b, err = json.Marshal(resp) if err != nil { t.Fatalf("failed to JSON marshal empty Response: %s", err) } if exp, got := `{"results":[{"last_insert_id":39,"rows_affected":45,"time":1234}]}`, string(b); exp != got { t.Fatalf("Incorrect marshal, exp: %s, got: %s", exp, got) } resp = NewResponse() resp.Results.QueryRows = []*command.QueryRows{{ Columns: []string{"id", "name"}, Types: []string{"int", "string"}, }} b, err = json.Marshal(resp) if err != nil { t.Fatalf("failed to JSON marshal empty Response: %s", err) } if exp, got := `{"results":[{"columns":["id","name"],"types":["int","string"]}]}`, string(b); exp != got { t.Fatalf("Incorrect marshal, exp: %s, got: %s", exp, got) } resp = NewResponse() resp.Results.QueryRows = []*command.QueryRows{{ Columns: []string{"id", "name"}, Types: []string{"int", "string"}, Values: []*command.Values{ { Parameters: []*command.Parameter{ { Value: &command.Parameter_S{ S: "fiona", }, }, { Value: &command.Parameter_I{ I: 5, }, }, }, }, }, }} b, err = json.Marshal(resp) if err != nil { t.Fatalf("failed to JSON marshal empty Response: %s", err) } if exp, got := `{"results":[{"columns":["id","name"],"types":["int","string"],"values":[["fiona",5]]}]}`, string(b); exp != got { t.Fatalf("Incorrect marshal, exp: %s, got: %s", exp, got) } } func Test_NormalizeAddr(t *testing.T) { tests := []struct { orig string norm string }{ { orig: "http://localhost:4001", norm: "http://localhost:4001", }, { orig: "https://localhost:4001", norm: "https://localhost:4001", }, { orig: "https://localhost:4001/foo", norm: "https://localhost:4001/foo", }, { orig: "localhost:4001", norm: "http://localhost:4001", }, { orig: "localhost", norm: "http://localhost", }, { orig: ":4001", norm: "http://:4001", }, } for _, tt := range tests { if NormalizeAddr(tt.orig) != tt.norm { t.Fatalf("%s not normalized correctly, got: %s", tt.orig, tt.norm) } } } func Test_EnsureHTTPS(t *testing.T) { tests := []struct { orig string ensured string }{ { orig: "http://localhost:4001", ensured: "https://localhost:4001", }, { orig: "https://localhost:4001", ensured: "https://localhost:4001", }, { orig: "https://localhost:4001/foo", ensured: "https://localhost:4001/foo", }, { orig: "localhost:4001", ensured: "https://localhost:4001", }, } for _, tt := range tests { if e := EnsureHTTPS(tt.orig); e != tt.ensured { t.Fatalf("%s not HTTPS ensured correctly, exp %s, got %s", tt.orig, tt.ensured, e) } } } func Test_AddBasicAuth(t *testing.T) { var u string var err error u, err = AddBasicAuth("http://example.com", "user1", "pass1") if err != nil { t.Fatalf("failed to add user info: %s", err.Error()) } if exp, got := "http://user1:pass1@example.com", u; exp != got { t.Fatalf("wrong URL created, exp %s, got %s", exp, got) } u, err = AddBasicAuth("http://example.com", "user1", "") if err != nil { t.Fatalf("failed to add user info: %s", err.Error()) } if exp, got := "http://user1:@example.com", u; exp != got { t.Fatalf("wrong URL created, exp %s, got %s", exp, got) } u, err = AddBasicAuth("http://example.com", "", "pass1") if err != nil { t.Fatalf("failed to add user info: %s", err.Error()) } if exp, got := "http://example.com", u; exp != got { t.Fatalf("wrong URL created, exp %s, got %s", exp, got) } u, err = AddBasicAuth("http://user1:pass1@example.com", "user2", "pass2") if err == nil { t.Fatalf("failed to get expected error when UserInfo exists") } } func Test_RemoveBasicAuth(t *testing.T) { tests := []struct { orig string removed string }{ { orig: "localhost", removed: "localhost", }, { orig: "http://localhost:4001", removed: "http://localhost:4001", }, { orig: "https://foo:bar@localhost", removed: "https://localhost", }, { orig: "https://foo:bar@localhost:4001", removed: "https://localhost:4001", }, { orig: "http://foo:bar@localhost:4001/path", removed: "http://localhost:4001/path", }, } for _, tt := range tests { if e := RemoveBasicAuth(tt.orig); e != tt.removed { t.Fatalf("%s BasicAuth not removed correctly, exp %s, got %s", tt.orig, tt.removed, e) } } } func Test_NewService(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if s == nil { t.Fatalf("failed to create new service") } if s.HTTPS() { t.Fatalf("expected service to report not HTTPS") } } func Test_HasVersionHeader(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() s.BuildInfo = map[string]interface{}{ "version": "the version", } url := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} resp, err := client.Get(url) if err != nil { t.Fatalf("failed to make request") } if resp.Header.Get("X-RQLITE-VERSION") != "the version" { t.Fatalf("incorrect build version present in HTTP response header") } } func Test_HasContentTypeJSON(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() client := &http.Client{} resp, err := client.Get(fmt.Sprintf("http://%s/status", s.Addr().String())) if err != nil { t.Fatalf("failed to make request") } h := resp.Header.Get("Content-Type") if h != "application/json; charset=utf-8" { t.Fatalf("incorrect Content-type in HTTP response: %s", h) } } func Test_HasContentTypeOctetStream(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() client := &http.Client{} resp, err := client.Get(fmt.Sprintf("http://%s/db/backup", s.Addr().String())) if err != nil { t.Fatalf("failed to make request") } h := resp.Header.Get("Content-Type") if h != "application/octet-stream" { t.Fatalf("incorrect Content-type in HTTP response: %s", h) } } func Test_HasVersionHeaderUnknown(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() url := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} resp, err := client.Get(url) if err != nil { t.Fatalf("failed to make request") } if resp.Header.Get("X-RQLITE-VERSION") != "unknown" { t.Fatalf("incorrect build version present in HTTP response header") } } func Test_404Routes(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} resp, err := client.Get(host + "/db/xxx") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 404 { t.Fatalf("failed to get expected 404, got %d", resp.StatusCode) } resp, err = client.Post(host+"/xxx", "", nil) if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 404 { t.Fatalf("failed to get expected 404, got %d", resp.StatusCode) } } func Test_404Routes_ExpvarPprofDisabled(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} for _, path := range []string{ "/debug/vars", "/debug/pprof/cmdline", "/debug/pprof/profile", "/debug/pprof/symbol", } { req, err := http.NewRequest("GET", host+path, nil) resp, err := client.Do(req) if err != nil { t.Fatalf("failed to make request: %s", err.Error()) } if resp.StatusCode != 404 { t.Fatalf("failed to get expected 404 for path %s, got %d", path, resp.StatusCode) } } } func Test_405Routes(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} resp, err := client.Get(host + "/db/execute") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Get(host + "/remove") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Post(host+"/remove", "", nil) if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Get(host + "/join") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Get(host + "/notify") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Post(host+"/db/backup", "", nil) if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } resp, err = client.Post(host+"/status", "", nil) if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 405 { t.Fatalf("failed to get expected 405, got %d", resp.StatusCode) } } func Test_400Routes(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} resp, err := client.Get(host + "/db/query?q=") if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 400 { t.Fatalf("failed to get expected 400, got %d", resp.StatusCode) } } func Test_401Routes_NoBasicAuth(t *testing.T) { c := &mockCredentialStore{CheckOK: false, HasPermOK: false} m := &MockStore{} n := &mockClusterService{} s := New("127.0.0.1:0", m, n, c) s.Expvar = true s.Pprof = true if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} for _, path := range []string{ "/db/execute/queue/_default", "/db/execute", "/db/query", "/db/backup", "/db/load", "/join", "/notify", "/remove", "/status", "/nodes", "/readyz", "/debug/vars", "/debug/pprof/cmdline", "/debug/pprof/profile", "/debug/pprof/symbol", } { resp, err := client.Get(host + path) if err != nil { t.Fatalf("failed to make request") } if resp.StatusCode != 401 { t.Fatalf("failed to get expected 401 for path %s, got %d", path, resp.StatusCode) } } } func Test_401Routes_BasicAuthBadPassword(t *testing.T) { c := &mockCredentialStore{CheckOK: false, HasPermOK: false} m := &MockStore{} n := &mockClusterService{} s := New("127.0.0.1:0", m, n, c) s.Expvar = true s.Pprof = true if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} for _, path := range []string{ "/db/execute/queue/_default", "/db/execute", "/db/query", "/db/backup", "/db/load", "/join", "/notify", "/status", "/nodes", "/readyz", "/debug/vars", "/debug/pprof/cmdline", "/debug/pprof/profile", "/debug/pprof/symbol", } { req, err := http.NewRequest("GET", host+path, nil) if err != nil { t.Fatalf("failed to create request: %s", err.Error()) } req.SetBasicAuth("username1", "password1") resp, err := client.Do(req) if err != nil { t.Fatalf("failed to make request: %s", err.Error()) } if resp.StatusCode != 401 { t.Fatalf("failed to get expected 401 for path %s, got %d", path, resp.StatusCode) } } } func Test_401Routes_BasicAuthBadPerm(t *testing.T) { c := &mockCredentialStore{CheckOK: true, HasPermOK: false} m := &MockStore{} n := &mockClusterService{} s := New("127.0.0.1:0", m, n, c) s.Expvar = true s.Pprof = true if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() host := fmt.Sprintf("http://%s", s.Addr().String()) client := &http.Client{} for _, path := range []string{ "/db/execute/queue/_default", "/db/execute", "/db/query", "/db/backup", "/db/load", "/join", "/notify", "/status", "/nodes", "/readyz", "/debug/vars", "/debug/pprof/cmdline", "/debug/pprof/profile", "/debug/pprof/symbol", } { req, err := http.NewRequest("GET", host+path, nil) if err != nil { t.Fatalf("failed to create request: %s", err.Error()) } req.SetBasicAuth("username1", "password1") resp, err := client.Do(req) if err != nil { t.Fatalf("failed to make request: %s", err.Error()) } if resp.StatusCode != 401 { t.Fatalf("failed to get expected 401 for path %s, got %d", path, resp.StatusCode) } } } func Test_BackupOK(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() m.backupFn = func(leader bool, f store.BackupFormat, dst io.Writer) error { return nil } client := &http.Client{} host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/db/backup") if err != nil { t.Fatalf("failed to make backup request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for backup, got %d", resp.StatusCode) } } func Test_BackupFlagsNoLeader(t *testing.T) { m := &MockStore{} c := &mockClusterService{ apiAddr: "http://1.2.3.4:999", } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() m.backupFn = func(leader bool, f store.BackupFormat, dst io.Writer) error { return store.ErrNotLeader } client := &http.Client{} client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/db/backup") if err != nil { t.Fatalf("failed to make backup request: %s", err.Error()) } if resp.StatusCode != http.StatusMovedPermanently { t.Fatalf("failed to get expected StatusServiceUnavailable for backup, got %d", resp.StatusCode) } } func Test_BackupFlagsNoLeaderOK(t *testing.T) { m := &MockStore{} c := &mockClusterService{ apiAddr: "http://1.2.3.4:999", } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() m.backupFn = func(leader bool, f store.BackupFormat, dst io.Writer) error { if !leader { return nil } return store.ErrNotLeader } client := &http.Client{} host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/db/backup?noleader") if err != nil { t.Fatalf("failed to make backup request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for backup, got %d", resp.StatusCode) } } func Test_RegisterStatus(t *testing.T) { var stats *mockStatusReporter m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.RegisterStatus("foo", stats); err != nil { t.Fatalf("failed to register statusReporter: %s", err.Error()) } if err := s.RegisterStatus("foo", stats); err == nil { t.Fatal("successfully re-registered statusReporter") } } func Test_FormRedirect(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) req := mustNewHTTPRequest("http://qux:4001") if rd := s.FormRedirect(req, "http://foo:4001"); rd != "http://foo:4001" { t.Fatal("failed to form redirect for simple URL") } } func Test_FormRedirectParam(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) req := mustNewHTTPRequest("http://qux:4001/db/query?x=y") if rd := s.FormRedirect(req, "http://foo:4001"); rd != "http://foo:4001/db/query?x=y" { t.Fatal("failed to form redirect for URL") } } func Test_FormRedirectHTTPS(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) req := mustNewHTTPRequest("http://qux:4001") if rd := s.FormRedirect(req, "https://foo:4001"); rd != "https://foo:4001" { t.Fatal("failed to form redirect for simple URL") } } func Test_Nodes(t *testing.T) { m := &MockStore{ leaderAddr: "foo:1234", } c := &mockClusterService{ apiAddr: "https://bar:5678", } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() client := &http.Client{} host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/nodes") if err != nil { t.Fatalf("failed to make nodes request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for nodes, got %d", resp.StatusCode) } } func Test_RootRedirectToStatus(t *testing.T) { m := &MockStore{} c := &mockClusterService{} s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } resp, err := client.Get(fmt.Sprintf("http://%s/", s.Addr().String())) if err != nil { t.Fatalf("failed to make root request") } if resp.StatusCode != http.StatusFound { t.Fatalf("failed to get expected StatusFound for root, got %d", resp.StatusCode) } if resp.Header["Location"][0] != "/status" { t.Fatalf("received incorrect redirect path") } resp, err = client.Get(fmt.Sprintf("http://%s", s.Addr().String())) if err != nil { t.Fatalf("failed to make root request") } if resp.StatusCode != http.StatusFound { t.Fatalf("failed to get expected StatusFound for root, got %d", resp.StatusCode) } if resp.Header["Location"][0] != "/status" { t.Fatalf("received incorrect redirect path") } } func Test_Readyz(t *testing.T) { m := &MockStore{ leaderAddr: "foo:1234", } c := &mockClusterService{ apiAddr: "https://bar:5678", } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() client := &http.Client{} host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/readyz") if err != nil { t.Fatalf("failed to make nodes request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for nodes, got %d", resp.StatusCode) } } func Test_ForwardingRedirectQuery(t *testing.T) { m := &MockStore{ leaderAddr: "foo:1234", } m.queryFn = func(qr *command.QueryRequest) ([]*command.QueryRows, error) { return nil, store.ErrNotLeader } c := &mockClusterService{ apiAddr: "https://bar:5678", } c.queryFn = func(qr *command.QueryRequest, addr string, timeout time.Duration) ([]*command.QueryRows, error) { rows := &command.QueryRows{ Columns: []string{}, Types: []string{}, } return []*command.QueryRows{rows}, nil } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() // Check queries. client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Get(host + "/db/query?pretty&timings&q=SELECT%20%2A%20FROM%20foo") if err != nil { t.Fatalf("failed to make query request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for nodes, got %d", resp.StatusCode) } resp, err = client.Get(host + "/db/query?redirect&pretty&timings&q=SELECT%20%2A%20FROM%20foo") if err != nil { t.Fatalf("failed to make redirected query request: %s", err) } if resp.StatusCode != http.StatusMovedPermanently { t.Fatalf("failed to get expected StatusMovedPermanently for query, got %d", resp.StatusCode) } // Check leader failure case. m.leaderAddr = "" resp, err = client.Get(host + "/db/query?pretty&timings&q=SELECT%20%2A%20FROM%20foo") if err != nil { t.Fatalf("failed to make query forwarded request") } if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("failed to get expected StatusServiceUnavailable for node with no leader, got %d", resp.StatusCode) } } func Test_ForwardingRedirectExecute(t *testing.T) { m := &MockStore{ leaderAddr: "foo:1234", } m.executeFn = func(er *command.ExecuteRequest) ([]*command.ExecuteResult, error) { return nil, store.ErrNotLeader } c := &mockClusterService{ apiAddr: "https://bar:5678", } c.executeFn = func(er *command.ExecuteRequest, addr string, timeout time.Duration) ([]*command.ExecuteResult, error) { result := &command.ExecuteResult{ LastInsertId: 1234, RowsAffected: 5678, } return []*command.ExecuteResult{result}, nil } s := New("127.0.0.1:0", m, c, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } defer s.Close() // Check executes. client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } host := fmt.Sprintf("http://%s", s.Addr().String()) resp, err := client.Post(host+"/db/execute", "application/json", strings.NewReader(`["Some SQL"]`)) if err != nil { t.Fatalf("failed to make execute request") } if resp.StatusCode != http.StatusOK { t.Fatalf("failed to get expected StatusOK for execute, got %d", resp.StatusCode) } resp, err = client.Post(host+"/db/execute?redirect", "application/json", strings.NewReader(`["Some SQL"]`)) if err != nil { t.Fatalf("failed to make redirected execute request: %s", err) } if resp.StatusCode != http.StatusMovedPermanently { t.Fatalf("failed to get expected StatusMovedPermanently for execute, got %d", resp.StatusCode) } // Check leader failure case. m.leaderAddr = "" resp, err = client.Post(host+"/db/execute", "application/json", strings.NewReader(`["Some SQL"]`)) if err != nil { t.Fatalf("failed to make execute request") } if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("failed to get expected StatusServiceUnavailable for node with no leader, got %d", resp.StatusCode) } } func Test_TLSServce(t *testing.T) { m := &MockStore{} c := &mockClusterService{} var s *Service tempDir := mustTempDir() s = New("127.0.0.1:0", m, c, nil) s.CertFile = x509.CertFile(tempDir) s.KeyFile = x509.KeyFile(tempDir) s.BuildInfo = map[string]interface{}{ "version": "the version", } if err := s.Start(); err != nil { t.Fatalf("failed to start service") } if !s.HTTPS() { t.Fatalf("expected service to report HTTPS") } defer s.Close() url := fmt.Sprintf("https://%s", s.Addr().String()) // Test connecting with an HTTP client. tn := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tn} resp, err := client.Get(url) if err != nil { t.Fatalf("failed to make HTTP request: %s", err) } if v := resp.Header.Get("X-RQLITE-VERSION"); v != "the version" { t.Fatalf("incorrect build version present in HTTP response header, got: %s", v) } // Test connecting with an HTTP/2 client. client = &http.Client{ Transport: &http2.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } resp, err = client.Get(url) if err != nil { t.Fatalf("failed to make HTTP/2 request: %s", err) } if v := resp.Header.Get("X-RQLITE-VERSION"); v != "the version" { t.Fatalf("incorrect build version present in HTTP/2 response header, got: %s", v) } } func Test_timeoutQueryParam(t *testing.T) { var req http.Request defStr := "10s" def := mustParseDuration(defStr) tests := []struct { u string dur string err bool }{ { u: "http://localhost:4001/nodes?timeout=5s", dur: "5s", }, { u: "http://localhost:4001/nodes?timeout=2m", dur: "2m", }, { u: "http://localhost:4001/nodes?x=777&timeout=5s", dur: "5s", }, { u: "http://localhost:4001/nodes", dur: defStr, }, { u: "http://localhost:4001/nodes?timeout=zdfjkh", err: true, }, } for _, tt := range tests { req.URL = mustURLParse(tt.u) timeout, err := timeoutParam(&req, def) if err != nil { if tt.err { // Error is expected, all is OK. continue } t.Fatalf("failed to get timeout as expected: %s", err) } if timeout != mustParseDuration(tt.dur) { t.Fatalf("got wrong timeout, expected %s, got %s", mustParseDuration(tt.dur), timeout) } } } type MockStore struct { executeFn func(er *command.ExecuteRequest) ([]*command.ExecuteResult, error) queryFn func(qr *command.QueryRequest) ([]*command.QueryRows, error) backupFn func(leader bool, f store.BackupFormat, dst io.Writer) error leaderAddr string } func (m *MockStore) Execute(er *command.ExecuteRequest) ([]*command.ExecuteResult, error) { if m.executeFn != nil { return m.executeFn(er) } return nil, nil } func (m *MockStore) Query(qr *command.QueryRequest) ([]*command.QueryRows, error) { if m.queryFn != nil { return m.queryFn(qr) } return nil, nil } func (m *MockStore) Load(lr *command.LoadRequest) error { return nil } func (m *MockStore) Join(id, addr string, voter bool) error { return nil } func (m *MockStore) Notify(id, addr string) error { return nil } func (m *MockStore) Remove(id string) error { return nil } func (m *MockStore) LeaderAddr() (string, error) { return m.leaderAddr, nil } func (m *MockStore) Stats() (map[string]interface{}, error) { return nil, nil } func (m *MockStore) Nodes() ([]*store.Server, error) { return nil, nil } func (m *MockStore) Backup(leader bool, f store.BackupFormat, w io.Writer) error { if m.backupFn == nil { return nil } return m.backupFn(leader, f, w) } type mockClusterService struct { apiAddr string executeFn func(er *command.ExecuteRequest, addr string, t time.Duration) ([]*command.ExecuteResult, error) queryFn func(qr *command.QueryRequest, addr string, t time.Duration) ([]*command.QueryRows, error) } func (m *mockClusterService) GetNodeAPIAddr(a string, t time.Duration) (string, error) { return m.apiAddr, nil } func (m *mockClusterService) Execute(er *command.ExecuteRequest, addr string, t time.Duration) ([]*command.ExecuteResult, error) { if m.executeFn != nil { return m.executeFn(er, addr, t) } return nil, nil } func (m *mockClusterService) Query(qr *command.QueryRequest, addr string, t time.Duration) ([]*command.QueryRows, error) { if m.queryFn != nil { return m.queryFn(qr, addr, t) } return nil, nil } type mockCredentialStore struct { CheckOK bool HasPermOK bool } func (m *mockCredentialStore) Check(username, password string) bool { return m.CheckOK } func (m *mockCredentialStore) HasPerm(username, perm string) bool { return m.HasPermOK } func (m *mockClusterService) Stats() (map[string]interface{}, error) { return nil, nil } func (m *mockCredentialStore) HasAnyPerm(username string, perm ...string) bool { return m.HasPermOK } type mockStatusReporter struct { } func (m *mockStatusReporter) Stats() (map[string]interface{}, error) { return nil, nil } func mustNewHTTPRequest(url string) *http.Request { req, err := http.NewRequest("GET", url, nil) if err != nil { panic("failed to create HTTP request for testing") } return req } func mustTempDir() string { var err error path, err := ioutil.TempDir("", "rqlilte-system-test-") if err != nil { panic("failed to create temp dir") } return path } func mustURLParse(s string) *url.URL { u, err := url.Parse(s) if err != nil { panic("failed to URL parse string") } return u } func mustParseDuration(d string) time.Duration { if dur, err := time.ParseDuration(d); err != nil { panic("failed to parse duration") } else { return dur } } func mustReadResponseBody(resp *http.Response) string { response, err := ioutil.ReadAll(resp.Body) if err != nil { panic("failed to ReadAll response body") } resp.Body.Close() return string(response) }