go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/api/v2http/http_test.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package v2http 16 17 import ( 18 "context" 19 "errors" 20 "net/http" 21 "net/http/httptest" 22 "sort" 23 "testing" 24 25 etcdErr "github.com/coreos/etcd/error" 26 "github.com/coreos/etcd/etcdserver" 27 "github.com/coreos/etcd/etcdserver/etcdserverpb" 28 "github.com/coreos/etcd/etcdserver/membership" 29 "github.com/coreos/etcd/pkg/types" 30 "github.com/coreos/etcd/raft/raftpb" 31 32 "github.com/coreos/go-semver/semver" 33 ) 34 35 type fakeCluster struct { 36 id uint64 37 clientURLs []string 38 members map[uint64]*membership.Member 39 } 40 41 func (c *fakeCluster) ID() types.ID { return types.ID(c.id) } 42 func (c *fakeCluster) ClientURLs() []string { return c.clientURLs } 43 func (c *fakeCluster) Members() []*membership.Member { 44 var ms membership.MembersByID 45 for _, m := range c.members { 46 ms = append(ms, m) 47 } 48 sort.Sort(ms) 49 return []*membership.Member(ms) 50 } 51 func (c *fakeCluster) Member(id types.ID) *membership.Member { return c.members[uint64(id)] } 52 func (c *fakeCluster) Version() *semver.Version { return nil } 53 54 // errServer implements the etcd.Server interface for testing. 55 // It returns the given error from any Do/Process/AddMember/RemoveMember calls. 56 type errServer struct { 57 err error 58 fakeServer 59 } 60 61 func (fs *errServer) Do(ctx context.Context, r etcdserverpb.Request) (etcdserver.Response, error) { 62 return etcdserver.Response{}, fs.err 63 } 64 func (fs *errServer) Process(ctx context.Context, m raftpb.Message) error { 65 return fs.err 66 } 67 func (fs *errServer) AddMember(ctx context.Context, m membership.Member) ([]*membership.Member, error) { 68 return nil, fs.err 69 } 70 func (fs *errServer) RemoveMember(ctx context.Context, id uint64) ([]*membership.Member, error) { 71 return nil, fs.err 72 } 73 func (fs *errServer) UpdateMember(ctx context.Context, m membership.Member) ([]*membership.Member, error) { 74 return nil, fs.err 75 } 76 77 func TestWriteError(t *testing.T) { 78 // nil error should not panic 79 rec := httptest.NewRecorder() 80 r := new(http.Request) 81 writeError(rec, r, nil) 82 h := rec.Header() 83 if len(h) > 0 { 84 t.Fatalf("unexpected non-empty headers: %#v", h) 85 } 86 b := rec.Body.String() 87 if len(b) > 0 { 88 t.Fatalf("unexpected non-empty body: %q", b) 89 } 90 91 tests := []struct { 92 err error 93 wcode int 94 wi string 95 }{ 96 { 97 etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123), 98 http.StatusNotFound, 99 "123", 100 }, 101 { 102 etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456), 103 http.StatusPreconditionFailed, 104 "456", 105 }, 106 { 107 err: errors.New("something went wrong"), 108 wcode: http.StatusInternalServerError, 109 }, 110 } 111 112 for i, tt := range tests { 113 rw := httptest.NewRecorder() 114 writeError(rw, r, tt.err) 115 if code := rw.Code; code != tt.wcode { 116 t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode) 117 } 118 if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi { 119 t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi) 120 } 121 } 122 } 123 124 func TestAllowMethod(t *testing.T) { 125 tests := []struct { 126 m string 127 ms []string 128 w bool 129 wh string 130 }{ 131 // Accepted methods 132 { 133 m: "GET", 134 ms: []string{"GET", "POST", "PUT"}, 135 w: true, 136 }, 137 { 138 m: "POST", 139 ms: []string{"POST"}, 140 w: true, 141 }, 142 // Made-up methods no good 143 { 144 m: "FAKE", 145 ms: []string{"GET", "POST", "PUT"}, 146 w: false, 147 wh: "GET,POST,PUT", 148 }, 149 // Empty methods no good 150 { 151 m: "", 152 ms: []string{"GET", "POST"}, 153 w: false, 154 wh: "GET,POST", 155 }, 156 // Empty accepted methods no good 157 { 158 m: "GET", 159 ms: []string{""}, 160 w: false, 161 wh: "", 162 }, 163 // No methods accepted 164 { 165 m: "GET", 166 ms: []string{}, 167 w: false, 168 wh: "", 169 }, 170 } 171 172 for i, tt := range tests { 173 rw := httptest.NewRecorder() 174 g := allowMethod(rw, tt.m, tt.ms...) 175 if g != tt.w { 176 t.Errorf("#%d: got allowMethod()=%t, want %t", i, g, tt.w) 177 } 178 if !tt.w { 179 if rw.Code != http.StatusMethodNotAllowed { 180 t.Errorf("#%d: code=%d, want %d", i, rw.Code, http.StatusMethodNotAllowed) 181 } 182 gh := rw.Header().Get("Allow") 183 if gh != tt.wh { 184 t.Errorf("#%d: Allow header=%q, want %q", i, gh, tt.wh) 185 } 186 } 187 } 188 }