http_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. package etcdhttp
  2. import (
  3. "errors"
  4. "net/http"
  5. "net/http/httptest"
  6. "net/url"
  7. "path"
  8. "reflect"
  9. "strings"
  10. "sync"
  11. "testing"
  12. "time"
  13. etcdErr "github.com/coreos/etcd/error"
  14. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  15. "github.com/coreos/etcd/store"
  16. "github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
  17. )
  18. func boolp(b bool) *bool { return &b }
  19. func mustNewURL(t *testing.T, s string) *url.URL {
  20. u, err := url.Parse(s)
  21. if err != nil {
  22. t.Fatalf("error creating URL from %q: %v", s, err)
  23. }
  24. return u
  25. }
  26. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  27. // an *http.Request referencing the resulting URL
  28. func mustNewRequest(t *testing.T, p string) *http.Request {
  29. return &http.Request{
  30. URL: mustNewURL(t, path.Join(keysPrefix, p)),
  31. }
  32. }
  33. // mustNewForm takes a set of Values and constructs a PUT *http.Request,
  34. // with a URL constructed from appending the given path to the standard keysPrefix
  35. func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
  36. u := mustNewURL(t, path.Join(keysPrefix, p))
  37. req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
  38. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  39. if err != nil {
  40. t.Fatalf("error creating new request: %v", err)
  41. }
  42. return req
  43. }
  44. func TestBadParseRequest(t *testing.T) {
  45. tests := []struct {
  46. in *http.Request
  47. wcode int
  48. }{
  49. {
  50. // parseForm failure
  51. &http.Request{
  52. Body: nil,
  53. Method: "PUT",
  54. },
  55. etcdErr.EcodeInvalidForm,
  56. },
  57. {
  58. // bad key prefix
  59. &http.Request{
  60. URL: mustNewURL(t, "/badprefix/"),
  61. },
  62. etcdErr.EcodeInvalidForm,
  63. },
  64. // bad values for prevIndex, waitIndex, ttl
  65. {
  66. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
  67. etcdErr.EcodeIndexNaN,
  68. },
  69. {
  70. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
  71. etcdErr.EcodeIndexNaN,
  72. },
  73. {
  74. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
  75. etcdErr.EcodeIndexNaN,
  76. },
  77. {
  78. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
  79. etcdErr.EcodeIndexNaN,
  80. },
  81. {
  82. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
  83. etcdErr.EcodeIndexNaN,
  84. },
  85. {
  86. mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
  87. etcdErr.EcodeTTLNaN,
  88. },
  89. // bad values for recursive, sorted, wait, prevExists
  90. {
  91. mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
  92. etcdErr.EcodeInvalidField,
  93. },
  94. {
  95. mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
  96. etcdErr.EcodeInvalidField,
  97. },
  98. {
  99. mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
  100. etcdErr.EcodeInvalidField,
  101. },
  102. {
  103. mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
  104. etcdErr.EcodeInvalidField,
  105. },
  106. {
  107. mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
  108. etcdErr.EcodeInvalidField,
  109. },
  110. {
  111. mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
  112. etcdErr.EcodeInvalidField,
  113. },
  114. {
  115. mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
  116. etcdErr.EcodeInvalidField,
  117. },
  118. {
  119. mustNewForm(t, "foo", url.Values{"prevExists": []string{"yes"}}),
  120. etcdErr.EcodeInvalidField,
  121. },
  122. {
  123. mustNewForm(t, "foo", url.Values{"prevExists": []string{"#2"}}),
  124. etcdErr.EcodeInvalidField,
  125. },
  126. // query values are considered
  127. {
  128. mustNewRequest(t, "foo?prevExists=wrong"),
  129. etcdErr.EcodeInvalidField,
  130. },
  131. {
  132. mustNewRequest(t, "foo?ttl=wrong"),
  133. etcdErr.EcodeTTLNaN,
  134. },
  135. // but body takes precedence if both are specified
  136. {
  137. mustNewForm(
  138. t,
  139. "foo?ttl=12",
  140. url.Values{"ttl": []string{"garbage"}},
  141. ),
  142. etcdErr.EcodeTTLNaN,
  143. },
  144. {
  145. mustNewForm(
  146. t,
  147. "foo?prevExists=false",
  148. url.Values{"prevExists": []string{"yes"}},
  149. ),
  150. etcdErr.EcodeInvalidField,
  151. },
  152. }
  153. for i, tt := range tests {
  154. got, err := parseRequest(tt.in, 1234)
  155. if err == nil {
  156. t.Errorf("#%d: unexpected nil error!", i)
  157. continue
  158. }
  159. ee, ok := err.(*etcdErr.Error)
  160. if !ok {
  161. t.Errorf("#%d: err is not etcd.Error!", i)
  162. continue
  163. }
  164. if ee.ErrorCode != tt.wcode {
  165. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  166. t.Logf("cause: %#v", ee.Cause)
  167. }
  168. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  169. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  170. }
  171. }
  172. }
  173. func TestGoodParseRequest(t *testing.T) {
  174. tests := []struct {
  175. in *http.Request
  176. w etcdserverpb.Request
  177. }{
  178. {
  179. // good prefix, all other values default
  180. mustNewRequest(t, "foo"),
  181. etcdserverpb.Request{
  182. Id: 1234,
  183. Path: "/foo",
  184. },
  185. },
  186. {
  187. // value specified
  188. mustNewForm(
  189. t,
  190. "foo",
  191. url.Values{"value": []string{"some_value"}},
  192. ),
  193. etcdserverpb.Request{
  194. Id: 1234,
  195. Method: "PUT",
  196. Val: "some_value",
  197. Path: "/foo",
  198. },
  199. },
  200. {
  201. // prevIndex specified
  202. mustNewForm(
  203. t,
  204. "foo",
  205. url.Values{"prevIndex": []string{"98765"}},
  206. ),
  207. etcdserverpb.Request{
  208. Id: 1234,
  209. Method: "PUT",
  210. PrevIndex: 98765,
  211. Path: "/foo",
  212. },
  213. },
  214. {
  215. // recursive specified
  216. mustNewForm(
  217. t,
  218. "foo",
  219. url.Values{"recursive": []string{"true"}},
  220. ),
  221. etcdserverpb.Request{
  222. Id: 1234,
  223. Method: "PUT",
  224. Recursive: true,
  225. Path: "/foo",
  226. },
  227. },
  228. {
  229. // sorted specified
  230. mustNewForm(
  231. t,
  232. "foo",
  233. url.Values{"sorted": []string{"true"}},
  234. ),
  235. etcdserverpb.Request{
  236. Id: 1234,
  237. Method: "PUT",
  238. Sorted: true,
  239. Path: "/foo",
  240. },
  241. },
  242. {
  243. // wait specified
  244. mustNewForm(
  245. t,
  246. "foo",
  247. url.Values{"wait": []string{"true"}},
  248. ),
  249. etcdserverpb.Request{
  250. Id: 1234,
  251. Method: "PUT",
  252. Wait: true,
  253. Path: "/foo",
  254. },
  255. },
  256. {
  257. // prevExists should be non-null if specified
  258. mustNewForm(
  259. t,
  260. "foo",
  261. url.Values{"prevExists": []string{"true"}},
  262. ),
  263. etcdserverpb.Request{
  264. Id: 1234,
  265. Method: "PUT",
  266. PrevExists: boolp(true),
  267. Path: "/foo",
  268. },
  269. },
  270. {
  271. // prevExists should be non-null if specified
  272. mustNewForm(
  273. t,
  274. "foo",
  275. url.Values{"prevExists": []string{"false"}},
  276. ),
  277. etcdserverpb.Request{
  278. Id: 1234,
  279. Method: "PUT",
  280. PrevExists: boolp(false),
  281. Path: "/foo",
  282. },
  283. },
  284. // mix various fields
  285. {
  286. mustNewForm(
  287. t,
  288. "foo",
  289. url.Values{
  290. "value": []string{"some value"},
  291. "prevExists": []string{"true"},
  292. "prevValue": []string{"previous value"},
  293. },
  294. ),
  295. etcdserverpb.Request{
  296. Id: 1234,
  297. Method: "PUT",
  298. PrevExists: boolp(true),
  299. PrevValue: "previous value",
  300. Val: "some value",
  301. Path: "/foo",
  302. },
  303. },
  304. // query parameters should be used if given
  305. {
  306. mustNewForm(
  307. t,
  308. "foo?prevValue=woof",
  309. url.Values{},
  310. ),
  311. etcdserverpb.Request{
  312. Id: 1234,
  313. Method: "PUT",
  314. PrevValue: "woof",
  315. Path: "/foo",
  316. },
  317. },
  318. // but form values should take precedence over query parameters
  319. {
  320. mustNewForm(
  321. t,
  322. "foo?prevValue=woof",
  323. url.Values{
  324. "prevValue": []string{"miaow"},
  325. },
  326. ),
  327. etcdserverpb.Request{
  328. Id: 1234,
  329. Method: "PUT",
  330. PrevValue: "miaow",
  331. Path: "/foo",
  332. },
  333. },
  334. }
  335. for i, tt := range tests {
  336. got, err := parseRequest(tt.in, 1234)
  337. if err != nil {
  338. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  339. }
  340. if !reflect.DeepEqual(got, tt.w) {
  341. t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
  342. }
  343. }
  344. }
  345. // eventingWatcher immediately returns a simple event of the given action on its channel
  346. type eventingWatcher struct {
  347. action string
  348. }
  349. func (w *eventingWatcher) EventChan() chan *store.Event {
  350. ch := make(chan *store.Event)
  351. go func() {
  352. ch <- &store.Event{
  353. Action: w.action,
  354. Node: &store.NodeExtern{},
  355. }
  356. }()
  357. return ch
  358. }
  359. func (w *eventingWatcher) Remove() {}
  360. func TestWriteError(t *testing.T) {
  361. // nil error should not panic
  362. rw := httptest.NewRecorder()
  363. writeError(rw, nil)
  364. h := rw.Header()
  365. if len(h) > 0 {
  366. t.Fatalf("unexpected non-empty headers: %#v", h)
  367. }
  368. b := rw.Body.String()
  369. if len(b) > 0 {
  370. t.Fatalf("unexpected non-empty body: %q", b)
  371. }
  372. tests := []struct {
  373. err error
  374. wcode int
  375. wi string
  376. }{
  377. {
  378. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123),
  379. http.StatusNotFound,
  380. "123",
  381. },
  382. {
  383. etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456),
  384. http.StatusPreconditionFailed,
  385. "456",
  386. },
  387. {
  388. err: errors.New("something went wrong"),
  389. wcode: http.StatusInternalServerError,
  390. },
  391. }
  392. for i, tt := range tests {
  393. rw := httptest.NewRecorder()
  394. writeError(rw, tt.err)
  395. if code := rw.Code; code != tt.wcode {
  396. t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode)
  397. }
  398. if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi {
  399. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi)
  400. }
  401. }
  402. }
  403. func TestWriteEvent(t *testing.T) {
  404. // nil event should not panic
  405. rw := httptest.NewRecorder()
  406. writeEvent(rw, nil)
  407. h := rw.Header()
  408. if len(h) > 0 {
  409. t.Fatalf("unexpected non-empty headers: %#v", h)
  410. }
  411. b := rw.Body.String()
  412. if len(b) > 0 {
  413. t.Fatalf("unexpected non-empty body: %q", b)
  414. }
  415. tests := []struct {
  416. ev *store.Event
  417. idx string
  418. code int
  419. err error
  420. }{
  421. // standard case, standard 200 response
  422. {
  423. &store.Event{
  424. Action: store.Get,
  425. Node: &store.NodeExtern{},
  426. PrevNode: &store.NodeExtern{},
  427. },
  428. "0",
  429. http.StatusOK,
  430. nil,
  431. },
  432. // check new nodes return StatusCreated
  433. {
  434. &store.Event{
  435. Action: store.Create,
  436. Node: &store.NodeExtern{},
  437. PrevNode: &store.NodeExtern{},
  438. },
  439. "0",
  440. http.StatusCreated,
  441. nil,
  442. },
  443. }
  444. for i, tt := range tests {
  445. rw := httptest.NewRecorder()
  446. writeEvent(rw, tt.ev)
  447. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  448. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  449. }
  450. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  451. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  452. }
  453. if rw.Code != tt.code {
  454. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  455. }
  456. }
  457. }
  458. type dummyWatcher struct {
  459. echan chan *store.Event
  460. }
  461. func (w *dummyWatcher) EventChan() chan *store.Event {
  462. return w.echan
  463. }
  464. func (w *dummyWatcher) Remove() {}
  465. type dummyResponseWriter struct {
  466. cnchan chan bool
  467. http.ResponseWriter
  468. }
  469. func (rw *dummyResponseWriter) CloseNotify() <-chan bool {
  470. return rw.cnchan
  471. }
  472. func TestWaitForEventChan(t *testing.T) {
  473. ctx := context.Background()
  474. ec := make(chan *store.Event)
  475. dw := &dummyWatcher{
  476. echan: ec,
  477. }
  478. w := httptest.NewRecorder()
  479. var wg sync.WaitGroup
  480. var ev *store.Event
  481. var err error
  482. wg.Add(1)
  483. go func() {
  484. ev, err = waitForEvent(ctx, w, dw)
  485. wg.Done()
  486. }()
  487. ec <- &store.Event{
  488. Action: store.Get,
  489. Node: &store.NodeExtern{
  490. Key: "/foo/bar",
  491. ModifiedIndex: 12345,
  492. },
  493. }
  494. wg.Wait()
  495. want := &store.Event{
  496. Action: store.Get,
  497. Node: &store.NodeExtern{
  498. Key: "/foo/bar",
  499. ModifiedIndex: 12345,
  500. },
  501. }
  502. if !reflect.DeepEqual(ev, want) {
  503. t.Fatalf("bad event: got %#v, want %#v", ev, want)
  504. }
  505. if err != nil {
  506. t.Fatalf("unexpected error: %v", err)
  507. }
  508. }
  509. func TestWaitForEventCloseNotify(t *testing.T) {
  510. ctx := context.Background()
  511. dw := &dummyWatcher{}
  512. cnchan := make(chan bool)
  513. w := &dummyResponseWriter{
  514. cnchan: cnchan,
  515. }
  516. var wg sync.WaitGroup
  517. var ev *store.Event
  518. var err error
  519. wg.Add(1)
  520. go func() {
  521. ev, err = waitForEvent(ctx, w, dw)
  522. wg.Done()
  523. }()
  524. close(cnchan)
  525. wg.Wait()
  526. if ev != nil {
  527. t.Fatalf("non-nil Event returned with CloseNotifier: %v", ev)
  528. }
  529. if err == nil {
  530. t.Fatalf("nil err returned with CloseNotifier!")
  531. }
  532. }
  533. func TestWaitForEventCancelledContext(t *testing.T) {
  534. cctx, cancel := context.WithCancel(context.Background())
  535. dw := &dummyWatcher{}
  536. w := httptest.NewRecorder()
  537. var wg sync.WaitGroup
  538. var ev *store.Event
  539. var err error
  540. wg.Add(1)
  541. go func() {
  542. ev, err = waitForEvent(cctx, w, dw)
  543. wg.Done()
  544. }()
  545. cancel()
  546. wg.Wait()
  547. if ev != nil {
  548. t.Fatalf("non-nil Event returned with cancelled context: %v", ev)
  549. }
  550. if err == nil {
  551. t.Fatalf("nil err returned with cancelled context!")
  552. }
  553. }
  554. func TestV2MachinesEndpoint(t *testing.T) {
  555. tests := []struct {
  556. method string
  557. wcode int
  558. }{
  559. {"GET", http.StatusOK},
  560. {"HEAD", http.StatusOK},
  561. {"POST", http.StatusMethodNotAllowed},
  562. }
  563. m := NewHandler(nil, Peers{}, time.Hour)
  564. s := httptest.NewServer(m)
  565. defer s.Close()
  566. for _, tt := range tests {
  567. req, err := http.NewRequest(tt.method, s.URL+machinesPrefix, nil)
  568. if err != nil {
  569. t.Fatal(err)
  570. }
  571. resp, err := http.DefaultClient.Do(req)
  572. if err != nil {
  573. t.Fatal(err)
  574. }
  575. if resp.StatusCode != tt.wcode {
  576. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  577. }
  578. }
  579. }
  580. func TestServeMachines(t *testing.T) {
  581. peers := Peers{}
  582. peers.Set("0xBEEF0=localhost:8080&0xBEEF1=localhost:8081&0xBEEF2=localhost:8082")
  583. writer := httptest.NewRecorder()
  584. req, err := http.NewRequest("GET", "", nil)
  585. if err != nil {
  586. t.Fatal(err)
  587. }
  588. h := &serverHandler{peers: peers}
  589. h.serveMachines(writer, req)
  590. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  591. if g := writer.Body.String(); g != w {
  592. t.Errorf("body = %s, want %s", g, w)
  593. }
  594. if writer.Code != http.StatusOK {
  595. t.Errorf("header = %d, want %d", writer.Code, http.StatusOK)
  596. }
  597. }
  598. func TestPeersEndpoints(t *testing.T) {
  599. tests := []struct {
  600. peers Peers
  601. endpoints []string
  602. }{
  603. // single peer with a single address
  604. {
  605. peers: Peers(map[int64][]string{
  606. 1: []string{"192.0.2.1"},
  607. }),
  608. endpoints: []string{"http://192.0.2.1"},
  609. },
  610. // single peer with a single address with a port
  611. {
  612. peers: Peers(map[int64][]string{
  613. 1: []string{"192.0.2.1:8001"},
  614. }),
  615. endpoints: []string{"http://192.0.2.1:8001"},
  616. },
  617. // several peers explicitly unsorted
  618. {
  619. peers: Peers(map[int64][]string{
  620. 2: []string{"192.0.2.3", "192.0.2.4"},
  621. 3: []string{"192.0.2.5", "192.0.2.6"},
  622. 1: []string{"192.0.2.1", "192.0.2.2"},
  623. }),
  624. endpoints: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"},
  625. },
  626. // no peers
  627. {
  628. peers: Peers(map[int64][]string{}),
  629. endpoints: []string{},
  630. },
  631. // peer with no endpoints
  632. {
  633. peers: Peers(map[int64][]string{
  634. 3: []string{},
  635. }),
  636. endpoints: []string{},
  637. },
  638. }
  639. for i, tt := range tests {
  640. endpoints := tt.peers.Endpoints()
  641. if !reflect.DeepEqual(tt.endpoints, endpoints) {
  642. t.Errorf("#%d: peers.Endpoints() incorrect: want=%#v got=%#v", i, tt.endpoints, endpoints)
  643. }
  644. }
  645. }
  646. func TestAllowMethod(t *testing.T) {
  647. tests := []struct {
  648. m string
  649. ms []string
  650. w bool
  651. wh string
  652. }{
  653. // Accepted methods
  654. {
  655. m: "GET",
  656. ms: []string{"GET", "POST", "PUT"},
  657. w: true,
  658. },
  659. {
  660. m: "POST",
  661. ms: []string{"POST"},
  662. w: true,
  663. },
  664. // Made-up methods no good
  665. {
  666. m: "FAKE",
  667. ms: []string{"GET", "POST", "PUT"},
  668. w: false,
  669. wh: "GET,POST,PUT",
  670. },
  671. // Empty methods no good
  672. {
  673. m: "",
  674. ms: []string{"GET", "POST"},
  675. w: false,
  676. wh: "GET,POST",
  677. },
  678. // Empty accepted methods no good
  679. {
  680. m: "GET",
  681. ms: []string{""},
  682. w: false,
  683. wh: "",
  684. },
  685. // No methods accepted
  686. {
  687. m: "GET",
  688. ms: []string{},
  689. w: false,
  690. wh: "",
  691. },
  692. }
  693. for i, tt := range tests {
  694. rw := httptest.NewRecorder()
  695. g := allowMethod(rw, tt.m, tt.ms...)
  696. if g != tt.w {
  697. t.Errorf("#%d: got allowMethod()=%t, want %t", i, g, tt.w)
  698. }
  699. if !tt.w {
  700. if rw.Code != http.StatusMethodNotAllowed {
  701. t.Errorf("#%d: code=%d, want %d", i, rw.Code, http.StatusMethodNotAllowed)
  702. }
  703. gh := rw.Header().Get("Allow")
  704. if gh != tt.wh {
  705. t.Errorf("#%d: Allow header=%q, want %q", i, gh, tt.wh)
  706. }
  707. }
  708. }
  709. }